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