# 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 at6//7//      http://www.apache.org/licenses/LICENSE-2.08//9// Unless required by applicable law or agreed to in writing, software10// 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 and13// limitations under the License.1415/**16 * @fileoverview The heart of the WebDriver JavaScript API.17 */1819goog.provide('webdriver.Alert');20goog.provide('webdriver.AlertPromise');21goog.provide('webdriver.UnhandledAlertError');22goog.provide('webdriver.WebDriver');23goog.provide('webdriver.WebElement');24goog.provide('webdriver.WebElementPromise');2526goog.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');394041//////////////////////////////////////////////////////////////////////////////42//43//  webdriver.WebDriver44//45//////////////////////////////////////////////////////////////////////////////46474849/**50 * Creates a new WebDriver client, which provides control over a browser.51 *52 * Every WebDriver command returns a {@code webdriver.promise.Promise} that53 * represents the result of that command. Callbacks may be registered on this54 * object to manipulate the command result or catch an expected error. Any55 * commands scheduled with a callback are considered sub-commands and will56 * 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 a69 *     known session or a promise that will be resolved to a session.70 * @param {!webdriver.CommandExecutor} executor The executor to use when71 *     sending commands to the browser.72 * @param {webdriver.promise.ControlFlow=} opt_flow The flow to73 *     schedule commands through. Defaults to the active flow object.74 * @constructor75 */76webdriver.WebDriver = function(session, executor, opt_flow) {7778  /** @private {!(webdriver.Session|webdriver.promise.Promise)} */79  this.session_ = session;8081  /** @private {!webdriver.CommandExecutor} */82  this.executor_ = executor;8384  /** @private {!webdriver.promise.ControlFlow} */85  this.flow_ = opt_flow || webdriver.promise.controlFlow();86};878889/**90 * Creates a new WebDriver client for an existing session.91 * @param {!webdriver.CommandExecutor} executor Command executor to use when92 *     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 driver95 *     commands should execute under. Defaults to the96 *     {@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};106107108/**109 * Creates a new WebDriver session.110 * @param {!webdriver.CommandExecutor} executor The executor to create the new111 *     session with.112 * @param {!webdriver.Capabilities} desiredCapabilities The desired113 *     capabilities for the new session.114 * @param {webdriver.promise.ControlFlow=} opt_flow The control flow all driver115 *     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};128129130/**131 * Sends a command to the server that is expected to return the details for a132 * {@link webdriver.Session}. This may either be an existing session, or a133 * newly created one.134 * @param {!webdriver.CommandExecutor} executor Command executor to use when135 *     querying for session details.136 * @param {!webdriver.Command} command The command to send to fetch the session137 *     details.138 * @param {string} description A descriptive debug label for this action.139 * @param {webdriver.promise.ControlFlow=} opt_flow The control flow all driver140 *     commands should execute under. Defaults to the141 *     {@link webdriver.promise.controlFlow() currently active} control flow.142 * @return {!webdriver.WebDriver} A new WebDriver client for the session.143 * @private144 */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};158159160/**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's165 *     server ID</li>166 * <li>if the object provides a "toJSON" function, the return value of this167 *     function will be returned</li>168 * <li>otherwise, the value of each key will be recursively converted according169 *     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 the174 *     input value's JSON representation.175 * @private176 * @see http://code.google.com/p/selenium/wiki/JsonWireProtocol177 */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};210211212/**213 * Converts a value from its JSON representation according to the WebDriver wire214 * protocol. Any JSON object containing a215 * {@code webdriver.WebElement.ELEMENT_KEY} key will be decoded to a216 * {@code webdriver.WebElement} object. All other values will be passed through217 * as is.218 * @param {!webdriver.WebDriver} driver The driver instance to use as the219 *     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/JsonWireProtocol223 * @private224 */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};239240241/**242 * Translates a command to its wire-protocol representation before passing it243 * 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 the247 *     command response.248 * @private249 */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};258259260/**261 * @return {!webdriver.promise.ControlFlow} The control flow used by this262 *     instance.263 */264webdriver.WebDriver.prototype.controlFlow = function() {265  return this.flow_;266};267268269/**270 * Schedules a {@code webdriver.Command} to be executed by this driver's271 * {@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 resolved275 *     with the command result.276 * @template T277 */278webdriver.WebDriver.prototype.schedule = function(command, description) {279  var self = this;280281  checkHasNotQuit();282  command.setParameter('sessionId', this.session_);283284  // If any of the command parameters are rejected promises, those285  // rejections may be reported as unhandled before the control flow286  // attempts to execute the command. To ensure parameters errors287  // propagate through the command itself, we resolve all of the288  // command parameters now, but suppress any errors until the ControlFlow289  // actually executes the command. This addresses scenarios like catching290  // 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);299300  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 event304    // loop as this |command|, which would prevent us from detecting that the305    // 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();308309    // Retrieve resolved command parameters; any previously suppressed errors310    // will now propagate up through the control flow as part of the command311    // 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  });331332  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};340341342// ----------------------------------------------------------------------------343// Client command functions:344// ----------------------------------------------------------------------------345346347/**348 * @return {!webdriver.promise.Promise.<!webdriver.Session>} A promise for this349 *     client's session.350 */351webdriver.WebDriver.prototype.getSession = function() {352  return webdriver.promise.when(this.session_);353};354355356/**357 * @return {!webdriver.promise.Promise.<!webdriver.Capabilities>} A promise358 *     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};365366367/**368 * Schedules a command to quit the current session. After calling quit, this369 * instance will be invalidated and may no longer be used to issue commands370 * against the browser.371 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved372 *     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 to379  // 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};384385386/**387 * Creates a new action sequence using this driver. The sequence will not be388 * scheduled for execution until {@link webdriver.ActionSequence#perform} is389 * 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};402403404/**405 * Schedules a command to execute JavaScript in the context of the currently406 * selected frame or window. The script fragment will be executed as the body407 * 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 target409 * window.410 *411 * Any arguments provided in addition to the script will be included as script412 * 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 item415 * 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, thus419 * {@code document} may be used to refer to the current document. Any local420 * 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 return424 * statement), then the following steps will be taken for resolving this425 * functions return value:426 * <ul>427 * <li>For a HTML element, the value will resolve to a428 *     {@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 to433 *     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 the439 *    scripts return value.440 * @template T441 */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};452453454/**455 * Schedules a command to execute asynchronous JavaScript in the context of the456 * currently selected frame or window. The script fragment will be executed as457 * the body of an anonymous function. If the script is provided as a function458 * object, that function will be converted to a string for injection into the459 * target window.460 *461 * Any arguments provided in addition to the script will be included as script462 * 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 item465 * adheres to the types previously mentioned.466 *467 * Unlike executing synchronous JavaScript with468 * {@code webdriver.WebDriver.prototype.executeScript}, scripts executed with469 * this function must explicitly signal they are finished by invoking the470 * provided callback. This callback will always be injected into the471 * executed function as the last argument, and thus may be referenced with472 * {@code arguments[arguments.length - 1]}. The following steps will be taken473 * for resolving this functions return value against the first argument to the474 * script's callback function:475 * <ul>476 * <li>For a HTML element, the value will resolve to a477 *     {@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 to482 *     the rules above</li>483 * </ul>484 *485 * Example #1: Performing a sleep that is synchronized with the currently486 * 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 this508 * example, the inject script is specified with a function literal. When using509 * this format, the function is converted to a string for injection, so it510 * should not reference any symbols not defined in the scope of the page under511 * 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 the531 *    scripts return value.532 * @template T533 */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};544545546/**547 * Schedules a command to execute a custom function.548 * @param {function(...): (T|webdriver.promise.Promise.<T>)} fn The function to549 *     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 T555 */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};569570571/**572 * Schedules a command to wait for a condition to hold, as defined by some573 * user supplied function. If any errors occur while evaluating the wait, they574 * will be allowed to propagate.575 *576 * <p>In the event a condition returns a {@link webdriver.promise.Promise}, the577 * polling loop will wait for it to be resolved and use the resolved value for578 * evaluating whether the condition has been satisfied. The resolution time for579 * 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 times584 *     out.585 * @return {!webdriver.promise.Promise} A promise that will be resolved when the586 *     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};591592593/**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 resolved597 *     when the sleep has finished.598 */599webdriver.WebDriver.prototype.sleep = function(ms) {600  return this.flow_.timeout(ms, 'WebDriver.sleep(' + ms + ')');601};602603604/**605 * Schedules a command to retrieve they current window handle.606 * @return {!webdriver.promise.Promise.<string>} A promise that will be607 *     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};614615616/**617 * Schedules a command to retrieve the current list of available window handles.618 * @return {!webdriver.promise.Promise.<!Array.<string>>} A promise that will619 *     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};626627628/**629 * Schedules a command to retrieve the current page's source. The page source630 * returned is a representation of the underlying DOM: do not expect it to be631 * formatted or escaped in the same way as the response sent from the web632 * server.633 * @return {!webdriver.promise.Promise.<string>} A promise that will be634 *     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};641642643/**644 * Schedules a command to close the current window.645 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved646 *     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};652653654/**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 resolved658 *     when the document has finished loading.659 */660webdriver.WebDriver.prototype.get = function(url) {661  return this.navigate().to(url);662};663664665/**666 * Schedules a command to retrieve the URL of the current page.667 * @return {!webdriver.promise.Promise.<string>} A promise that will be668 *     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};675676677/**678 * Schedules a command to retrieve the current page's title.679 * @return {!webdriver.promise.Promise.<string>} A promise that will be680 *     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};686687688/**689 * Schedule a command to find an element on the page. If the element cannot be690 * found, a {@link bot.ErrorCode.NO_SUCH_ELEMENT} result will be returned691 * by the driver. Unlike other commands, this error cannot be suppressed. In692 * other words, scheduling a command to find an element doubles as an assert693 * that the element is present on the page. To test whether an element is694 * present on the page, use {@link #isElementPresent} instead.695 *696 * <p>The search criteria for an element may be defined using one of the697 * factories in the {@link webdriver.By} namespace, or as a short-hand698 * {@link webdriver.By.Hash} object. For example, the following two statements699 * 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 input706 * this WebDriver instance and returns a {@link webdriver.WebElement}, or a707 * promise that will resolve to a WebElement. For example, to find the first708 * 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 elements723 * 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. A725 * reference to the DOM element will be stored in a known location and this726 * driver will attempt to retrieve it through {@link #executeScript}. If the727 * element cannot be found (eg, it belongs to a different document than the728 * one this instance is currently focused on), a729 * {@link bot.ErrorCode.NO_SUCH_ELEMENT} error will be returned.730 *731 * @param {!(webdriver.Locator|webdriver.By.Hash|Element|Function)} locator The732 *     locator to use.733 * @return {!webdriver.WebElement} A WebElement that can be used to issue734 *     commands against the located element. If the element is not found, the735 *     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};762763764/**765 * @param {!Function} locatorFn The locator function to use.766 * @param {!(webdriver.WebDriver|webdriver.WebElement)} context The search767 *     context.768 * @return {!webdriver.promise.Promise.<!webdriver.WebElement>} A769 *     promise that will resolve to a list of WebElements.770 * @private771 */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};784785786/**787 * Locates a DOM element so that commands may be issued against it using the788 * {@link webdriver.WebElement} class. This is accomplished by storing a789 * reference to the element in an object on the element's ownerDocument.790 * {@link #executeScript} will then be used to create a WebElement from this791 * reference. This requires this driver to currently be focused on the792 * ownerDocument's window+frame.793794 * @param {!Element} element The element to locate.795 * @return {!webdriver.promise.Promise.<webdriver.WebElement>} A promise that796 *     will be fulfilled with the located element, or null if the element797 *     could not be found.798 * @private799 */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;806807  function cleanUp() {808    delete store[id];809  }810811  function lookupElement(id) {812    var store = document['$webdriver$'];813    if (!store) {814      return null;815    }816817    var element = store[id];818    if (!element || element[id] !== id) {819      return null;820    }821    return element;822  }823824  /** @type {!webdriver.promise.Promise.<webdriver.WebElement>} */825  var foundElement = this.executeScript(lookupElement, id);826  foundElement.thenFinally(cleanUp);827  return foundElement;828};829830831/**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 the835 * document the driver is currently focused on. Otherwise, the function will836 * 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 actual840 *     DOM element to be located by the server.841 * @return {!webdriver.promise.Promise.<boolean>} A promise that will resolve842 *     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};854855856/**857 * Schedule a command to search for multiple elements on the page.858 *859 * @param {!(webdriver.Locator|webdriver.By.Hash|Function)} locator The locator860 *     strategy to use when searching for the element.861 * @return {!webdriver.promise.Promise.<!Array.<!webdriver.WebElement>>} A862 *     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};875876877/**878 * @param {!Function} locatorFn The locator function to use.879 * @param {!(webdriver.WebDriver|webdriver.WebElement)} context The search880 *     context.881 * @return {!webdriver.promise.Promise.<!Array.<!webdriver.WebElement>>} A882 *     promise that will resolve to an array of WebElements.883 * @private884 */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    }891892    if (!goog.isArray(result)) {893      return [];894    }895896    return goog.array.filter(result, function(item) {897      return item instanceof webdriver.WebElement;898    });899  });900};901902903/**904 * Schedule a command to take a screenshot. The driver makes a best effort to905 * return a screenshot of the following, in order of preference:906 * <ol>907 *   <li>Entire page908 *   <li>Current window909 *   <li>Visible portion of the current frame910 *   <li>The screenshot of the entire display containing the browser911 * </ol>912 *913 * @return {!webdriver.promise.Promise.<string>} A promise that will be914 *     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};920921922/**923 * @return {!webdriver.WebDriver.Options} The options interface for this924 *     instance.925 */926webdriver.WebDriver.prototype.manage = function() {927  return new webdriver.WebDriver.Options(this);928};929930931/**932 * @return {!webdriver.WebDriver.Navigation} The navigation interface for this933 *     instance.934 */935webdriver.WebDriver.prototype.navigate = function() {936  return new webdriver.WebDriver.Navigation(this);937};938939940/**941 * @return {!webdriver.WebDriver.TargetLocator} The target locator interface for942 *     this instance.943 */944webdriver.WebDriver.prototype.switchTo = function() {945  return new webdriver.WebDriver.TargetLocator(this);946};947948949950/**951 * Interface for navigating back and forth in the browser history.952 * @param {!webdriver.WebDriver} driver The parent driver.953 * @constructor954 */955webdriver.WebDriver.Navigation = function(driver) {956957  /** @private {!webdriver.WebDriver} */958  this.driver_ = driver;959};960961962/**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 resolved966 *     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};974975976/**977 * Schedules a command to move backwards in the browser history.978 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved979 *     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};986987988/**989 * Schedules a command to move forwards in the browser history.990 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved991 *     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};9989991000/**1001 * Schedules a command to refresh the current page.1002 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved1003 *     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};1010101110121013/**1014 * Provides methods for managing browser and driver state.1015 * @param {!webdriver.WebDriver} driver The parent driver.1016 * @constructor1017 */1018webdriver.WebDriver.Options = function(driver) {10191020  /** @private {!webdriver.WebDriver} */1021  this.driver_ = driver;1022};102310241025/**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_Object1036 */1037webdriver.WebDriver.Options.Cookie;103810391040/**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 as1048 *     a number, should be in milliseconds since midnight, January 1, 1970 UTC.1049 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved1050 *     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  }10581059  // We do not allow ';' in value.1060  if (/;/.test(value)) {1061    throw Error('Invalid cookie value "' + value + '"');1062  }10631064  var cookieString = name + '=' + value +1065      (opt_domain ? ';domain=' + opt_domain : '') +1066      (opt_path ? ';path=' + opt_path : '') +1067      (opt_isSecure ? ';secure' : '');10681069  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  }10821083  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': expiry1092          }),1093      'WebDriver.manage().addCookie(' + cookieString + ')');1094};109510961097/**1098 * Schedules a command to delete all cookies visible to the current page.1099 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved1100 *     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};110711081109/**1110 * Schedules a command to delete the cookie with the given name. This command is1111 * a no-op if there is no cookie with the given name visible to the current1112 * page.1113 * @param {string} name The name of the cookie to delete.1114 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved1115 *     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};112311241125/**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 WebDriver1128 * wire protocol.1129 * @return {!webdriver.promise.Promise.<1130 *     !Array.<webdriver.WebDriver.Options.Cookie>>} A promise that will be1131 *     resolved with the cookies visible to the current page.1132 * @see http://code.google.com/p/selenium/wiki/JsonWireProtocol#Cookie_JSON_Object1133 */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};113911401141/**1142 * Schedules a command to retrieve the cookie with the given name. Returns null1143 * if there is no such cookie. The cookie will be returned as a JSON object as1144 * 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>} A1147 *     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_Object1150 */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};115811591160/**1161 * @return {!webdriver.WebDriver.Logs} The interface for managing driver1162 *     logs.1163 */1164webdriver.WebDriver.Options.prototype.logs = function() {1165  return new webdriver.WebDriver.Logs(this.driver_);1166};116711681169/**1170 * @return {!webdriver.WebDriver.Timeouts} The interface for managing driver1171 *     timeouts.1172 */1173webdriver.WebDriver.Options.prototype.timeouts = function() {1174  return new webdriver.WebDriver.Timeouts(this.driver_);1175};117611771178/**1179 * @return {!webdriver.WebDriver.Window} The interface for managing the1180 *     current window.1181 */1182webdriver.WebDriver.Options.prototype.window = function() {1183  return new webdriver.WebDriver.Window(this.driver_);1184};1185118611871188/**1189 * An interface for managing timeout behavior for WebDriver instances.1190 * @param {!webdriver.WebDriver} driver The parent driver.1191 * @constructor1192 */1193webdriver.WebDriver.Timeouts = function(driver) {11941195  /** @private {!webdriver.WebDriver} */1196  this.driver_ = driver;1197};119811991200/**1201 * Specifies the amount of time the driver should wait when searching for an1202 * element if it is not immediately present.1203 * <p/>1204 * When searching for a single element, the driver should poll the page1205 * until the element has been found, or this timeout expires before failing1206 * with a {@code bot.ErrorCode.NO_SUCH_ELEMENT} error. When searching1207 * for multiple elements, the driver should poll the page until at least one1208 * element has been found or this timeout has expired.1209 * <p/>1210 * Setting the wait timeout to 0 (its default value), disables implicit1211 * waiting.1212 * <p/>1213 * Increasing the implicit wait timeout should be used judiciously as it1214 * will have an adverse effect on test run time, especially when used with1215 * 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 resolved1219 *     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};122712281229/**1230 * Sets the amount of time to wait, in milliseconds, for an asynchronous script1231 * to finish execution before returning an error. If the timeout is less than or1232 * 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 resolved1236 *     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};124412451246/**1247 * Sets the amount of time to wait for a page load to complete before returning1248 * 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 resolved1251 *     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};1260126112621263/**1264 * An interface for managing the current window.1265 * @param {!webdriver.WebDriver} driver The parent driver.1266 * @constructor1267 */1268webdriver.WebDriver.Window = function(driver) {12691270  /** @private {!webdriver.WebDriver} */1271  this.driver_ = driver;1272};127312741275/**1276 * Retrieves the window's current position, relative to the top left corner of1277 * the screen.1278 * @return {!webdriver.promise.Promise.<{x: number, y: number}>} A promise that1279 *     will be resolved with the window's position in the form of a1280 *     {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};128812891290/**1291 * Repositions the current window.1292 * @param {number} x The desired horizontal position, relative to the left side1293 *     of the screen.1294 * @param {number} y The desired vertical position, relative to the top of the1295 *     of the screen.1296 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved1297 *     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};130713081309/**1310 * Retrieves the window's current size.1311 * @return {!webdriver.promise.Promise.<{width: number, height: number}>} A1312 *     promise that will be resolved with the window's size in the form of a1313 *     {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};132113221323/**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 resolved1328 *     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};133813391340/**1341 * Maximizes the current window.1342 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved1343 *     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};135113521353/**1354 * Interface for managing WebDriver log records.1355 * @param {!webdriver.WebDriver} driver The parent driver.1356 * @constructor1357 */1358webdriver.WebDriver.Logs = function(driver) {13591360  /** @private {!webdriver.WebDriver} */1361  this.driver_ = driver;1362};136313641365/**1366 * Fetches available log entries for the given type.1367 *1368 * <p/>Note that log buffers are reset after each call, meaning that1369 * available log entries correspond to those entries not yet returned for a1370 * given log type. In practice, this means that this call will return the1371 * available log entries since the last call, or from the start of the1372 * session.1373 *1374 * @param {!webdriver.logging.Type} type The desired log type.1375 * @return {!webdriver.promise.Promise.<!Array.<!webdriver.logging.Entry>>} A1376 *   promise that will resolve to a list of log entries for the specified1377 *   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};139513961397/**1398 * Retrieves the log types available to this driver.1399 * @return {!webdriver.promise.Promise.<!Array.<!webdriver.logging.Type>>} A1400 *     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};1407140814091410/**1411 * An interface for changing the focus of the driver to another frame or window.1412 * @param {!webdriver.WebDriver} driver The parent driver.1413 * @constructor1414 */1415webdriver.WebDriver.TargetLocator = function(driver) {14161417  /** @private {!webdriver.WebDriver} */1418  this.driver_ = driver;1419};142014211422/**1423 * Schedules a command retrieve the {@code document.activeElement} element on1424 * the current document, or {@code document.body} if activeElement is not1425 * 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};143414351436/**1437 * Schedules a command to switch focus of all future commands to the first frame1438 * on the page.1439 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved1440 *     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};144814491450/**1451 * Schedules a command to switch the focus of all future commands to another1452 * frame on the page.1453 * <p/>1454 * If the frame is specified by a number, the command will switch to the frame1455 * 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 by1458 * its name or ID. To select sub-frames, simply separate the frame names/IDs by1459 * 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 errback1463 * 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 resolved1466 *     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};147414751476/**1477 * Schedules a command to switch the focus of all future commands to another1478 * window. Windows may be specified by their {@code window.name} attribute or1479 * 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 errback1482 * with a {@code bot.ErrorCode.NO_SUCH_WINDOW} error.1483 * @param {string} nameOrHandle The name or window handle of the window to1484 *     switch focus to.1485 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved1486 *     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};149414951496/**1497 * Schedules a command to change focus to the active alert dialog. This command1498 * will return a {@link bot.ErrorCode.NO_SUCH_ALERT} error if an alert dialog1499 * 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};151115121513/**1514 * Simulate pressing many keys at once in a "chord". Takes a sequence of1515 * {@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 returns1517 * the resultant string.1518 *1519 * Note: when the low-level webdriver key handlers see Keys.NULL, active1520 * 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=791525 */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};153515361537//////////////////////////////////////////////////////////////////////////////1538//1539//  webdriver.WebElement1540//1541//////////////////////////////////////////////////////////////////////////////1542154315441545/**1546 * Represents a DOM element. WebElements can be found by searching from the1547 * document root using a {@code webdriver.WebDriver} instance, or by searching1548 * 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 promise1557 * API. It will always resolve itself when its internal state has been fully1558 * resolved and commands may be issued against the element. This can be used to1559 * 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 this1569 *     element.1570 * @param {!(webdriver.promise.Promise.<webdriver.WebElement.Id>|1571 *           webdriver.WebElement.Id)} id The server-assigned opaque ID for the1572 *     underlying DOM element.1573 * @constructor1574 */1575webdriver.WebElement = function(driver, id) {15761577  /** @private {!webdriver.WebDriver} */1578  this.driver_ = driver;15791580  /** @private {!webdriver.promise.Promise.<webdriver.WebElement.Id>} */1581  this.id_ = id instanceof webdriver.promise.Promise ?1582      id : webdriver.promise.fulfilled(id);1583};158415851586/**1587 * Wire protocol definition of a WebElement ID.1588 * @typedef {{ELEMENT: string}}1589 * @see http://code.google.com/p/selenium/wiki/JsonWireProtocol1590 */1591webdriver.WebElement.Id;159215931594/**1595 * The property key used in the wire protocol to indicate that a JSON object1596 * contains the ID of a WebElement.1597 * @type {string}1598 * @const1599 */1600webdriver.WebElement.ELEMENT_KEY = 'ELEMENT';160116021603/**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 be1608 *     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 considered1617    // equal. Otherwise, they may still be equivalent, but we'll need to1618    // 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    }16231624    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};163016311632/**1633 * @return {!webdriver.WebDriver} The parent driver for this instance.1634 */1635webdriver.WebElement.prototype.getDriver = function() {1636  return this.driver_;1637};163816391640/**1641 * @return {!webdriver.promise.Promise.<webdriver.WebElement.Id>} A promise1642 *     that resolves to this element's JSON representation as defined by the1643 *     WebDriver wire protocol.1644 * @see http://code.google.com/p/selenium/wiki/JsonWireProtocol1645 */1646webdriver.WebElement.prototype.getId = function() {1647  return this.id_;1648};164916501651/**1652 * Schedules a command that targets this element with the parent WebDriver1653 * instance. Will ensure this element's ID is included in the command parameters1654 * 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 resolved1658 *     with the command result.1659 * @template T1660 * @see webdriver.WebDriver.prototype.schedule1661 * @private1662 */1663webdriver.WebElement.prototype.schedule_ = function(command, description) {1664  command.setParameter('id', this.getId());1665  return this.driver_.schedule(command, description);1666};166716681669/**1670 * Schedule a command to find a descendant of this element. If the element1671 * cannot be found, a {@code bot.ErrorCode.NO_SUCH_ELEMENT} result will1672 * be returned by the driver. Unlike other commands, this error cannot be1673 * suppressed. In other words, scheduling a command to find an element doubles1674 * as an assert that the element is present on the page. To test whether an1675 * 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 the1678 * factories in the {@link webdriver.By} namespace, or as a short-hand1679 * {@link webdriver.By.Hash} object. For example, the following two statements1680 * 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 input1687 * this WebDriver instance and returns a {@link webdriver.WebElement}, or a1688 * promise that will resolve to a WebElement. For example, to find the first1689 * 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 The1704 *     locator strategy to use when searching for the element.1705 * @return {!webdriver.WebElement} A WebElement that can be used to issue1706 *     commands against the located element. If the element is not found, the1707 *     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};172317241725/**1726 * Schedules a command to test if there is at least one descendant of this1727 * element that matches the given search criteria.1728 *1729 * @param {!(webdriver.Locator|webdriver.By.Hash|Function)} locator The1730 *     locator strategy to use when searching for the element.1731 * @return {!webdriver.promise.Promise.<boolean>} A promise that will be1732 *     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};173917401741/**1742 * Schedules a command to find all of the descendants of this element that1743 * match the given search criteria.1744 *1745 * @param {!(webdriver.Locator|webdriver.By.Hash|Function)} locator The1746 *     locator strategy to use when searching for the elements.1747 * @return {!webdriver.promise.Promise.<!Array.<!webdriver.WebElement>>} A1748 *     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};176217631764/**1765 * Schedules a command to click on this element.1766 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved1767 *     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};177417751776/**1777 * Schedules a command to type a sequence on the DOM element represented by this1778 * instance.1779 * <p/>1780 * Modifier keys (SHIFT, CONTROL, ALT, META) are stateful; once a modifier is1781 * processed in the keysequence, that key state is toggled until one of the1782 * following occurs:1783 * <ul>1784 * <li>The modifier key is encountered again in the sequence. At this point the1785 * 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. When1788 * this key is encountered, all modifier keys current in the down state are1789 * released (with accompanying keyup events). The NULL key can be used to1790 * 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 keys1801 * to type, all depressed modifier keys are released (with accompanying keyup1802 * events).1803 * </li>1804 * </ul>1805 * <strong>Note:</strong> On browsers where native keyboard events are not yet1806 * supported (e.g. Firefox on OS X), key events will be synthesized. Special1807 * punctionation keys will be synthesized according to a standard QWERTY en-us1808 * keyboard layout.1809 *1810 * @param {...string} var_args The sequence of keys to1811 *     type. All arguments will be joined into a single sequence (var_args is1812 *     permitted for convenience).1813 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved1814 *     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 that1818  // ignore the jsdoc and give us a number (which ends up causing problems on1819  // 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};183118321833/**1834 * Schedules a command to query for the tag/node name of this element.1835 * @return {!webdriver.promise.Promise.<string>} A promise that will be1836 *     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};184318441845/**1846 * Schedules a command to query for the computed style of the element1847 * represented by this instance. If the element inherits the named style from1848 * its parent, the parent will be queried for its value.  Where possible, color1849 * values will be converted to their hex representation (e.g. #00ff00 instead of1850 * rgb(0, 255, 0)).1851 * <p/>1852 * <em>Warning:</em> the value returned will be as the browser interprets it, so1853 * it may be tricky to form a proper assertion.1854 *1855 * @param {string} cssStyleProperty The name of the CSS style property to look1856 *     up.1857 * @return {!webdriver.promise.Promise.<string>} A promise that will be1858 *     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};186718681869/**1870 * Schedules a command to query for the value of the given attribute of the1871 * element. Will return the current value, even if it has been modified after1872 * the page has been loaded. More exactly, this method will return the value of1873 * the given attribute, unless that attribute is not present, in which case the1874 * value of the property with the same name is returned. If neither value is1875 * set, null is returned (for example, the "value" property of a textarea1876 * element). The "style" attribute is converted as best can be to a1877 * text representation with a trailing semi-colon. The following are deemed to1878 * 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, willvalidate1886 *1887 * <p>Finally, the following commonly mis-capitalized attribute/property names1888 * 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 be1895 *     resolved with the attribute's value. The returned value will always be1896 *     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};190419051906/**1907 * Get the visible (i.e. not hidden by CSS) innerText of this element, including1908 * sub-elements, without any leading or trailing whitespace.1909 * @return {!webdriver.promise.Promise.<string>} A promise that will be1910 *     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};191719181919/**1920 * Schedules a command to compute the size of this element's bounding box, in1921 * pixels.1922 * @return {!webdriver.promise.Promise.<{width: number, height: number}>} A1923 *     promise that will be resolved with the element's size as a1924 *     {@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};193119321933/**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 that1936 *     will be resolved to the element's location as a1937 *     {@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};194419451946/**1947 * Schedules a command to query whether the DOM element represented by this1948 * instance is enabled, as dicted by the {@code disabled} attribute.1949 * @return {!webdriver.promise.Promise.<boolean>} A promise that will be1950 *     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};195719581959/**1960 * Schedules a command to query whether this element is selected.1961 * @return {!webdriver.promise.Promise.<boolean>} A promise that will be1962 *     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};196919701971/**1972 * Schedules a command to submit the form containing this element (or this1973 * element if it is a FORM element). This command is a no-op if the element is1974 * not contained in a form.1975 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved1976 *     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};198319841985/**1986 * Schedules a command to clear the {@code value} of this element. This command1987 * has no effect if the underlying DOM element is neither a text INPUT element1988 * nor a TEXTAREA element.1989 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved1990 *     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};199719981999/**2000 * Schedules a command to test whether this element is currently displayed.2001 * @return {!webdriver.promise.Promise.<boolean>} A promise that will be2002 *     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};200920102011/**2012 * Schedules a command to retrieve the outer HTML of this element.2013 * @return {!webdriver.promise.Promise.<string>} A promise that will be2014 *     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};202820292030/**2031 * Schedules a command to retrieve the inner HTML of this element.2032 * @return {!webdriver.promise.Promise.<string>} A promise that will be2033 *     resolved with the element's inner HTML.2034 */2035webdriver.WebElement.prototype.getInnerHtml = function() {2036  return this.driver_.executeScript('return arguments[0].innerHTML', this);2037};2038203920402041/**2042 * WebElementPromise is a promise that will be fulfilled with a WebElement.2043 * This serves as a forward proxy on WebElement, allowing calls to be2044 * scheduled without directly on this instance before the underlying2045 * WebElement has been fulfilled. In other words, the following two statements2046 * 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 this2055 *     element.2056 * @param {!webdriver.promise.Promise.<!webdriver.WebElement>} el A promise2057 *     that will resolve to the promised element.2058 * @constructor2059 * @extends {webdriver.WebElement}2060 * @implements {webdriver.promise.Thenable.<!webdriver.WebElement>}2061 * @final2062 */2063webdriver.WebElementPromise = function(driver, el) {2064  webdriver.WebElement.call(this, driver, {'ELEMENT': 'unused'});20652066  /** @override */2067  this.cancel = goog.bind(el.cancel, el);20682069  /** @override */2070  this.isPending = goog.bind(el.isPending, el);20712072  /** @override */2073  this.then = goog.bind(el.then, el);20742075  /** @override */2076  this.thenCatch = goog.bind(el.thenCatch, el);20772078  /** @override */2079  this.thenFinally = goog.bind(el.thenFinally, el);20802081  /**2082   * Defers returning the element ID until the wrapped WebElement has been2083   * resolved.2084   * @override2085   */2086  this.getId = function() {2087    return el.then(function(el) {2088      return el.getId();2089    });2090  };2091};2092goog.inherits(webdriver.WebElementPromise, webdriver.WebElement);209320942095/**2096 * Represents a modal dialog such as {@code alert}, {@code confirm}, or2097 * {@code prompt}. Provides functions to retrieve the message displayed with2098 * the alert, accept or dismiss the alert, and set the response text (in the2099 * case of {@code prompt}).2100 * @param {!webdriver.WebDriver} driver The driver controlling the browser this2101 *     alert is attached to.2102 * @param {string} text The message text displayed with this alert.2103 * @constructor2104 */2105webdriver.Alert = function(driver, text) {2106  /** @private {!webdriver.WebDriver} */2107  this.driver_ = driver;21082109  /** @private {!webdriver.promise.Promise.<string>} */2110  this.text_ = webdriver.promise.when(text);2111};211221132114/**2115 * Retrieves the message text displayed with this alert. For instance, if the2116 * alert were opened with alert("hello"), then this would return "hello".2117 * @return {!webdriver.promise.Promise.<string>} A promise that will be2118 *     resolved to the text displayed with this alert.2119 */2120webdriver.Alert.prototype.getText = function() {2121  return this.text_;2122};212321242125/**2126 * Accepts this alert.2127 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved2128 *     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};213521362137/**2138 * Dismisses this alert.2139 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved2140 *     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};214721482149/**2150 * Sets the response text on this alert. This command will return an error if2151 * the underlying alert does not support response text (e.g. window.alert and2152 * window.confirm).2153 * @param {string} text The text to set.2154 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved2155 *     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};2163216421652166/**2167 * AlertPromise is a promise that will be fulfilled with an Alert. This promise2168 * serves as a forward proxy on an Alert, allowing calls to be scheduled2169 * directly on this instance before the underlying Alert has been fulfilled. In2170 * 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 this2179 *     alert is attached to.2180 * @param {!webdriver.promise.Thenable.<!webdriver.Alert>} alert A thenable2181 *     that will be fulfilled with the promised alert.2182 * @constructor2183 * @extends {webdriver.Alert}2184 * @implements {webdriver.promise.Thenable.<!webdriver.Alert>}2185 * @final2186 */2187webdriver.AlertPromise = function(driver, alert) {2188  webdriver.Alert.call(this, driver, 'unused');21892190  /** @override */2191  this.cancel = goog.bind(alert.cancel, alert);21922193  /** @override */2194  this.isPending = goog.bind(alert.isPending, alert);21952196  /** @override */2197  this.then = goog.bind(alert.then, alert);21982199  /** @override */2200  this.thenCatch = goog.bind(alert.thenCatch, alert);22012202  /** @override */2203  this.thenFinally = goog.bind(alert.thenFinally, alert);22042205  /**2206   * Defer returning text until the promised alert has been resolved.2207   * @override2208   */2209  this.getText = function() {2210    return alert.then(function(alert) {2211      return alert.getText();2212    });2213  };22142215  /**2216   * Defers action until the alert has been located.2217   * @override2218   */2219  this.accept = function() {2220    return alert.then(function(alert) {2221      return alert.accept();2222    });2223  };22242225  /**2226   * Defers action until the alert has been located.2227   * @override2228   */2229  this.dismiss = function() {2230    return alert.then(function(alert) {2231      return alert.dismiss();2232    });2233  };22342235  /**2236   * Defers action until the alert has been located.2237   * @override2238   */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);2246224722482249/**2250 * An error returned to indicate that there is an unhandled modal dialog on the2251 * 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 * @constructor2256 * @extends {bot.Error}2257 */2258webdriver.UnhandledAlertError = function(message, text, alert) {2259  goog.base(this, bot.ErrorCode.UNEXPECTED_ALERT_OPEN, message);22602261  /** @private {string} */2262  this.text_ = text;22632264  /** @private {!webdriver.Alert} */2265  this.alert_ = alert;2266};2267goog.inherits(webdriver.UnhandledAlertError, bot.Error);226822692270/**2271 * @return {string} The text displayed with the unhandled alert.2272 */2273webdriver.UnhandledAlertError.prototype.getAlertText = function() {2274  return this.text_;2275};227622772278/**2279 * @return {!webdriver.Alert} The open alert.2280 * @deprecated Use {@link #getAlertText}. This method will be removed in2281 *     2.45.0.2282 */2283webdriver.UnhandledAlertError.prototype.getAlert = function() {2284  return this.alert_;2285};