lib/webdriver/until.js

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