lib/webdriver/until.js

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