1 | // Licensed to the Software Freedom Conservancy (SFC) under one
|
2 | // or more contributor license agreements. See the NOTICE file
|
3 | // distributed with this work for additional information
|
4 | // regarding copyright ownership. The SFC licenses this file
|
5 | // to you under the Apache License, Version 2.0 (the
|
6 | // "License"); you may not use this file except in compliance
|
7 | // with the License. You may obtain a copy of the License at
|
8 | //
|
9 | // http://www.apache.org/licenses/LICENSE-2.0
|
10 | //
|
11 | // Unless required by applicable law or agreed to in writing,
|
12 | // software distributed under the License is distributed on an
|
13 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
14 | // KIND, either express or implied. See the License for the
|
15 | // specific language governing permissions and limitations
|
16 | // under the License.
|
17 |
|
18 | /**
|
19 | * @fileoverview Defines common conditions for use with
|
20 | * {@link webdriver.WebDriver#wait WebDriver wait}.
|
21 | *
|
22 | * Sample usage:
|
23 | *
|
24 | * driver.get('http://www.google.com/ncr');
|
25 | *
|
26 | * var query = driver.wait(until.elementLocated(By.name('q')));
|
27 | * query.sendKeys('webdriver\n');
|
28 | *
|
29 | * driver.wait(until.titleIs('webdriver - Google Search'));
|
30 | *
|
31 | * To define a custom condition, simply call WebDriver.wait with a function
|
32 | * that will eventually return a truthy-value (neither null, undefined, false,
|
33 | * 0, or the empty string):
|
34 | *
|
35 | * driver.wait(function() {
|
36 | * return driver.getTitle().then(function(title) {
|
37 | * return title === 'webdriver - Google Search';
|
38 | * });
|
39 | * }, 1000);
|
40 | */
|
41 |
|
42 | ;
|
43 |
|
44 | const by = require('./by');
|
45 | const By = require('./by').By;
|
46 | const error = require('./error');
|
47 | const webdriver = require('./webdriver'),
|
48 | Condition = webdriver.Condition,
|
49 | WebElementCondition = webdriver.WebElementCondition;
|
50 |
|
51 |
|
52 | /**
|
53 | * Creates a condition that will wait until the input driver is able to switch
|
54 | * to the designated frame. The target frame may be specified as
|
55 | *
|
56 | * 1. a numeric index into
|
57 | * [window.frames](https://developer.mozilla.org/en-US/docs/Web/API/Window.frames)
|
58 | * for the currently selected frame.
|
59 | * 2. a {@link ./webdriver.WebElement}, which must reference a FRAME or IFRAME
|
60 | * element on the current page.
|
61 | * 3. a locator which may be used to first locate a FRAME or IFRAME on the
|
62 | * current page before attempting to switch to it.
|
63 | *
|
64 | * Upon successful resolution of this condition, the driver will be left
|
65 | * focused on the new frame.
|
66 | *
|
67 | * @param {!(number|./webdriver.WebElement|By|
|
68 | * function(!./webdriver.WebDriver): !./webdriver.WebElement)} frame
|
69 | * The frame identifier.
|
70 | * @return {!Condition<boolean>} A new condition.
|
71 | */
|
72 | exports.ableToSwitchToFrame = function ableToSwitchToFrame(frame) {
|
73 | var condition;
|
74 | if (typeof frame === 'number' || frame instanceof webdriver.WebElement) {
|
75 | condition = attemptToSwitchFrames;
|
76 | } else {
|
77 | condition = function(driver) {
|
78 | let locator = /** @type {!(By|Function)} */(frame);
|
79 | return driver.findElements(locator).then(function(els) {
|
80 | if (els.length) {
|
81 | return attemptToSwitchFrames(driver, els[0]);
|
82 | }
|
83 | });
|
84 | };
|
85 | }
|
86 |
|
87 | return new Condition('to be able to switch to frame', condition);
|
88 |
|
89 | function attemptToSwitchFrames(driver, frame) {
|
90 | return driver.switchTo().frame(frame).then(
|
91 | function() { return true; },
|
92 | function(e) {
|
93 | if (!(e instanceof error.NoSuchFrameError)) {
|
94 | throw e;
|
95 | }
|
96 | });
|
97 | }
|
98 | };
|
99 |
|
100 |
|
101 | /**
|
102 | * Creates a condition that waits for an alert to be opened. Upon success, the
|
103 | * returned promise will be fulfilled with the handle for the opened alert.
|
104 | *
|
105 | * @return {!Condition<!./webdriver.Alert>} The new condition.
|
106 | */
|
107 | exports.alertIsPresent = function alertIsPresent() {
|
108 | return new Condition('for alert to be present', function(driver) {
|
109 | return driver.switchTo().alert().catch(function(e) {
|
110 | if (!(e instanceof error.NoSuchAlertError
|
111 | // XXX: Workaround for GeckoDriver error `TypeError: can't convert null
|
112 | // to object`. For more details, see
|
113 | // https://github.com/SeleniumHQ/selenium/pull/2137
|
114 | || (e instanceof error.WebDriverError
|
115 | && e.message === `can't convert null to object`)
|
116 | )) {
|
117 | throw e;
|
118 | }
|
119 | });
|
120 | });
|
121 | };
|
122 |
|
123 |
|
124 | /**
|
125 | * Creates a condition that will wait for the current page's title to match the
|
126 | * given value.
|
127 | *
|
128 | * @param {string} title The expected page title.
|
129 | * @return {!Condition<boolean>} The new condition.
|
130 | */
|
131 | exports.titleIs = function titleIs(title) {
|
132 | return new Condition(
|
133 | 'for title to be ' + JSON.stringify(title),
|
134 | function(driver) {
|
135 | return driver.getTitle().then(function(t) {
|
136 | return t === title;
|
137 | });
|
138 | });
|
139 | };
|
140 |
|
141 |
|
142 | /**
|
143 | * Creates a condition that will wait for the current page's title to contain
|
144 | * the given substring.
|
145 | *
|
146 | * @param {string} substr The substring that should be present in the page
|
147 | * title.
|
148 | * @return {!Condition<boolean>} The new condition.
|
149 | */
|
150 | exports.titleContains = function titleContains(substr) {
|
151 | return new Condition(
|
152 | 'for title to contain ' + JSON.stringify(substr),
|
153 | function(driver) {
|
154 | return driver.getTitle().then(function(title) {
|
155 | return title.indexOf(substr) !== -1;
|
156 | });
|
157 | });
|
158 | };
|
159 |
|
160 |
|
161 | /**
|
162 | * Creates a condition that will wait for the current page's title to match the
|
163 | * given regular expression.
|
164 | *
|
165 | * @param {!RegExp} regex The regular expression to test against.
|
166 | * @return {!Condition<boolean>} The new condition.
|
167 | */
|
168 | exports.titleMatches = function titleMatches(regex) {
|
169 | return new Condition('for title to match ' + regex, function(driver) {
|
170 | return driver.getTitle().then(function(title) {
|
171 | return regex.test(title);
|
172 | });
|
173 | });
|
174 | };
|
175 |
|
176 |
|
177 | /**
|
178 | * Creates a condition that will wait for the current page's url to match the
|
179 | * given value.
|
180 | *
|
181 | * @param {string} url The expected page url.
|
182 | * @return {!Condition<boolean>} The new condition.
|
183 | */
|
184 | exports.urlIs = function urlIs(url) {
|
185 | return new Condition(
|
186 | 'for URL to be ' + JSON.stringify(url),
|
187 | function(driver) {
|
188 | return driver.getCurrentUrl().then(function(u) {
|
189 | return u === url;
|
190 | });
|
191 | });
|
192 | };
|
193 |
|
194 |
|
195 | /**
|
196 | * Creates a condition that will wait for the current page's url to contain
|
197 | * the given substring.
|
198 | *
|
199 | * @param {string} substrUrl The substring that should be present in the current
|
200 | * URL.
|
201 | * @return {!Condition<boolean>} The new condition.
|
202 | */
|
203 | exports.urlContains = function urlContains(substrUrl) {
|
204 | return new Condition(
|
205 | 'for URL to contain ' + JSON.stringify(substrUrl),
|
206 | function(driver) {
|
207 | return driver.getCurrentUrl().then(function(url) {
|
208 | return url.indexOf(substrUrl) !== -1;
|
209 | });
|
210 | });
|
211 | };
|
212 |
|
213 |
|
214 | /**
|
215 | * Creates a condition that will wait for the current page's url to match the
|
216 | * given regular expression.
|
217 | *
|
218 | * @param {!RegExp} regex The regular expression to test against.
|
219 | * @return {!Condition<boolean>} The new condition.
|
220 | */
|
221 | exports.urlMatches = function urlMatches(regex) {
|
222 | return new Condition('for URL to match ' + regex, function(driver) {
|
223 | return driver.getCurrentUrl().then(function(url) {
|
224 | return regex.test(url);
|
225 | });
|
226 | });
|
227 | };
|
228 |
|
229 |
|
230 | /**
|
231 | * Creates a condition that will loop until an element is
|
232 | * {@link ./webdriver.WebDriver#findElement found} with the given locator.
|
233 | *
|
234 | * @param {!(By|Function)} locator The locator to use.
|
235 | * @return {!WebElementCondition} The new condition.
|
236 | */
|
237 | exports.elementLocated = function elementLocated(locator) {
|
238 | locator = by.checkedLocator(locator);
|
239 | let locatorStr =
|
240 | typeof locator === 'function' ? 'by function()' : locator + '';
|
241 | return new WebElementCondition('for element to be located ' + locatorStr,
|
242 | function(driver) {
|
243 | return driver.findElements(locator).then(function(elements) {
|
244 | return elements[0];
|
245 | });
|
246 | });
|
247 | };
|
248 |
|
249 |
|
250 | /**
|
251 | * Creates a condition that will loop until at least one element is
|
252 | * {@link ./webdriver.WebDriver#findElement found} with the given locator.
|
253 | *
|
254 | * @param {!(By|Function)} locator The locator to use.
|
255 | * @return {!Condition<!Array<!./webdriver.WebElement>>} The new
|
256 | * condition.
|
257 | */
|
258 | exports.elementsLocated = function elementsLocated(locator) {
|
259 | locator = by.checkedLocator(locator);
|
260 | let locatorStr =
|
261 | typeof locator === 'function' ? 'by function()' : locator + '';
|
262 | return new Condition(
|
263 | 'for at least one element to be located ' + locatorStr,
|
264 | function(driver) {
|
265 | return driver.findElements(locator).then(function(elements) {
|
266 | return elements.length > 0 ? elements : null;
|
267 | });
|
268 | });
|
269 | };
|
270 |
|
271 |
|
272 | /**
|
273 | * Creates a condition that will wait for the given element to become stale. An
|
274 | * element is considered stale once it is removed from the DOM, or a new page
|
275 | * has loaded.
|
276 | *
|
277 | * @param {!./webdriver.WebElement} element The element that should become stale.
|
278 | * @return {!Condition<boolean>} The new condition.
|
279 | */
|
280 | exports.stalenessOf = function stalenessOf(element) {
|
281 | return new Condition('element to become stale', function() {
|
282 | return element.getTagName().then(
|
283 | function() { return false; },
|
284 | function(e) {
|
285 | if (e instanceof error.StaleElementReferenceError) {
|
286 | return true;
|
287 | }
|
288 | throw e;
|
289 | });
|
290 | });
|
291 | };
|
292 |
|
293 |
|
294 | /**
|
295 | * Creates a condition that will wait for the given element to become visible.
|
296 | *
|
297 | * @param {!./webdriver.WebElement} element The element to test.
|
298 | * @return {!WebElementCondition} The new condition.
|
299 | * @see ./webdriver.WebDriver#isDisplayed
|
300 | */
|
301 | exports.elementIsVisible = function elementIsVisible(element) {
|
302 | return new WebElementCondition('until element is visible', function() {
|
303 | return element.isDisplayed().then(v => v ? element : null);
|
304 | });
|
305 | };
|
306 |
|
307 |
|
308 | /**
|
309 | * Creates a condition that will wait for the given element to be in the DOM,
|
310 | * yet not visible to the user.
|
311 | *
|
312 | * @param {!./webdriver.WebElement} element The element to test.
|
313 | * @return {!WebElementCondition} The new condition.
|
314 | * @see ./webdriver.WebDriver#isDisplayed
|
315 | */
|
316 | exports.elementIsNotVisible = function elementIsNotVisible(element) {
|
317 | return new WebElementCondition('until element is not visible', function() {
|
318 | return element.isDisplayed().then(v => v ? null : element);
|
319 | });
|
320 | };
|
321 |
|
322 |
|
323 | /**
|
324 | * Creates a condition that will wait for the given element to be enabled.
|
325 | *
|
326 | * @param {!./webdriver.WebElement} element The element to test.
|
327 | * @return {!WebElementCondition} The new condition.
|
328 | * @see webdriver.WebDriver#isEnabled
|
329 | */
|
330 | exports.elementIsEnabled = function elementIsEnabled(element) {
|
331 | return new WebElementCondition('until element is enabled', function() {
|
332 | return element.isEnabled().then(v => v ? element : null);
|
333 | });
|
334 | };
|
335 |
|
336 |
|
337 | /**
|
338 | * Creates a condition that will wait for the given element to be disabled.
|
339 | *
|
340 | * @param {!./webdriver.WebElement} element The element to test.
|
341 | * @return {!WebElementCondition} The new condition.
|
342 | * @see webdriver.WebDriver#isEnabled
|
343 | */
|
344 | exports.elementIsDisabled = function elementIsDisabled(element) {
|
345 | return new WebElementCondition('until element is disabled', function() {
|
346 | return element.isEnabled().then(v => v ? null : element);
|
347 | });
|
348 | };
|
349 |
|
350 |
|
351 | /**
|
352 | * Creates a condition that will wait for the given element to be selected.
|
353 | * @param {!./webdriver.WebElement} element The element to test.
|
354 | * @return {!WebElementCondition} The new condition.
|
355 | * @see webdriver.WebDriver#isSelected
|
356 | */
|
357 | exports.elementIsSelected = function elementIsSelected(element) {
|
358 | return new WebElementCondition('until element is selected', function() {
|
359 | return element.isSelected().then(v => v ? element : null);
|
360 | });
|
361 | };
|
362 |
|
363 |
|
364 | /**
|
365 | * Creates a condition that will wait for the given element to be deselected.
|
366 | *
|
367 | * @param {!./webdriver.WebElement} element The element to test.
|
368 | * @return {!WebElementCondition} The new condition.
|
369 | * @see webdriver.WebDriver#isSelected
|
370 | */
|
371 | exports.elementIsNotSelected = function elementIsNotSelected(element) {
|
372 | return new WebElementCondition('until element is not selected', function() {
|
373 | return element.isSelected().then(v => v ? null : element);
|
374 | });
|
375 | };
|
376 |
|
377 |
|
378 | /**
|
379 | * Creates a condition that will wait for the given element's
|
380 | * {@link webdriver.WebDriver#getText visible text} to match the given
|
381 | * {@code text} exactly.
|
382 | *
|
383 | * @param {!./webdriver.WebElement} element The element to test.
|
384 | * @param {string} text The expected text.
|
385 | * @return {!WebElementCondition} The new condition.
|
386 | * @see webdriver.WebDriver#getText
|
387 | */
|
388 | exports.elementTextIs = function elementTextIs(element, text) {
|
389 | return new WebElementCondition('until element text is', function() {
|
390 | return element.getText().then(t => t === text ? element : null);
|
391 | });
|
392 | };
|
393 |
|
394 |
|
395 | /**
|
396 | * Creates a condition that will wait for the given element's
|
397 | * {@link webdriver.WebDriver#getText visible text} to contain the given
|
398 | * substring.
|
399 | *
|
400 | * @param {!./webdriver.WebElement} element The element to test.
|
401 | * @param {string} substr The substring to search for.
|
402 | * @return {!WebElementCondition} The new condition.
|
403 | * @see webdriver.WebDriver#getText
|
404 | */
|
405 | exports.elementTextContains = function elementTextContains(element, substr) {
|
406 | return new WebElementCondition('until element text contains', function() {
|
407 | return element.getText()
|
408 | .then(t => t.indexOf(substr) != -1 ? element : null);
|
409 | });
|
410 | };
|
411 |
|
412 |
|
413 | /**
|
414 | * Creates a condition that will wait for the given element's
|
415 | * {@link webdriver.WebDriver#getText visible text} to match a regular
|
416 | * expression.
|
417 | *
|
418 | * @param {!./webdriver.WebElement} element The element to test.
|
419 | * @param {!RegExp} regex The regular expression to test against.
|
420 | * @return {!WebElementCondition} The new condition.
|
421 | * @see webdriver.WebDriver#getText
|
422 | */
|
423 | exports.elementTextMatches = function elementTextMatches(element, regex) {
|
424 | return new WebElementCondition('until element text matches', function() {
|
425 | return element.getText().then(t => regex.test(t) ? element : null);
|
426 | });
|
427 | };
|