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