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