# 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 |
42 | goog.provide('webdriver.until');
43 |
44 | goog.require('bot.ErrorCode');
45 | goog.require('goog.array');
46 | goog.require('goog.string');
47 | goog.require('webdriver.Locator');
48 |
49 |
50 |
51 | goog.scope(function() {
52 |
53 | var 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 | */
67 | until.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. */
77 | until.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 | */
103 | until.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 | */
139 | until.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 | */
157 | until.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 | */
176 | until.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 | */
194 | until.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 | */
211 | until.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 | */
232 | until.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 | */
253 | until.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 | */
274 | until.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 | */
289 | until.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 | */
305 | until.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 | */
319 | until.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 | */
334 | until.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 | */
348 | until.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 | */
367 | until.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 | */
386 | until.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 | */
405 | until.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