Skip to content

Commit 8edce40

Browse files
authored
JavaScript keywords to support arguments (#1176)
JavaScript keywords to support arguments with JAVASCRIPT and ARGUMENT markers.
1 parent e5a981e commit 8edce40

12 files changed

+291
-32
lines changed

atest/acceptance/keywords/async_javascript.robot

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,19 @@ Resource ../resource.robot
66

77
*** Test Cases ***
88
Should Not Timeout If Callback Invoked Immediately
9-
${result} = Execute Async Javascript arguments[arguments.length - 1](123);
9+
${result} = Execute Async Javascript
10+
... JAVASCRIPT
11+
... arguments[arguments.length - 1](123);
1012
Should Be Equal ${result} ${123}
1113

14+
Execute Async Javascript With ARGUMENTS and JAVASCRIPT Marker
15+
Execute Async Javascript
16+
... ARGUMENTS
17+
... 123
18+
... JAVASCRIPT
19+
... alert(arguments[0]);
20+
Alert Should Be Present 123 timeout=10 s
21+
1222
Should Be Able To Return Javascript Primitives From Async Scripts Neither None Nor Undefined
1323
${result} = Execute Async Javascript arguments[arguments.length - 1](123);
1424
Should Be Equal ${result} ${123}

atest/acceptance/keywords/javascript.robot

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,63 @@ Mouse Down On Link
2020
Mouse Up link_mousedown
2121

2222
Execute Javascript
23-
[Documentation] LOG 2 Executing JavaScript:
23+
[Documentation]
24+
... LOG 2 Executing JavaScript:
2425
... window.add_content('button_target', 'Inserted directly')
26+
... Without any arguments.
2527
Execute Javascript window.add_content('button_target', 'Inserted directly')
2628
Page Should Contain Inserted directly
2729

30+
Execute Javascript With ARGUMENTS and JAVASCRIPT Marker
31+
Execute Javascript
32+
... ARGUMENTS
33+
... 123
34+
... JAVASCRIPT
35+
... alert(arguments[0]);
36+
Alert Should Be Present 123 timeout=10 s
37+
38+
Execute Javascript With JAVASCRIPT and ARGUMENTS Marker
39+
[Documentation]
40+
... LOG 2 Executing JavaScript:
41+
... alert(arguments[0]);
42+
... By using argument:
43+
... '123'
44+
Execute Javascript
45+
... JAVASCRIPT
46+
... alert(arguments[0]);
47+
... ARGUMENTS
48+
... 123
49+
Alert Should Be Present 123 timeout=10 s
50+
51+
Execute Javascript With ARGUMENTS Marker Only
52+
[Documentation]
53+
... LOG 2 Executing JavaScript:
54+
... alert(arguments[0]);
55+
... By using arguments:
56+
... '123' and '0987'
57+
Execute Javascript
58+
... alert(arguments[0]);
59+
... ARGUMENTS
60+
... 123
61+
... 0987
62+
Alert Should Be Present 123 timeout=10 s
63+
2864
Execute Javascript from File
29-
[Documentation] LOG 2:1 REGEXP: Reading JavaScript from file .*
65+
[Documentation]
66+
... LOG 2:1 REGEXP: Reading JavaScript from file .*executed_by_execute_javascript.*
3067
... LOG 2:2 Executing JavaScript:
3168
... window.add_content('button_target', 'Inserted via file')
69+
... Without any arguments.
3270
Execute Javascript ${CURDIR}/executed_by_execute_javascript.js
3371
Page Should Contain Inserted via file
3472

73+
Execute Javascript from File With ARGUMENTS Marker
74+
Execute Javascript
75+
... ${CURDIR}/javascript_alert.js
76+
... ARGUMENTS
77+
... 123
78+
Alert Should Be Present 123 timeout=10 s
79+
3580
Open Context Menu
3681
[Tags] Known Issue Safari
3782
Go To Page "javascript/context_menu.html"
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
alert(arguments[0]);

requirements-dev.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
mockito >= 1.0.0
55
robotstatuschecker
6-
approvaltests >= 0.2.3
6+
approvaltests >= 0.2.4
77

88
# Include normal dependencies from requirements.txt. Makes it possible to use
99
# requirements-dev.txt as a single requirement file in PyCharm and other IDEs.

src/SeleniumLibrary/keywords/javascript.py

Lines changed: 98 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,28 @@
1515
# limitations under the License.
1616

1717
import os
18+
from collections import namedtuple
19+
20+
from robot.utils import plural_or_not, seq2str
1821

1922
from SeleniumLibrary.base import LibraryComponent, keyword
2023

2124

2225
class JavaScriptKeywords(LibraryComponent):
2326

27+
js_marker = 'JAVASCRIPT'
28+
arg_marker = 'ARGUMENTS'
29+
2430
@keyword
2531
def execute_javascript(self, *code):
26-
"""Executes the given JavaScript code.
32+
"""Executes the given JavaScript code with possible arguments.
2733
28-
``code`` may contain multiple lines of code and may be divided into
29-
multiple cells in the test data. In that case, the parts are
30-
concatenated together without adding spaces.
34+
``code`` may be divided into multiple cells in the test data and
35+
``code`` may contain multiple lines of code and arguments. In that case,
36+
the JavaScript code parts are concatenated together without adding
37+
spaces and optional arguments are separated from ``code``.
3138
32-
If ``code`` is an absolute path to an existing file, the JavaScript
39+
If ``code`` is a path to an existing file, the JavaScript
3340
to execute will be read from that file. Forward slashes work as
3441
a path separator on all operating systems.
3542
@@ -42,19 +49,30 @@ def execute_javascript(self, *code):
4249
This keyword returns whatever the executed JavaScript code returns.
4350
Return values are converted to the appropriate Python types.
4451
52+
Starting from SeleniumLibrary 3.2 it is possible to provide JavaScript
53+
[https://seleniumhq.github.io/selenium/docs/api/py/webdriver_remote/selenium.webdriver.remote.webdriver.html#selenium.webdriver.remote.webdriver.WebDriver.execute_script|
54+
arguments] as part of ``code`` argument. The JavaScript code and
55+
arguments must be separated with `JAVASCRIPT` and `ARGUMENTS` markers
56+
and must used exactly with this format. If the Javascript code is
57+
first, then the `JAVASCRIPT` marker is optional. The order of
58+
`JAVASCRIPT` and `ARGUMENTS` markers can swapped, but if `ARGUMENTS`
59+
is first marker, then `JAVASCRIPT` marker is mandatory. It is only
60+
allowed to use `JAVASCRIPT` and `ARGUMENTS` markers only one time in the
61+
``code`` argument.
62+
4563
Examples:
4664
| `Execute JavaScript` | window.myFunc('arg1', 'arg2') |
4765
| `Execute JavaScript` | ${CURDIR}/js_to_execute.js |
48-
| ${sum} = | `Execute JavaScript` | return 1 + 1; |
49-
| `Should Be Equal` | ${sum} | ${2} |
66+
| `Execute JavaScript` | alert(arguments[0]); | ARGUMENTS | 123 |
67+
| `Execute JavaScript` | ARGUMENTS | 123 | JAVASCRIPT | alert(arguments[0]); |
5068
"""
51-
js = self._get_javascript_to_execute(code)
52-
self.info("Executing JavaScript:\n%s" % js)
53-
return self.driver.execute_script(js)
69+
js_code, js_args = self._get_javascript_to_execute(code)
70+
self._js_logger('Executing JavaScript', js_code, js_args)
71+
return self.driver.execute_script(js_code, *js_args)
5472

5573
@keyword
5674
def execute_async_javascript(self, *code):
57-
"""Executes asynchronous JavaScript code.
75+
"""Executes asynchronous JavaScript code with possible arguments.
5876
5977
Similar to `Execute Javascript` except that scripts executed with
6078
this keyword must explicitly signal they are finished by invoking the
@@ -64,6 +82,11 @@ def execute_async_javascript(self, *code):
6482
Scripts must complete within the script timeout or this keyword will
6583
fail. See the `Timeouts` section for more information.
6684
85+
Starting from SeleniumLibrary 3.2 it is possible to provide JavaScript
86+
[https://seleniumhq.github.io/selenium/docs/api/py/webdriver_remote/selenium.webdriver.remote.webdriver.html#selenium.webdriver.remote.webdriver.WebDriver.execute_async_script|
87+
arguments] as part of ``code`` argument. See `Execute Javascript` for
88+
more details.
89+
6790
Examples:
6891
| `Execute Async JavaScript` | var callback = arguments[arguments.length - 1]; window.setTimeout(callback, 2000); |
6992
| `Execute Async JavaScript` | ${CURDIR}/async_js_to_execute.js |
@@ -73,19 +96,72 @@ def execute_async_javascript(self, *code):
7396
| ... | window.setTimeout(answer, 2000); |
7497
| `Should Be Equal` | ${result} | text |
7598
"""
76-
js = self._get_javascript_to_execute(code)
77-
self.info("Executing Asynchronous JavaScript:\n%s" % js)
78-
return self.driver.execute_async_script(js)
79-
80-
def _get_javascript_to_execute(self, lines):
81-
code = ''.join(lines)
82-
path = code.replace('/', os.sep)
83-
if os.path.isabs(path) and os.path.isfile(path):
84-
code = self._read_javascript_from_file(path)
85-
return code
99+
js_code, js_args = self._get_javascript_to_execute(code)
100+
self._js_logger('Executing Asynchronous JavaScript', js_code, js_args)
101+
return self.driver.execute_async_script(js_code, *js_args)
102+
103+
def _js_logger(self, base, code, args):
104+
message = '%s:\n%s\n' % (base, code)
105+
if args:
106+
message = ('%sBy using argument%s:\n%s'
107+
% (message, plural_or_not(args), seq2str(args)))
108+
else:
109+
message = '%sWithout any arguments.' % message
110+
self.info(message)
111+
112+
def _get_javascript_to_execute(self, code):
113+
js_code, js_args = self._separate_code_and_args(code)
114+
if not js_code:
115+
raise ValueError('JavaScript code was not found from code argument.')
116+
js_code = ''.join(js_code)
117+
path = js_code.replace('/', os.sep)
118+
if os.path.isfile(path):
119+
js_code = self._read_javascript_from_file(path)
120+
return js_code, js_args
121+
122+
def _separate_code_and_args(self, code):
123+
code = list(code)
124+
self._check_marker_error(code)
125+
index = self._get_marker_index(code)
126+
if self.arg_marker not in code:
127+
return code[index.js + 1:], []
128+
if self.js_marker not in code:
129+
return code[0:index.arg], code[index.arg + 1:]
130+
else:
131+
if index.js == 0:
132+
return code[index.js + 1:index.arg], code[index.arg + 1:]
133+
else:
134+
return code[index.js + 1:], code[index.arg + 1:index.js]
135+
136+
def _check_marker_error(self, code):
137+
if not code:
138+
raise ValueError('There must be at least one argument defined.')
139+
message = None
140+
template = '%s marker was found two times in the code.'
141+
if code.count(self.js_marker) > 1:
142+
message = template % self.js_marker
143+
if code.count(self.arg_marker) > 1:
144+
message = template % self.arg_marker
145+
index = self._get_marker_index(code)
146+
if index.js > 0 and index.arg != 0:
147+
message = template % self.js_marker
148+
if message:
149+
raise ValueError(message)
150+
151+
def _get_marker_index(self, code):
152+
Index = namedtuple('Index', 'js arg')
153+
if self.js_marker in code:
154+
js = code.index(self.js_marker)
155+
else:
156+
js = -1
157+
if self.arg_marker in code:
158+
arg = code.index(self.arg_marker)
159+
else:
160+
arg = -1
161+
return Index(js=js, arg=arg)
86162

87163
def _read_javascript_from_file(self, path):
88164
self.info('Reading JavaScript from file <a href="file://%s">%s</a>.'
89-
.format(path.replace(os.sep, '/'), path), html=True)
165+
% (path.replace(os.sep, '/'), path), html=True)
90166
with open(path) as file:
91167
return file.read().strip()
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
22
"subdirectory": "approved_files"
3-
}
3+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
error
2+
3+
0) There must be at least one argument defined.
4+
1) ARGUMENTS marker was found two times in the code.
5+
2) JAVASCRIPT marker was found two times in the code.
6+
3) ARGUMENTS marker was found two times in the code.
7+
4) JAVASCRIPT marker was found two times in the code.
8+
5) ARGUMENTS marker was found two times in the code.
9+
6) JAVASCRIPT marker was found two times in the code.
10+
7) There must be at least one argument defined.
11+
8) None
12+
9) None
13+
10) None
14+
11) None
15+
12) None
16+
13) None
17+
14) None
18+
15) None
19+
16) None
20+
17) None
21+
18) None
22+
19) None
23+
20) None
24+
21) None
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
code here
1+
codehere + []
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
JavaScript code was not found from code argument.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
index
2+
3+
0) Index(js=-1, arg=-1)
4+
1) Index(js=-1, arg=-1)
5+
2) Index(js=-1, arg=-1)
6+
3) Index(js=0, arg=-1)
7+
4) Index(js=-1, arg=2)
8+
5) Index(js=-1, arg=-1)
9+
6) Index(js=-1, arg=-1)
10+
7) Index(js=0, arg=3)
11+
8) Index(js=0, arg=-1)
12+
9) Index(js=1, arg=0)
13+
10) Index(js=0, arg=3)
14+
11) Index(js=3, arg=0)
15+
12) Index(js=-1, arg=-1)
16+
13) Index(js=-1, arg=-1)
17+
14) Index(js=0, arg=-1)

0 commit comments

Comments
 (0)