Skip to content

Commit 65d9957

Browse files
pekkaklarckaaltat
authored andcommitted
Fixes to alerts (robotframework#935)
* `Set Selenium Speed` return speed as timestr, not int. This is same behavior as in 1.8. Also added a FIXME about totally stupid tests inside suite setup (which did actually reveal this small regression...). * Fix Dismiss Alert and Confirm Action regressions. Fixes robotframework#934. * Major alert keyword rewrite. 1. Introduce new keywords: Alert Should Not Be Present Handle Alert Input Text Into Alert 2. Add new `action` argument to Alert Should Be Present. 3. Silently depracate all other existing alert related keywords. 4. Change alert polling logic to use WebDriverWait only. Timeout will be made configurable later. 5. Documentation cleanup. * Add utils.is_noney() Also cleanup is_truthy/falsy tests. * Fix waiting timeout given as integer zero * Add timeout support for alerts. * Firefox test fixes * Doc tuning based on review comment * Run tests with --dotted on CI when supported. This makes it a lot easier to see failed tests. Would have changed run_tests.py, but we still want to support RF 2.8 where --dotted isn't supported. * run_test: Use given opts only w/ Robot, not w/ Rebot
1 parent d506f19 commit 65d9957

File tree

18 files changed

+475
-233
lines changed

18 files changed

+475
-233
lines changed

.travis.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,13 @@ matrix:
3535
- BROWSER=chrome
3636
- SELENIUM=3.5.0
3737
- ROBOTFRAMEWORK=3.0.2
38-
- ROBOT_OPTIONS=
38+
- ROBOT_OPTIONS=--dotted
3939
- python: "2.7"
4040
env:
4141
- BROWSER=chrome
4242
- SELENIUM=2.53.6
4343
- ROBOTFRAMEWORK=2.9.2
44-
- ROBOT_OPTIONS=
44+
- ROBOT_OPTIONS=--dotted
4545
- python: "2.7"
4646
env:
4747
- BROWSER=chrome
@@ -53,13 +53,13 @@ matrix:
5353
- BROWSER=chrome
5454
- SELENIUM=2.53.6
5555
- ROBOTFRAMEWORK=3.0.2
56-
- ROBOT_OPTIONS=
56+
- ROBOT_OPTIONS=--dotted
5757
- python: "3.3"
5858
env:
5959
- BROWSER=firefox
6060
- SELENIUM=3.5.0
6161
- ROBOTFRAMEWORK=3.0.2
62-
- ROBOT_OPTIONS="--exclude Known_Issue_Firefox"
62+
- ROBOT_OPTIONS="--exclude Known_Issue_Firefox --dotted"
6363
before_script:
6464
- "export DISPLAY=:99.0"
6565
- "sh -e /etc/init.d/xvfb start"

src/SeleniumLibrary/__init__.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -210,17 +210,17 @@ class SeleniumLibrary(DynamicCore):
210210
It also explains the `time format` that can be used when setting various
211211
timeouts, waits and delays.
212212
213-
== Timeouts ==
213+
== Timeout ==
214214
215-
SeleniumLibrary contains various ``Wait ...`` keywords that can be used
216-
to wait, for example, for a dynamically created elements to appear on
217-
a page. All these keywords accept an optional ``timeout`` argument that
218-
tells the maximum time to wait.
215+
SeleniumLibrary contains various keywords that have an optional
216+
``timeout`` argument that specifies how long these keywords should
217+
wait for certain events or actions. These keywords include, for example,
218+
``Wait ...`` keywords and keywords related to alerts.
219219
220220
The default timeout these keywords use can be set globally either by
221221
using the `Set Selenium Timeout` keyword or with the ``timeout`` argument
222-
when `importing` the library. The same timeout also applies to the
223-
`Execute Async Javascript` keyword.
222+
when `importing` the library. See `time format` below for supported
223+
timeout syntax.
224224
225225
== Implicit wait ==
226226
@@ -230,6 +230,8 @@ class SeleniumLibrary(DynamicCore):
230230
the library. See [http://seleniumhq.org/docs/04_webdriver_advanced.html|
231231
Selenium documentation] for more information about this functionality.
232232
233+
See `time format` below for supported syntax.
234+
233235
== Selenium speed ==
234236
235237
Selenium execution speed can be slowed down globally by using `Set
@@ -238,6 +240,8 @@ class SeleniumLibrary(DynamicCore):
238240
appear on a page is not a good idea, and the above explained timeouts
239241
and waits should be used instead.
240242
243+
See `time format` below for supported syntax.
244+
241245
== Time format ==
242246
243247
All timeouts and waits can be given as numbers considered seconds

src/SeleniumLibrary/base/librarycomponent.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
from robot.api import logger
2020
from robot.libraries.BuiltIn import BuiltIn, RobotNotRunningError
2121

22+
from SeleniumLibrary.utils import is_noney, timestr_to_secs
23+
2224
from .context import ContextAware
2325
from .robotlibcore import PY2
2426

@@ -54,6 +56,11 @@ def assert_page_not_contains(self, locator, tag=None, message=None,
5456
self.element_finder.assert_page_not_contains(locator, tag, message,
5557
loglevel)
5658

59+
def get_timeout(self, timeout=None):
60+
if is_noney(timeout):
61+
return self.ctx.timeout
62+
return timestr_to_secs(timeout)
63+
5764
@property
5865
def log_dir(self):
5966
try:

src/SeleniumLibrary/keywords/alert.py

Lines changed: 150 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -14,152 +14,202 @@
1414
# See the License for the specific language governing permissions and
1515
# limitations under the License.
1616

17-
import time
18-
1917
from selenium.common.exceptions import WebDriverException
2018
from selenium.webdriver.support import expected_conditions as EC
2119
from selenium.webdriver.support.ui import WebDriverWait
2220

23-
from SeleniumLibrary.base import keyword
24-
from SeleniumLibrary.base import LibraryComponent
25-
from SeleniumLibrary.utils import is_truthy
21+
from SeleniumLibrary.base import keyword, LibraryComponent
22+
from SeleniumLibrary.utils import is_truthy, secs_to_timestr, timestr_to_secs
2623

2724

2825
class AlertKeywords(LibraryComponent):
26+
ACCEPT = 'ACCEPT'
27+
DISMISS = 'DISMISS'
28+
LEAVE = 'LEAVE'
29+
_next_alert_action = ACCEPT
2930

30-
ACCEPT_ALERT = 'accept'
31-
DISMISS_ALERT = 'dismiss'
31+
@keyword
32+
def input_text_into_prompt(self, text):
33+
"""Deprecated. Use `Input Text Into Alert` instead.
3234
33-
def __init__(self, ctx):
34-
LibraryComponent.__init__(self, ctx)
35-
self._next_alert_dismiss_type = self.ACCEPT_ALERT
35+
Types the given ``text`` into an input field in an alert.
36+
Leaves the alert open.
37+
"""
38+
self.input_text_into_alert(text, self.LEAVE)
3639

3740
@keyword
38-
def input_text_into_prompt(self, text):
39-
"""Types the given `text` into alert box. """
40-
try:
41-
alert = self._wait_alert()
42-
alert.send_keys(text)
43-
except WebDriverException:
44-
raise RuntimeError('There were no alerts')
41+
def input_text_into_alert(self, text, action=ACCEPT, timeout=None):
42+
"""Types the given ``text`` into an input field in an alert.
43+
44+
The alert is accepted by default, but that behavior can be controlled
45+
by using the ``action`` argument same way as with `Handle Alert`.
46+
47+
``timeout`` specifies how long to wait for the alert to appear.
48+
If it is not given, the global default `timeout` is used instead.
49+
50+
New in SeleniumLibrary 3.0.
51+
"""
52+
alert = self._wait_alert(timeout)
53+
alert.send_keys(text)
54+
self._handle_alert(alert, action)
55+
56+
@keyword
57+
def alert_should_be_present(self, text='', action=ACCEPT, timeout=None):
58+
"""Verifies that an alert is present and, by default, accepts it.
59+
60+
Fails if no alert is present. If ``text`` is a non-empty string,
61+
then it is used to verify alert's message. The alert is accepted
62+
by default, but that behavior can be controlled by using the
63+
``action`` argument same way as with `Handle Alert`.
64+
65+
``timeout`` specifies how long to wait for the alert to appear.
66+
If it is not given, the global default `timeout` is used instead.
67+
68+
``action`` and ``timeout`` arguments are new in SeleniumLibrary 3.0.
69+
In earlier versions the alert was always accepted and timeout was
70+
hard coded to one second.
71+
"""
72+
message = self.handle_alert(action, timeout)
73+
if text and text != message:
74+
raise AssertionError("Alert message should have been '%s' but it "
75+
"was '%s'." % (text, message))
4576

4677
@keyword
47-
def alert_should_be_present(self, text=''):
48-
"""Verifies an alert is present and dismisses it.
78+
def alert_should_not_be_present(self, action=ACCEPT, timeout=0):
79+
"""Verifies that no alert is present.
4980
50-
If `text` is a non-empty string, then it is also verified that the
51-
message of the alert equals to `text`.
81+
If the alert actually exists, the ``action`` argument determines
82+
how it should be handled. By default the alert is accepted, but
83+
it can be also dismissed or left open the same way as with the
84+
`Handle Alert` keyword.
5285
53-
Will fail if no alert is present. Note that following keywords
54-
will fail unless the alert is dismissed by this
55-
keyword or another like `Get Alert Message`.
86+
``timeout`` specifies how long to wait for the alert to appear.
87+
By default the alert is not waited at all, but a custom time can
88+
be given if alert may be delayed. See the `time format` section
89+
for information about the syntax.
90+
91+
New in SeleniumLibrary 3.0.
5692
"""
57-
alert_text = self._handle_alert(self.ACCEPT_ALERT)
58-
if text and alert_text != text:
59-
raise AssertionError("Alert text should have been "
60-
"'%s' but was '%s'"
61-
% (text, alert_text))
93+
try:
94+
alert = self._wait_alert(timeout)
95+
except AssertionError:
96+
return
97+
text = self._handle_alert(alert, action)
98+
raise AssertionError("Alert with message '%s' present." % text)
6299

63100
@keyword
64101
def choose_cancel_on_next_confirmation(self):
65-
"""Cancel will be selected the next time `Confirm Action` is used."""
66-
self._next_alert_dismiss_type = self.DISMISS_ALERT
102+
"""Deprecated. Use `Handle Alert` directly instead.
103+
104+
In versions prior to SeleniumLibrary 3.0, the alert handling
105+
approach needed to be set separately before using the `Confirm
106+
Action` keyword. New `Handle Alert` keyword accepts the action how
107+
to handle the alert as a normal argument and should be used instead.
108+
"""
109+
self._next_alert_action = self.DISMISS
67110

68111
@keyword
69112
def choose_ok_on_next_confirmation(self):
70-
"""Undo the effect of using keywords `Choose Cancel On Next Confirmation`. Note
71-
that Selenium's overridden window.confirm() function will normally
72-
automatically return true, as if the user had manually clicked OK, so
73-
you shouldn't need to use this command unless for some reason you need
74-
to change your mind prior to the next confirmation. After any
75-
confirmation, Selenium will resume using the default behavior for
76-
future confirmations, automatically returning true (OK) unless/until
77-
you explicitly use `Choose Cancel On Next Confirmation` for each
78-
confirmation.
79-
80-
Note that every time a confirmation comes up, you must
81-
consume it by using a keywords such as `Get Alert Message`, or else
82-
the following selenium operations will fail.
113+
"""Deprecated. Use `Handle Alert` directly instead.
114+
115+
In versions prior to SeleniumLibrary 3.0, the alert handling
116+
approach needed to be set separately before using the `Confirm
117+
Action` keyword. New `Handle Alert` keyword accepts the action how
118+
to handle the alert as a normal argument and should be used instead.
83119
"""
84-
self._next_alert_dismiss_type = self.ACCEPT_ALERT
120+
self._next_alert_action = self.ACCEPT
85121

86122
@keyword
87123
def confirm_action(self):
88-
"""Dismisses currently shown confirmation dialog and returns it's message.
124+
"""Deprecated. Use `Handle Alert` instead.
89125
90-
By default, this keyword chooses 'OK' option from the dialog. If
91-
'Cancel' needs to be chosen, keyword `Choose Cancel On Next
92-
Confirmation` must be called before the action that causes the
93-
confirmation dialog to be shown.
94-
95-
Examples:
96-
| Click Button | Send | # Shows a confirmation dialog |
97-
| ${message}= | Confirm Action | # Chooses Ok |
98-
| Should Be Equal | ${message} | Are your sure? |
99-
| | | |
100-
| Choose Cancel On Next Confirmation | | |
101-
| Click Button | Send | # Shows a confirmation dialog |
102-
| Confirm Action | | # Chooses Cancel |
126+
By default accepts an alert, but this behavior can be altered
127+
with `Choose Cancel On Next Confirmation` and `Choose Ok On Next
128+
Confirmation` keywords. New `Handle Alert` keyword accepts the action
129+
how to handle the alert as a normal argument and should be used
130+
instead.
103131
"""
104-
text = self._handle_alert(self._next_alert_dismiss_type)
105-
self._next_alert_dismiss_type = self.DISMISS_ALERT
132+
text = self.handle_alert(self._next_alert_action)
133+
self._next_alert_action = self.ACCEPT
106134
return text
107135

108136
@keyword
109137
def get_alert_message(self, dismiss=True):
110-
"""Returns the text of current JavaScript alert.
138+
"""Deprecated. Use `Handle Alert` instead.
111139
112-
By default the current JavaScript alert will be dismissed.
113-
This keyword will fail if no alert is present. Note that
114-
following keywords will fail unless the alert is
115-
dismissed by this keyword or another like `Dismiss Alert`.
140+
Returns the message the alert has. Dismisses the alert by default
141+
(i.e. presses ``Cancel``) and setting ``dismiss`` to false leaves
142+
the alert open. There is no support to accept the alert (i.e. to
143+
press ``Ok``).
144+
145+
`Handle Alert` has better support for controlling should the alert
146+
be accepted, dismissed, or left open.
116147
"""
117-
if is_truthy(dismiss):
118-
return self._handle_alert(self.DISMISS_ALERT)
119-
else:
120-
return self._handle_alert()
148+
action = self.DISMISS if is_truthy(dismiss) else self.LEAVE
149+
return self.handle_alert(action)
121150

122151
@keyword
123152
def dismiss_alert(self, accept=True):
124-
""" Returns true if alert was confirmed, false if it was dismissed
153+
"""Deprecated. Use `Handle Alert` instead.
154+
155+
Contrary to its name, this keyword accepts the alert by default
156+
(i.e. presses ``Ok``). ``accept`` can be set to a false value
157+
to dismiss the alert (i.e. to press ``Cancel``).
125158
126-
This keyword will fail if no alert is present. Note that
127-
following keywords will fail unless the alert is
128-
dismissed by this keyword or another like `Get Alert Message`.
159+
`Handle Alert` has better support for controlling should the alert
160+
be accepted, dismissed, or left open.
129161
"""
130162
if is_truthy(accept):
131-
return self._handle_alert(self.ACCEPT_ALERT)
132-
else:
133-
return self._handle_alert()
163+
self.handle_alert(self.ACCEPT)
164+
return True
165+
self.handle_alert(self.DISMISS)
166+
return False
134167

135-
def _handle_alert(self, dismiss_type=None):
136-
"""Alert re-try for Chrome
168+
@keyword
169+
def handle_alert(self, action=ACCEPT, timeout=None):
170+
"""Handles the current alert and returns its message.
171+
172+
By default the alert is accepted, but this can be controlled
173+
with the ``action`` argument that supports the following
174+
case-insensitive values:
137175
138-
Because Chrome has difficulties to handle alerts, like::
176+
- ``ACCEPT``: Accept the alert i.e. press ``Ok``. Default.
177+
- ``DISMISS``: Dismiss the alert i.e. press ``Cancel``.
178+
- ``LEAVE``: Leave the alert open.
139179
140-
alert.text
141-
alert.dismiss
180+
The ``timeout`` argument specifies how long to wait for the alert
181+
to appear. If it is not given, the global default `timeout` is used
182+
instead.
142183
143-
This function creates a re-try functionality to better support
144-
alerts in Chrome.
184+
Examples:
185+
| Handle Alert | | | # Accept alert. |
186+
| Handle Alert | action=DISMISS | | # Dismiss alert. |
187+
| Handle Alert | timeout=10 s | | # Use custom timeout and accept alert. |
188+
| Handle Alert | DISMISS | 1 min | # Use custom timeout and dismiss alert. |
189+
| ${message} = | Handle Alert | | # Accept alert and get its message. |
190+
| ${message} = | Handle Alert | LEAVE | # Leave alert open and get its message. |
191+
192+
New in SeleniumLibrary 3.0.
145193
"""
146-
retry = 0
147-
while retry < 4:
148-
try:
149-
return self._alert_worker(dismiss_type)
150-
except WebDriverException:
151-
time.sleep(0.2)
152-
retry += 1
153-
raise RuntimeError('There were no alerts')
154-
155-
def _alert_worker(self, dismiss_type=None):
156-
alert = self._wait_alert()
194+
alert = self._wait_alert(timeout)
195+
return self._handle_alert(alert, action)
196+
197+
def _handle_alert(self, alert, action):
198+
action = action.upper()
157199
text = ' '.join(alert.text.splitlines())
158-
if dismiss_type == self.DISMISS_ALERT:
159-
alert.dismiss()
160-
elif dismiss_type == self.ACCEPT_ALERT:
200+
if action == self.ACCEPT:
161201
alert.accept()
202+
elif action == self.DISMISS:
203+
alert.dismiss()
204+
elif action != self.LEAVE:
205+
raise ValueError("Invalid alert action '%s'." % action)
162206
return text
163207

164-
def _wait_alert(self):
165-
return WebDriverWait(self.browser, 1).until(EC.alert_is_present())
208+
def _wait_alert(self, timeout=None):
209+
timeout = self.get_timeout(timeout)
210+
wait = WebDriverWait(self.browser, timeout)
211+
try:
212+
return wait.until(EC.alert_is_present())
213+
except WebDriverException:
214+
raise AssertionError('Alert not found in %s.'
215+
% secs_to_timestr(timeout))

0 commit comments

Comments
 (0)