# 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');39goog.require('webdriver.until');404142//////////////////////////////////////////////////////////////////////////////43//44//  webdriver.WebDriver45//46//////////////////////////////////////////////////////////////////////////////47484950/**51 * Creates a new WebDriver client, which provides control over a browser.52 *53 * Every WebDriver command returns a {@code webdriver.promise.Promise} that54 * represents the result of that command. Callbacks may be registered on this55 * object to manipulate the command result or catch an expected error. Any56 * commands scheduled with a callback are considered sub-commands and will57 * 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 a70 *     known session or a promise that will be resolved to a session.71 * @param {!webdriver.CommandExecutor} executor The executor to use when72 *     sending commands to the browser.73 * @param {webdriver.promise.ControlFlow=} opt_flow The flow to74 *     schedule commands through. Defaults to the active flow object.75 * @constructor76 */77webdriver.WebDriver = function(session, executor, opt_flow) {7879  /** @private {!(webdriver.Session|webdriver.promise.Promise)} */80  this.session_ = session;8182  /** @private {!webdriver.CommandExecutor} */83  this.executor_ = executor;8485  /** @private {!webdriver.promise.ControlFlow} */86  this.flow_ = opt_flow || webdriver.promise.controlFlow();87};888990/**91 * Creates a new WebDriver client for an existing session.92 * @param {!webdriver.CommandExecutor} executor Command executor to use when93 *     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 driver96 *     commands should execute under. Defaults to the97 *     {@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};107108109/**110 * Creates a new WebDriver session.111 * @param {!webdriver.CommandExecutor} executor The executor to create the new112 *     session with.113 * @param {!webdriver.Capabilities} desiredCapabilities The desired114 *     capabilities for the new session.115 * @param {webdriver.promise.ControlFlow=} opt_flow The control flow all driver116 *     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};129130131/**132 * Sends a command to the server that is expected to return the details for a133 * {@link webdriver.Session}. This may either be an existing session, or a134 * newly created one.135 * @param {!webdriver.CommandExecutor} executor Command executor to use when136 *     querying for session details.137 * @param {!webdriver.Command} command The command to send to fetch the session138 *     details.139 * @param {string} description A descriptive debug label for this action.140 * @param {webdriver.promise.ControlFlow=} opt_flow The control flow all driver141 *     commands should execute under. Defaults to the142 *     {@link webdriver.promise.controlFlow() currently active} control flow.143 * @return {!webdriver.WebDriver} A new WebDriver client for the session.144 * @private145 */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};159160161/**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's166 *     server ID</li>167 * <li>if the object provides a "toJSON" function, the return value of this168 *     function will be returned</li>169 * <li>otherwise, the value of each key will be recursively converted according170 *     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 the175 *     input value's JSON representation.176 * @private177 * @see http://code.google.com/p/selenium/wiki/JsonWireProtocol178 */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));184185  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  }210211  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    }217218    var numResolved = 0;219    var done = webdriver.promise.defer();220221    // forEach will stop iteration at undefined, where we want to convert222    // 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    };229230    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      }237238      function setValue(value) {239        ret[key] = value;240        maybeFulfill();241      }242    });243244    return done.promise;245246    function maybeFulfill() {247      if (++numResolved === numKeys) {248        done.fulfill(ret);249      }250    }251  }252};253254255/**256 * Converts a value from its JSON representation according to the WebDriver wire257 * protocol. Any JSON object containing a258 * {@code webdriver.WebElement.ELEMENT_KEY} key will be decoded to a259 * {@code webdriver.WebElement} object. All other values will be passed through260 * as is.261 * @param {!webdriver.WebDriver} driver The driver instance to use as the262 *     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/JsonWireProtocol266 * @private267 */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};282283284/**285 * Translates a command to its wire-protocol representation before passing it286 * 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 the290 *     command response.291 * @private292 */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};301302303/**304 * @return {!webdriver.promise.ControlFlow} The control flow used by this305 *     instance.306 */307webdriver.WebDriver.prototype.controlFlow = function() {308  return this.flow_;309};310311312/**313 * Schedules a {@code webdriver.Command} to be executed by this driver's314 * {@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 resolved318 *     with the command result.319 * @template T320 */321webdriver.WebDriver.prototype.schedule = function(command, description) {322  var self = this;323324  checkHasNotQuit();325  command.setParameter('sessionId', this.session_);326327  // If any of the command parameters are rejected promises, those328  // rejections may be reported as unhandled before the control flow329  // attempts to execute the command. To ensure parameters errors330  // propagate through the command itself, we resolve all of the331  // command parameters now, but suppress any errors until the ControlFlow332  // actually executes the command. This addresses scenarios like catching333  // 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);342343  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 event347    // loop as this |command|, which would prevent us from detecting that the348    // 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();351352    // Retrieve resolved command parameters; any previously suppressed errors353    // will now propagate up through the control flow as part of the command354    // 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  });374375  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};383384385// ----------------------------------------------------------------------------386// Client command functions:387// ----------------------------------------------------------------------------388389390/**391 * @return {!webdriver.promise.Promise.<!webdriver.Session>} A promise for this392 *     client's session.393 */394webdriver.WebDriver.prototype.getSession = function() {395  return webdriver.promise.when(this.session_);396};397398399/**400 * @return {!webdriver.promise.Promise.<!webdriver.Capabilities>} A promise401 *     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};408409410/**411 * Schedules a command to quit the current session. After calling quit, this412 * instance will be invalidated and may no longer be used to issue commands413 * against the browser.414 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved415 *     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 to422  // 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};427428429/**430 * Creates a new action sequence using this driver. The sequence will not be431 * scheduled for execution until {@link webdriver.ActionSequence#perform} is432 * 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};445446447/**448 * Schedules a command to execute JavaScript in the context of the currently449 * selected frame or window. The script fragment will be executed as the body450 * 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 target452 * window.453 *454 * Any arguments provided in addition to the script will be included as script455 * 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 item458 * 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, thus462 * {@code document} may be used to refer to the current document. Any local463 * 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 return467 * statement), then the following steps will be taken for resolving this468 * functions return value:469 * <ul>470 * <li>For a HTML element, the value will resolve to a471 *     {@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 to476 *     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 the482 *    scripts return value.483 * @template T484 */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};496497498/**499 * Schedules a command to execute asynchronous JavaScript in the context of the500 * currently selected frame or window. The script fragment will be executed as501 * the body of an anonymous function. If the script is provided as a function502 * object, that function will be converted to a string for injection into the503 * target window.504 *505 * Any arguments provided in addition to the script will be included as script506 * 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 item509 * adheres to the types previously mentioned.510 *511 * Unlike executing synchronous JavaScript with512 * {@code webdriver.WebDriver.prototype.executeScript}, scripts executed with513 * this function must explicitly signal they are finished by invoking the514 * provided callback. This callback will always be injected into the515 * executed function as the last argument, and thus may be referenced with516 * {@code arguments[arguments.length - 1]}. The following steps will be taken517 * for resolving this functions return value against the first argument to the518 * script's callback function:519 * <ul>520 * <li>For a HTML element, the value will resolve to a521 *     {@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 to526 *     the rules above</li>527 * </ul>528 *529 * Example #1: Performing a sleep that is synchronized with the currently530 * 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 this552 * example, the inject script is specified with a function literal. When using553 * this format, the function is converted to a string for injection, so it554 * should not reference any symbols not defined in the scope of the page under555 * 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 the575 *    scripts return value.576 * @template T577 */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};588589590/**591 * Schedules a command to execute a custom function.592 * @param {function(...): (T|webdriver.promise.Promise.<T>)} fn The function to593 *     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 T599 */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};613614615/**616 * Schedules a command to wait for a condition to hold, as defined by some617 * user supplied function. If any errors occur while evaluating the wait, they618 * will be allowed to propagate.619 *620 * <p>In the event a condition returns a {@link webdriver.promise.Promise}, the621 * polling loop will wait for it to be resolved and use the resolved value for622 * evaluating whether the condition has been satisfied. The resolution time for623 * 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 condition627 *     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 times630 *     out.631 * @return {!webdriver.promise.Promise.<T>} A promise that will be fulfilled632 *     with the first truthy value returned by the condition function, or633 *     rejected if the condition times out.634 * @template T635 */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  }644645  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};653654655/**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 resolved659 *     when the sleep has finished.660 */661webdriver.WebDriver.prototype.sleep = function(ms) {662  return this.flow_.timeout(ms, 'WebDriver.sleep(' + ms + ')');663};664665666/**667 * Schedules a command to retrieve they current window handle.668 * @return {!webdriver.promise.Promise.<string>} A promise that will be669 *     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};676677678/**679 * Schedules a command to retrieve the current list of available window handles.680 * @return {!webdriver.promise.Promise.<!Array.<string>>} A promise that will681 *     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};688689690/**691 * Schedules a command to retrieve the current page's source. The page source692 * returned is a representation of the underlying DOM: do not expect it to be693 * formatted or escaped in the same way as the response sent from the web694 * server.695 * @return {!webdriver.promise.Promise.<string>} A promise that will be696 *     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};703704705/**706 * Schedules a command to close the current window.707 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved708 *     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};714715716/**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 resolved720 *     when the document has finished loading.721 */722webdriver.WebDriver.prototype.get = function(url) {723  return this.navigate().to(url);724};725726727/**728 * Schedules a command to retrieve the URL of the current page.729 * @return {!webdriver.promise.Promise.<string>} A promise that will be730 *     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};737738739/**740 * Schedules a command to retrieve the current page's title.741 * @return {!webdriver.promise.Promise.<string>} A promise that will be742 *     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};748749750/**751 * Schedule a command to find an element on the page. If the element cannot be752 * found, a {@link bot.ErrorCode.NO_SUCH_ELEMENT} result will be returned753 * by the driver. Unlike other commands, this error cannot be suppressed. In754 * other words, scheduling a command to find an element doubles as an assert755 * that the element is present on the page. To test whether an element is756 * present on the page, use {@link #isElementPresent} instead.757 *758 * <p>The search criteria for an element may be defined using one of the759 * factories in the {@link webdriver.By} namespace, or as a short-hand760 * {@link webdriver.By.Hash} object. For example, the following two statements761 * 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 input768 * this WebDriver instance and returns a {@link webdriver.WebElement}, or a769 * promise that will resolve to a WebElement. For example, to find the first770 * 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 elements785 * 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. A787 * reference to the DOM element will be stored in a known location and this788 * driver will attempt to retrieve it through {@link #executeScript}. If the789 * element cannot be found (eg, it belongs to a different document than the790 * one this instance is currently focused on), a791 * {@link bot.ErrorCode.NO_SUCH_ELEMENT} error will be returned.792 *793 * @param {!(webdriver.Locator|webdriver.By.Hash|Element|Function)} locator The794 *     locator to use.795 * @return {!webdriver.WebElement} A WebElement that can be used to issue796 *     commands against the located element. If the element is not found, the797 *     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};824825826/**827 * @param {!Function} locatorFn The locator function to use.828 * @param {!(webdriver.WebDriver|webdriver.WebElement)} context The search829 *     context.830 * @return {!webdriver.promise.Promise.<!webdriver.WebElement>} A831 *     promise that will resolve to a list of WebElements.832 * @private833 */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};846847848/**849 * Locates a DOM element so that commands may be issued against it using the850 * {@link webdriver.WebElement} class. This is accomplished by storing a851 * reference to the element in an object on the element's ownerDocument.852 * {@link #executeScript} will then be used to create a WebElement from this853 * reference. This requires this driver to currently be focused on the854 * ownerDocument's window+frame.855856 * @param {!Element} element The element to locate.857 * @return {!webdriver.promise.Promise.<webdriver.WebElement>} A promise that858 *     will be fulfilled with the located element, or null if the element859 *     could not be found.860 * @private861 */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;868869  function cleanUp() {870    delete store[id];871  }872873  function lookupElement(id) {874    var store = document['$webdriver$'];875    if (!store) {876      return null;877    }878879    var element = store[id];880    if (!element || element[id] !== id) {881      return null;882    }883    return element;884  }885886  /** @type {!webdriver.promise.Promise.<webdriver.WebElement>} */887  var foundElement = this.executeScript(lookupElement, id);888  foundElement.thenFinally(cleanUp);889  return foundElement;890};891892893/**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 the897 * document the driver is currently focused on. Otherwise, the function will898 * 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 actual902 *     DOM element to be located by the server.903 * @return {!webdriver.promise.Promise.<boolean>} A promise that will resolve904 *     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};916917918/**919 * Schedule a command to search for multiple elements on the page.920 *921 * @param {!(webdriver.Locator|webdriver.By.Hash|Function)} locator The locator922 *     strategy to use when searching for the element.923 * @return {!webdriver.promise.Promise.<!Array.<!webdriver.WebElement>>} A924 *     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};937938939/**940 * @param {!Function} locatorFn The locator function to use.941 * @param {!(webdriver.WebDriver|webdriver.WebElement)} context The search942 *     context.943 * @return {!webdriver.promise.Promise.<!Array.<!webdriver.WebElement>>} A944 *     promise that will resolve to an array of WebElements.945 * @private946 */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    }953954    if (!goog.isArray(result)) {955      return [];956    }957958    return goog.array.filter(result, function(item) {959      return item instanceof webdriver.WebElement;960    });961  });962};963964965/**966 * Schedule a command to take a screenshot. The driver makes a best effort to967 * return a screenshot of the following, in order of preference:968 * <ol>969 *   <li>Entire page970 *   <li>Current window971 *   <li>Visible portion of the current frame972 *   <li>The screenshot of the entire display containing the browser973 * </ol>974 *975 * @return {!webdriver.promise.Promise.<string>} A promise that will be976 *     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};982983984/**985 * @return {!webdriver.WebDriver.Options} The options interface for this986 *     instance.987 */988webdriver.WebDriver.prototype.manage = function() {989  return new webdriver.WebDriver.Options(this);990};991992993/**994 * @return {!webdriver.WebDriver.Navigation} The navigation interface for this995 *     instance.996 */997webdriver.WebDriver.prototype.navigate = function() {998  return new webdriver.WebDriver.Navigation(this);999};100010011002/**1003 * @return {!webdriver.WebDriver.TargetLocator} The target locator interface for1004 *     this instance.1005 */1006webdriver.WebDriver.prototype.switchTo = function() {1007  return new webdriver.WebDriver.TargetLocator(this);1008};1009101010111012/**1013 * Interface for navigating back and forth in the browser history.1014 * @param {!webdriver.WebDriver} driver The parent driver.1015 * @constructor1016 */1017webdriver.WebDriver.Navigation = function(driver) {10181019  /** @private {!webdriver.WebDriver} */1020  this.driver_ = driver;1021};102210231024/**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 resolved1028 *     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};103610371038/**1039 * Schedules a command to move backwards in the browser history.1040 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved1041 *     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};104810491050/**1051 * Schedules a command to move forwards in the browser history.1052 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved1053 *     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};106010611062/**1063 * Schedules a command to refresh the current page.1064 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved1065 *     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};1072107310741075/**1076 * Provides methods for managing browser and driver state.1077 * @param {!webdriver.WebDriver} driver The parent driver.1078 * @constructor1079 */1080webdriver.WebDriver.Options = function(driver) {10811082  /** @private {!webdriver.WebDriver} */1083  this.driver_ = driver;1084};108510861087/**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_Object1098 */1099webdriver.WebDriver.Options.Cookie;110011011102/**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 as1110 *     a number, should be in milliseconds since midnight, January 1, 1970 UTC.1111 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved1112 *     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  }11201121  // We do not allow ';' in value.1122  if (/;/.test(value)) {1123    throw Error('Invalid cookie value "' + value + '"');1124  }11251126  var cookieString = name + '=' + value +1127      (opt_domain ? ';domain=' + opt_domain : '') +1128      (opt_path ? ';path=' + opt_path : '') +1129      (opt_isSecure ? ';secure' : '');11301131  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  }11441145  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': expiry1154          }),1155      'WebDriver.manage().addCookie(' + cookieString + ')');1156};115711581159/**1160 * Schedules a command to delete all cookies visible to the current page.1161 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved1162 *     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};116911701171/**1172 * Schedules a command to delete the cookie with the given name. This command is1173 * a no-op if there is no cookie with the given name visible to the current1174 * page.1175 * @param {string} name The name of the cookie to delete.1176 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved1177 *     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};118511861187/**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 WebDriver1190 * wire protocol.1191 * @return {!webdriver.promise.Promise.<1192 *     !Array.<webdriver.WebDriver.Options.Cookie>>} A promise that will be1193 *     resolved with the cookies visible to the current page.1194 * @see http://code.google.com/p/selenium/wiki/JsonWireProtocol#Cookie_JSON_Object1195 */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};120112021203/**1204 * Schedules a command to retrieve the cookie with the given name. Returns null1205 * if there is no such cookie. The cookie will be returned as a JSON object as1206 * 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>} A1209 *     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_Object1212 */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};122012211222/**1223 * @return {!webdriver.WebDriver.Logs} The interface for managing driver1224 *     logs.1225 */1226webdriver.WebDriver.Options.prototype.logs = function() {1227  return new webdriver.WebDriver.Logs(this.driver_);1228};122912301231/**1232 * @return {!webdriver.WebDriver.Timeouts} The interface for managing driver1233 *     timeouts.1234 */1235webdriver.WebDriver.Options.prototype.timeouts = function() {1236  return new webdriver.WebDriver.Timeouts(this.driver_);1237};123812391240/**1241 * @return {!webdriver.WebDriver.Window} The interface for managing the1242 *     current window.1243 */1244webdriver.WebDriver.Options.prototype.window = function() {1245  return new webdriver.WebDriver.Window(this.driver_);1246};1247124812491250/**1251 * An interface for managing timeout behavior for WebDriver instances.1252 * @param {!webdriver.WebDriver} driver The parent driver.1253 * @constructor1254 */1255webdriver.WebDriver.Timeouts = function(driver) {12561257  /** @private {!webdriver.WebDriver} */1258  this.driver_ = driver;1259};126012611262/**1263 * Specifies the amount of time the driver should wait when searching for an1264 * element if it is not immediately present.1265 * <p/>1266 * When searching for a single element, the driver should poll the page1267 * until the element has been found, or this timeout expires before failing1268 * with a {@code bot.ErrorCode.NO_SUCH_ELEMENT} error. When searching1269 * for multiple elements, the driver should poll the page until at least one1270 * element has been found or this timeout has expired.1271 * <p/>1272 * Setting the wait timeout to 0 (its default value), disables implicit1273 * waiting.1274 * <p/>1275 * Increasing the implicit wait timeout should be used judiciously as it1276 * will have an adverse effect on test run time, especially when used with1277 * 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 resolved1281 *     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};128912901291/**1292 * Sets the amount of time to wait, in milliseconds, for an asynchronous script1293 * to finish execution before returning an error. If the timeout is less than or1294 * 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 resolved1298 *     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};130613071308/**1309 * Sets the amount of time to wait for a page load to complete before returning1310 * 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 resolved1313 *     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};1322132313241325/**1326 * An interface for managing the current window.1327 * @param {!webdriver.WebDriver} driver The parent driver.1328 * @constructor1329 */1330webdriver.WebDriver.Window = function(driver) {13311332  /** @private {!webdriver.WebDriver} */1333  this.driver_ = driver;1334};133513361337/**1338 * Retrieves the window's current position, relative to the top left corner of1339 * the screen.1340 * @return {!webdriver.promise.Promise.<{x: number, y: number}>} A promise that1341 *     will be resolved with the window's position in the form of a1342 *     {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};135013511352/**1353 * Repositions the current window.1354 * @param {number} x The desired horizontal position, relative to the left side1355 *     of the screen.1356 * @param {number} y The desired vertical position, relative to the top of the1357 *     of the screen.1358 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved1359 *     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};136913701371/**1372 * Retrieves the window's current size.1373 * @return {!webdriver.promise.Promise.<{width: number, height: number}>} A1374 *     promise that will be resolved with the window's size in the form of a1375 *     {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};138313841385/**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 resolved1390 *     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};140014011402/**1403 * Maximizes the current window.1404 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved1405 *     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};141314141415/**1416 * Interface for managing WebDriver log records.1417 * @param {!webdriver.WebDriver} driver The parent driver.1418 * @constructor1419 */1420webdriver.WebDriver.Logs = function(driver) {14211422  /** @private {!webdriver.WebDriver} */1423  this.driver_ = driver;1424};142514261427/**1428 * Fetches available log entries for the given type.1429 *1430 * <p/>Note that log buffers are reset after each call, meaning that1431 * available log entries correspond to those entries not yet returned for a1432 * given log type. In practice, this means that this call will return the1433 * available log entries since the last call, or from the start of the1434 * session.1435 *1436 * @param {!webdriver.logging.Type} type The desired log type.1437 * @return {!webdriver.promise.Promise.<!Array.<!webdriver.logging.Entry>>} A1438 *   promise that will resolve to a list of log entries for the specified1439 *   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};145714581459/**1460 * Retrieves the log types available to this driver.1461 * @return {!webdriver.promise.Promise.<!Array.<!webdriver.logging.Type>>} A1462 *     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};1469147014711472/**1473 * An interface for changing the focus of the driver to another frame or window.1474 * @param {!webdriver.WebDriver} driver The parent driver.1475 * @constructor1476 */1477webdriver.WebDriver.TargetLocator = function(driver) {14781479  /** @private {!webdriver.WebDriver} */1480  this.driver_ = driver;1481};148214831484/**1485 * Schedules a command retrieve the {@code document.activeElement} element on1486 * the current document, or {@code document.body} if activeElement is not1487 * 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};149614971498/**1499 * Schedules a command to switch focus of all future commands to the first frame1500 * on the page.1501 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved1502 *     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};151015111512/**1513 * Schedules a command to switch the focus of all future commands to another1514 * frame on the page.1515 * <p/>1516 * If the frame is specified by a number, the command will switch to the frame1517 * 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 by1520 * its name or ID. To select sub-frames, simply separate the frame names/IDs by1521 * 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 errback1525 * 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 resolved1528 *     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};153615371538/**1539 * Schedules a command to switch the focus of all future commands to another1540 * window. Windows may be specified by their {@code window.name} attribute or1541 * 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 errback1544 * with a {@code bot.ErrorCode.NO_SUCH_WINDOW} error.1545 * @param {string} nameOrHandle The name or window handle of the window to1546 *     switch focus to.1547 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved1548 *     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};155615571558/**1559 * Schedules a command to change focus to the active alert dialog. This command1560 * will return a {@link bot.ErrorCode.NO_SUCH_ALERT} error if an alert dialog1561 * 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};157315741575/**1576 * Simulate pressing many keys at once in a "chord". Takes a sequence of1577 * {@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 returns1579 * the resultant string.1580 *1581 * Note: when the low-level webdriver key handlers see Keys.NULL, active1582 * 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=791587 */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};159715981599//////////////////////////////////////////////////////////////////////////////1600//1601//  webdriver.WebElement1602//1603//////////////////////////////////////////////////////////////////////////////1604160516061607/**1608 * Represents a DOM element. WebElements can be found by searching from the1609 * document root using a {@code webdriver.WebDriver} instance, or by searching1610 * 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 promise1619 * API. It will always resolve itself when its internal state has been fully1620 * resolved and commands may be issued against the element. This can be used to1621 * 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 this1631 *     element.1632 * @param {!(webdriver.promise.Promise.<webdriver.WebElement.Id>|1633 *           webdriver.WebElement.Id)} id The server-assigned opaque ID for the1634 *     underlying DOM element.1635 * @constructor1636 */1637webdriver.WebElement = function(driver, id) {16381639  /** @private {!webdriver.WebDriver} */1640  this.driver_ = driver;16411642  /** @private {!webdriver.promise.Promise.<webdriver.WebElement.Id>} */1643  this.id_ = id instanceof webdriver.promise.Promise ?1644      id : webdriver.promise.fulfilled(id);1645};164616471648/**1649 * Wire protocol definition of a WebElement ID.1650 * @typedef {{ELEMENT: string}}1651 * @see http://code.google.com/p/selenium/wiki/JsonWireProtocol1652 */1653webdriver.WebElement.Id;165416551656/**1657 * The property key used in the wire protocol to indicate that a JSON object1658 * contains the ID of a WebElement.1659 * @type {string}1660 * @const1661 */1662webdriver.WebElement.ELEMENT_KEY = 'ELEMENT';166316641665/**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 be1670 *     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 considered1679    // equal. Otherwise, they may still be equivalent, but we'll need to1680    // 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    }16851686    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};169216931694/**1695 * @return {!webdriver.WebDriver} The parent driver for this instance.1696 */1697webdriver.WebElement.prototype.getDriver = function() {1698  return this.driver_;1699};170017011702/**1703 * @return {!webdriver.promise.Promise.<webdriver.WebElement.Id>} A promise1704 *     that resolves to this element's JSON representation as defined by the1705 *     WebDriver wire protocol.1706 * @see http://code.google.com/p/selenium/wiki/JsonWireProtocol1707 */1708webdriver.WebElement.prototype.getId = function() {1709  return this.id_;1710};171117121713/**1714 * Schedules a command that targets this element with the parent WebDriver1715 * instance. Will ensure this element's ID is included in the command parameters1716 * 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 resolved1720 *     with the command result.1721 * @template T1722 * @see webdriver.WebDriver.prototype.schedule1723 * @private1724 */1725webdriver.WebElement.prototype.schedule_ = function(command, description) {1726  command.setParameter('id', this.getId());1727  return this.driver_.schedule(command, description);1728};172917301731/**1732 * Schedule a command to find a descendant of this element. If the element1733 * cannot be found, a {@code bot.ErrorCode.NO_SUCH_ELEMENT} result will1734 * be returned by the driver. Unlike other commands, this error cannot be1735 * suppressed. In other words, scheduling a command to find an element doubles1736 * as an assert that the element is present on the page. To test whether an1737 * 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 the1740 * factories in the {@link webdriver.By} namespace, or as a short-hand1741 * {@link webdriver.By.Hash} object. For example, the following two statements1742 * 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 input1749 * this WebDriver instance and returns a {@link webdriver.WebElement}, or a1750 * promise that will resolve to a WebElement. For example, to find the first1751 * 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 The1766 *     locator strategy to use when searching for the element.1767 * @return {!webdriver.WebElement} A WebElement that can be used to issue1768 *     commands against the located element. If the element is not found, the1769 *     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};178517861787/**1788 * Schedules a command to test if there is at least one descendant of this1789 * element that matches the given search criteria.1790 *1791 * @param {!(webdriver.Locator|webdriver.By.Hash|Function)} locator The1792 *     locator strategy to use when searching for the element.1793 * @return {!webdriver.promise.Promise.<boolean>} A promise that will be1794 *     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};180118021803/**1804 * Schedules a command to find all of the descendants of this element that1805 * match the given search criteria.1806 *1807 * @param {!(webdriver.Locator|webdriver.By.Hash|Function)} locator The1808 *     locator strategy to use when searching for the elements.1809 * @return {!webdriver.promise.Promise.<!Array.<!webdriver.WebElement>>} A1810 *     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};182418251826/**1827 * Schedules a command to click on this element.1828 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved1829 *     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};183618371838/**1839 * Schedules a command to type a sequence on the DOM element represented by this1840 * instance.1841 * <p/>1842 * Modifier keys (SHIFT, CONTROL, ALT, META) are stateful; once a modifier is1843 * processed in the keysequence, that key state is toggled until one of the1844 * following occurs:1845 * <ul>1846 * <li>The modifier key is encountered again in the sequence. At this point the1847 * 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. When1850 * this key is encountered, all modifier keys current in the down state are1851 * released (with accompanying keyup events). The NULL key can be used to1852 * 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 keys1863 * to type, all depressed modifier keys are released (with accompanying keyup1864 * events).1865 * </li>1866 * </ul>1867 * <strong>Note:</strong> On browsers where native keyboard events are not yet1868 * supported (e.g. Firefox on OS X), key events will be synthesized. Special1869 * punctionation keys will be synthesized according to a standard QWERTY en-us1870 * keyboard layout.1871 *1872 * @param {...string} var_args The sequence of keys to1873 *     type. All arguments will be joined into a single sequence (var_args is1874 *     permitted for convenience).1875 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved1876 *     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 that1880  // ignore the jsdoc and give us a number (which ends up causing problems on1881  // 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};189318941895/**1896 * Schedules a command to query for the tag/node name of this element.1897 * @return {!webdriver.promise.Promise.<string>} A promise that will be1898 *     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};190519061907/**1908 * Schedules a command to query for the computed style of the element1909 * represented by this instance. If the element inherits the named style from1910 * its parent, the parent will be queried for its value.  Where possible, color1911 * values will be converted to their hex representation (e.g. #00ff00 instead of1912 * rgb(0, 255, 0)).1913 * <p/>1914 * <em>Warning:</em> the value returned will be as the browser interprets it, so1915 * it may be tricky to form a proper assertion.1916 *1917 * @param {string} cssStyleProperty The name of the CSS style property to look1918 *     up.1919 * @return {!webdriver.promise.Promise.<string>} A promise that will be1920 *     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};192919301931/**1932 * Schedules a command to query for the value of the given attribute of the1933 * element. Will return the current value, even if it has been modified after1934 * the page has been loaded. More exactly, this method will return the value of1935 * the given attribute, unless that attribute is not present, in which case the1936 * value of the property with the same name is returned. If neither value is1937 * set, null is returned (for example, the "value" property of a textarea1938 * element). The "style" attribute is converted as best can be to a1939 * text representation with a trailing semi-colon. The following are deemed to1940 * 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, willvalidate1948 *1949 * <p>Finally, the following commonly mis-capitalized attribute/property names1950 * 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 be1957 *     resolved with the attribute's value. The returned value will always be1958 *     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};196619671968/**1969 * Get the visible (i.e. not hidden by CSS) innerText of this element, including1970 * sub-elements, without any leading or trailing whitespace.1971 * @return {!webdriver.promise.Promise.<string>} A promise that will be1972 *     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};197919801981/**1982 * Schedules a command to compute the size of this element's bounding box, in1983 * pixels.1984 * @return {!webdriver.promise.Promise.<{width: number, height: number}>} A1985 *     promise that will be resolved with the element's size as a1986 *     {@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};199319941995/**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 that1998 *     will be resolved to the element's location as a1999 *     {@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};200620072008/**2009 * Schedules a command to query whether the DOM element represented by this2010 * instance is enabled, as dicted by the {@code disabled} attribute.2011 * @return {!webdriver.promise.Promise.<boolean>} A promise that will be2012 *     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};201920202021/**2022 * Schedules a command to query whether this element is selected.2023 * @return {!webdriver.promise.Promise.<boolean>} A promise that will be2024 *     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};203120322033/**2034 * Schedules a command to submit the form containing this element (or this2035 * element if it is a FORM element). This command is a no-op if the element is2036 * not contained in a form.2037 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved2038 *     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};204520462047/**2048 * Schedules a command to clear the {@code value} of this element. This command2049 * has no effect if the underlying DOM element is neither a text INPUT element2050 * nor a TEXTAREA element.2051 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved2052 *     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};205920602061/**2062 * Schedules a command to test whether this element is currently displayed.2063 * @return {!webdriver.promise.Promise.<boolean>} A promise that will be2064 *     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};207120722073/**2074 * Schedules a command to retrieve the outer HTML of this element.2075 * @return {!webdriver.promise.Promise.<string>} A promise that will be2076 *     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};209020912092/**2093 * Schedules a command to retrieve the inner HTML of this element.2094 * @return {!webdriver.promise.Promise.<string>} A promise that will be2095 *     resolved with the element's inner HTML.2096 */2097webdriver.WebElement.prototype.getInnerHtml = function() {2098  return this.driver_.executeScript('return arguments[0].innerHTML', this);2099};2100210121022103/**2104 * WebElementPromise is a promise that will be fulfilled with a WebElement.2105 * This serves as a forward proxy on WebElement, allowing calls to be2106 * scheduled without directly on this instance before the underlying2107 * WebElement has been fulfilled. In other words, the following two statements2108 * 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 this2117 *     element.2118 * @param {!webdriver.promise.Promise.<!webdriver.WebElement>} el A promise2119 *     that will resolve to the promised element.2120 * @constructor2121 * @extends {webdriver.WebElement}2122 * @implements {webdriver.promise.Thenable.<!webdriver.WebElement>}2123 * @final2124 */2125webdriver.WebElementPromise = function(driver, el) {2126  webdriver.WebElement.call(this, driver, {'ELEMENT': 'unused'});21272128  /** @override */2129  this.cancel = goog.bind(el.cancel, el);21302131  /** @override */2132  this.isPending = goog.bind(el.isPending, el);21332134  /** @override */2135  this.then = goog.bind(el.then, el);21362137  /** @override */2138  this.thenCatch = goog.bind(el.thenCatch, el);21392140  /** @override */2141  this.thenFinally = goog.bind(el.thenFinally, el);21422143  /**2144   * Defers returning the element ID until the wrapped WebElement has been2145   * resolved.2146   * @override2147   */2148  this.getId = function() {2149    return el.then(function(el) {2150      return el.getId();2151    });2152  };2153};2154goog.inherits(webdriver.WebElementPromise, webdriver.WebElement);215521562157/**2158 * Represents a modal dialog such as {@code alert}, {@code confirm}, or2159 * {@code prompt}. Provides functions to retrieve the message displayed with2160 * the alert, accept or dismiss the alert, and set the response text (in the2161 * case of {@code prompt}).2162 * @param {!webdriver.WebDriver} driver The driver controlling the browser this2163 *     alert is attached to.2164 * @param {string} text The message text displayed with this alert.2165 * @constructor2166 */2167webdriver.Alert = function(driver, text) {2168  /** @private {!webdriver.WebDriver} */2169  this.driver_ = driver;21702171  /** @private {!webdriver.promise.Promise.<string>} */2172  this.text_ = webdriver.promise.when(text);2173};217421752176/**2177 * Retrieves the message text displayed with this alert. For instance, if the2178 * alert were opened with alert("hello"), then this would return "hello".2179 * @return {!webdriver.promise.Promise.<string>} A promise that will be2180 *     resolved to the text displayed with this alert.2181 */2182webdriver.Alert.prototype.getText = function() {2183  return this.text_;2184};218521862187/**2188 * Accepts this alert.2189 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved2190 *     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};219721982199/**2200 * Dismisses this alert.2201 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved2202 *     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};220922102211/**2212 * Sets the response text on this alert. This command will return an error if2213 * the underlying alert does not support response text (e.g. window.alert and2214 * window.confirm).2215 * @param {string} text The text to set.2216 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved2217 *     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};2225222622272228/**2229 * AlertPromise is a promise that will be fulfilled with an Alert. This promise2230 * serves as a forward proxy on an Alert, allowing calls to be scheduled2231 * directly on this instance before the underlying Alert has been fulfilled. In2232 * 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 this2241 *     alert is attached to.2242 * @param {!webdriver.promise.Thenable.<!webdriver.Alert>} alert A thenable2243 *     that will be fulfilled with the promised alert.2244 * @constructor2245 * @extends {webdriver.Alert}2246 * @implements {webdriver.promise.Thenable.<!webdriver.Alert>}2247 * @final2248 */2249webdriver.AlertPromise = function(driver, alert) {2250  webdriver.Alert.call(this, driver, 'unused');22512252  /** @override */2253  this.cancel = goog.bind(alert.cancel, alert);22542255  /** @override */2256  this.isPending = goog.bind(alert.isPending, alert);22572258  /** @override */2259  this.then = goog.bind(alert.then, alert);22602261  /** @override */2262  this.thenCatch = goog.bind(alert.thenCatch, alert);22632264  /** @override */2265  this.thenFinally = goog.bind(alert.thenFinally, alert);22662267  /**2268   * Defer returning text until the promised alert has been resolved.2269   * @override2270   */2271  this.getText = function() {2272    return alert.then(function(alert) {2273      return alert.getText();2274    });2275  };22762277  /**2278   * Defers action until the alert has been located.2279   * @override2280   */2281  this.accept = function() {2282    return alert.then(function(alert) {2283      return alert.accept();2284    });2285  };22862287  /**2288   * Defers action until the alert has been located.2289   * @override2290   */2291  this.dismiss = function() {2292    return alert.then(function(alert) {2293      return alert.dismiss();2294    });2295  };22962297  /**2298   * Defers action until the alert has been located.2299   * @override2300   */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);2308230923102311/**2312 * An error returned to indicate that there is an unhandled modal dialog on the2313 * 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 * @constructor2318 * @extends {bot.Error}2319 */2320webdriver.UnhandledAlertError = function(message, text, alert) {2321  goog.base(this, bot.ErrorCode.UNEXPECTED_ALERT_OPEN, message);23222323  /** @private {string} */2324  this.text_ = text;23252326  /** @private {!webdriver.Alert} */2327  this.alert_ = alert;2328};2329goog.inherits(webdriver.UnhandledAlertError, bot.Error);233023312332/**2333 * @return {string} The text displayed with the unhandled alert.2334 */2335webdriver.UnhandledAlertError.prototype.getAlertText = function() {2336  return this.text_;2337};233823392340/**2341 * @return {!webdriver.Alert} The open alert.2342 * @deprecated Use {@link #getAlertText}. This method will be removed in2343 *     2.45.0.2344 */2345webdriver.UnhandledAlertError.prototype.getAlert = function() {2346  return this.alert_;2347};