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