lib/webdriver/webdriver.js

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