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 error = require('./error')
|
46 | const webdriver = require('./webdriver'),
|
47 | Condition = webdriver.Condition,
|
48 | WebElementCondition = webdriver.WebElementCondition
|
49 |
|
50 | /**
|
51 | * Creates a condition that will wait until the input driver is able to switch
|
52 | * to the designated frame. The target frame may be specified as
|
53 | *
|
54 | * 1. a numeric index into
|
55 | * [window.frames](https://developer.mozilla.org/en-US/docs/Web/API/Window.frames)
|
56 | * for the currently selected frame.
|
57 | * 2. a {@link ./webdriver.WebElement}, which must reference a FRAME or IFRAME
|
58 | * element on the current page.
|
59 | * 3. a locator which may be used to first locate a FRAME or IFRAME on the
|
60 | * current page before attempting to switch to it.
|
61 | *
|
62 | * Upon successful resolution of this condition, the driver will be left
|
63 | * focused on the new frame.
|
64 | *
|
65 | * @param {!(number|./webdriver.WebElement|By|
|
66 | * function(!./webdriver.WebDriver): !./webdriver.WebElement)} frame
|
67 | * The frame identifier.
|
68 | * @return {!Condition<boolean>} A new condition.
|
69 | */
|
70 | exports.ableToSwitchToFrame = function ableToSwitchToFrame(frame) {
|
71 | let condition
|
72 | if (typeof frame === 'number' || frame instanceof webdriver.WebElement) {
|
73 | condition = (driver) => attemptToSwitchFrames(driver, frame)
|
74 | } else {
|
75 | condition = function (driver) {
|
76 | let locator = /** @type {!(By|Function)} */ (frame)
|
77 | return driver.findElements(locator).then(function (els) {
|
78 | if (els.length) {
|
79 | return attemptToSwitchFrames(driver, els[0])
|
80 | }
|
81 | })
|
82 | }
|
83 | }
|
84 |
|
85 | return new Condition('to be able to switch to frame', condition)
|
86 |
|
87 | function attemptToSwitchFrames(driver, frame) {
|
88 | return driver
|
89 | .switchTo()
|
90 | .frame(frame)
|
91 | .then(
|
92 | function () {
|
93 | return true
|
94 | },
|
95 | function (e) {
|
96 | if (!(e instanceof error.NoSuchFrameError)) {
|
97 | throw e
|
98 | }
|
99 | }
|
100 | )
|
101 | }
|
102 | }
|
103 |
|
104 | /**
|
105 | * Creates a condition that waits for an alert to be opened. Upon success, the
|
106 | * returned promise will be fulfilled with the handle for the opened alert.
|
107 | *
|
108 | * @return {!Condition<!./webdriver.Alert>} The new condition.
|
109 | */
|
110 | exports.alertIsPresent = function alertIsPresent() {
|
111 | return new Condition('for alert to be present', function (driver) {
|
112 | return driver
|
113 | .switchTo()
|
114 | .alert()
|
115 | .catch(function (e) {
|
116 | if (
|
117 | !(
|
118 | e instanceof error.NoSuchAlertError ||
|
119 | // XXX: Workaround for GeckoDriver error `TypeError: can't convert null
|
120 | // to object`. For more details, see
|
121 | // https://github.com/SeleniumHQ/selenium/pull/2137
|
122 | (e instanceof error.WebDriverError &&
|
123 | e.message === `can't convert null to object`)
|
124 | )
|
125 | ) {
|
126 | throw e
|
127 | }
|
128 | })
|
129 | })
|
130 | }
|
131 |
|
132 | /**
|
133 | * Creates a condition that will wait for the current page's title to match the
|
134 | * given value.
|
135 | *
|
136 | * @param {string} title The expected page title.
|
137 | * @return {!Condition<boolean>} The new condition.
|
138 | */
|
139 | exports.titleIs = function titleIs(title) {
|
140 | return new Condition('for title to be ' + JSON.stringify(title), function (
|
141 | driver
|
142 | ) {
|
143 | return driver.getTitle().then(function (t) {
|
144 | return t === title
|
145 | })
|
146 | })
|
147 | }
|
148 |
|
149 | /**
|
150 | * Creates a condition that will wait for the current page's title to contain
|
151 | * the given substring.
|
152 | *
|
153 | * @param {string} substr The substring that should be present in the page
|
154 | * title.
|
155 | * @return {!Condition<boolean>} The new condition.
|
156 | */
|
157 | exports.titleContains = function titleContains(substr) {
|
158 | return new Condition(
|
159 | 'for title to contain ' + JSON.stringify(substr),
|
160 | function (driver) {
|
161 | return driver.getTitle().then(function (title) {
|
162 | return title.indexOf(substr) !== -1
|
163 | })
|
164 | }
|
165 | )
|
166 | }
|
167 |
|
168 | /**
|
169 | * Creates a condition that will wait for the current page's title to match the
|
170 | * given regular expression.
|
171 | *
|
172 | * @param {!RegExp} regex The regular expression to test against.
|
173 | * @return {!Condition<boolean>} The new condition.
|
174 | */
|
175 | exports.titleMatches = function titleMatches(regex) {
|
176 | return new Condition('for title to match ' + regex, function (driver) {
|
177 | return driver.getTitle().then(function (title) {
|
178 | return regex.test(title)
|
179 | })
|
180 | })
|
181 | }
|
182 |
|
183 | /**
|
184 | * Creates a condition that will wait for the current page's url to match the
|
185 | * given value.
|
186 | *
|
187 | * @param {string} url The expected page url.
|
188 | * @return {!Condition<boolean>} The new condition.
|
189 | */
|
190 | exports.urlIs = function urlIs(url) {
|
191 | return new Condition('for URL to be ' + JSON.stringify(url), function (
|
192 | driver
|
193 | ) {
|
194 | return driver.getCurrentUrl().then(function (u) {
|
195 | return u === url
|
196 | })
|
197 | })
|
198 | }
|
199 |
|
200 | /**
|
201 | * Creates a condition that will wait for the current page's url to contain
|
202 | * the given substring.
|
203 | *
|
204 | * @param {string} substrUrl The substring that should be present in the current
|
205 | * URL.
|
206 | * @return {!Condition<boolean>} The new condition.
|
207 | */
|
208 | exports.urlContains = function urlContains(substrUrl) {
|
209 | return new Condition(
|
210 | 'for URL to contain ' + JSON.stringify(substrUrl),
|
211 | function (driver) {
|
212 | return driver.getCurrentUrl().then(function (url) {
|
213 | return url && url.indexOf(substrUrl) !== -1
|
214 | })
|
215 | }
|
216 | )
|
217 | }
|
218 |
|
219 | /**
|
220 | * Creates a condition that will wait for the current page's url to match the
|
221 | * given regular expression.
|
222 | *
|
223 | * @param {!RegExp} regex The regular expression to test against.
|
224 | * @return {!Condition<boolean>} The new condition.
|
225 | */
|
226 | exports.urlMatches = function urlMatches(regex) {
|
227 | return new Condition('for URL to match ' + regex, function (driver) {
|
228 | return driver.getCurrentUrl().then(function (url) {
|
229 | return regex.test(url)
|
230 | })
|
231 | })
|
232 | }
|
233 |
|
234 | /**
|
235 | * Creates a condition that will loop until an element is
|
236 | * {@link ./webdriver.WebDriver#findElement found} with the given locator.
|
237 | *
|
238 | * @param {!(By|Function)} locator The locator to use.
|
239 | * @return {!WebElementCondition} The new condition.
|
240 | */
|
241 | exports.elementLocated = function elementLocated(locator) {
|
242 | locator = by.checkedLocator(locator)
|
243 | let locatorStr =
|
244 | typeof locator === 'function' ? 'by function()' : locator + ''
|
245 | return new WebElementCondition(
|
246 | 'for element to be located ' + locatorStr,
|
247 | function (driver) {
|
248 | return driver.findElements(locator).then(function (elements) {
|
249 | return elements[0]
|
250 | })
|
251 | }
|
252 | )
|
253 | }
|
254 |
|
255 | /**
|
256 | * Creates a condition that will loop until at least one element is
|
257 | * {@link ./webdriver.WebDriver#findElement found} with the given locator.
|
258 | *
|
259 | * @param {!(By|Function)} locator The locator to use.
|
260 | * @return {!Condition<!Array<!./webdriver.WebElement>>} The new
|
261 | * condition.
|
262 | */
|
263 | exports.elementsLocated = function elementsLocated(locator) {
|
264 | locator = by.checkedLocator(locator)
|
265 | let locatorStr =
|
266 | typeof locator === 'function' ? 'by function()' : locator + ''
|
267 | return new Condition(
|
268 | 'for at least one element to be located ' + locatorStr,
|
269 | function (driver) {
|
270 | return driver.findElements(locator).then(function (elements) {
|
271 | return elements.length > 0 ? elements : null
|
272 | })
|
273 | }
|
274 | )
|
275 | }
|
276 |
|
277 | /**
|
278 | * Creates a condition that will wait for the given element to become stale. An
|
279 | * element is considered stale once it is removed from the DOM, or a new page
|
280 | * has loaded.
|
281 | *
|
282 | * @param {!./webdriver.WebElement} element The element that should become stale.
|
283 | * @return {!Condition<boolean>} The new condition.
|
284 | */
|
285 | exports.stalenessOf = function stalenessOf(element) {
|
286 | return new Condition('element to become stale', function () {
|
287 | return element.getTagName().then(
|
288 | function () {
|
289 | return false
|
290 | },
|
291 | function (e) {
|
292 | if (e instanceof error.StaleElementReferenceError) {
|
293 | return true
|
294 | }
|
295 | throw e
|
296 | }
|
297 | )
|
298 | })
|
299 | }
|
300 |
|
301 | /**
|
302 | * Creates a condition that will wait for the given element to become visible.
|
303 | *
|
304 | * @param {!./webdriver.WebElement} element The element to test.
|
305 | * @return {!WebElementCondition} The new condition.
|
306 | * @see ./webdriver.WebDriver#isDisplayed
|
307 | */
|
308 | exports.elementIsVisible = function elementIsVisible(element) {
|
309 | return new WebElementCondition('until element is visible', function () {
|
310 | return element.isDisplayed().then((v) => (v ? element : null))
|
311 | })
|
312 | }
|
313 |
|
314 | /**
|
315 | * Creates a condition that will wait for the given element to be in the DOM,
|
316 | * yet not visible to the user.
|
317 | *
|
318 | * @param {!./webdriver.WebElement} element The element to test.
|
319 | * @return {!WebElementCondition} The new condition.
|
320 | * @see ./webdriver.WebDriver#isDisplayed
|
321 | */
|
322 | exports.elementIsNotVisible = function elementIsNotVisible(element) {
|
323 | return new WebElementCondition('until element is not visible', function () {
|
324 | return element.isDisplayed().then((v) => (v ? null : element))
|
325 | })
|
326 | }
|
327 |
|
328 | /**
|
329 | * Creates a condition that will wait for the given element to be enabled.
|
330 | *
|
331 | * @param {!./webdriver.WebElement} element The element to test.
|
332 | * @return {!WebElementCondition} The new condition.
|
333 | * @see webdriver.WebDriver#isEnabled
|
334 | */
|
335 | exports.elementIsEnabled = function elementIsEnabled(element) {
|
336 | return new WebElementCondition('until element is enabled', function () {
|
337 | return element.isEnabled().then((v) => (v ? element : null))
|
338 | })
|
339 | }
|
340 |
|
341 | /**
|
342 | * Creates a condition that will wait for the given element to be disabled.
|
343 | *
|
344 | * @param {!./webdriver.WebElement} element The element to test.
|
345 | * @return {!WebElementCondition} The new condition.
|
346 | * @see webdriver.WebDriver#isEnabled
|
347 | */
|
348 | exports.elementIsDisabled = function elementIsDisabled(element) {
|
349 | return new WebElementCondition('until element is disabled', function () {
|
350 | return element.isEnabled().then((v) => (v ? null : element))
|
351 | })
|
352 | }
|
353 |
|
354 | /**
|
355 | * Creates a condition that will wait for the given element to be selected.
|
356 | * @param {!./webdriver.WebElement} element The element to test.
|
357 | * @return {!WebElementCondition} The new condition.
|
358 | * @see webdriver.WebDriver#isSelected
|
359 | */
|
360 | exports.elementIsSelected = function elementIsSelected(element) {
|
361 | return new WebElementCondition('until element is selected', function () {
|
362 | return element.isSelected().then((v) => (v ? element : null))
|
363 | })
|
364 | }
|
365 |
|
366 | /**
|
367 | * Creates a condition that will wait for the given element to be deselected.
|
368 | *
|
369 | * @param {!./webdriver.WebElement} element The element to test.
|
370 | * @return {!WebElementCondition} The new condition.
|
371 | * @see webdriver.WebDriver#isSelected
|
372 | */
|
373 | exports.elementIsNotSelected = function elementIsNotSelected(element) {
|
374 | return new WebElementCondition('until element is not selected', function () {
|
375 | return element.isSelected().then((v) => (v ? null : element))
|
376 | })
|
377 | }
|
378 |
|
379 | /**
|
380 | * Creates a condition that will wait for the given element's
|
381 | * {@link webdriver.WebDriver#getText visible text} to match the given
|
382 | * {@code text} exactly.
|
383 | *
|
384 | * @param {!./webdriver.WebElement} element The element to test.
|
385 | * @param {string} text The expected text.
|
386 | * @return {!WebElementCondition} The new condition.
|
387 | * @see webdriver.WebDriver#getText
|
388 | */
|
389 | exports.elementTextIs = function elementTextIs(element, text) {
|
390 | return new WebElementCondition('until element text is', function () {
|
391 | return element.getText().then((t) => (t === text ? element : null))
|
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
|
408 | .getText()
|
409 | .then((t) => (t.indexOf(substr) != -1 ? element : null))
|
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 | }
|