UNPKG

13.9 kBJavaScriptView Raw
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'use strict'
43
44const by = require('./by')
45const error = require('./error')
46const 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 */
70exports.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 */
110exports.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 */
139exports.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 */
157exports.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 */
175exports.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 */
190exports.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 */
208exports.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 */
226exports.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 */
241exports.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 */
263exports.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 */
285exports.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 */
308exports.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 */
322exports.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 */
335exports.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 */
348exports.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 */
360exports.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 */
373exports.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 */
389exports.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 */
405exports.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 */
423exports.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}