lib/webdriver/webdriver.js

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