# 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 |
39 | goog.provide('webdriver.until');
40 |
41 | goog.require('bot.ErrorCode');
42 | goog.require('goog.array');
43 | goog.require('goog.string');
44 |
45 |
46 |
47 | goog.scope(function() {
48 |
49 | var 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 | */
63 | until.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. */
73 | until.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 | */
99 | until.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 | */
135 | until.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 | */
153 | until.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 | */
172 | until.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 | */
190 | until.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 | */
207 | until.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 | */
227 | until.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 | */
247 | until.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 | */
268 | until.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 | */
283 | until.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 | */
299 | until.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 | */
313 | until.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 | */
328 | until.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 | */
342 | until.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 | */
361 | until.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 | */
380 | until.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 | */
399 | until.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