lib/webdriver/webdriver.js

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