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
19goog.provide('webdriver.Alert');
20goog.provide('webdriver.AlertPromise');
21goog.provide('webdriver.FileDetector');
22goog.provide('webdriver.UnhandledAlertError');
23goog.provide('webdriver.WebDriver');
24goog.provide('webdriver.WebElement');
25goog.provide('webdriver.WebElementPromise');
26
27goog.require('bot.Error');
28goog.require('bot.ErrorCode');
29goog.require('bot.response');
30goog.require('goog.array');
31goog.require('goog.object');
32goog.require('webdriver.ActionSequence');
33goog.require('webdriver.Command');
34goog.require('webdriver.CommandName');
35goog.require('webdriver.Key');
36goog.require('webdriver.Locator');
37goog.require('webdriver.Serializable');
38goog.require('webdriver.Session');
39goog.require('webdriver.TouchSequence');
40goog.require('webdriver.logging');
41goog.require('webdriver.promise');
42goog.require('webdriver.until');
43goog.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 */
80webdriver.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 */
106webdriver.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 */
127webdriver.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 */
152webdriver.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 */
188webdriver.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 */
283webdriver.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 */
308webdriver.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 */
322webdriver.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 */
336webdriver.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 */
405webdriver.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 */
419webdriver.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 */
428webdriver.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 */
442webdriver.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 */
467webdriver.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 */
484webdriver.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 */
526webdriver.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 */
615webdriver.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 */
637webdriver.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 */
698webdriver.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 */
729webdriver.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 */
739webdriver.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 */
751webdriver.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 */
766webdriver.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 */
778webdriver.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 */
790webdriver.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 */
800webdriver.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 */
812webdriver.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 */
865webdriver.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 */
900webdriver.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 */
928webdriver.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 */
972webdriver.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 */
992webdriver.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 */
1013webdriver.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 */
1044webdriver.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 */
1054webdriver.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 */
1063webdriver.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 */
1072webdriver.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 */
1083webdriver.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 */
1096webdriver.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 */
1109webdriver.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 */
1121webdriver.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 */
1133webdriver.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 */
1146webdriver.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 */
1165webdriver.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 */
1180webdriver.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 */
1230webdriver.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 */
1245webdriver.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 */
1262webdriver.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 */
1279webdriver.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 */
1292webdriver.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 */
1301webdriver.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 */
1310webdriver.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 */
1321webdriver.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 */
1349webdriver.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 */
1366webdriver.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 */
1381webdriver.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 */
1396webdriver.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 */
1410webdriver.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 */
1427webdriver.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 */
1443webdriver.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 */
1458webdriver.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 */
1473webdriver.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 */
1486webdriver.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 */
1506webdriver.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 */
1529webdriver.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 */
1542webdriver.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 */
1555webdriver.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 */
1569webdriver.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 */
1597webdriver.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 */
1618webdriver.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 */
1632webdriver.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 */
1656webdriver.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 */
1704webdriver.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};
1714goog.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 */
1722webdriver.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 */
1731webdriver.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 */
1741webdriver.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 */
1766webdriver.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 */
1777webdriver.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 */
1788webdriver.WebElement.prototype.getRawId = function() {
1789 return this.id_.then(function(value) {
1790 return value['ELEMENT'];
1791 });
1792};
1793
1794
1795/** @override */
1796webdriver.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 */
1813webdriver.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 */
1857webdriver.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 */
1882webdriver.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 */
1898webdriver.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 */
1917webdriver.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 */
1977webdriver.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 */
2015webdriver.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 */
2037webdriver.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 */
2075webdriver.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 */
2089webdriver.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 */
2103webdriver.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 */
2116webdriver.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 */
2129webdriver.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 */
2141webdriver.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 */
2155webdriver.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 */
2169webdriver.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 */
2181webdriver.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 */
2193webdriver.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 */
2212webdriver.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 */
2239webdriver.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};
2268goog.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 */
2281webdriver.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 */
2296webdriver.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 */
2306webdriver.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 */
2318webdriver.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 */
2333webdriver.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 */
2362webdriver.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};
2420goog.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 */
2433webdriver.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};
2442goog.inherits(webdriver.UnhandledAlertError, bot.Error);
2443
2444
2445/**
2446 * @return {string} The text displayed with the unhandled alert.
2447 */
2448webdriver.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 */
2458webdriver.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 */
2477webdriver.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});