# lib/webdriver/webdriver.js

1 | // Copyright 2011 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 The heart of the WebDriver JavaScript API.
17 | */
18 |
19 | goog.provide('webdriver.Alert');
20 | goog.provide('webdriver.AlertPromise');
21 | goog.provide('webdriver.FileDetector');
22 | goog.provide('webdriver.UnhandledAlertError');
23 | goog.provide('webdriver.WebDriver');
24 | goog.provide('webdriver.WebElement');
25 | goog.provide('webdriver.WebElementPromise');
26 |
27 | goog.require('bot.Error');
28 | goog.require('bot.ErrorCode');
29 | goog.require('bot.response');
30 | goog.require('goog.array');
31 | goog.require('goog.object');
32 | goog.require('webdriver.ActionSequence');
33 | goog.require('webdriver.Command');
34 | goog.require('webdriver.CommandName');
35 | goog.require('webdriver.Key');
36 | goog.require('webdriver.Locator');
37 | goog.require('webdriver.Serializable');
38 | goog.require('webdriver.Session');
39 | goog.require('webdriver.TouchSequence');
40 | goog.require('webdriver.logging');
41 | goog.require('webdriver.promise');
42 | goog.require('webdriver.until');
43 | goog.require('webdriver.promise.Thenable');
44 |
45 |
46 | //////////////////////////////////////////////////////////////////////////////
47 | //
48 | // webdriver.WebDriver
49 | //
50 | //////////////////////////////////////////////////////////////////////////////
51 |
52 |
53 |
54 | /**
55 | * Creates a new WebDriver client, which provides control over a browser.
56 | *
57 | * Every WebDriver command returns a {@code webdriver.promise.Promise} that
58 | * represents the result of that command. Callbacks may be registered on this
59 | * object to manipulate the command result or catch an expected error. Any
60 | * commands scheduled with a callback are considered sub-commands and will
61 | * execute before the next command in the current frame. For example:
62 | *
63 | * var message = [];
64 | * driver.call(message.push, message, 'a').then(function() {
65 | * driver.call(message.push, message, 'b');
66 | * });
67 | * driver.call(message.push, message, 'c');
68 | * driver.call(function() {
69 | * alert('message is abc? ' + (message.join('') == 'abc'));
70 | * });
71 | *
72 | * @param {!(webdriver.Session|webdriver.promise.Promise)} session Either a
73 | * known session or a promise that will be resolved to a session.
74 | * @param {!webdriver.CommandExecutor} executor The executor to use when
75 | * sending commands to the browser.
76 | * @param {webdriver.promise.ControlFlow=} opt_flow The flow to
77 | * schedule commands through. Defaults to the active flow object.
78 | * @constructor
79 | */
80 | webdriver.WebDriver = function(session, executor, opt_flow) {
81 |
82 | /** @private {!(webdriver.Session|webdriver.promise.Promise)} */
83 | this.session_ = session;
84 |
85 | /** @private {!webdriver.CommandExecutor} */
86 | this.executor_ = executor;
87 |
88 | /** @private {!webdriver.promise.ControlFlow} */
89 | this.flow_ = opt_flow || webdriver.promise.controlFlow();
90 |
91 | /** @private {webdriver.FileDetector} */
92 | this.fileDetector_ = null;
93 | };
94 |
95 |
96 | /**
97 | * Creates a new WebDriver client for an existing session.
98 | * @param {!webdriver.CommandExecutor} executor Command executor to use when
99 | * querying for session details.
100 | * @param {string} sessionId ID of the session to attach to.
101 | * @param {webdriver.promise.ControlFlow=} opt_flow The control flow all driver
102 | * commands should execute under. Defaults to the
103 | * {@link webdriver.promise.controlFlow() currently active} control flow.
104 | * @return {!webdriver.WebDriver} A new client for the specified session.
105 | */
106 | webdriver.WebDriver.attachToSession = function(executor, sessionId, opt_flow) {
107 | return webdriver.WebDriver.acquireSession_(executor,
108 | new webdriver.Command(webdriver.CommandName.DESCRIBE_SESSION).
109 | setParameter('sessionId', sessionId),
110 | 'WebDriver.attachToSession()',
111 | opt_flow);
112 | };
113 |
114 |
115 | /**
116 | * Creates a new WebDriver session.
117 | * @param {!webdriver.CommandExecutor} executor The executor to create the new
118 | * session with.
119 | * @param {!webdriver.Capabilities} desiredCapabilities The desired
120 | * capabilities for the new session.
121 | * @param {webdriver.promise.ControlFlow=} opt_flow The control flow all driver
122 | * commands should execute under, including the initial session creation.
123 | * Defaults to the {@link webdriver.promise.controlFlow() currently active}
124 | * control flow.
125 | * @return {!webdriver.WebDriver} The driver for the newly created session.
126 | */
127 | webdriver.WebDriver.createSession = function(
128 | executor, desiredCapabilities, opt_flow) {
129 | return webdriver.WebDriver.acquireSession_(executor,
130 | new webdriver.Command(webdriver.CommandName.NEW_SESSION).
131 | setParameter('desiredCapabilities', desiredCapabilities),
132 | 'WebDriver.createSession()',
133 | opt_flow);
134 | };
135 |
136 |
137 | /**
138 | * Sends a command to the server that is expected to return the details for a
139 | * {@link webdriver.Session}. This may either be an existing session, or a
140 | * newly created one.
141 | * @param {!webdriver.CommandExecutor} executor Command executor to use when
142 | * querying for session details.
143 | * @param {!webdriver.Command} command The command to send to fetch the session
144 | * details.
145 | * @param {string} description A descriptive debug label for this action.
146 | * @param {webdriver.promise.ControlFlow=} opt_flow The control flow all driver
147 | * commands should execute under. Defaults to the
148 | * {@link webdriver.promise.controlFlow() currently active} control flow.
149 | * @return {!webdriver.WebDriver} A new WebDriver client for the session.
150 | * @private
151 | */
152 | webdriver.WebDriver.acquireSession_ = function(
153 | executor, command, description, opt_flow) {
154 | var flow = opt_flow || webdriver.promise.controlFlow();
155 | var session = flow.execute(function() {
156 | return webdriver.WebDriver.executeCommand_(executor, command).
157 | then(function(response) {
158 | bot.response.checkResponse(response);
159 | return new webdriver.Session(response['sessionId'],
160 | response['value']);
161 | });
162 | }, description);
163 | return new webdriver.WebDriver(session, executor, flow);
164 | };
165 |
166 |
167 | /**
168 | * Converts an object to its JSON representation in the WebDriver wire protocol.
169 | * When converting values of type object, the following steps will be taken:
170 | * <ol>
171 | * <li>if the object is a WebElement, the return value will be the element's
172 | * server ID
173 | * <li>if the object is a Serializable, its
174 | * {@link webdriver.Serializable#serialize} function will be invoked and
175 | * this algorithm will recursively be applied to the result
176 | * <li>if the object provides a "toJSON" function, this algorithm will
177 | * recursively be applied to the result of that function
178 | * <li>otherwise, the value of each key will be recursively converted according
179 | * to the rules above.
180 | * </ol>
181 | *
182 | * @param {*} obj The object to convert.
183 | * @return {!webdriver.promise.Promise.<?>} A promise that will resolve to the
184 | * input value's JSON representation.
185 | * @private
186 | * @see http://code.google.com/p/selenium/wiki/JsonWireProtocol
187 | */
188 | webdriver.WebDriver.toWireValue_ = function(obj) {
189 | if (webdriver.promise.isPromise(obj)) {
190 | return obj.then(webdriver.WebDriver.toWireValue_);
191 | }
192 | return webdriver.promise.fulfilled(convertValue(obj));
193 |
194 | function convertValue(value) {
195 | switch (goog.typeOf(value)) {
196 | case 'array':
197 | return convertKeys(value, true);
198 | case 'object':
199 | // NB: WebElement is a Serializable, but we know its serialized form
200 | // is a promise for its wire format. This is a micro optimization to
201 | // avoid creating extra promises by recursing on the promised id.
202 | if (value instanceof webdriver.WebElement) {
203 | return value.getId();
204 | }
205 | if (value instanceof webdriver.Serializable) {
206 | return webdriver.WebDriver.toWireValue_(value.serialize());
207 | }
208 | if (goog.isFunction(value.toJSON)) {
209 | return webdriver.WebDriver.toWireValue_(value.toJSON());
210 | }
211 | if (goog.isNumber(value.nodeType) && goog.isString(value.nodeName)) {
212 | throw new TypeError(
213 | 'Invalid argument type: ' + value.nodeName +
214 | '(' + value.nodeType + ')');
215 | }
216 | return convertKeys(value, false);
217 | case 'function':
218 | return '' + value;
219 | case 'undefined':
220 | return null;
221 | default:
222 | return value;
223 | }
224 | }
225 |
226 | function convertKeys(obj, isArray) {
227 | var numKeys = isArray ? obj.length : goog.object.getCount(obj);
228 | var ret = isArray ? new Array(numKeys) : {};
229 | if (!numKeys) {
230 | return webdriver.promise.fulfilled(ret);
231 | }
232 |
233 | var numResolved = 0;
234 | var done = webdriver.promise.defer();
235 |
236 | // forEach will stop iteration at undefined, where we want to convert
237 | // these to null and keep iterating.
238 | var forEachKey = !isArray ? goog.object.forEach : function(arr, fn) {
239 | var n = arr.length;
240 | for (var i = 0; i < n; i++) {
241 | fn(arr[i], i);
242 | }
243 | };
244 |
245 | forEachKey(obj, function(value, key) {
246 | if (webdriver.promise.isPromise(value)) {
247 | value.then(webdriver.WebDriver.toWireValue_).
248 | then(setValue, done.reject);
249 | } else {
250 | webdriver.promise.asap(convertValue(value), setValue, done.reject);
251 | }
252 |
253 | function setValue(value) {
254 | ret[key] = value;
255 | maybeFulfill();
256 | }
257 | });
258 |
259 | return done.promise;
260 |
261 | function maybeFulfill() {
262 | if (++numResolved === numKeys) {
263 | done.fulfill(ret);
264 | }
265 | }
266 | }
267 | };
268 |
269 |
270 | /**
271 | * Converts a value from its JSON representation according to the WebDriver wire
272 | * protocol. Any JSON object containing a
273 | * {@code webdriver.WebElement.ELEMENT_KEY} key will be decoded to a
274 | * {@code webdriver.WebElement} object. All other values will be passed through
275 | * as is.
276 | * @param {!webdriver.WebDriver} driver The driver instance to use as the
277 | * parent of any unwrapped {@code webdriver.WebElement} values.
278 | * @param {*} value The value to convert.
279 | * @return {*} The converted value.
280 | * @see http://code.google.com/p/selenium/wiki/JsonWireProtocol
281 | * @private
282 | */
283 | webdriver.WebDriver.fromWireValue_ = function(driver, value) {
284 | if (goog.isArray(value)) {
285 | value = goog.array.map(/**@type {goog.array.ArrayLike}*/ (value),
286 | goog.partial(webdriver.WebDriver.fromWireValue_, driver));
287 | } else if (value && goog.isObject(value) && !goog.isFunction(value)) {
288 | if (webdriver.WebElement.ELEMENT_KEY in value) {
289 | value = new webdriver.WebElement(driver, value);
290 | } else {
291 | value = goog.object.map(/**@type {!Object}*/ (value),
292 | goog.partial(webdriver.WebDriver.fromWireValue_, driver));
293 | }
294 | }
295 | return value;
296 | };
297 |
298 |
299 | /**
300 | * Translates a command to its wire-protocol representation before passing it
301 | * to the given {@code executor} for execution.
302 | * @param {!webdriver.CommandExecutor} executor The executor to use.
303 | * @param {!webdriver.Command} command The command to execute.
304 | * @return {!webdriver.promise.Promise} A promise that will resolve with the
305 | * command response.
306 | * @private
307 | */
308 | webdriver.WebDriver.executeCommand_ = function(executor, command) {
309 | return webdriver.WebDriver.toWireValue_(command.getParameters()).
310 | then(function(parameters) {
311 | command.setParameters(parameters);
312 | return webdriver.promise.checkedNodeCall(
313 | goog.bind(executor.execute, executor, command));
314 | });
315 | };
316 |
317 |
318 | /**
319 | * @return {!webdriver.promise.ControlFlow} The control flow used by this
320 | * instance.
321 | */
322 | webdriver.WebDriver.prototype.controlFlow = function() {
323 | return this.flow_;
324 | };
325 |
326 |
327 | /**
328 | * Schedules a {@code webdriver.Command} to be executed by this driver's
329 | * {@code webdriver.CommandExecutor}.
330 | * @param {!webdriver.Command} command The command to schedule.
331 | * @param {string} description A description of the command for debugging.
332 | * @return {!webdriver.promise.Promise.<T>} A promise that will be resolved
333 | * with the command result.
334 | * @template T
335 | */
336 | webdriver.WebDriver.prototype.schedule = function(command, description) {
337 | var self = this;
338 |
339 | checkHasNotQuit();
340 | command.setParameter('sessionId', this.session_);
341 |
342 | // If any of the command parameters are rejected promises, those
343 | // rejections may be reported as unhandled before the control flow
344 | // attempts to execute the command. To ensure parameters errors
345 | // propagate through the command itself, we resolve all of the
346 | // command parameters now, but suppress any errors until the ControlFlow
347 | // actually executes the command. This addresses scenarios like catching
348 | // an element not found error in:
349 | //
350 | // driver.findElement(By.id('foo')).click().thenCatch(function(e) {
351 | // if (e.code === bot.ErrorCode.NO_SUCH_ELEMENT) {
352 | // // Do something.
353 | // }
354 | // });
355 | var prepCommand = webdriver.WebDriver.toWireValue_(command.getParameters());
356 | prepCommand.thenCatch(goog.nullFunction);
357 |
358 | var flow = this.flow_;
359 | var executor = this.executor_;
360 | return flow.execute(function() {
361 | // A call to WebDriver.quit() may have been scheduled in the same event
362 | // loop as this |command|, which would prevent us from detecting that the
363 | // driver has quit above. Therefore, we need to make another quick check.
364 | // We still check above so we can fail as early as possible.
365 | checkHasNotQuit();
366 |
367 | // Retrieve resolved command parameters; any previously suppressed errors
368 | // will now propagate up through the control flow as part of the command
369 | // execution.
370 | return prepCommand.then(function(parameters) {
371 | command.setParameters(parameters);
372 | return webdriver.promise.checkedNodeCall(
373 | goog.bind(executor.execute, executor, command));
374 | });
375 | }, description).then(function(response) {
376 | try {
377 | bot.response.checkResponse(response);
378 | } catch (ex) {
379 | var value = response['value'];
380 | if (ex.code === bot.ErrorCode.UNEXPECTED_ALERT_OPEN) {
381 | var text = value && value['alert'] ? value['alert']['text'] : '';
382 | throw new webdriver.UnhandledAlertError(ex.message, text,
383 | new webdriver.Alert(self, text));
384 | }
385 | throw ex;
386 | }
387 | return webdriver.WebDriver.fromWireValue_(self, response['value']);
388 | });
389 |
390 | function checkHasNotQuit() {
391 | if (!self.session_) {
392 | throw new Error('This driver instance does not have a valid session ID ' +
393 | '(did you call WebDriver.quit()?) and may no longer be ' +
394 | 'used.');
395 | }
396 | }
397 | };
398 |
399 |
400 | /**
401 | * Sets the {@linkplain webdriver.FileDetector file detector} that should be
402 | * used with this instance.
403 | * @param {webdriver.FileDetector} detector The detector to use or {@code null}.
404 | */
405 | webdriver.WebDriver.prototype.setFileDetector = function(detector) {
406 | this.fileDetector_ = detector;
407 | };
408 |
409 |
410 | // ----------------------------------------------------------------------------
411 | // Client command functions:
412 | // ----------------------------------------------------------------------------
413 |
414 |
415 | /**
416 | * @return {!webdriver.promise.Promise.<!webdriver.Session>} A promise for this
417 | * client's session.
418 | */
419 | webdriver.WebDriver.prototype.getSession = function() {
420 | return webdriver.promise.when(this.session_);
421 | };
422 |
423 |
424 | /**
425 | * @return {!webdriver.promise.Promise.<!webdriver.Capabilities>} A promise
426 | * that will resolve with the this instance's capabilities.
427 | */
428 | webdriver.WebDriver.prototype.getCapabilities = function() {
429 | return webdriver.promise.when(this.session_, function(session) {
430 | return session.getCapabilities();
431 | });
432 | };
433 |
434 |
435 | /**
436 | * Schedules a command to quit the current session. After calling quit, this
437 | * instance will be invalidated and may no longer be used to issue commands
438 | * against the browser.
439 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
440 | * when the command has completed.
441 | */
442 | webdriver.WebDriver.prototype.quit = function() {
443 | var result = this.schedule(
444 | new webdriver.Command(webdriver.CommandName.QUIT),
445 | 'WebDriver.quit()');
446 | // Delete our session ID when the quit command finishes; this will allow us to
447 | // throw an error when attemnpting to use a driver post-quit.
448 | return result.thenFinally(goog.bind(function() {
449 | delete this.session_;
450 | }, this));
451 | };
452 |
453 |
454 | /**
455 | * Creates a new action sequence using this driver. The sequence will not be
456 | * scheduled for execution until {@link webdriver.ActionSequence#perform} is
457 | * called. Example:
458 | *
459 | * driver.actions().
460 | * mouseDown(element1).
461 | * mouseMove(element2).
462 | * mouseUp().
463 | * perform();
464 | *
465 | * @return {!webdriver.ActionSequence} A new action sequence for this instance.
466 | */
467 | webdriver.WebDriver.prototype.actions = function() {
468 | return new webdriver.ActionSequence(this);
469 | };
470 |
471 |
472 | /**
473 | * Creates a new touch sequence using this driver. The sequence will not be
474 | * scheduled for execution until {@link webdriver.TouchSequence#perform} is
475 | * called. Example:
476 | *
477 | * driver.touchActions().
478 | * tap(element1).
479 | * doubleTap(element2).
480 | * perform();
481 | *
482 | * @return {!webdriver.TouchSequence} A new touch sequence for this instance.
483 | */
484 | webdriver.WebDriver.prototype.touchActions = function() {
485 | return new webdriver.TouchSequence(this);
486 | };
487 |
488 |
489 | /**
490 | * Schedules a command to execute JavaScript in the context of the currently
491 | * selected frame or window. The script fragment will be executed as the body
492 | * of an anonymous function. If the script is provided as a function object,
493 | * that function will be converted to a string for injection into the target
494 | * window.
495 | *
496 | * Any arguments provided in addition to the script will be included as script
497 | * arguments and may be referenced using the {@code arguments} object.
498 | * Arguments may be a boolean, number, string, or {@code webdriver.WebElement}.
499 | * Arrays and objects may also be used as script arguments as long as each item
500 | * adheres to the types previously mentioned.
501 | *
502 | * The script may refer to any variables accessible from the current window.
503 | * Furthermore, the script will execute in the window's context, thus
504 | * {@code document} may be used to refer to the current document. Any local
505 | * variables will not be available once the script has finished executing,
506 | * though global variables will persist.
507 | *
508 | * If the script has a return value (i.e. if the script contains a return
509 | * statement), then the following steps will be taken for resolving this
510 | * functions return value:
511 | *
512 | * - For a HTML element, the value will resolve to a
513 | * {@link webdriver.WebElement}
514 | * - Null and undefined return values will resolve to null</li>
515 | * - Booleans, numbers, and strings will resolve as is</li>
516 | * - Functions will resolve to their string representation</li>
517 | * - For arrays and objects, each member item will be converted according to
518 | * the rules above
519 | *
520 | * @param {!(string|Function)} script The script to execute.
521 | * @param {...*} var_args The arguments to pass to the script.
522 | * @return {!webdriver.promise.Promise.<T>} A promise that will resolve to the
523 | * scripts return value.
524 | * @template T
525 | */
526 | webdriver.WebDriver.prototype.executeScript = function(script, var_args) {
527 | if (goog.isFunction(script)) {
528 | script = 'return (' + script + ').apply(null, arguments);';
529 | }
530 | var args = arguments.length > 1 ? goog.array.slice(arguments, 1) : [];
531 | return this.schedule(
532 | new webdriver.Command(webdriver.CommandName.EXECUTE_SCRIPT).
533 | setParameter('script', script).
534 | setParameter('args', args),
535 | 'WebDriver.executeScript()');
536 | };
537 |
538 |
539 | /**
540 | * Schedules a command to execute asynchronous JavaScript in the context of the
541 | * currently selected frame or window. The script fragment will be executed as
542 | * the body of an anonymous function. If the script is provided as a function
543 | * object, that function will be converted to a string for injection into the
544 | * target window.
545 | *
546 | * Any arguments provided in addition to the script will be included as script
547 | * arguments and may be referenced using the {@code arguments} object.
548 | * Arguments may be a boolean, number, string, or {@code webdriver.WebElement}.
549 | * Arrays and objects may also be used as script arguments as long as each item
550 | * adheres to the types previously mentioned.
551 | *
552 | * Unlike executing synchronous JavaScript with {@link #executeScript},
553 | * scripts executed with this function must explicitly signal they are finished
554 | * by invoking the provided callback. This callback will always be injected
555 | * into the executed function as the last argument, and thus may be referenced
556 | * with {@code arguments[arguments.length - 1]}. The following steps will be
557 | * taken for resolving this functions return value against the first argument
558 | * to the script's callback function:
559 | *
560 | * - For a HTML element, the value will resolve to a
561 | * {@link webdriver.WebElement}
562 | * - Null and undefined return values will resolve to null
563 | * - Booleans, numbers, and strings will resolve as is
564 | * - Functions will resolve to their string representation
565 | * - For arrays and objects, each member item will be converted according to
566 | * the rules above
567 | *
568 | * __Example #1:__ Performing a sleep that is synchronized with the currently
569 | * selected window:
570 | *
571 | * var start = new Date().getTime();
572 | * driver.executeAsyncScript(
573 | * 'window.setTimeout(arguments[arguments.length - 1], 500);').
574 | * then(function() {
575 | * console.log(
576 | * 'Elapsed time: ' + (new Date().getTime() - start) + ' ms');
577 | * });
578 | *
579 | * __Example #2:__ Synchronizing a test with an AJAX application:
580 | *
581 | * var button = driver.findElement(By.id('compose-button'));
582 | * button.click();
583 | * driver.executeAsyncScript(
584 | * 'var callback = arguments[arguments.length - 1];' +
585 | * 'mailClient.getComposeWindowWidget().onload(callback);');
586 | * driver.switchTo().frame('composeWidget');
587 | * driver.findElement(By.id('to')).sendKeys('dog@example.com');
588 | *
589 | * __Example #3:__ Injecting a XMLHttpRequest and waiting for the result. In
590 | * this example, the inject script is specified with a function literal. When
591 | * using this format, the function is converted to a string for injection, so it
592 | * should not reference any symbols not defined in the scope of the page under
593 | * test.
594 | *
595 | * driver.executeAsyncScript(function() {
596 | * var callback = arguments[arguments.length - 1];
597 | * var xhr = new XMLHttpRequest();
598 | * xhr.open("GET", "/resource/data.json", true);
599 | * xhr.onreadystatechange = function() {
600 | * if (xhr.readyState == 4) {
601 | * callback(xhr.responseText);
602 | * }
603 | * }
604 | * xhr.send('');
605 | * }).then(function(str) {
606 | * console.log(JSON.parse(str)['food']);
607 | * });
608 | *
609 | * @param {!(string|Function)} script The script to execute.
610 | * @param {...*} var_args The arguments to pass to the script.
611 | * @return {!webdriver.promise.Promise.<T>} A promise that will resolve to the
612 | * scripts return value.
613 | * @template T
614 | */
615 | webdriver.WebDriver.prototype.executeAsyncScript = function(script, var_args) {
616 | if (goog.isFunction(script)) {
617 | script = 'return (' + script + ').apply(null, arguments);';
618 | }
619 | return this.schedule(
620 | new webdriver.Command(webdriver.CommandName.EXECUTE_ASYNC_SCRIPT).
621 | setParameter('script', script).
622 | setParameter('args', goog.array.slice(arguments, 1)),
623 | 'WebDriver.executeScript()');
624 | };
625 |
626 |
627 | /**
628 | * Schedules a command to execute a custom function.
629 | * @param {function(...): (T|webdriver.promise.Promise.<T>)} fn The function to
630 | * execute.
631 | * @param {Object=} opt_scope The object in whose scope to execute the function.
632 | * @param {...*} var_args Any arguments to pass to the function.
633 | * @return {!webdriver.promise.Promise.<T>} A promise that will be resolved'
634 | * with the function's result.
635 | * @template T
636 | */
637 | webdriver.WebDriver.prototype.call = function(fn, opt_scope, var_args) {
638 | var args = goog.array.slice(arguments, 2);
639 | var flow = this.flow_;
640 | return flow.execute(function() {
641 | return webdriver.promise.fullyResolved(args).then(function(args) {
642 | if (webdriver.promise.isGenerator(fn)) {
643 | args.unshift(fn, opt_scope);
644 | return webdriver.promise.consume.apply(null, args);
645 | }
646 | return fn.apply(opt_scope, args);
647 | });
648 | }, 'WebDriver.call(' + (fn.name || 'function') + ')');
649 | };
650 |
651 |
652 | /**
653 | * Schedules a command to wait for a condition to hold. The condition may be
654 | * specified by a {@link webdriver.until.Condition}, as a custom function, or
655 | * as a {@link webdriver.promise.Promise}.
656 | *
657 | * For a {@link webdriver.until.Condition} or function, the wait will repeatedly
658 | * evaluate the condition until it returns a truthy value. If any errors occur
659 | * while evaluating the condition, they will be allowed to propagate. In the
660 | * event a condition returns a {@link webdriver.promise.Promise promise}, the
661 | * polling loop will wait for it to be resolved and use the resolved value for
662 | * whether the condition has been satisified. Note the resolution time for
663 | * a promise is factored into whether a wait has timed out.
664 | *
665 | * *Example:* waiting up to 10 seconds for an element to be present and visible
666 | * on the page.
667 | *
668 | * var button = driver.wait(until.elementLocated(By.id('foo'), 10000);
669 | * button.click();
670 | *
671 | * This function may also be used to block the command flow on the resolution
672 | * of a {@link webdriver.promise.Promise promise}. When given a promise, the
673 | * command will simply wait for its resolution before completing. A timeout may
674 | * be provided to fail the command if the promise does not resolve before the
675 | * timeout expires.
676 | *
677 | * *Example:* Suppose you have a function, `startTestServer`, that returns a
678 | * promise for when a server is ready for requests. You can block a `WebDriver`
679 | * client on this promise with:
680 | *
681 | * var started = startTestServer();
682 | * driver.wait(started, 5 * 1000, 'Server should start within 5 seconds');
683 | * driver.get(getServerUrl());
684 | *
685 | * @param {!(webdriver.promise.Promise<T>|
686 | * webdriver.until.Condition<T>|
687 | * function(!webdriver.WebDriver): T)} condition The condition to
688 | * wait on, defined as a promise, condition object, or a function to
689 | * evaluate as a condition.
690 | * @param {number=} opt_timeout How long to wait for the condition to be true.
691 | * @param {string=} opt_message An optional message to use if the wait times
692 | * out.
693 | * @return {!webdriver.promise.Promise<T>} A promise that will be fulfilled
694 | * with the first truthy value returned by the condition function, or
695 | * rejected if the condition times out.
696 | * @template T
697 | */
698 | webdriver.WebDriver.prototype.wait = function(
699 | condition, opt_timeout, opt_message) {
700 | if (webdriver.promise.isPromise(condition)) {
701 | return this.flow_.wait(
702 | /** @type {!webdriver.promise.Promise} */(condition),
703 | opt_timeout, opt_message);
704 | }
705 |
706 | var message = opt_message;
707 | var fn = /** @type {!Function} */(condition);
708 | if (condition instanceof webdriver.until.Condition) {
709 | message = message || condition.description();
710 | fn = condition.fn;
711 | }
712 |
713 | var driver = this;
714 | return this.flow_.wait(function() {
715 | if (webdriver.promise.isGenerator(fn)) {
716 | return webdriver.promise.consume(fn, null, [driver]);
717 | }
718 | return fn(driver);
719 | }, opt_timeout, message);
720 | };
721 |
722 |
723 | /**
724 | * Schedules a command to make the driver sleep for the given amount of time.
725 | * @param {number} ms The amount of time, in milliseconds, to sleep.
726 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
727 | * when the sleep has finished.
728 | */
729 | webdriver.WebDriver.prototype.sleep = function(ms) {
730 | return this.flow_.timeout(ms, 'WebDriver.sleep(' + ms + ')');
731 | };
732 |
733 |
734 | /**
735 | * Schedules a command to retrieve they current window handle.
736 | * @return {!webdriver.promise.Promise.<string>} A promise that will be
737 | * resolved with the current window handle.
738 | */
739 | webdriver.WebDriver.prototype.getWindowHandle = function() {
740 | return this.schedule(
741 | new webdriver.Command(webdriver.CommandName.GET_CURRENT_WINDOW_HANDLE),
742 | 'WebDriver.getWindowHandle()');
743 | };
744 |
745 |
746 | /**
747 | * Schedules a command to retrieve the current list of available window handles.
748 | * @return {!webdriver.promise.Promise.<!Array.<string>>} A promise that will
749 | * be resolved with an array of window handles.
750 | */
751 | webdriver.WebDriver.prototype.getAllWindowHandles = function() {
752 | return this.schedule(
753 | new webdriver.Command(webdriver.CommandName.GET_WINDOW_HANDLES),
754 | 'WebDriver.getAllWindowHandles()');
755 | };
756 |
757 |
758 | /**
759 | * Schedules a command to retrieve the current page's source. The page source
760 | * returned is a representation of the underlying DOM: do not expect it to be
761 | * formatted or escaped in the same way as the response sent from the web
762 | * server.
763 | * @return {!webdriver.promise.Promise.<string>} A promise that will be
764 | * resolved with the current page source.
765 | */
766 | webdriver.WebDriver.prototype.getPageSource = function() {
767 | return this.schedule(
768 | new webdriver.Command(webdriver.CommandName.GET_PAGE_SOURCE),
769 | 'WebDriver.getAllWindowHandles()');
770 | };
771 |
772 |
773 | /**
774 | * Schedules a command to close the current window.
775 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
776 | * when this command has completed.
777 | */
778 | webdriver.WebDriver.prototype.close = function() {
779 | return this.schedule(new webdriver.Command(webdriver.CommandName.CLOSE),
780 | 'WebDriver.close()');
781 | };
782 |
783 |
784 | /**
785 | * Schedules a command to navigate to the given URL.
786 | * @param {string} url The fully qualified URL to open.
787 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
788 | * when the document has finished loading.
789 | */
790 | webdriver.WebDriver.prototype.get = function(url) {
791 | return this.navigate().to(url);
792 | };
793 |
794 |
795 | /**
796 | * Schedules a command to retrieve the URL of the current page.
797 | * @return {!webdriver.promise.Promise.<string>} A promise that will be
798 | * resolved with the current URL.
799 | */
800 | webdriver.WebDriver.prototype.getCurrentUrl = function() {
801 | return this.schedule(
802 | new webdriver.Command(webdriver.CommandName.GET_CURRENT_URL),
803 | 'WebDriver.getCurrentUrl()');
804 | };
805 |
806 |
807 | /**
808 | * Schedules a command to retrieve the current page's title.
809 | * @return {!webdriver.promise.Promise.<string>} A promise that will be
810 | * resolved with the current page's title.
811 | */
812 | webdriver.WebDriver.prototype.getTitle = function() {
813 | return this.schedule(new webdriver.Command(webdriver.CommandName.GET_TITLE),
814 | 'WebDriver.getTitle()');
815 | };
816 |
817 |
818 | /**
819 | * Schedule a command to find an element on the page. If the element cannot be
820 | * found, a {@link bot.ErrorCode.NO_SUCH_ELEMENT} result will be returned
821 | * by the driver. Unlike other commands, this error cannot be suppressed. In
822 | * other words, scheduling a command to find an element doubles as an assert
823 | * that the element is present on the page. To test whether an element is
824 | * present on the page, use {@link #isElementPresent} instead.
825 | *
826 | * The search criteria for an element may be defined using one of the
827 | * factories in the {@link webdriver.By} namespace, or as a short-hand
828 | * {@link webdriver.By.Hash} object. For example, the following two statements
829 | * are equivalent:
830 | *
831 | * var e1 = driver.findElement(By.id('foo'));
832 | * var e2 = driver.findElement({id:'foo'});
833 | *
834 | * You may also provide a custom locator function, which takes as input
835 | * this WebDriver instance and returns a {@link webdriver.WebElement}, or a
836 | * promise that will resolve to a WebElement. For example, to find the first
837 | * visible link on a page, you could write:
838 | *
839 | * var link = driver.findElement(firstVisibleLink);
840 | *
841 | * function firstVisibleLink(driver) {
842 | * var links = driver.findElements(By.tagName('a'));
843 | * return webdriver.promise.filter(links, function(link) {
844 | * return links.isDisplayed();
845 | * }).then(function(visibleLinks) {
846 | * return visibleLinks[0];
847 | * });
848 | * }
849 | *
850 | * When running in the browser, a WebDriver cannot manipulate DOM elements
851 | * directly; it may do so only through a {@link webdriver.WebElement} reference.
852 | * This function may be used to generate a WebElement from a DOM element. A
853 | * reference to the DOM element will be stored in a known location and this
854 | * driver will attempt to retrieve it through {@link #executeScript}. If the
855 | * element cannot be found (eg, it belongs to a different document than the
856 | * one this instance is currently focused on), a
857 | * {@link bot.ErrorCode.NO_SUCH_ELEMENT} error will be returned.
858 | *
859 | * @param {!(webdriver.Locator|webdriver.By.Hash|Element|Function)} locator The
860 | * locator to use.
861 | * @return {!webdriver.WebElement} A WebElement that can be used to issue
862 | * commands against the located element. If the element is not found, the
863 | * element will be invalidated and all scheduled commands aborted.
864 | */
865 | webdriver.WebDriver.prototype.findElement = function(locator) {
866 | var id;
867 | if ('nodeType' in locator && 'ownerDocument' in locator) {
868 | var element = /** @type {!Element} */ (locator);
869 | id = this.findDomElement_(element).then(function(element) {
870 | if (!element) {
871 | throw new bot.Error(bot.ErrorCode.NO_SUCH_ELEMENT,
872 | 'Unable to locate element. Is WebDriver focused on its ' +
873 | 'ownerDocument\'s frame?');
874 | }
875 | return element;
876 | });
877 | } else {
878 | locator = webdriver.Locator.checkLocator(locator);
879 | if (goog.isFunction(locator)) {
880 | id = this.findElementInternal_(locator, this);
881 | } else {
882 | var command = new webdriver.Command(webdriver.CommandName.FIND_ELEMENT).
883 | setParameter('using', locator.using).
884 | setParameter('value', locator.value);
885 | id = this.schedule(command, 'WebDriver.findElement(' + locator + ')');
886 | }
887 | }
888 | return new webdriver.WebElementPromise(this, id);
889 | };
890 |
891 |
892 | /**
893 | * @param {!Function} locatorFn The locator function to use.
894 | * @param {!(webdriver.WebDriver|webdriver.WebElement)} context The search
895 | * context.
896 | * @return {!webdriver.promise.Promise.<!webdriver.WebElement>} A
897 | * promise that will resolve to a list of WebElements.
898 | * @private
899 | */
900 | webdriver.WebDriver.prototype.findElementInternal_ = function(
901 | locatorFn, context) {
902 | return this.call(goog.partial(locatorFn, context)).then(function(result) {
903 | if (goog.isArray(result)) {
904 | result = result[0];
905 | }
906 | if (!(result instanceof webdriver.WebElement)) {
907 | throw new TypeError('Custom locator did not return a WebElement');
908 | }
909 | return result;
910 | });
911 | };
912 |
913 |
914 | /**
915 | * Locates a DOM element so that commands may be issued against it using the
916 | * {@link webdriver.WebElement} class. This is accomplished by storing a
917 | * reference to the element in an object on the element's ownerDocument.
918 | * {@link #executeScript} will then be used to create a WebElement from this
919 | * reference. This requires this driver to currently be focused on the
920 | * ownerDocument's window+frame.
921 |
922 | * @param {!Element} element The element to locate.
923 | * @return {!webdriver.promise.Promise.<webdriver.WebElement>} A promise that
924 | * will be fulfilled with the located element, or null if the element
925 | * could not be found.
926 | * @private
927 | */
928 | webdriver.WebDriver.prototype.findDomElement_ = function(element) {
929 | var doc = element.ownerDocument;
930 | var store = doc['$webdriver$'] = doc['$webdriver$'] || {};
931 | var id = Math.floor(Math.random() * goog.now()).toString(36);
932 | store[id] = element;
933 | element[id] = id;
934 |
935 | function cleanUp() {
936 | delete store[id];
937 | }
938 |
939 | function lookupElement(id) {
940 | var store = document['$webdriver$'];
941 | if (!store) {
942 | return null;
943 | }
944 |
945 | var element = store[id];
946 | if (!element || element[id] !== id) {
947 | return null;
948 | }
949 | return element;
950 | }
951 |
952 | /** @type {!webdriver.promise.Promise.<webdriver.WebElement>} */
953 | var foundElement = this.executeScript(lookupElement, id);
954 | foundElement.thenFinally(cleanUp);
955 | return foundElement;
956 | };
957 |
958 |
959 | /**
960 | * Schedules a command to test if an element is present on the page.
961 | *
962 | * If given a DOM element, this function will check if it belongs to the
963 | * document the driver is currently focused on. Otherwise, the function will
964 | * test if at least one element can be found with the given search criteria.
965 | *
966 | * @param {!(webdriver.Locator|webdriver.By.Hash|Element|
967 | * Function)} locatorOrElement The locator to use, or the actual
968 | * DOM element to be located by the server.
969 | * @return {!webdriver.promise.Promise.<boolean>} A promise that will resolve
970 | * with whether the element is present on the page.
971 | */
972 | webdriver.WebDriver.prototype.isElementPresent = function(locatorOrElement) {
973 | if ('nodeType' in locatorOrElement && 'ownerDocument' in locatorOrElement) {
974 | return this.findDomElement_(/** @type {!Element} */ (locatorOrElement)).
975 | then(function(result) { return !!result; });
976 | } else {
977 | return this.findElements.apply(this, arguments).then(function(result) {
978 | return !!result.length;
979 | });
980 | }
981 | };
982 |
983 |
984 | /**
985 | * Schedule a command to search for multiple elements on the page.
986 | *
987 | * @param {!(webdriver.Locator|webdriver.By.Hash|Function)} locator The locator
988 | * strategy to use when searching for the element.
989 | * @return {!webdriver.promise.Promise.<!Array.<!webdriver.WebElement>>} A
990 | * promise that will resolve to an array of WebElements.
991 | */
992 | webdriver.WebDriver.prototype.findElements = function(locator) {
993 | locator = webdriver.Locator.checkLocator(locator);
994 | if (goog.isFunction(locator)) {
995 | return this.findElementsInternal_(locator, this);
996 | } else {
997 | var command = new webdriver.Command(webdriver.CommandName.FIND_ELEMENTS).
998 | setParameter('using', locator.using).
999 | setParameter('value', locator.value);
1000 | return this.schedule(command, 'WebDriver.findElements(' + locator + ')');
1001 | }
1002 | };
1003 |
1004 |
1005 | /**
1006 | * @param {!Function} locatorFn The locator function to use.
1007 | * @param {!(webdriver.WebDriver|webdriver.WebElement)} context The search
1008 | * context.
1009 | * @return {!webdriver.promise.Promise.<!Array.<!webdriver.WebElement>>} A
1010 | * promise that will resolve to an array of WebElements.
1011 | * @private
1012 | */
1013 | webdriver.WebDriver.prototype.findElementsInternal_ = function(
1014 | locatorFn, context) {
1015 | return this.call(goog.partial(locatorFn, context)).then(function(result) {
1016 | if (result instanceof webdriver.WebElement) {
1017 | return [result];
1018 | }
1019 |
1020 | if (!goog.isArray(result)) {
1021 | return [];
1022 | }
1023 |
1024 | return goog.array.filter(result, function(item) {
1025 | return item instanceof webdriver.WebElement;
1026 | });
1027 | });
1028 | };
1029 |
1030 |
1031 | /**
1032 | * Schedule a command to take a screenshot. The driver makes a best effort to
1033 | * return a screenshot of the following, in order of preference:
1034 | * <ol>
1035 | * <li>Entire page
1036 | * <li>Current window
1037 | * <li>Visible portion of the current frame
1038 | * <li>The screenshot of the entire display containing the browser
1039 | * </ol>
1040 | *
1041 | * @return {!webdriver.promise.Promise.<string>} A promise that will be
1042 | * resolved to the screenshot as a base-64 encoded PNG.
1043 | */
1044 | webdriver.WebDriver.prototype.takeScreenshot = function() {
1045 | return this.schedule(new webdriver.Command(webdriver.CommandName.SCREENSHOT),
1046 | 'WebDriver.takeScreenshot()');
1047 | };
1048 |
1049 |
1050 | /**
1051 | * @return {!webdriver.WebDriver.Options} The options interface for this
1052 | * instance.
1053 | */
1054 | webdriver.WebDriver.prototype.manage = function() {
1055 | return new webdriver.WebDriver.Options(this);
1056 | };
1057 |
1058 |
1059 | /**
1060 | * @return {!webdriver.WebDriver.Navigation} The navigation interface for this
1061 | * instance.
1062 | */
1063 | webdriver.WebDriver.prototype.navigate = function() {
1064 | return new webdriver.WebDriver.Navigation(this);
1065 | };
1066 |
1067 |
1068 | /**
1069 | * @return {!webdriver.WebDriver.TargetLocator} The target locator interface for
1070 | * this instance.
1071 | */
1072 | webdriver.WebDriver.prototype.switchTo = function() {
1073 | return new webdriver.WebDriver.TargetLocator(this);
1074 | };
1075 |
1076 |
1077 |
1078 | /**
1079 | * Interface for navigating back and forth in the browser history.
1080 | * @param {!webdriver.WebDriver} driver The parent driver.
1081 | * @constructor
1082 | */
1083 | webdriver.WebDriver.Navigation = function(driver) {
1084 |
1085 | /** @private {!webdriver.WebDriver} */
1086 | this.driver_ = driver;
1087 | };
1088 |
1089 |
1090 | /**
1091 | * Schedules a command to navigate to a new URL.
1092 | * @param {string} url The URL to navigate to.
1093 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
1094 | * when the URL has been loaded.
1095 | */
1096 | webdriver.WebDriver.Navigation.prototype.to = function(url) {
1097 | return this.driver_.schedule(
1098 | new webdriver.Command(webdriver.CommandName.GET).
1099 | setParameter('url', url),
1100 | 'WebDriver.navigate().to(' + url + ')');
1101 | };
1102 |
1103 |
1104 | /**
1105 | * Schedules a command to move backwards in the browser history.
1106 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
1107 | * when the navigation event has completed.
1108 | */
1109 | webdriver.WebDriver.Navigation.prototype.back = function() {
1110 | return this.driver_.schedule(
1111 | new webdriver.Command(webdriver.CommandName.GO_BACK),
1112 | 'WebDriver.navigate().back()');
1113 | };
1114 |
1115 |
1116 | /**
1117 | * Schedules a command to move forwards in the browser history.
1118 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
1119 | * when the navigation event has completed.
1120 | */
1121 | webdriver.WebDriver.Navigation.prototype.forward = function() {
1122 | return this.driver_.schedule(
1123 | new webdriver.Command(webdriver.CommandName.GO_FORWARD),
1124 | 'WebDriver.navigate().forward()');
1125 | };
1126 |
1127 |
1128 | /**
1129 | * Schedules a command to refresh the current page.
1130 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
1131 | * when the navigation event has completed.
1132 | */
1133 | webdriver.WebDriver.Navigation.prototype.refresh = function() {
1134 | return this.driver_.schedule(
1135 | new webdriver.Command(webdriver.CommandName.REFRESH),
1136 | 'WebDriver.navigate().refresh()');
1137 | };
1138 |
1139 |
1140 |
1141 | /**
1142 | * Provides methods for managing browser and driver state.
1143 | * @param {!webdriver.WebDriver} driver The parent driver.
1144 | * @constructor
1145 | */
1146 | webdriver.WebDriver.Options = function(driver) {
1147 |
1148 | /** @private {!webdriver.WebDriver} */
1149 | this.driver_ = driver;
1150 | };
1151 |
1152 |
1153 | /**
1154 | * A JSON description of a browser cookie.
1155 | * @typedef {{
1156 | * name: string,
1157 | * value: string,
1158 | * path: (string|undefined),
1159 | * domain: (string|undefined),
1160 | * secure: (boolean|undefined),
1161 | * expiry: (number|undefined)
1162 | * }}
1163 | * @see http://code.google.com/p/selenium/wiki/JsonWireProtocol#Cookie_JSON_Object
1164 | */
1165 | webdriver.WebDriver.Options.Cookie;
1166 |
1167 |
1168 | /**
1169 | * Schedules a command to add a cookie.
1170 | * @param {string} name The cookie name.
1171 | * @param {string} value The cookie value.
1172 | * @param {string=} opt_path The cookie path.
1173 | * @param {string=} opt_domain The cookie domain.
1174 | * @param {boolean=} opt_isSecure Whether the cookie is secure.
1175 | * @param {(number|!Date)=} opt_expiry When the cookie expires. If specified as
1176 | * a number, should be in milliseconds since midnight, January 1, 1970 UTC.
1177 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
1178 | * when the cookie has been added to the page.
1179 | */
1180 | webdriver.WebDriver.Options.prototype.addCookie = function(
1181 | name, value, opt_path, opt_domain, opt_isSecure, opt_expiry) {
1182 | // We do not allow '=' or ';' in the name.
1183 | if (/[;=]/.test(name)) {
1184 | throw Error('Invalid cookie name "' + name + '"');
1185 | }
1186 |
1187 | // We do not allow ';' in value.
1188 | if (/;/.test(value)) {
1189 | throw Error('Invalid cookie value "' + value + '"');
1190 | }
1191 |
1192 | var cookieString = name + '=' + value +
1193 | (opt_domain ? ';domain=' + opt_domain : '') +
1194 | (opt_path ? ';path=' + opt_path : '') +
1195 | (opt_isSecure ? ';secure' : '');
1196 |
1197 | var expiry;
1198 | if (goog.isDef(opt_expiry)) {
1199 | var expiryDate;
1200 | if (goog.isNumber(opt_expiry)) {
1201 | expiryDate = new Date(opt_expiry);
1202 | } else {
1203 | expiryDate = /** @type {!Date} */ (opt_expiry);
1204 | opt_expiry = expiryDate.getTime();
1205 | }
1206 | cookieString += ';expires=' + expiryDate.toUTCString();
1207 | // Convert from milliseconds to seconds.
1208 | expiry = Math.floor(/** @type {number} */ (opt_expiry) / 1000);
1209 | }
1210 |
1211 | return this.driver_.schedule(
1212 | new webdriver.Command(webdriver.CommandName.ADD_COOKIE).
1213 | setParameter('cookie', {
1214 | 'name': name,
1215 | 'value': value,
1216 | 'path': opt_path,
1217 | 'domain': opt_domain,
1218 | 'secure': !!opt_isSecure,
1219 | 'expiry': expiry
1220 | }),
1221 | 'WebDriver.manage().addCookie(' + cookieString + ')');
1222 | };
1223 |
1224 |
1225 | /**
1226 | * Schedules a command to delete all cookies visible to the current page.
1227 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
1228 | * when all cookies have been deleted.
1229 | */
1230 | webdriver.WebDriver.Options.prototype.deleteAllCookies = function() {
1231 | return this.driver_.schedule(
1232 | new webdriver.Command(webdriver.CommandName.DELETE_ALL_COOKIES),
1233 | 'WebDriver.manage().deleteAllCookies()');
1234 | };
1235 |
1236 |
1237 | /**
1238 | * Schedules a command to delete the cookie with the given name. This command is
1239 | * a no-op if there is no cookie with the given name visible to the current
1240 | * page.
1241 | * @param {string} name The name of the cookie to delete.
1242 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
1243 | * when the cookie has been deleted.
1244 | */
1245 | webdriver.WebDriver.Options.prototype.deleteCookie = function(name) {
1246 | return this.driver_.schedule(
1247 | new webdriver.Command(webdriver.CommandName.DELETE_COOKIE).
1248 | setParameter('name', name),
1249 | 'WebDriver.manage().deleteCookie(' + name + ')');
1250 | };
1251 |
1252 |
1253 | /**
1254 | * Schedules a command to retrieve all cookies visible to the current page.
1255 | * Each cookie will be returned as a JSON object as described by the WebDriver
1256 | * wire protocol.
1257 | * @return {!webdriver.promise.Promise.<
1258 | * !Array.<webdriver.WebDriver.Options.Cookie>>} A promise that will be
1259 | * resolved with the cookies visible to the current page.
1260 | * @see http://code.google.com/p/selenium/wiki/JsonWireProtocol#Cookie_JSON_Object
1261 | */
1262 | webdriver.WebDriver.Options.prototype.getCookies = function() {
1263 | return this.driver_.schedule(
1264 | new webdriver.Command(webdriver.CommandName.GET_ALL_COOKIES),
1265 | 'WebDriver.manage().getCookies()');
1266 | };
1267 |
1268 |
1269 | /**
1270 | * Schedules a command to retrieve the cookie with the given name. Returns null
1271 | * if there is no such cookie. The cookie will be returned as a JSON object as
1272 | * described by the WebDriver wire protocol.
1273 | * @param {string} name The name of the cookie to retrieve.
1274 | * @return {!webdriver.promise.Promise.<?webdriver.WebDriver.Options.Cookie>} A
1275 | * promise that will be resolved with the named cookie, or {@code null}
1276 | * if there is no such cookie.
1277 | * @see http://code.google.com/p/selenium/wiki/JsonWireProtocol#Cookie_JSON_Object
1278 | */
1279 | webdriver.WebDriver.Options.prototype.getCookie = function(name) {
1280 | return this.getCookies().then(function(cookies) {
1281 | return goog.array.find(cookies, function(cookie) {
1282 | return cookie && cookie['name'] == name;
1283 | });
1284 | });
1285 | };
1286 |
1287 |
1288 | /**
1289 | * @return {!webdriver.WebDriver.Logs} The interface for managing driver
1290 | * logs.
1291 | */
1292 | webdriver.WebDriver.Options.prototype.logs = function() {
1293 | return new webdriver.WebDriver.Logs(this.driver_);
1294 | };
1295 |
1296 |
1297 | /**
1298 | * @return {!webdriver.WebDriver.Timeouts} The interface for managing driver
1299 | * timeouts.
1300 | */
1301 | webdriver.WebDriver.Options.prototype.timeouts = function() {
1302 | return new webdriver.WebDriver.Timeouts(this.driver_);
1303 | };
1304 |
1305 |
1306 | /**
1307 | * @return {!webdriver.WebDriver.Window} The interface for managing the
1308 | * current window.
1309 | */
1310 | webdriver.WebDriver.Options.prototype.window = function() {
1311 | return new webdriver.WebDriver.Window(this.driver_);
1312 | };
1313 |
1314 |
1315 |
1316 | /**
1317 | * An interface for managing timeout behavior for WebDriver instances.
1318 | * @param {!webdriver.WebDriver} driver The parent driver.
1319 | * @constructor
1320 | */
1321 | webdriver.WebDriver.Timeouts = function(driver) {
1322 |
1323 | /** @private {!webdriver.WebDriver} */
1324 | this.driver_ = driver;
1325 | };
1326 |
1327 |
1328 | /**
1329 | * Specifies the amount of time the driver should wait when searching for an
1330 | * element if it is not immediately present.
1331 | *
1332 | * When searching for a single element, the driver should poll the page
1333 | * until the element has been found, or this timeout expires before failing
1334 | * with a {@link bot.ErrorCode.NO_SUCH_ELEMENT} error. When searching
1335 | * for multiple elements, the driver should poll the page until at least one
1336 | * element has been found or this timeout has expired.
1337 | *
1338 | * Setting the wait timeout to 0 (its default value), disables implicit
1339 | * waiting.
1340 | *
1341 | * Increasing the implicit wait timeout should be used judiciously as it
1342 | * will have an adverse effect on test run time, especially when used with
1343 | * slower location strategies like XPath.
1344 | *
1345 | * @param {number} ms The amount of time to wait, in milliseconds.
1346 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
1347 | * when the implicit wait timeout has been set.
1348 | */
1349 | webdriver.WebDriver.Timeouts.prototype.implicitlyWait = function(ms) {
1350 | return this.driver_.schedule(
1351 | new webdriver.Command(webdriver.CommandName.IMPLICITLY_WAIT).
1352 | setParameter('ms', ms < 0 ? 0 : ms),
1353 | 'WebDriver.manage().timeouts().implicitlyWait(' + ms + ')');
1354 | };
1355 |
1356 |
1357 | /**
1358 | * Sets the amount of time to wait, in milliseconds, for an asynchronous script
1359 | * to finish execution before returning an error. If the timeout is less than or
1360 | * equal to 0, the script will be allowed to run indefinitely.
1361 | *
1362 | * @param {number} ms The amount of time to wait, in milliseconds.
1363 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
1364 | * when the script timeout has been set.
1365 | */
1366 | webdriver.WebDriver.Timeouts.prototype.setScriptTimeout = function(ms) {
1367 | return this.driver_.schedule(
1368 | new webdriver.Command(webdriver.CommandName.SET_SCRIPT_TIMEOUT).
1369 | setParameter('ms', ms < 0 ? 0 : ms),
1370 | 'WebDriver.manage().timeouts().setScriptTimeout(' + ms + ')');
1371 | };
1372 |
1373 |
1374 | /**
1375 | * Sets the amount of time to wait for a page load to complete before returning
1376 | * an error. If the timeout is negative, page loads may be indefinite.
1377 | * @param {number} ms The amount of time to wait, in milliseconds.
1378 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
1379 | * when the timeout has been set.
1380 | */
1381 | webdriver.WebDriver.Timeouts.prototype.pageLoadTimeout = function(ms) {
1382 | return this.driver_.schedule(
1383 | new webdriver.Command(webdriver.CommandName.SET_TIMEOUT).
1384 | setParameter('type', 'page load').
1385 | setParameter('ms', ms),
1386 | 'WebDriver.manage().timeouts().pageLoadTimeout(' + ms + ')');
1387 | };
1388 |
1389 |
1390 |
1391 | /**
1392 | * An interface for managing the current window.
1393 | * @param {!webdriver.WebDriver} driver The parent driver.
1394 | * @constructor
1395 | */
1396 | webdriver.WebDriver.Window = function(driver) {
1397 |
1398 | /** @private {!webdriver.WebDriver} */
1399 | this.driver_ = driver;
1400 | };
1401 |
1402 |
1403 | /**
1404 | * Retrieves the window's current position, relative to the top left corner of
1405 | * the screen.
1406 | * @return {!webdriver.promise.Promise.<{x: number, y: number}>} A promise that
1407 | * will be resolved with the window's position in the form of a
1408 | * {x:number, y:number} object literal.
1409 | */
1410 | webdriver.WebDriver.Window.prototype.getPosition = function() {
1411 | return this.driver_.schedule(
1412 | new webdriver.Command(webdriver.CommandName.GET_WINDOW_POSITION).
1413 | setParameter('windowHandle', 'current'),
1414 | 'WebDriver.manage().window().getPosition()');
1415 | };
1416 |
1417 |
1418 | /**
1419 | * Repositions the current window.
1420 | * @param {number} x The desired horizontal position, relative to the left side
1421 | * of the screen.
1422 | * @param {number} y The desired vertical position, relative to the top of the
1423 | * of the screen.
1424 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
1425 | * when the command has completed.
1426 | */
1427 | webdriver.WebDriver.Window.prototype.setPosition = function(x, y) {
1428 | return this.driver_.schedule(
1429 | new webdriver.Command(webdriver.CommandName.SET_WINDOW_POSITION).
1430 | setParameter('windowHandle', 'current').
1431 | setParameter('x', x).
1432 | setParameter('y', y),
1433 | 'WebDriver.manage().window().setPosition(' + x + ', ' + y + ')');
1434 | };
1435 |
1436 |
1437 | /**
1438 | * Retrieves the window's current size.
1439 | * @return {!webdriver.promise.Promise.<{width: number, height: number}>} A
1440 | * promise that will be resolved with the window's size in the form of a
1441 | * {width:number, height:number} object literal.
1442 | */
1443 | webdriver.WebDriver.Window.prototype.getSize = function() {
1444 | return this.driver_.schedule(
1445 | new webdriver.Command(webdriver.CommandName.GET_WINDOW_SIZE).
1446 | setParameter('windowHandle', 'current'),
1447 | 'WebDriver.manage().window().getSize()');
1448 | };
1449 |
1450 |
1451 | /**
1452 | * Resizes the current window.
1453 | * @param {number} width The desired window width.
1454 | * @param {number} height The desired window height.
1455 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
1456 | * when the command has completed.
1457 | */
1458 | webdriver.WebDriver.Window.prototype.setSize = function(width, height) {
1459 | return this.driver_.schedule(
1460 | new webdriver.Command(webdriver.CommandName.SET_WINDOW_SIZE).
1461 | setParameter('windowHandle', 'current').
1462 | setParameter('width', width).
1463 | setParameter('height', height),
1464 | 'WebDriver.manage().window().setSize(' + width + ', ' + height + ')');
1465 | };
1466 |
1467 |
1468 | /**
1469 | * Maximizes the current window.
1470 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
1471 | * when the command has completed.
1472 | */
1473 | webdriver.WebDriver.Window.prototype.maximize = function() {
1474 | return this.driver_.schedule(
1475 | new webdriver.Command(webdriver.CommandName.MAXIMIZE_WINDOW).
1476 | setParameter('windowHandle', 'current'),
1477 | 'WebDriver.manage().window().maximize()');
1478 | };
1479 |
1480 |
1481 | /**
1482 | * Interface for managing WebDriver log records.
1483 | * @param {!webdriver.WebDriver} driver The parent driver.
1484 | * @constructor
1485 | */
1486 | webdriver.WebDriver.Logs = function(driver) {
1487 |
1488 | /** @private {!webdriver.WebDriver} */
1489 | this.driver_ = driver;
1490 | };
1491 |
1492 |
1493 | /**
1494 | * Fetches available log entries for the given type.
1495 | *
1496 | * Note that log buffers are reset after each call, meaning that available
1497 | * log entries correspond to those entries not yet returned for a given log
1498 | * type. In practice, this means that this call will return the available log
1499 | * entries since the last call, or from the start of the session.
1500 | *
1501 | * @param {!webdriver.logging.Type} type The desired log type.
1502 | * @return {!webdriver.promise.Promise.<!Array.<!webdriver.logging.Entry>>} A
1503 | * promise that will resolve to a list of log entries for the specified
1504 | * type.
1505 | */
1506 | webdriver.WebDriver.Logs.prototype.get = function(type) {
1507 | return this.driver_.schedule(
1508 | new webdriver.Command(webdriver.CommandName.GET_LOG).
1509 | setParameter('type', type),
1510 | 'WebDriver.manage().logs().get(' + type + ')').
1511 | then(function(entries) {
1512 | return goog.array.map(entries, function(entry) {
1513 | if (!(entry instanceof webdriver.logging.Entry)) {
1514 | return new webdriver.logging.Entry(
1515 | entry['level'], entry['message'], entry['timestamp'],
1516 | entry['type']);
1517 | }
1518 | return entry;
1519 | });
1520 | });
1521 | };
1522 |
1523 |
1524 | /**
1525 | * Retrieves the log types available to this driver.
1526 | * @return {!webdriver.promise.Promise.<!Array.<!webdriver.logging.Type>>} A
1527 | * promise that will resolve to a list of available log types.
1528 | */
1529 | webdriver.WebDriver.Logs.prototype.getAvailableLogTypes = function() {
1530 | return this.driver_.schedule(
1531 | new webdriver.Command(webdriver.CommandName.GET_AVAILABLE_LOG_TYPES),
1532 | 'WebDriver.manage().logs().getAvailableLogTypes()');
1533 | };
1534 |
1535 |
1536 |
1537 | /**
1538 | * An interface for changing the focus of the driver to another frame or window.
1539 | * @param {!webdriver.WebDriver} driver The parent driver.
1540 | * @constructor
1541 | */
1542 | webdriver.WebDriver.TargetLocator = function(driver) {
1543 |
1544 | /** @private {!webdriver.WebDriver} */
1545 | this.driver_ = driver;
1546 | };
1547 |
1548 |
1549 | /**
1550 | * Schedules a command retrieve the {@code document.activeElement} element on
1551 | * the current document, or {@code document.body} if activeElement is not
1552 | * available.
1553 | * @return {!webdriver.WebElementPromise} The active element.
1554 | */
1555 | webdriver.WebDriver.TargetLocator.prototype.activeElement = function() {
1556 | var id = this.driver_.schedule(
1557 | new webdriver.Command(webdriver.CommandName.GET_ACTIVE_ELEMENT),
1558 | 'WebDriver.switchTo().activeElement()');
1559 | return new webdriver.WebElementPromise(this.driver_, id);
1560 | };
1561 |
1562 |
1563 | /**
1564 | * Schedules a command to switch focus of all future commands to the first frame
1565 | * on the page.
1566 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
1567 | * when the driver has changed focus to the default content.
1568 | */
1569 | webdriver.WebDriver.TargetLocator.prototype.defaultContent = function() {
1570 | return this.driver_.schedule(
1571 | new webdriver.Command(webdriver.CommandName.SWITCH_TO_FRAME).
1572 | setParameter('id', null),
1573 | 'WebDriver.switchTo().defaultContent()');
1574 | };
1575 |
1576 |
1577 | /**
1578 | * Schedules a command to switch the focus of all future commands to another
1579 | * frame on the page.
1580 | *
1581 | * If the frame is specified by a number, the command will switch to the frame
1582 | * by its (zero-based) index into
1583 | * [window.frames](https://developer.mozilla.org/en-US/docs/Web/API/Window.frames).
1584 | *
1585 | * If the frame is specified by a string, the command will select the frame by
1586 | * its name or ID. To select sub-frames, simply separate the frame names/IDs by
1587 | * dots. As an example, "main.child" will select the frame with the name "main"
1588 | * and then its child "child".
1589 | *
1590 | * If the specified frame can not be found, the deferred result will errback
1591 | * with a {@link bot.ErrorCode.NO_SUCH_FRAME} error.
1592 | *
1593 | * @param {string|number} nameOrIndex The frame locator.
1594 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
1595 | * when the driver has changed focus to the specified frame.
1596 | */
1597 | webdriver.WebDriver.TargetLocator.prototype.frame = function(nameOrIndex) {
1598 | return this.driver_.schedule(
1599 | new webdriver.Command(webdriver.CommandName.SWITCH_TO_FRAME).
1600 | setParameter('id', nameOrIndex),
1601 | 'WebDriver.switchTo().frame(' + nameOrIndex + ')');
1602 | };
1603 |
1604 |
1605 | /**
1606 | * Schedules a command to switch the focus of all future commands to another
1607 | * window. Windows may be specified by their {@code window.name} attribute or
1608 | * by its handle (as returned by {@link webdriver.WebDriver#getWindowHandles}).
1609 | *
1610 | * If the specificed window can not be found, the deferred result will errback
1611 | * with a {@link bot.ErrorCode.NO_SUCH_WINDOW} error.
1612 | *
1613 | * @param {string} nameOrHandle The name or window handle of the window to
1614 | * switch focus to.
1615 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
1616 | * when the driver has changed focus to the specified window.
1617 | */
1618 | webdriver.WebDriver.TargetLocator.prototype.window = function(nameOrHandle) {
1619 | return this.driver_.schedule(
1620 | new webdriver.Command(webdriver.CommandName.SWITCH_TO_WINDOW).
1621 | setParameter('name', nameOrHandle),
1622 | 'WebDriver.switchTo().window(' + nameOrHandle + ')');
1623 | };
1624 |
1625 |
1626 | /**
1627 | * Schedules a command to change focus to the active alert dialog. This command
1628 | * will return a {@link bot.ErrorCode.NO_SUCH_ALERT} error if an alert dialog
1629 | * is not currently open.
1630 | * @return {!webdriver.AlertPromise} The open alert.
1631 | */
1632 | webdriver.WebDriver.TargetLocator.prototype.alert = function() {
1633 | var text = this.driver_.schedule(
1634 | new webdriver.Command(webdriver.CommandName.GET_ALERT_TEXT),
1635 | 'WebDriver.switchTo().alert()');
1636 | var driver = this.driver_;
1637 | return new webdriver.AlertPromise(driver, text.then(function(text) {
1638 | return new webdriver.Alert(driver, text);
1639 | }));
1640 | };
1641 |
1642 |
1643 | /**
1644 | * Simulate pressing many keys at once in a "chord". Takes a sequence of
1645 | * {@link webdriver.Key}s or strings, appends each of the values to a string,
1646 | * and adds the chord termination key ({@link webdriver.Key.NULL}) and returns
1647 | * the resultant string.
1648 | *
1649 | * Note: when the low-level webdriver key handlers see Keys.NULL, active
1650 | * modifier keys (CTRL/ALT/SHIFT/etc) release via a keyup event.
1651 | *
1652 | * @param {...string} var_args The key sequence to concatenate.
1653 | * @return {string} The null-terminated key sequence.
1654 | * @see http://code.google.com/p/webdriver/issues/detail?id=79
1655 | */
1656 | webdriver.Key.chord = function(var_args) {
1657 | var sequence = goog.array.reduce(
1658 | goog.array.slice(arguments, 0),
1659 | function(str, key) {
1660 | return str + key;
1661 | }, '');
1662 | sequence += webdriver.Key.NULL;
1663 | return sequence;
1664 | };
1665 |
1666 |
1667 | //////////////////////////////////////////////////////////////////////////////
1668 | //
1669 | // webdriver.WebElement
1670 | //
1671 | //////////////////////////////////////////////////////////////////////////////
1672 |
1673 |
1674 |
1675 | /**
1676 | * Represents a DOM element. WebElements can be found by searching from the
1677 | * document root using a {@link webdriver.WebDriver} instance, or by searching
1678 | * under another WebElement:
1679 | *
1680 | * driver.get('http://www.google.com');
1681 | * var searchForm = driver.findElement(By.tagName('form'));
1682 | * var searchBox = searchForm.findElement(By.name('q'));
1683 | * searchBox.sendKeys('webdriver');
1684 | *
1685 | * The WebElement is implemented as a promise for compatibility with the promise
1686 | * API. It will always resolve itself when its internal state has been fully
1687 | * resolved and commands may be issued against the element. This can be used to
1688 | * catch errors when an element cannot be located on the page:
1689 | *
1690 | * driver.findElement(By.id('not-there')).then(function(element) {
1691 | * alert('Found an element that was not expected to be there!');
1692 | * }, function(error) {
1693 | * alert('The element was not found, as expected');
1694 | * });
1695 | *
1696 | * @param {!webdriver.WebDriver} driver The parent WebDriver instance for this
1697 | * element.
1698 | * @param {!(webdriver.promise.Promise.<webdriver.WebElement.Id>|
1699 | * webdriver.WebElement.Id)} id The server-assigned opaque ID for the
1700 | * underlying DOM element.
1701 | * @constructor
1702 | * @extends {webdriver.Serializable.<webdriver.WebElement.Id>}
1703 | */
1704 | webdriver.WebElement = function(driver, id) {
1705 | webdriver.Serializable.call(this);
1706 |
1707 | /** @private {!webdriver.WebDriver} */
1708 | this.driver_ = driver;
1709 |
1710 | /** @private {!webdriver.promise.Promise.<webdriver.WebElement.Id>} */
1711 | this.id_ = id instanceof webdriver.promise.Promise ?
1712 | id : webdriver.promise.fulfilled(id);
1713 | };
1714 | goog.inherits(webdriver.WebElement, webdriver.Serializable);
1715 |
1716 |
1717 | /**
1718 | * Wire protocol definition of a WebElement ID.
1719 | * @typedef {{ELEMENT: string}}
1720 | * @see http://code.google.com/p/selenium/wiki/JsonWireProtocol
1721 | */
1722 | webdriver.WebElement.Id;
1723 |
1724 |
1725 | /**
1726 | * The property key used in the wire protocol to indicate that a JSON object
1727 | * contains the ID of a WebElement.
1728 | * @type {string}
1729 | * @const
1730 | */
1731 | webdriver.WebElement.ELEMENT_KEY = 'ELEMENT';
1732 |
1733 |
1734 | /**
1735 | * Compares to WebElements for equality.
1736 | * @param {!webdriver.WebElement} a A WebElement.
1737 | * @param {!webdriver.WebElement} b A WebElement.
1738 | * @return {!webdriver.promise.Promise.<boolean>} A promise that will be
1739 | * resolved to whether the two WebElements are equal.
1740 | */
1741 | webdriver.WebElement.equals = function(a, b) {
1742 | if (a == b) {
1743 | return webdriver.promise.fulfilled(true);
1744 | }
1745 | var ids = [a.getId(), b.getId()];
1746 | return webdriver.promise.all(ids).then(function(ids) {
1747 | // If the two element's have the same ID, they should be considered
1748 | // equal. Otherwise, they may still be equivalent, but we'll need to
1749 | // ask the server to check for us.
1750 | if (ids[0][webdriver.WebElement.ELEMENT_KEY] ==
1751 | ids[1][webdriver.WebElement.ELEMENT_KEY]) {
1752 | return true;
1753 | }
1754 |
1755 | var command = new webdriver.Command(webdriver.CommandName.ELEMENT_EQUALS);
1756 | command.setParameter('id', ids[0]);
1757 | command.setParameter('other', ids[1]);
1758 | return a.driver_.schedule(command, 'webdriver.WebElement.equals()');
1759 | });
1760 | };
1761 |
1762 |
1763 | /**
1764 | * @return {!webdriver.WebDriver} The parent driver for this instance.
1765 | */
1766 | webdriver.WebElement.prototype.getDriver = function() {
1767 | return this.driver_;
1768 | };
1769 |
1770 |
1771 | /**
1772 | * @return {!webdriver.promise.Promise.<webdriver.WebElement.Id>} A promise
1773 | * that resolves to this element's JSON representation as defined by the
1774 | * WebDriver wire protocol.
1775 | * @see http://code.google.com/p/selenium/wiki/JsonWireProtocol
1776 | */
1777 | webdriver.WebElement.prototype.getId = function() {
1778 | return this.id_;
1779 | };
1780 |
1781 |
1782 | /**
1783 | * Returns the raw ID string ID for this element.
1784 | * @return {!webdriver.promise.Promise<string>} A promise that resolves to this
1785 | * element's raw ID as a string value.
1786 | * @package
1787 | */
1788 | webdriver.WebElement.prototype.getRawId = function() {
1789 | return this.id_.then(function(value) {
1790 | return value['ELEMENT'];
1791 | });
1792 | };
1793 |
1794 |
1795 | /** @override */
1796 | webdriver.WebElement.prototype.serialize = function() {
1797 | return this.getId();
1798 | };
1799 |
1800 |
1801 | /**
1802 | * Schedules a command that targets this element with the parent WebDriver
1803 | * instance. Will ensure this element's ID is included in the command parameters
1804 | * under the "id" key.
1805 | * @param {!webdriver.Command} command The command to schedule.
1806 | * @param {string} description A description of the command for debugging.
1807 | * @return {!webdriver.promise.Promise.<T>} A promise that will be resolved
1808 | * with the command result.
1809 | * @template T
1810 | * @see webdriver.WebDriver.prototype.schedule
1811 | * @private
1812 | */
1813 | webdriver.WebElement.prototype.schedule_ = function(command, description) {
1814 | command.setParameter('id', this.getId());
1815 | return this.driver_.schedule(command, description);
1816 | };
1817 |
1818 |
1819 | /**
1820 | * Schedule a command to find a descendant of this element. If the element
1821 | * cannot be found, a {@link bot.ErrorCode.NO_SUCH_ELEMENT} result will
1822 | * be returned by the driver. Unlike other commands, this error cannot be
1823 | * suppressed. In other words, scheduling a command to find an element doubles
1824 | * as an assert that the element is present on the page. To test whether an
1825 | * element is present on the page, use {@link #isElementPresent} instead.
1826 | *
1827 | * The search criteria for an element may be defined using one of the
1828 | * factories in the {@link webdriver.By} namespace, or as a short-hand
1829 | * {@link webdriver.By.Hash} object. For example, the following two statements
1830 | * are equivalent:
1831 | *
1832 | * var e1 = element.findElement(By.id('foo'));
1833 | * var e2 = element.findElement({id:'foo'});
1834 | *
1835 | * You may also provide a custom locator function, which takes as input
1836 | * this WebDriver instance and returns a {@link webdriver.WebElement}, or a
1837 | * promise that will resolve to a WebElement. For example, to find the first
1838 | * visible link on a page, you could write:
1839 | *
1840 | * var link = element.findElement(firstVisibleLink);
1841 | *
1842 | * function firstVisibleLink(element) {
1843 | * var links = element.findElements(By.tagName('a'));
1844 | * return webdriver.promise.filter(links, function(link) {
1845 | * return links.isDisplayed();
1846 | * }).then(function(visibleLinks) {
1847 | * return visibleLinks[0];
1848 | * });
1849 | * }
1850 | *
1851 | * @param {!(webdriver.Locator|webdriver.By.Hash|Function)} locator The
1852 | * locator strategy to use when searching for the element.
1853 | * @return {!webdriver.WebElement} A WebElement that can be used to issue
1854 | * commands against the located element. If the element is not found, the
1855 | * element will be invalidated and all scheduled commands aborted.
1856 | */
1857 | webdriver.WebElement.prototype.findElement = function(locator) {
1858 | locator = webdriver.Locator.checkLocator(locator);
1859 | var id;
1860 | if (goog.isFunction(locator)) {
1861 | id = this.driver_.findElementInternal_(locator, this);
1862 | } else {
1863 | var command = new webdriver.Command(
1864 | webdriver.CommandName.FIND_CHILD_ELEMENT).
1865 | setParameter('using', locator.using).
1866 | setParameter('value', locator.value);
1867 | id = this.schedule_(command, 'WebElement.findElement(' + locator + ')');
1868 | }
1869 | return new webdriver.WebElementPromise(this.driver_, id);
1870 | };
1871 |
1872 |
1873 | /**
1874 | * Schedules a command to test if there is at least one descendant of this
1875 | * element that matches the given search criteria.
1876 | *
1877 | * @param {!(webdriver.Locator|webdriver.By.Hash|Function)} locator The
1878 | * locator strategy to use when searching for the element.
1879 | * @return {!webdriver.promise.Promise.<boolean>} A promise that will be
1880 | * resolved with whether an element could be located on the page.
1881 | */
1882 | webdriver.WebElement.prototype.isElementPresent = function(locator) {
1883 | return this.findElements(locator).then(function(result) {
1884 | return !!result.length;
1885 | });
1886 | };
1887 |
1888 |
1889 | /**
1890 | * Schedules a command to find all of the descendants of this element that
1891 | * match the given search criteria.
1892 | *
1893 | * @param {!(webdriver.Locator|webdriver.By.Hash|Function)} locator The
1894 | * locator strategy to use when searching for the elements.
1895 | * @return {!webdriver.promise.Promise.<!Array.<!webdriver.WebElement>>} A
1896 | * promise that will resolve to an array of WebElements.
1897 | */
1898 | webdriver.WebElement.prototype.findElements = function(locator) {
1899 | locator = webdriver.Locator.checkLocator(locator);
1900 | if (goog.isFunction(locator)) {
1901 | return this.driver_.findElementsInternal_(locator, this);
1902 | } else {
1903 | var command = new webdriver.Command(
1904 | webdriver.CommandName.FIND_CHILD_ELEMENTS).
1905 | setParameter('using', locator.using).
1906 | setParameter('value', locator.value);
1907 | return this.schedule_(command, 'WebElement.findElements(' + locator + ')');
1908 | }
1909 | };
1910 |
1911 |
1912 | /**
1913 | * Schedules a command to click on this element.
1914 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
1915 | * when the click command has completed.
1916 | */
1917 | webdriver.WebElement.prototype.click = function() {
1918 | return this.schedule_(
1919 | new webdriver.Command(webdriver.CommandName.CLICK_ELEMENT),
1920 | 'WebElement.click()');
1921 | };
1922 |
1923 |
1924 | /**
1925 | * Schedules a command to type a sequence on the DOM element represented by this
1926 | * instance.
1927 | *
1928 | * Modifier keys (SHIFT, CONTROL, ALT, META) are stateful; once a modifier is
1929 | * processed in the keysequence, that key state is toggled until one of the
1930 | * following occurs:
1931 | *
1932 | * - The modifier key is encountered again in the sequence. At this point the
1933 | * state of the key is toggled (along with the appropriate keyup/down events).
1934 | * - The {@link webdriver.Key.NULL} key is encountered in the sequence. When
1935 | * this key is encountered, all modifier keys current in the down state are
1936 | * released (with accompanying keyup events). The NULL key can be used to
1937 | * simulate common keyboard shortcuts:
1938 | *
1939 | * element.sendKeys("text was",
1940 | * webdriver.Key.CONTROL, "a", webdriver.Key.NULL,
1941 | * "now text is");
1942 | * // Alternatively:
1943 | * element.sendKeys("text was",
1944 | * webdriver.Key.chord(webdriver.Key.CONTROL, "a"),
1945 | * "now text is");
1946 | *
1947 | * - The end of the keysequence is encountered. When there are no more keys
1948 | * to type, all depressed modifier keys are released (with accompanying keyup
1949 | * events).
1950 | *
1951 | * If this element is a file input ({@code <input type="file">}), the
1952 | * specified key sequence should specify the path to the file to attach to
1953 | * the element. This is analgous to the user clicking "Browse..." and entering
1954 | * the path into the file select dialog.
1955 | *
1956 | * var form = driver.findElement(By.css('form'));
1957 | * var element = form.findElement(By.css('input[type=file]'));
1958 | * element.sendKeys('/path/to/file.txt');
1959 | * form.submit();
1960 | *
1961 | * For uploads to function correctly, the entered path must reference a file
1962 | * on the _browser's_ machine, not the local machine running this script. When
1963 | * running against a remote Selenium server, a {@link webdriver.FileDetector}
1964 | * may be used to transparently copy files to the remote machine before
1965 | * attempting to upload them in the browser.
1966 | *
1967 | * __Note:__ On browsers where native keyboard events are not supported
1968 | * (e.g. Firefox on OS X), key events will be synthesized. Special
1969 | * punctionation keys will be synthesized according to a standard QWERTY en-us
1970 | * keyboard layout.
1971 | *
1972 | * @param {...(string|!webdriver.promise.Promise<string>)} var_args The sequence
1973 | * of keys to type. All arguments will be joined into a single sequence.
1974 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
1975 | * when all keys have been typed.
1976 | */
1977 | webdriver.WebElement.prototype.sendKeys = function(var_args) {
1978 | // Coerce every argument to a string. This protects us from users that
1979 | // ignore the jsdoc and give us a number (which ends up causing problems on
1980 | // the server, which requires strings).
1981 | var keys = webdriver.promise.all(goog.array.slice(arguments, 0)).
1982 | then(function(keys) {
1983 | return goog.array.map(keys, String);
1984 | });
1985 | if (!this.driver_.fileDetector_) {
1986 | return this.schedule_(
1987 | new webdriver.Command(webdriver.CommandName.SEND_KEYS_TO_ELEMENT).
1988 | setParameter('value', keys),
1989 | 'WebElement.sendKeys()');
1990 | }
1991 |
1992 | // Suppress unhandled rejection errors until the flow executes the command.
1993 | keys.thenCatch(goog.nullFunction);
1994 |
1995 | var element = this;
1996 | return this.driver_.flow_.execute(function() {
1997 | return keys.then(function(keys) {
1998 | return element.driver_.fileDetector_
1999 | .handleFile(element.driver_, keys.join(''));
2000 | }).then(function(keys) {
2001 | return element.schedule_(
2002 | new webdriver.Command(webdriver.CommandName.SEND_KEYS_TO_ELEMENT).
2003 | setParameter('value', [keys]),
2004 | 'WebElement.sendKeys()');
2005 | });
2006 | }, 'WebElement.sendKeys()');
2007 | };
2008 |
2009 |
2010 | /**
2011 | * Schedules a command to query for the tag/node name of this element.
2012 | * @return {!webdriver.promise.Promise.<string>} A promise that will be
2013 | * resolved with the element's tag name.
2014 | */
2015 | webdriver.WebElement.prototype.getTagName = function() {
2016 | return this.schedule_(
2017 | new webdriver.Command(webdriver.CommandName.GET_ELEMENT_TAG_NAME),
2018 | 'WebElement.getTagName()');
2019 | };
2020 |
2021 |
2022 | /**
2023 | * Schedules a command to query for the computed style of the element
2024 | * represented by this instance. If the element inherits the named style from
2025 | * its parent, the parent will be queried for its value. Where possible, color
2026 | * values will be converted to their hex representation (e.g. #00ff00 instead of
2027 | * rgb(0, 255, 0)).
2028 | *
2029 | * _Warning:_ the value returned will be as the browser interprets it, so
2030 | * it may be tricky to form a proper assertion.
2031 | *
2032 | * @param {string} cssStyleProperty The name of the CSS style property to look
2033 | * up.
2034 | * @return {!webdriver.promise.Promise.<string>} A promise that will be
2035 | * resolved with the requested CSS value.
2036 | */
2037 | webdriver.WebElement.prototype.getCssValue = function(cssStyleProperty) {
2038 | var name = webdriver.CommandName.GET_ELEMENT_VALUE_OF_CSS_PROPERTY;
2039 | return this.schedule_(
2040 | new webdriver.Command(name).
2041 | setParameter('propertyName', cssStyleProperty),
2042 | 'WebElement.getCssValue(' + cssStyleProperty + ')');
2043 | };
2044 |
2045 |
2046 | /**
2047 | * Schedules a command to query for the value of the given attribute of the
2048 | * element. Will return the current value, even if it has been modified after
2049 | * the page has been loaded. More exactly, this method will return the value of
2050 | * the given attribute, unless that attribute is not present, in which case the
2051 | * value of the property with the same name is returned. If neither value is
2052 | * set, null is returned (for example, the "value" property of a textarea
2053 | * element). The "style" attribute is converted as best can be to a
2054 | * text representation with a trailing semi-colon. The following are deemed to
2055 | * be "boolean" attributes and will return either "true" or null:
2056 | *
2057 | * async, autofocus, autoplay, checked, compact, complete, controls, declare,
2058 | * defaultchecked, defaultselected, defer, disabled, draggable, ended,
2059 | * formnovalidate, hidden, indeterminate, iscontenteditable, ismap, itemscope,
2060 | * loop, multiple, muted, nohref, noresize, noshade, novalidate, nowrap, open,
2061 | * paused, pubdate, readonly, required, reversed, scoped, seamless, seeking,
2062 | * selected, spellcheck, truespeed, willvalidate
2063 | *
2064 | * Finally, the following commonly mis-capitalized attribute/property names
2065 | * are evaluated as expected:
2066 | *
2067 | * - "class"
2068 | * - "readonly"
2069 | *
2070 | * @param {string} attributeName The name of the attribute to query.
2071 | * @return {!webdriver.promise.Promise.<?string>} A promise that will be
2072 | * resolved with the attribute's value. The returned value will always be
2073 | * either a string or null.
2074 | */
2075 | webdriver.WebElement.prototype.getAttribute = function(attributeName) {
2076 | return this.schedule_(
2077 | new webdriver.Command(webdriver.CommandName.GET_ELEMENT_ATTRIBUTE).
2078 | setParameter('name', attributeName),
2079 | 'WebElement.getAttribute(' + attributeName + ')');
2080 | };
2081 |
2082 |
2083 | /**
2084 | * Get the visible (i.e. not hidden by CSS) innerText of this element, including
2085 | * sub-elements, without any leading or trailing whitespace.
2086 | * @return {!webdriver.promise.Promise.<string>} A promise that will be
2087 | * resolved with the element's visible text.
2088 | */
2089 | webdriver.WebElement.prototype.getText = function() {
2090 | return this.schedule_(
2091 | new webdriver.Command(webdriver.CommandName.GET_ELEMENT_TEXT),
2092 | 'WebElement.getText()');
2093 | };
2094 |
2095 |
2096 | /**
2097 | * Schedules a command to compute the size of this element's bounding box, in
2098 | * pixels.
2099 | * @return {!webdriver.promise.Promise.<{width: number, height: number}>} A
2100 | * promise that will be resolved with the element's size as a
2101 | * {@code {width:number, height:number}} object.
2102 | */
2103 | webdriver.WebElement.prototype.getSize = function() {
2104 | return this.schedule_(
2105 | new webdriver.Command(webdriver.CommandName.GET_ELEMENT_SIZE),
2106 | 'WebElement.getSize()');
2107 | };
2108 |
2109 |
2110 | /**
2111 | * Schedules a command to compute the location of this element in page space.
2112 | * @return {!webdriver.promise.Promise.<{x: number, y: number}>} A promise that
2113 | * will be resolved to the element's location as a
2114 | * {@code {x:number, y:number}} object.
2115 | */
2116 | webdriver.WebElement.prototype.getLocation = function() {
2117 | return this.schedule_(
2118 | new webdriver.Command(webdriver.CommandName.GET_ELEMENT_LOCATION),
2119 | 'WebElement.getLocation()');
2120 | };
2121 |
2122 |
2123 | /**
2124 | * Schedules a command to query whether the DOM element represented by this
2125 | * instance is enabled, as dicted by the {@code disabled} attribute.
2126 | * @return {!webdriver.promise.Promise.<boolean>} A promise that will be
2127 | * resolved with whether this element is currently enabled.
2128 | */
2129 | webdriver.WebElement.prototype.isEnabled = function() {
2130 | return this.schedule_(
2131 | new webdriver.Command(webdriver.CommandName.IS_ELEMENT_ENABLED),
2132 | 'WebElement.isEnabled()');
2133 | };
2134 |
2135 |
2136 | /**
2137 | * Schedules a command to query whether this element is selected.
2138 | * @return {!webdriver.promise.Promise.<boolean>} A promise that will be
2139 | * resolved with whether this element is currently selected.
2140 | */
2141 | webdriver.WebElement.prototype.isSelected = function() {
2142 | return this.schedule_(
2143 | new webdriver.Command(webdriver.CommandName.IS_ELEMENT_SELECTED),
2144 | 'WebElement.isSelected()');
2145 | };
2146 |
2147 |
2148 | /**
2149 | * Schedules a command to submit the form containing this element (or this
2150 | * element if it is a FORM element). This command is a no-op if the element is
2151 | * not contained in a form.
2152 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
2153 | * when the form has been submitted.
2154 | */
2155 | webdriver.WebElement.prototype.submit = function() {
2156 | return this.schedule_(
2157 | new webdriver.Command(webdriver.CommandName.SUBMIT_ELEMENT),
2158 | 'WebElement.submit()');
2159 | };
2160 |
2161 |
2162 | /**
2163 | * Schedules a command to clear the {@code value} of this element. This command
2164 | * has no effect if the underlying DOM element is neither a text INPUT element
2165 | * nor a TEXTAREA element.
2166 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
2167 | * when the element has been cleared.
2168 | */
2169 | webdriver.WebElement.prototype.clear = function() {
2170 | return this.schedule_(
2171 | new webdriver.Command(webdriver.CommandName.CLEAR_ELEMENT),
2172 | 'WebElement.clear()');
2173 | };
2174 |
2175 |
2176 | /**
2177 | * Schedules a command to test whether this element is currently displayed.
2178 | * @return {!webdriver.promise.Promise.<boolean>} A promise that will be
2179 | * resolved with whether this element is currently visible on the page.
2180 | */
2181 | webdriver.WebElement.prototype.isDisplayed = function() {
2182 | return this.schedule_(
2183 | new webdriver.Command(webdriver.CommandName.IS_ELEMENT_DISPLAYED),
2184 | 'WebElement.isDisplayed()');
2185 | };
2186 |
2187 |
2188 | /**
2189 | * Schedules a command to retrieve the outer HTML of this element.
2190 | * @return {!webdriver.promise.Promise.<string>} A promise that will be
2191 | * resolved with the element's outer HTML.
2192 | */
2193 | webdriver.WebElement.prototype.getOuterHtml = function() {
2194 | return this.driver_.executeScript(function() {
2195 | var element = arguments[0];
2196 | if ('outerHTML' in element) {
2197 | return element.outerHTML;
2198 | } else {
2199 | var div = element.ownerDocument.createElement('div');
2200 | div.appendChild(element.cloneNode(true));
2201 | return div.innerHTML;
2202 | }
2203 | }, this);
2204 | };
2205 |
2206 |
2207 | /**
2208 | * Schedules a command to retrieve the inner HTML of this element.
2209 | * @return {!webdriver.promise.Promise.<string>} A promise that will be
2210 | * resolved with the element's inner HTML.
2211 | */
2212 | webdriver.WebElement.prototype.getInnerHtml = function() {
2213 | return this.driver_.executeScript('return arguments[0].innerHTML', this);
2214 | };
2215 |
2216 |
2217 |
2218 | /**
2219 | * WebElementPromise is a promise that will be fulfilled with a WebElement.
2220 | * This serves as a forward proxy on WebElement, allowing calls to be
2221 | * scheduled without directly on this instance before the underlying
2222 | * WebElement has been fulfilled. In other words, the following two statements
2223 | * are equivalent:
2224 | *
2225 | * driver.findElement({id: 'my-button'}).click();
2226 | * driver.findElement({id: 'my-button'}).then(function(el) {
2227 | * return el.click();
2228 | * });
2229 | *
2230 | * @param {!webdriver.WebDriver} driver The parent WebDriver instance for this
2231 | * element.
2232 | * @param {!webdriver.promise.Promise.<!webdriver.WebElement>} el A promise
2233 | * that will resolve to the promised element.
2234 | * @constructor
2235 | * @extends {webdriver.WebElement}
2236 | * @implements {webdriver.promise.Thenable.<!webdriver.WebElement>}
2237 | * @final
2238 | */
2239 | webdriver.WebElementPromise = function(driver, el) {
2240 | webdriver.WebElement.call(this, driver, {'ELEMENT': 'unused'});
2241 |
2242 | /** @override */
2243 | this.cancel = goog.bind(el.cancel, el);
2244 |
2245 | /** @override */
2246 | this.isPending = goog.bind(el.isPending, el);
2247 |
2248 | /** @override */
2249 | this.then = goog.bind(el.then, el);
2250 |
2251 | /** @override */
2252 | this.thenCatch = goog.bind(el.thenCatch, el);
2253 |
2254 | /** @override */
2255 | this.thenFinally = goog.bind(el.thenFinally, el);
2256 |
2257 | /**
2258 | * Defers returning the element ID until the wrapped WebElement has been
2259 | * resolved.
2260 | * @override
2261 | */
2262 | this.getId = function() {
2263 | return el.then(function(el) {
2264 | return el.getId();
2265 | });
2266 | };
2267 | };
2268 | goog.inherits(webdriver.WebElementPromise, webdriver.WebElement);
2269 |
2270 |
2271 | /**
2272 | * Represents a modal dialog such as {@code alert}, {@code confirm}, or
2273 | * {@code prompt}. Provides functions to retrieve the message displayed with
2274 | * the alert, accept or dismiss the alert, and set the response text (in the
2275 | * case of {@code prompt}).
2276 | * @param {!webdriver.WebDriver} driver The driver controlling the browser this
2277 | * alert is attached to.
2278 | * @param {string} text The message text displayed with this alert.
2279 | * @constructor
2280 | */
2281 | webdriver.Alert = function(driver, text) {
2282 | /** @private {!webdriver.WebDriver} */
2283 | this.driver_ = driver;
2284 |
2285 | /** @private {!webdriver.promise.Promise.<string>} */
2286 | this.text_ = webdriver.promise.when(text);
2287 | };
2288 |
2289 |
2290 | /**
2291 | * Retrieves the message text displayed with this alert. For instance, if the
2292 | * alert were opened with alert("hello"), then this would return "hello".
2293 | * @return {!webdriver.promise.Promise.<string>} A promise that will be
2294 | * resolved to the text displayed with this alert.
2295 | */
2296 | webdriver.Alert.prototype.getText = function() {
2297 | return this.text_;
2298 | };
2299 |
2300 |
2301 | /**
2302 | * Accepts this alert.
2303 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
2304 | * when this command has completed.
2305 | */
2306 | webdriver.Alert.prototype.accept = function() {
2307 | return this.driver_.schedule(
2308 | new webdriver.Command(webdriver.CommandName.ACCEPT_ALERT),
2309 | 'WebDriver.switchTo().alert().accept()');
2310 | };
2311 |
2312 |
2313 | /**
2314 | * Dismisses this alert.
2315 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
2316 | * when this command has completed.
2317 | */
2318 | webdriver.Alert.prototype.dismiss = function() {
2319 | return this.driver_.schedule(
2320 | new webdriver.Command(webdriver.CommandName.DISMISS_ALERT),
2321 | 'WebDriver.switchTo().alert().dismiss()');
2322 | };
2323 |
2324 |
2325 | /**
2326 | * Sets the response text on this alert. This command will return an error if
2327 | * the underlying alert does not support response text (e.g. window.alert and
2328 | * window.confirm).
2329 | * @param {string} text The text to set.
2330 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
2331 | * when this command has completed.
2332 | */
2333 | webdriver.Alert.prototype.sendKeys = function(text) {
2334 | return this.driver_.schedule(
2335 | new webdriver.Command(webdriver.CommandName.SET_ALERT_TEXT).
2336 | setParameter('text', text),
2337 | 'WebDriver.switchTo().alert().sendKeys(' + text + ')');
2338 | };
2339 |
2340 |
2341 |
2342 | /**
2343 | * AlertPromise is a promise that will be fulfilled with an Alert. This promise
2344 | * serves as a forward proxy on an Alert, allowing calls to be scheduled
2345 | * directly on this instance before the underlying Alert has been fulfilled. In
2346 | * other words, the following two statements are equivalent:
2347 | *
2348 | * driver.switchTo().alert().dismiss();
2349 | * driver.switchTo().alert().then(function(alert) {
2350 | * return alert.dismiss();
2351 | * });
2352 | *
2353 | * @param {!webdriver.WebDriver} driver The driver controlling the browser this
2354 | * alert is attached to.
2355 | * @param {!webdriver.promise.Thenable.<!webdriver.Alert>} alert A thenable
2356 | * that will be fulfilled with the promised alert.
2357 | * @constructor
2358 | * @extends {webdriver.Alert}
2359 | * @implements {webdriver.promise.Thenable.<!webdriver.Alert>}
2360 | * @final
2361 | */
2362 | webdriver.AlertPromise = function(driver, alert) {
2363 | webdriver.Alert.call(this, driver, 'unused');
2364 |
2365 | /** @override */
2366 | this.cancel = goog.bind(alert.cancel, alert);
2367 |
2368 | /** @override */
2369 | this.isPending = goog.bind(alert.isPending, alert);
2370 |
2371 | /** @override */
2372 | this.then = goog.bind(alert.then, alert);
2373 |
2374 | /** @override */
2375 | this.thenCatch = goog.bind(alert.thenCatch, alert);
2376 |
2377 | /** @override */
2378 | this.thenFinally = goog.bind(alert.thenFinally, alert);
2379 |
2380 | /**
2381 | * Defer returning text until the promised alert has been resolved.
2382 | * @override
2383 | */
2384 | this.getText = function() {
2385 | return alert.then(function(alert) {
2386 | return alert.getText();
2387 | });
2388 | };
2389 |
2390 | /**
2391 | * Defers action until the alert has been located.
2392 | * @override
2393 | */
2394 | this.accept = function() {
2395 | return alert.then(function(alert) {
2396 | return alert.accept();
2397 | });
2398 | };
2399 |
2400 | /**
2401 | * Defers action until the alert has been located.
2402 | * @override
2403 | */
2404 | this.dismiss = function() {
2405 | return alert.then(function(alert) {
2406 | return alert.dismiss();
2407 | });
2408 | };
2409 |
2410 | /**
2411 | * Defers action until the alert has been located.
2412 | * @override
2413 | */
2414 | this.sendKeys = function(text) {
2415 | return alert.then(function(alert) {
2416 | return alert.sendKeys(text);
2417 | });
2418 | };
2419 | };
2420 | goog.inherits(webdriver.AlertPromise, webdriver.Alert);
2421 |
2422 |
2423 |
2424 | /**
2425 | * An error returned to indicate that there is an unhandled modal dialog on the
2426 | * current page.
2427 | * @param {string} message The error message.
2428 | * @param {string} text The text displayed with the unhandled alert.
2429 | * @param {!webdriver.Alert} alert The alert handle.
2430 | * @constructor
2431 | * @extends {bot.Error}
2432 | */
2433 | webdriver.UnhandledAlertError = function(message, text, alert) {
2434 | goog.base(this, bot.ErrorCode.UNEXPECTED_ALERT_OPEN, message);
2435 |
2436 | /** @private {string} */
2437 | this.text_ = text;
2438 |
2439 | /** @private {!webdriver.Alert} */
2440 | this.alert_ = alert;
2441 | };
2442 | goog.inherits(webdriver.UnhandledAlertError, bot.Error);
2443 |
2444 |
2445 | /**
2446 | * @return {string} The text displayed with the unhandled alert.
2447 | */
2448 | webdriver.UnhandledAlertError.prototype.getAlertText = function() {
2449 | return this.text_;
2450 | };
2451 |
2452 |
2453 | /**
2454 | * @return {!webdriver.Alert} The open alert.
2455 | * @deprecated Use {@link #getAlertText}. This method will be removed in
2456 | * 2.45.0.
2457 | */
2458 | webdriver.UnhandledAlertError.prototype.getAlert = function() {
2459 | return this.alert_;
2460 | };
2461 |
2462 |
2463 |
2464 | /**
2465 | * Used with {@link webdriver.WebElement#sendKeys WebElement#sendKeys} on file
2466 | * input elements ({@code <input type="file">}) to detect when the entered key
2467 | * sequence defines the path to a file.
2468 | *
2469 | * By default, {@linkplain webdriver.WebElement WebElement's} will enter all
2470 | * key sequences exactly as entered. You may set a
2471 | * {@linkplain webdriver.WebDriver#setFileDetector file detector} on the parent
2472 | * WebDriver instance to define custom behavior for handling file elements. Of
2473 | * particular note is the {@link selenium-webdriver/remote.FileDetector}, which
2474 | * should be used when running against a remote
2475 | * [Selenium Server](http://docs.seleniumhq.org/download/).
2476 | */
2477 | webdriver.FileDetector = goog.defineClass(null, {
2478 | /** @constructor */
2479 | constructor: function() {},
2480 |
2481 | /**
2482 | * Handles the file specified by the given path, preparing it for use with
2483 | * the current browser. If the path does not refer to a valid file, it will
2484 | * be returned unchanged, otherwisee a path suitable for use with the current
2485 | * browser will be returned.
2486 | *
2487 | * This default implementation is a no-op. Subtypes may override this
2488 | * function for custom tailored file handling.
2489 | *
2490 | * @param {!webdriver.WebDriver} driver The driver for the current browser.
2491 | * @param {string} path The path to process.
2492 | * @return {!webdriver.promise.Promise<string>} A promise for the processed
2493 | * file path.
2494 | * @package
2495 | */
2496 | handleFile: function(driver, path) {
2497 | return webdriver.promise.fulfilled(path);
2498 | }
2499 | });