# 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.UnhandledAlertError');21goog.provide('webdriver.WebDriver');22goog.provide('webdriver.WebElement');2324goog.require('bot.Error');25goog.require('bot.ErrorCode');26goog.require('bot.response');27goog.require('goog.array');28goog.require('goog.object');29goog.require('webdriver.ActionSequence');30goog.require('webdriver.Command');31goog.require('webdriver.CommandName');32goog.require('webdriver.Key');33goog.require('webdriver.Locator');34goog.require('webdriver.Session');35goog.require('webdriver.logging');36goog.require('webdriver.promise');373839//////////////////////////////////////////////////////////////////////////////40//41//  webdriver.WebDriver42//43//////////////////////////////////////////////////////////////////////////////44454647/**48 * Creates a new WebDriver client, which provides control over a browser.49 *50 * Every WebDriver command returns a {@code webdriver.promise.Promise} that51 * represents the result of that command. Callbacks may be registered on this52 * object to manipulate the command result or catch an expected error. Any53 * commands scheduled with a callback are considered sub-commands and will54 * execute before the next command in the current frame. For example:55 * <pre><code>56 *   var message = [];57 *   driver.call(message.push, message, 'a').then(function() {58 *     driver.call(message.push, message, 'b');59 *   });60 *   driver.call(message.push, message, 'c');61 *   driver.call(function() {62 *     alert('message is abc? ' + (message.join('') == 'abc'));63 *   });64 * </code></pre>65 *66 * @param {!(webdriver.Session|webdriver.promise.Promise)} session Either a67 *     known session or a promise that will be resolved to a session.68 * @param {!webdriver.CommandExecutor} executor The executor to use when69 *     sending commands to the browser.70 * @param {webdriver.promise.ControlFlow=} opt_flow The flow to71 *     schedule commands through. Defaults to the active flow object.72 * @constructor73 */74webdriver.WebDriver = function(session, executor, opt_flow) {7576  /** @private {!(webdriver.Session|webdriver.promise.Promise)} */77  this.session_ = session;7879  /** @private {!webdriver.CommandExecutor} */80  this.executor_ = executor;8182  /** @private {!webdriver.promise.ControlFlow} */83  this.flow_ = opt_flow || webdriver.promise.controlFlow();84};858687/**88 * Creates a new WebDriver client for an existing session.89 * @param {!webdriver.CommandExecutor} executor Command executor to use when90 *     querying for session details.91 * @param {string} sessionId ID of the session to attach to.92 * @return {!webdriver.WebDriver} A new client for the specified session.93 */94webdriver.WebDriver.attachToSession = function(executor, sessionId) {95  return webdriver.WebDriver.acquireSession_(executor,96      new webdriver.Command(webdriver.CommandName.DESCRIBE_SESSION).97          setParameter('sessionId', sessionId),98      'WebDriver.attachToSession()');99};100101102/**103 * Creates a new WebDriver session.104 * @param {!webdriver.CommandExecutor} executor The executor to create the new105 *     session with.106 * @param {!webdriver.Capabilities} desiredCapabilities The desired107 *     capabilities for the new session.108 * @return {!webdriver.WebDriver} The driver for the newly created session.109 */110webdriver.WebDriver.createSession = function(executor, desiredCapabilities) {111  return webdriver.WebDriver.acquireSession_(executor,112      new webdriver.Command(webdriver.CommandName.NEW_SESSION).113          setParameter('desiredCapabilities', desiredCapabilities),114      'WebDriver.createSession()');115};116117118/**119 * Sends a command to the server that is expected to return the details for a120 * {@link webdriver.Session}. This may either be an existing session, or a121 * newly created one.122 * @param {!webdriver.CommandExecutor} executor Command executor to use when123 *     querying for session details.124 * @param {!webdriver.Command} command The command to send to fetch the session125 *     details.126 * @param {string} description A descriptive debug label for this action.127 * @return {!webdriver.WebDriver} A new WebDriver client for the session.128 * @private129 */130webdriver.WebDriver.acquireSession_ = function(executor, command, description) {131  var session = webdriver.promise.controlFlow().execute(function() {132    return webdriver.WebDriver.executeCommand_(executor, command).133        then(function(response) {134          bot.response.checkResponse(response);135          return new webdriver.Session(response['sessionId'],136              response['value']);137        });138  }, description);139  return new webdriver.WebDriver(session, executor);140};141142143/**144 * Converts an object to its JSON representation in the WebDriver wire protocol.145 * When converting values of type object, the following steps will be taken:146 * <ol>147 * <li>if the object provides a "toWireValue" function, the return value will148 *     be returned in its fully resolved state (e.g. this function may return149 *     promise values)</li>150 * <li>if the object provides a "toJSON" function, the return value of this151 *     function will be returned</li>152 * <li>otherwise, the value of each key will be recursively converted according153 *     to the rules above.</li>154 * </ol>155 *156 * @param {*} obj The object to convert.157 * @return {!webdriver.promise.Promise} A promise that will resolve to the158 *     input value's JSON representation.159 * @private160 * @see http://code.google.com/p/selenium/wiki/JsonWireProtocol161 */162webdriver.WebDriver.toWireValue_ = function(obj) {163  switch (goog.typeOf(obj)) {164    case 'array':165      return webdriver.promise.fullyResolved(166          goog.array.map(/** @type {!Array} */ (obj),167              webdriver.WebDriver.toWireValue_));168    case 'object':169      if (goog.isFunction(obj.toWireValue)) {170        return webdriver.promise.fullyResolved(obj.toWireValue());171      }172      if (goog.isFunction(obj.toJSON)) {173        return webdriver.promise.fulfilled(obj.toJSON());174      }175      if (goog.isNumber(obj.nodeType) && goog.isString(obj.nodeName)) {176        throw Error([177          'Invalid argument type: ', obj.nodeName, '(', obj.nodeType, ')'178        ].join(''));179      }180      return webdriver.promise.fullyResolved(181          goog.object.map(/** @type {!Object} */ (obj),182              webdriver.WebDriver.toWireValue_));183    case 'function':184      return webdriver.promise.fulfilled('' + obj);185    case 'undefined':186      return webdriver.promise.fulfilled(null);187    default:188      return webdriver.promise.fulfilled(obj);189  }190};191192193/**194 * Converts a value from its JSON representation according to the WebDriver wire195 * protocol. Any JSON object containing a196 * {@code webdriver.WebElement.ELEMENT_KEY} key will be decoded to a197 * {@code webdriver.WebElement} object. All other values will be passed through198 * as is.199 * @param {!webdriver.WebDriver} driver The driver instance to use as the200 *     parent of any unwrapped {@code webdriver.WebElement} values.201 * @param {*} value The value to convert.202 * @return {*} The converted value.203 * @see http://code.google.com/p/selenium/wiki/JsonWireProtocol204 * @private205 */206webdriver.WebDriver.fromWireValue_ = function(driver, value) {207  if (goog.isArray(value)) {208    value = goog.array.map(/**@type {goog.array.ArrayLike}*/ (value),209        goog.partial(webdriver.WebDriver.fromWireValue_, driver));210  } else if (value && goog.isObject(value) && !goog.isFunction(value)) {211    if (webdriver.WebElement.ELEMENT_KEY in value) {212      value = new webdriver.WebElement(driver,213          value[webdriver.WebElement.ELEMENT_KEY]);214    } else {215      value = goog.object.map(/**@type {!Object}*/ (value),216          goog.partial(webdriver.WebDriver.fromWireValue_, driver));217    }218  }219  return value;220};221222223/**224 * Translates a command to its wire-protocol representation before passing it225 * to the given {@code executor} for execution.226 * @param {!webdriver.CommandExecutor} executor The executor to use.227 * @param {!webdriver.Command} command The command to execute.228 * @return {!webdriver.promise.Promise} A promise that will resolve with the229 *     command response.230 * @private231 */232webdriver.WebDriver.executeCommand_ = function(executor, command) {233  return webdriver.promise.fullyResolved(command.getParameters()).234      then(webdriver.WebDriver.toWireValue_).235      then(function(parameters) {236        command.setParameters(parameters);237        return webdriver.promise.checkedNodeCall(238            goog.bind(executor.execute, executor, command));239      });240};241242243/**244 * @return {!webdriver.promise.ControlFlow} The control flow used by this245 *     instance.246 */247webdriver.WebDriver.prototype.controlFlow = function() {248  return this.flow_;249};250251252/**253 * Schedules a {@code webdriver.Command} to be executed by this driver's254 * {@code webdriver.CommandExecutor}.255 * @param {!webdriver.Command} command The command to schedule.256 * @param {string} description A description of the command for debugging.257 * @return {!webdriver.promise.Promise.<T>} A promise that will be resolved258 *     with the command result.259 * @template T260 */261webdriver.WebDriver.prototype.schedule = function(command, description) {262  var self = this;263264  checkHasNotQuit();265  command.setParameter('sessionId', this.session_);266267  var flow = this.flow_;268  return flow.execute(function() {269    // A call to WebDriver.quit() may have been scheduled in the same event270    // loop as this |command|, which would prevent us from detecting that the271    // driver has quit above.  Therefore, we need to make another quick check.272    // We still check above so we can fail as early as possible.273    checkHasNotQuit();274    return webdriver.WebDriver.executeCommand_(self.executor_, command);275  }, description).then(function(response) {276    try {277      bot.response.checkResponse(response);278    } catch (ex) {279      var value = response['value'];280      if (ex.code === bot.ErrorCode.MODAL_DIALOG_OPENED) {281        var text = value && value['alert'] ? value['alert']['text'] : '';282        throw new webdriver.UnhandledAlertError(ex.message,283            new webdriver.Alert(self, text));284      }285      throw ex;286    }287    return webdriver.WebDriver.fromWireValue_(self, response['value']);288  });289290  function checkHasNotQuit() {291    if (!self.session_) {292      throw new Error('This driver instance does not have a valid session ID ' +293                      '(did you call WebDriver.quit()?) and may no longer be ' +294                      'used.');295    }296  }297};298299300// ----------------------------------------------------------------------------301// Client command functions:302// ----------------------------------------------------------------------------303304305/**306 * @return {!webdriver.promise.Promise.<!webdriver.Session>} A promise for this307 *     client's session.308 */309webdriver.WebDriver.prototype.getSession = function() {310  return webdriver.promise.when(this.session_);311};312313314/**315 * @return {!webdriver.promise.Promise.<!webdriver.Capabilities>} A promise316 *     that will resolve with the this instance's capabilities.317 */318webdriver.WebDriver.prototype.getCapabilities = function() {319  return webdriver.promise.when(this.session_, function(session) {320    return session.getCapabilities();321  });322};323324325/**326 * Schedules a command to quit the current session. After calling quit, this327 * instance will be invalidated and may no longer be used to issue commands328 * against the browser.329 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved330 *     when the command has completed.331 */332webdriver.WebDriver.prototype.quit = function() {333  var result = this.schedule(334      new webdriver.Command(webdriver.CommandName.QUIT),335      'WebDriver.quit()');336  // Delete our session ID when the quit command finishes; this will allow us to337  // throw an error when attemnpting to use a driver post-quit.338  return result.thenFinally(goog.bind(function() {339    delete this.session_;340  }, this));341};342343344/**345 * Creates a new action sequence using this driver. The sequence will not be346 * scheduled for execution until {@link webdriver.ActionSequence#perform} is347 * called. Example:348 * <pre><code>349 *   driver.actions().350 *       mouseDown(element1).351 *       mouseMove(element2).352 *       mouseUp().353 *       perform();354 * </code></pre>355 * @return {!webdriver.ActionSequence} A new action sequence for this instance.356 */357webdriver.WebDriver.prototype.actions = function() {358  return new webdriver.ActionSequence(this);359};360361362/**363 * Schedules a command to execute JavaScript in the context of the currently364 * selected frame or window. The script fragment will be executed as the body365 * of an anonymous function. If the script is provided as a function object,366 * that function will be converted to a string for injection into the target367 * window.368 *369 * Any arguments provided in addition to the script will be included as script370 * arguments and may be referenced using the {@code arguments} object.371 * Arguments may be a boolean, number, string, or {@code webdriver.WebElement}.372 * Arrays and objects may also be used as script arguments as long as each item373 * adheres to the types previously mentioned.374 *375 * The script may refer to any variables accessible from the current window.376 * Furthermore, the script will execute in the window's context, thus377 * {@code document} may be used to refer to the current document. Any local378 * variables will not be available once the script has finished executing,379 * though global variables will persist.380 *381 * If the script has a return value (i.e. if the script contains a return382 * statement), then the following steps will be taken for resolving this383 * functions return value:384 * <ul>385 * <li>For a HTML element, the value will resolve to a386 *     {@code webdriver.WebElement}</li>387 * <li>Null and undefined return values will resolve to null</li>388 * <li>Booleans, numbers, and strings will resolve as is</li>389 * <li>Functions will resolve to their string representation</li>390 * <li>For arrays and objects, each member item will be converted according to391 *     the rules above</li>392 * </ul>393 *394 * @param {!(string|Function)} script The script to execute.395 * @param {...*} var_args The arguments to pass to the script.396 * @return {!webdriver.promise.Promise.<T>} A promise that will resolve to the397 *    scripts return value.398 * @template T399 */400webdriver.WebDriver.prototype.executeScript = function(script, var_args) {401  if (goog.isFunction(script)) {402    script = 'return (' + script + ').apply(null, arguments);';403  }404  return this.schedule(405      new webdriver.Command(webdriver.CommandName.EXECUTE_SCRIPT).406          setParameter('script', script).407          setParameter('args', goog.array.slice(arguments, 1)),408      'WebDriver.executeScript()');409};410411412/**413 * Schedules a command to execute asynchronous JavaScript in the context of the414 * currently selected frame or window. The script fragment will be executed as415 * the body of an anonymous function. If the script is provided as a function416 * object, that function will be converted to a string for injection into the417 * target window.418 *419 * Any arguments provided in addition to the script will be included as script420 * arguments and may be referenced using the {@code arguments} object.421 * Arguments may be a boolean, number, string, or {@code webdriver.WebElement}.422 * Arrays and objects may also be used as script arguments as long as each item423 * adheres to the types previously mentioned.424 *425 * Unlike executing synchronous JavaScript with426 * {@code webdriver.WebDriver.prototype.executeScript}, scripts executed with427 * this function must explicitly signal they are finished by invoking the428 * provided callback. This callback will always be injected into the429 * executed function as the last argument, and thus may be referenced with430 * {@code arguments[arguments.length - 1]}. The following steps will be taken431 * for resolving this functions return value against the first argument to the432 * script's callback function:433 * <ul>434 * <li>For a HTML element, the value will resolve to a435 *     {@code webdriver.WebElement}</li>436 * <li>Null and undefined return values will resolve to null</li>437 * <li>Booleans, numbers, and strings will resolve as is</li>438 * <li>Functions will resolve to their string representation</li>439 * <li>For arrays and objects, each member item will be converted according to440 *     the rules above</li>441 * </ul>442 *443 * Example #1: Performing a sleep that is synchronized with the currently444 * selected window:445 * <code><pre>446 * var start = new Date().getTime();447 * driver.executeAsyncScript(448 *     'window.setTimeout(arguments[arguments.length - 1], 500);').449 *     then(function() {450 *       console.log('Elapsed time: ' + (new Date().getTime() - start) + ' ms');451 *     });452 * </pre></code>453 *454 * Example #2: Synchronizing a test with an AJAX application:455 * <code><pre>456 * var button = driver.findElement(By.id('compose-button'));457 * button.click();458 * driver.executeAsyncScript(459 *     'var callback = arguments[arguments.length - 1];' +460 *     'mailClient.getComposeWindowWidget().onload(callback);');461 * driver.switchTo().frame('composeWidget');462 * driver.findElement(By.id('to')).sendKEys('dog@example.com');463 * </pre></code>464 *465 * Example #3: Injecting a XMLHttpRequest and waiting for the result. In this466 * example, the inject script is specified with a function literal. When using467 * this format, the function is converted to a string for injection, so it468 * should not reference any symbols not defined in the scope of the page under469 * test.470 * <code><pre>471 * driver.executeAsyncScript(function() {472 *   var callback = arguments[arguments.length - 1];473 *   var xhr = new XMLHttpRequest();474 *   xhr.open("GET", "/resource/data.json", true);475 *   xhr.onreadystatechange = function() {476 *     if (xhr.readyState == 4) {477 *       callback(xhr.resposneText);478 *     }479 *   }480 *   xhr.send('');481 * }).then(function(str) {482 *   console.log(JSON.parse(str)['food']);483 * });484 * </pre></code>485 *486 * @param {!(string|Function)} script The script to execute.487 * @param {...*} var_args The arguments to pass to the script.488 * @return {!webdriver.promise.Promise.<T>} A promise that will resolve to the489 *    scripts return value.490 * @template T491 */492webdriver.WebDriver.prototype.executeAsyncScript = function(script, var_args) {493  if (goog.isFunction(script)) {494    script = 'return (' + script + ').apply(null, arguments);';495  }496  return this.schedule(497      new webdriver.Command(webdriver.CommandName.EXECUTE_ASYNC_SCRIPT).498          setParameter('script', script).499          setParameter('args', goog.array.slice(arguments, 1)),500      'WebDriver.executeScript()');501};502503504/**505 * Schedules a command to execute a custom function.506 * @param {function(...): (T|webdriver.promise.Promise.<T>)} fn The function to507 *     execute.508 * @param {Object=} opt_scope The object in whose scope to execute the function.509 * @param {...*} var_args Any arguments to pass to the function.510 * @return {!webdriver.promise.Promise.<T>} A promise that will be resolved'511 *     with the function's result.512 * @template T513 */514webdriver.WebDriver.prototype.call = function(fn, opt_scope, var_args) {515  var args = goog.array.slice(arguments, 2);516  var flow = this.flow_;517  return flow.execute(function() {518    return webdriver.promise.fullyResolved(args).then(function(args) {519      return fn.apply(opt_scope, args);520    });521  }, 'WebDriver.call(' + (fn.name || 'function') + ')');522};523524525/**526 * Schedules a command to wait for a condition to hold, as defined by some527 * user supplied function. If any errors occur while evaluating the wait, they528 * will be allowed to propagate.529 *530 * <p>In the event a condition returns a {@link webdriver.promise.Promise}, the 531 * polling loop will wait for it to be resolved and use the resolved value for 532 * evaluating whether the condition has been satisfied. The resolution time for533 * a promise is factored into whether a wait has timed out.534 *535 * @param {function():boolean} fn The function to evaluate as a wait condition.536 * @param {number} timeout How long to wait for the condition to be true.537 * @param {string=} opt_message An optional message to use if the wait times538 *     out.539 * @return {!webdriver.promise.Promise} A promise that will be resolved when the540 *     wait condition has been satisfied.541 */542webdriver.WebDriver.prototype.wait = function(fn, timeout, opt_message) {543  return this.flow_.wait(fn, timeout, opt_message);544};545546547/**548 * Schedules a command to make the driver sleep for the given amount of time.549 * @param {number} ms The amount of time, in milliseconds, to sleep.550 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved551 *     when the sleep has finished.552 */553webdriver.WebDriver.prototype.sleep = function(ms) {554  return this.flow_.timeout(ms, 'WebDriver.sleep(' + ms + ')');555};556557558/**559 * Schedules a command to retrieve they current window handle.560 * @return {!webdriver.promise.Promise.<string>} A promise that will be561 *     resolved with the current window handle.562 */563webdriver.WebDriver.prototype.getWindowHandle = function() {564  return this.schedule(565      new webdriver.Command(webdriver.CommandName.GET_CURRENT_WINDOW_HANDLE),566      'WebDriver.getWindowHandle()');567};568569570/**571 * Schedules a command to retrieve the current list of available window handles.572 * @return {!webdriver.promise.Promise.<!Array.<string>>} A promise that will573 *     be resolved with an array of window handles.574 */575webdriver.WebDriver.prototype.getAllWindowHandles = function() {576  return this.schedule(577      new webdriver.Command(webdriver.CommandName.GET_WINDOW_HANDLES),578      'WebDriver.getAllWindowHandles()');579};580581582/**583 * Schedules a command to retrieve the current page's source. The page source584 * returned is a representation of the underlying DOM: do not expect it to be585 * formatted or escaped in the same way as the response sent from the web586 * server.587 * @return {!webdriver.promise.Promise.<string>} A promise that will be588 *     resolved with the current page source.589 */590webdriver.WebDriver.prototype.getPageSource = function() {591  return this.schedule(592      new webdriver.Command(webdriver.CommandName.GET_PAGE_SOURCE),593      'WebDriver.getAllWindowHandles()');594};595596597/**598 * Schedules a command to close the current window.599 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved600 *     when this command has completed.601 */602webdriver.WebDriver.prototype.close = function() {603  return this.schedule(new webdriver.Command(webdriver.CommandName.CLOSE),604                       'WebDriver.close()');605};606607608/**609 * Schedules a command to navigate to the given URL.610 * @param {string} url The fully qualified URL to open.611 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved612 *     when the document has finished loading.613 */614webdriver.WebDriver.prototype.get = function(url) {615  return this.navigate().to(url);616};617618619/**620 * Schedules a command to retrieve the URL of the current page.621 * @return {!webdriver.promise.Promise.<string>} A promise that will be622 *     resolved with the current URL.623 */624webdriver.WebDriver.prototype.getCurrentUrl = function() {625  return this.schedule(626      new webdriver.Command(webdriver.CommandName.GET_CURRENT_URL),627      'WebDriver.getCurrentUrl()');628};629630631/**632 * Schedules a command to retrieve the current page's title.633 * @return {!webdriver.promise.Promise.<string>} A promise that will be634 *     resolved with the current page's title.635 */636webdriver.WebDriver.prototype.getTitle = function() {637  return this.schedule(new webdriver.Command(webdriver.CommandName.GET_TITLE),638                       'WebDriver.getTitle()');639};640641642/**643 * Schedule a command to find an element on the page. If the element cannot be644 * found, a {@link bot.ErrorCode.NO_SUCH_ELEMENT} result will be returned645 * by the driver. Unlike other commands, this error cannot be suppressed. In646 * other words, scheduling a command to find an element doubles as an assert647 * that the element is present on the page. To test whether an element is648 * present on the page, use {@link #isElementPresent} instead.649 *650 * <p>The search criteria for an element may be defined using one of the651 * factories in the {@link webdriver.By} namespace, or as a short-hand652 * {@link webdriver.By.Hash} object. For example, the following two statements653 * are equivalent:654 * <code><pre>655 * var e1 = driver.findElement(By.id('foo'));656 * var e2 = driver.findElement({id:'foo'});657 * </pre></code>658 *659 * <p>You may also provide a custom locator function, which takes as input660 * this WebDriver instance and returns a {@link webdriver.WebElement}, or a661 * promise that will resolve to a WebElement. For example, to find the first662 * visible link on a page, you could write:663 * <code><pre>664 * var link = driver.findElement(firstVisibleLink);665 *666 * function firstVisibleLink(driver) {667 *   var links = driver.findElements(By.tagName('a'));668 *   return webdriver.promise.filter(links, function(link) {669 *     return links.isDisplayed();670 *   }).then(function(visibleLinks) {671 *     return visibleLinks[0];672 *   });673 * }674 * </pre></code>675 *676 * <p>When running in the browser, a WebDriver cannot manipulate DOM elements677 * directly; it may do so only through a {@link webdriver.WebElement} reference.678 * This function may be used to generate a WebElement from a DOM element. A679 * reference to the DOM element will be stored in a known location and this680 * driver will attempt to retrieve it through {@link #executeScript}. If the681 * element cannot be found (eg, it belongs to a different document than the682 * one this instance is currently focused on), a683 * {@link bot.ErrorCode.NO_SUCH_ELEMENT} error will be returned.684 *685 * @param {!(webdriver.Locator|webdriver.By.Hash|Element|Function)} locator The686 *     locator to use.687 * @return {!webdriver.WebElement} A WebElement that can be used to issue688 *     commands against the located element. If the element is not found, the689 *     element will be invalidated and all scheduled commands aborted.690 */691webdriver.WebDriver.prototype.findElement = function(locator) {692  var id;693  if ('nodeType' in locator && 'ownerDocument' in locator) {694    var element = /** @type {!Element} */ (locator);695    id = this.findDomElement_(element).then(function(element) {696      if (!element) {697        throw new bot.Error(bot.ErrorCode.NO_SUCH_ELEMENT,698            'Unable to locate element. Is WebDriver focused on its ' +699                'ownerDocument\'s frame?');700      }701      return element;702    });703  } else {704    locator = webdriver.Locator.checkLocator(locator);705    if (goog.isFunction(locator)) {706      id = this.findElementInternal_(locator, this);707    } else {708      var command = new webdriver.Command(webdriver.CommandName.FIND_ELEMENT).709          setParameter('using', locator.using).710          setParameter('value', locator.value);711      id = this.schedule(command, 'WebDriver.findElement(' + locator + ')');712    }713  }714  return new webdriver.WebElement(this, id);715};716717718/**719 * @param {!Function} locatorFn The locator function to use.720 * @param {!(webdriver.WebDriver|webdriver.WebElement)} context The search721 *     context.722 * @return {!webdriver.promise.Promise.<!webdriver.WebElement>} A723 *     promise that will resolve to a list of WebElements.724 * @private725 */726webdriver.WebDriver.prototype.findElementInternal_ = function(727    locatorFn, context) {728  return this.call(goog.partial(locatorFn, context)).then(function(result) {729    if (goog.isArray(result)) {730      result = result[0];731    }732    if (!(result instanceof webdriver.WebElement)) {733      throw new TypeError('Custom locator did not return a WebElement');734    }735    return result;736  });737};738739740/**741 * Locates a DOM element so that commands may be issued against it using the742 * {@link webdriver.WebElement} class. This is accomplished by storing a743 * reference to the element in an object on the element's ownerDocument.744 * {@link #executeScript} will then be used to create a WebElement from this745 * reference. This requires this driver to currently be focused on the746 * ownerDocument's window+frame.747748 * @param {!Element} element The element to locate.749 * @return {!webdriver.promise.Promise.<webdriver.WebElement>} A promise that750 *     will be fulfilled with the located element, or null if the element751 *     could not be found.752 * @private753 */754webdriver.WebDriver.prototype.findDomElement_ = function(element) {755  var doc = element.ownerDocument;756  var store = doc['$webdriver$'] = doc['$webdriver$'] || {};757  var id = Math.floor(Math.random() * goog.now()).toString(36);758  store[id] = element;759  element[id] = id;760761  function cleanUp() {762    delete store[id];763  }764765  function lookupElement(id) {766    var store = document['$webdriver$'];767    if (!store) {768      return null;769    }770771    var element = store[id];772    if (!element || element[id] !== id) {773      return null;774    }775    return element;776  }777778  /** @type {!webdriver.promise.Promise.<webdriver.WebElement>} */779  var foundElement = this.executeScript(lookupElement, id);780  foundElement.thenFinally(cleanUp);781  return foundElement;782};783784785/**786 * Schedules a command to test if an element is present on the page.787 *788 * <p>If given a DOM element, this function will check if it belongs to the789 * document the driver is currently focused on. Otherwise, the function will790 * test if at least one element can be found with the given search criteria.791 *792 * @param {!(webdriver.Locator|webdriver.By.Hash|Element|793 *           Function)} locatorOrElement The locator to use, or the actual794 *     DOM element to be located by the server.795 * @return {!webdriver.promise.Promise.<boolean>} A promise that will resolve796 *     with whether the element is present on the page.797 */798webdriver.WebDriver.prototype.isElementPresent = function(locatorOrElement) {799  if ('nodeType' in locatorOrElement && 'ownerDocument' in locatorOrElement) {800    return this.findDomElement_(/** @type {!Element} */ (locatorOrElement)).801        then(function(result) { return !!result; });802  } else {803    return this.findElements.apply(this, arguments).then(function(result) {804      return !!result.length;805    });806  }807};808809810/**811 * Schedule a command to search for multiple elements on the page.812 *813 * @param {!(webdriver.Locator|webdriver.By.Hash|Function)} locator The locator814 *     strategy to use when searching for the element.815 * @return {!webdriver.promise.Promise.<!Array.<!webdriver.WebElement>>} A816 *     promise that will resolve to an array of WebElements.817 */818webdriver.WebDriver.prototype.findElements = function(locator) {819  locator = webdriver.Locator.checkLocator(locator);820  if (goog.isFunction(locator)) {821    return this.findElementsInternal_(locator, this);822  } else {823    var command = new webdriver.Command(webdriver.CommandName.FIND_ELEMENTS).824        setParameter('using', locator.using).825        setParameter('value', locator.value);826    return this.schedule(command, 'WebDriver.findElements(' + locator + ')');827  }828};829830831/**832 * @param {!Function} locatorFn The locator function to use.833 * @param {!(webdriver.WebDriver|webdriver.WebElement)} context The search834 *     context.835 * @return {!webdriver.promise.Promise.<!Array.<!webdriver.WebElement>>} A836 *     promise that will resolve to an array of WebElements.837 * @private838 */839webdriver.WebDriver.prototype.findElementsInternal_ = function(840    locatorFn, context) {841  return this.call(goog.partial(locatorFn, context)).then(function(result) {842    if (result instanceof webdriver.WebElement) {843      return [result];844    }845846    if (!goog.isArray(result)) {847      return [];848    }849850    return goog.array.filter(result, function(item) {851      return item instanceof webdriver.WebElement;852    });853  });854};855856857/**858 * Schedule a command to take a screenshot. The driver makes a best effort to859 * return a screenshot of the following, in order of preference:860 * <ol>861 *   <li>Entire page862 *   <li>Current window863 *   <li>Visible portion of the current frame864 *   <li>The screenshot of the entire display containing the browser865 * </ol>866 *867 * @return {!webdriver.promise.Promise.<string>} A promise that will be868 *     resolved to the screenshot as a base-64 encoded PNG.869 */870webdriver.WebDriver.prototype.takeScreenshot = function() {871  return this.schedule(new webdriver.Command(webdriver.CommandName.SCREENSHOT),872      'WebDriver.takeScreenshot()');873};874875876/**877 * @return {!webdriver.WebDriver.Options} The options interface for this878 *     instance.879 */880webdriver.WebDriver.prototype.manage = function() {881  return new webdriver.WebDriver.Options(this);882};883884885/**886 * @return {!webdriver.WebDriver.Navigation} The navigation interface for this887 *     instance.888 */889webdriver.WebDriver.prototype.navigate = function() {890  return new webdriver.WebDriver.Navigation(this);891};892893894/**895 * @return {!webdriver.WebDriver.TargetLocator} The target locator interface for896 *     this instance.897 */898webdriver.WebDriver.prototype.switchTo = function() {899  return new webdriver.WebDriver.TargetLocator(this);900};901902903904/**905 * Interface for navigating back and forth in the browser history.906 * @param {!webdriver.WebDriver} driver The parent driver.907 * @constructor908 */909webdriver.WebDriver.Navigation = function(driver) {910911  /** @private {!webdriver.WebDriver} */912  this.driver_ = driver;913};914915916/**917 * Schedules a command to navigate to a new URL.918 * @param {string} url The URL to navigate to.919 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved920 *     when the URL has been loaded.921 */922webdriver.WebDriver.Navigation.prototype.to = function(url) {923  return this.driver_.schedule(924      new webdriver.Command(webdriver.CommandName.GET).925          setParameter('url', url),926      'WebDriver.navigate().to(' + url + ')');927};928929930/**931 * Schedules a command to move backwards in the browser history.932 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved933 *     when the navigation event has completed.934 */935webdriver.WebDriver.Navigation.prototype.back = function() {936  return this.driver_.schedule(937      new webdriver.Command(webdriver.CommandName.GO_BACK),938      'WebDriver.navigate().back()');939};940941942/**943 * Schedules a command to move forwards in the browser history.944 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved945 *     when the navigation event has completed.946 */947webdriver.WebDriver.Navigation.prototype.forward = function() {948  return this.driver_.schedule(949      new webdriver.Command(webdriver.CommandName.GO_FORWARD),950      'WebDriver.navigate().forward()');951};952953954/**955 * Schedules a command to refresh the current page.956 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved957 *     when the navigation event has completed.958 */959webdriver.WebDriver.Navigation.prototype.refresh = function() {960  return this.driver_.schedule(961      new webdriver.Command(webdriver.CommandName.REFRESH),962      'WebDriver.navigate().refresh()');963};964965966967/**968 * Provides methods for managing browser and driver state.969 * @param {!webdriver.WebDriver} driver The parent driver.970 * @constructor971 */972webdriver.WebDriver.Options = function(driver) {973974  /** @private {!webdriver.WebDriver} */975  this.driver_ = driver;976};977978979/**980 * A JSON description of a browser cookie.981 * @typedef {{982 *     name: string,983 *     value: string,984 *     path: (string|undefined),985 *     domain: (string|undefined),986 *     secure: (boolean|undefined),987 *     expiry: (number|undefined)988 * }}989 * @see http://code.google.com/p/selenium/wiki/JsonWireProtocol#Cookie_JSON_Object990 */991webdriver.WebDriver.Options.Cookie;992993994/**995 * Schedules a command to add a cookie.996 * @param {string} name The cookie name.997 * @param {string} value The cookie value.998 * @param {string=} opt_path The cookie path.999 * @param {string=} opt_domain The cookie domain.1000 * @param {boolean=} opt_isSecure Whether the cookie is secure.1001 * @param {(number|!Date)=} opt_expiry When the cookie expires. If specified as1002 *     a number, should be in milliseconds since midnight, January 1, 1970 UTC.1003 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved1004 *     when the cookie has been added to the page.1005 */1006webdriver.WebDriver.Options.prototype.addCookie = function(1007    name, value, opt_path, opt_domain, opt_isSecure, opt_expiry) {1008  // We do not allow '=' or ';' in the name.1009  if (/[;=]/.test(name)) {1010    throw Error('Invalid cookie name "' + name + '"');1011  }10121013  // We do not allow ';' in value.1014  if (/;/.test(value)) {1015    throw Error('Invalid cookie value "' + value + '"');1016  }10171018  var cookieString = name + '=' + value +1019      (opt_domain ? ';domain=' + opt_domain : '') +1020      (opt_path ? ';path=' + opt_path : '') +1021      (opt_isSecure ? ';secure' : '');10221023  var expiry;1024  if (goog.isDef(opt_expiry)) {1025    var expiryDate;1026    if (goog.isNumber(opt_expiry)) {1027      expiryDate = new Date(opt_expiry);1028    } else {1029      expiryDate = /** @type {!Date} */ (opt_expiry);1030      opt_expiry = expiryDate.getTime();1031    }1032    cookieString += ';expires=' + expiryDate.toUTCString();1033    // Convert from milliseconds to seconds.1034    expiry = Math.floor(/** @type {number} */ (opt_expiry) / 1000);1035  }10361037  return this.driver_.schedule(1038      new webdriver.Command(webdriver.CommandName.ADD_COOKIE).1039          setParameter('cookie', {1040            'name': name,1041            'value': value,1042            'path': opt_path,1043            'domain': opt_domain,1044            'secure': !!opt_isSecure,1045            'expiry': expiry1046          }),1047      'WebDriver.manage().addCookie(' + cookieString + ')');1048};104910501051/**1052 * Schedules a command to delete all cookies visible to the current page.1053 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved1054 *     when all cookies have been deleted.1055 */1056webdriver.WebDriver.Options.prototype.deleteAllCookies = function() {1057  return this.driver_.schedule(1058      new webdriver.Command(webdriver.CommandName.DELETE_ALL_COOKIES),1059      'WebDriver.manage().deleteAllCookies()');1060};106110621063/**1064 * Schedules a command to delete the cookie with the given name. This command is1065 * a no-op if there is no cookie with the given name visible to the current1066 * page.1067 * @param {string} name The name of the cookie to delete.1068 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved1069 *     when the cookie has been deleted.1070 */1071webdriver.WebDriver.Options.prototype.deleteCookie = function(name) {1072  return this.driver_.schedule(1073      new webdriver.Command(webdriver.CommandName.DELETE_COOKIE).1074          setParameter('name', name),1075      'WebDriver.manage().deleteCookie(' + name + ')');1076};107710781079/**1080 * Schedules a command to retrieve all cookies visible to the current page.1081 * Each cookie will be returned as a JSON object as described by the WebDriver1082 * wire protocol.1083 * @return {!webdriver.promise.Promise.<1084 *     !Array.<webdriver.WebDriver.Options.Cookie>>} A promise that will be1085 *     resolved with the cookies visible to the current page.1086 * @see http://code.google.com/p/selenium/wiki/JsonWireProtocol#Cookie_JSON_Object1087 */1088webdriver.WebDriver.Options.prototype.getCookies = function() {1089  return this.driver_.schedule(1090      new webdriver.Command(webdriver.CommandName.GET_ALL_COOKIES),1091      'WebDriver.manage().getCookies()');1092};109310941095/**1096 * Schedules a command to retrieve the cookie with the given name. Returns null1097 * if there is no such cookie. The cookie will be returned as a JSON object as1098 * described by the WebDriver wire protocol.1099 * @param {string} name The name of the cookie to retrieve.1100 * @return {!webdriver.promise.Promise.<?webdriver.WebDriver.Options.Cookie>} A1101 *     promise that will be resolved with the named cookie, or {@code null}1102 *     if there is no such cookie.1103 * @see http://code.google.com/p/selenium/wiki/JsonWireProtocol#Cookie_JSON_Object1104 */1105webdriver.WebDriver.Options.prototype.getCookie = function(name) {1106  return this.getCookies().then(function(cookies) {1107    return goog.array.find(cookies, function(cookie) {1108      return cookie && cookie['name'] == name;1109    });1110  });1111};111211131114/**1115 * @return {!webdriver.WebDriver.Logs} The interface for managing driver1116 *     logs.1117 */1118webdriver.WebDriver.Options.prototype.logs = function() {1119  return new webdriver.WebDriver.Logs(this.driver_);1120};112111221123/**1124 * @return {!webdriver.WebDriver.Timeouts} The interface for managing driver1125 *     timeouts.1126 */1127webdriver.WebDriver.Options.prototype.timeouts = function() {1128  return new webdriver.WebDriver.Timeouts(this.driver_);1129};113011311132/**1133 * @return {!webdriver.WebDriver.Window} The interface for managing the1134 *     current window.1135 */1136webdriver.WebDriver.Options.prototype.window = function() {1137  return new webdriver.WebDriver.Window(this.driver_);1138};1139114011411142/**1143 * An interface for managing timeout behavior for WebDriver instances.1144 * @param {!webdriver.WebDriver} driver The parent driver.1145 * @constructor1146 */1147webdriver.WebDriver.Timeouts = function(driver) {11481149  /** @private {!webdriver.WebDriver} */1150  this.driver_ = driver;1151};115211531154/**1155 * Specifies the amount of time the driver should wait when searching for an1156 * element if it is not immediately present.1157 * <p/>1158 * When searching for a single element, the driver should poll the page1159 * until the element has been found, or this timeout expires before failing1160 * with a {@code bot.ErrorCode.NO_SUCH_ELEMENT} error. When searching1161 * for multiple elements, the driver should poll the page until at least one1162 * element has been found or this timeout has expired.1163 * <p/>1164 * Setting the wait timeout to 0 (its default value), disables implicit1165 * waiting.1166 * <p/>1167 * Increasing the implicit wait timeout should be used judiciously as it1168 * will have an adverse effect on test run time, especially when used with1169 * slower location strategies like XPath.1170 *1171 * @param {number} ms The amount of time to wait, in milliseconds.1172 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved1173 *     when the implicit wait timeout has been set.1174 */1175webdriver.WebDriver.Timeouts.prototype.implicitlyWait = function(ms) {1176  return this.driver_.schedule(1177      new webdriver.Command(webdriver.CommandName.IMPLICITLY_WAIT).1178          setParameter('ms', ms < 0 ? 0 : ms),1179      'WebDriver.manage().timeouts().implicitlyWait(' + ms + ')');1180};118111821183/**1184 * Sets the amount of time to wait, in milliseconds, for an asynchronous script1185 * to finish execution before returning an error. If the timeout is less than or1186 * equal to 0, the script will be allowed to run indefinitely.1187 *1188 * @param {number} ms The amount of time to wait, in milliseconds.1189 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved1190 *     when the script timeout has been set.1191 */1192webdriver.WebDriver.Timeouts.prototype.setScriptTimeout = function(ms) {1193  return this.driver_.schedule(1194      new webdriver.Command(webdriver.CommandName.SET_SCRIPT_TIMEOUT).1195          setParameter('ms', ms < 0 ? 0 : ms),1196      'WebDriver.manage().timeouts().setScriptTimeout(' + ms + ')');1197};119811991200/**1201 * Sets the amount of time to wait for a page load to complete before returning1202 * an error.  If the timeout is negative, page loads may be indefinite.1203 * @param {number} ms The amount of time to wait, in milliseconds.1204 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved1205 *     when the timeout has been set.1206 */1207webdriver.WebDriver.Timeouts.prototype.pageLoadTimeout = function(ms) {1208  return this.driver_.schedule(1209      new webdriver.Command(webdriver.CommandName.SET_TIMEOUT).1210          setParameter('type', 'page load').1211          setParameter('ms', ms),1212      'WebDriver.manage().timeouts().pageLoadTimeout(' + ms + ')');1213};1214121512161217/**1218 * An interface for managing the current window.1219 * @param {!webdriver.WebDriver} driver The parent driver.1220 * @constructor1221 */1222webdriver.WebDriver.Window = function(driver) {12231224  /** @private {!webdriver.WebDriver} */1225  this.driver_ = driver;1226};122712281229/**1230 * Retrieves the window's current position, relative to the top left corner of1231 * the screen.1232 * @return {!webdriver.promise.Promise.<{x: number, y: number}>} A promise that1233 *     will be resolved with the window's position in the form of a1234 *     {x:number, y:number} object literal.1235 */1236webdriver.WebDriver.Window.prototype.getPosition = function() {1237  return this.driver_.schedule(1238      new webdriver.Command(webdriver.CommandName.GET_WINDOW_POSITION).1239          setParameter('windowHandle', 'current'),1240      'WebDriver.manage().window().getPosition()');1241};124212431244/**1245 * Repositions the current window.1246 * @param {number} x The desired horizontal position, relative to the left side1247 *     of the screen.1248 * @param {number} y The desired vertical position, relative to the top of the1249 *     of the screen.1250 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved1251 *     when the command has completed.1252 */1253webdriver.WebDriver.Window.prototype.setPosition = function(x, y) {1254  return this.driver_.schedule(1255      new webdriver.Command(webdriver.CommandName.SET_WINDOW_POSITION).1256          setParameter('windowHandle', 'current').1257          setParameter('x', x).1258          setParameter('y', y),1259      'WebDriver.manage().window().setPosition(' + x + ', ' + y + ')');1260};126112621263/**1264 * Retrieves the window's current size.1265 * @return {!webdriver.promise.Promise.<{width: number, height: number}>} A1266 *     promise that will be resolved with the window's size in the form of a1267 *     {width:number, height:number} object literal.1268 */1269webdriver.WebDriver.Window.prototype.getSize = function() {1270  return this.driver_.schedule(1271      new webdriver.Command(webdriver.CommandName.GET_WINDOW_SIZE).1272          setParameter('windowHandle', 'current'),1273      'WebDriver.manage().window().getSize()');1274};127512761277/**1278 * Resizes the current window.1279 * @param {number} width The desired window width.1280 * @param {number} height The desired window height.1281 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved1282 *     when the command has completed.1283 */1284webdriver.WebDriver.Window.prototype.setSize = function(width, height) {1285  return this.driver_.schedule(1286      new webdriver.Command(webdriver.CommandName.SET_WINDOW_SIZE).1287          setParameter('windowHandle', 'current').1288          setParameter('width', width).1289          setParameter('height', height),1290      'WebDriver.manage().window().setSize(' + width + ', ' + height + ')');1291};129212931294/**1295 * Maximizes the current window.1296 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved1297 *     when the command has completed.1298 */1299webdriver.WebDriver.Window.prototype.maximize = function() {1300  return this.driver_.schedule(1301      new webdriver.Command(webdriver.CommandName.MAXIMIZE_WINDOW).1302          setParameter('windowHandle', 'current'),1303      'WebDriver.manage().window().maximize()');1304};130513061307/**1308 * Interface for managing WebDriver log records.1309 * @param {!webdriver.WebDriver} driver The parent driver.1310 * @constructor1311 */1312webdriver.WebDriver.Logs = function(driver) {13131314  /** @private {!webdriver.WebDriver} */1315  this.driver_ = driver;1316};131713181319/**1320 * Fetches available log entries for the given type.1321 *1322 * <p/>Note that log buffers are reset after each call, meaning that1323 * available log entries correspond to those entries not yet returned for a1324 * given log type. In practice, this means that this call will return the1325 * available log entries since the last call, or from the start of the1326 * session.1327 *1328 * @param {!webdriver.logging.Type} type The desired log type.1329 * @return {!webdriver.promise.Promise.<!Array.<!webdriver.logging.Entry>>} A1330 *   promise that will resolve to a list of log entries for the specified1331 *   type.1332 */1333webdriver.WebDriver.Logs.prototype.get = function(type) {1334  return this.driver_.schedule(1335      new webdriver.Command(webdriver.CommandName.GET_LOG).1336          setParameter('type', type),1337      'WebDriver.manage().logs().get(' + type + ')').1338      then(function(entries) {1339        return goog.array.map(entries, function(entry) {1340          if (!(entry instanceof webdriver.logging.Entry)) {1341            return new webdriver.logging.Entry(1342                entry['level'], entry['message'], entry['timestamp']);1343          }1344          return entry;1345        });1346      });1347};134813491350/**1351 * Retrieves the log types available to this driver.1352 * @return {!webdriver.promise.Promise.<!Array.<!webdriver.logging.Type>>} A1353 *     promise that will resolve to a list of available log types.1354 */1355webdriver.WebDriver.Logs.prototype.getAvailableLogTypes = function() {1356  return this.driver_.schedule(1357      new webdriver.Command(webdriver.CommandName.GET_AVAILABLE_LOG_TYPES),1358      'WebDriver.manage().logs().getAvailableLogTypes()');1359};1360136113621363/**1364 * An interface for changing the focus of the driver to another frame or window.1365 * @param {!webdriver.WebDriver} driver The parent driver.1366 * @constructor1367 */1368webdriver.WebDriver.TargetLocator = function(driver) {13691370  /** @private {!webdriver.WebDriver} */1371  this.driver_ = driver;1372};137313741375/**1376 * Schedules a command retrieve the {@code document.activeElement} element on1377 * the current document, or {@code document.body} if activeElement is not1378 * available.1379 * @return {!webdriver.WebElement} The active element.1380 */1381webdriver.WebDriver.TargetLocator.prototype.activeElement = function() {1382  var id = this.driver_.schedule(1383      new webdriver.Command(webdriver.CommandName.GET_ACTIVE_ELEMENT),1384      'WebDriver.switchTo().activeElement()');1385  return new webdriver.WebElement(this.driver_, id);1386};138713881389/**1390 * Schedules a command to switch focus of all future commands to the first frame1391 * on the page.1392 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved1393 *     when the driver has changed focus to the default content.1394 */1395webdriver.WebDriver.TargetLocator.prototype.defaultContent = function() {1396  return this.driver_.schedule(1397      new webdriver.Command(webdriver.CommandName.SWITCH_TO_FRAME).1398          setParameter('id', null),1399      'WebDriver.switchTo().defaultContent()');1400};140114021403/**1404 * Schedules a command to switch the focus of all future commands to another1405 * frame on the page.1406 * <p/>1407 * If the frame is specified by a number, the command will switch to the frame1408 * by its (zero-based) index into the {@code window.frames} collection.1409 * <p/>1410 * If the frame is specified by a string, the command will select the frame by1411 * its name or ID. To select sub-frames, simply separate the frame names/IDs by1412 * dots. As an example, "main.child" will select the frame with the name "main"1413 * and then its child "child".1414 * <p/>1415 * If the specified frame can not be found, the deferred result will errback1416 * with a {@code bot.ErrorCode.NO_SUCH_FRAME} error.1417 * @param {string|number} nameOrIndex The frame locator.1418 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved1419 *     when the driver has changed focus to the specified frame.1420 */1421webdriver.WebDriver.TargetLocator.prototype.frame = function(nameOrIndex) {1422  return this.driver_.schedule(1423      new webdriver.Command(webdriver.CommandName.SWITCH_TO_FRAME).1424          setParameter('id', nameOrIndex),1425      'WebDriver.switchTo().frame(' + nameOrIndex + ')');1426};142714281429/**1430 * Schedules a command to switch the focus of all future commands to another1431 * window. Windows may be specified by their {@code window.name} attribute or1432 * by its handle (as returned by {@code webdriver.WebDriver#getWindowHandles}).1433 * <p/>1434 * If the specificed window can not be found, the deferred result will errback1435 * with a {@code bot.ErrorCode.NO_SUCH_WINDOW} error.1436 * @param {string} nameOrHandle The name or window handle of the window to1437 *     switch focus to.1438 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved1439 *     when the driver has changed focus to the specified window.1440 */1441webdriver.WebDriver.TargetLocator.prototype.window = function(nameOrHandle) {1442  return this.driver_.schedule(1443      new webdriver.Command(webdriver.CommandName.SWITCH_TO_WINDOW).1444          setParameter('name', nameOrHandle),1445      'WebDriver.switchTo().window(' + nameOrHandle + ')');1446};144714481449/**1450 * Schedules a command to change focus to the active alert dialog. This command1451 * will return a {@link bot.ErrorCode.NO_MODAL_DIALOG_OPEN} error if a modal1452 * dialog is not currently open.1453 * @return {!webdriver.Alert} The open alert.1454 */1455webdriver.WebDriver.TargetLocator.prototype.alert = function() {1456  var text = this.driver_.schedule(1457      new webdriver.Command(webdriver.CommandName.GET_ALERT_TEXT),1458      'WebDriver.switchTo().alert()');1459  return new webdriver.Alert(this.driver_, text);1460};146114621463/**1464 * Simulate pressing many keys at once in a "chord". Takes a sequence of1465 * {@link webdriver.Key}s or strings, appends each of the values to a string,1466 * and adds the chord termination key ({@link webdriver.Key.NULL}) and returns1467 * the resultant string.1468 *1469 * Note: when the low-level webdriver key handlers see Keys.NULL, active1470 * modifier keys (CTRL/ALT/SHIFT/etc) release via a keyup event.1471 *1472 * @param {...string} var_args The key sequence to concatenate.1473 * @return {string} The null-terminated key sequence.1474 * @see http://code.google.com/p/webdriver/issues/detail?id=791475 */1476webdriver.Key.chord = function(var_args) {1477  var sequence = goog.array.reduce(1478      goog.array.slice(arguments, 0),1479      function(str, key) {1480        return str + key;1481      }, '');1482  sequence += webdriver.Key.NULL;1483  return sequence;1484};148514861487//////////////////////////////////////////////////////////////////////////////1488//1489//  webdriver.WebElement1490//1491//////////////////////////////////////////////////////////////////////////////1492149314941495/**1496 * Represents a DOM element. WebElements can be found by searching from the1497 * document root using a {@code webdriver.WebDriver} instance, or by searching1498 * under another {@code webdriver.WebElement}:1499 * <pre><code>1500 *   driver.get('http://www.google.com');1501 *   var searchForm = driver.findElement(By.tagName('form'));1502 *   var searchBox = searchForm.findElement(By.name('q'));1503 *   searchBox.sendKeys('webdriver');1504 * </code></pre>1505 *1506 * The WebElement is implemented as a promise for compatibility with the promise1507 * API. It will always resolve itself when its internal state has been fully1508 * resolved and commands may be issued against the element. This can be used to1509 * catch errors when an element cannot be located on the page:1510 * <pre><code>1511 *   driver.findElement(By.id('not-there')).then(function(element) {1512 *     alert('Found an element that was not expected to be there!');1513 *   }, function(error) {1514 *     alert('The element was not found, as expected');1515 *   });1516 * </code></pre>1517 *1518 * @param {!webdriver.WebDriver} driver The parent WebDriver instance for this1519 *     element.1520 * @param {!(string|webdriver.promise.Promise)} id Either the opaque ID for the1521 *     underlying DOM element assigned by the server, or a promise that will1522 *     resolve to that ID or another WebElement.1523 * @constructor1524 * @extends {webdriver.promise.Deferred}1525 */1526webdriver.WebElement = function(driver, id) {1527  webdriver.promise.Deferred.call(this, null, driver.controlFlow());15281529  /**1530   * The parent WebDriver instance for this element.1531   * @private {!webdriver.WebDriver}1532   */1533  this.driver_ = driver;15341535  // This class is responsible for resolving itself; delete the resolve and1536  // reject methods so they may not be accessed by consumers of this class.1537  var fulfill = goog.partial(this.fulfill, this);1538  var reject = this.reject;1539  delete this.promise;1540  delete this.fulfill;1541  delete this.reject;15421543  /**1544   * A promise that resolves to the JSON representation of this WebElement's1545   * ID, as defined by the WebDriver wire protocol.1546   * @private {!webdriver.promise.Promise.<webdriver.WebElement.Id>}1547   * @see http://code.google.com/p/selenium/wiki/JsonWireProtocol1548   */1549  this.id_ = webdriver.promise.when(id, function(id) {1550    if (id instanceof webdriver.WebElement) {1551      return id.id_;1552    } else if (goog.isDef(id[webdriver.WebElement.ELEMENT_KEY])) {1553      return id;1554    }15551556    var json = {};1557    json[webdriver.WebElement.ELEMENT_KEY] = id;1558    return json;1559  });15601561  // This WebElement should not be resolved until its ID has been1562  // fully resolved.1563  this.id_.then(fulfill, reject);1564};1565goog.inherits(webdriver.WebElement, webdriver.promise.Deferred);156615671568/**1569 * Wire protocol definition of a WebElement ID.1570 * @typedef {{ELEMENT: string}}1571 * @see http://code.google.com/p/selenium/wiki/JsonWireProtocol1572 */1573webdriver.WebElement.Id;157415751576/**1577 * The property key used in the wire protocol to indicate that a JSON object1578 * contains the ID of a WebElement.1579 * @type {string}1580 * @const1581 */1582webdriver.WebElement.ELEMENT_KEY = 'ELEMENT';158315841585/**1586 * Compares to WebElements for equality.1587 * @param {!webdriver.WebElement} a A WebElement.1588 * @param {!webdriver.WebElement} b A WebElement.1589 * @return {!webdriver.promise.Promise.<boolean>} A promise that will be1590 *     resolved to whether the two WebElements are equal.1591 */1592webdriver.WebElement.equals = function(a, b) {1593  if (a == b) {1594    return webdriver.promise.fulfilled(true);1595  }1596  return webdriver.promise.fullyResolved([a.id_, b.id_]).then(function(ids) {1597    // If the two element's have the same ID, they should be considered1598    // equal. Otherwise, they may still be equivalent, but we'll need to1599    // ask the server to check for us.1600    if (ids[0][webdriver.WebElement.ELEMENT_KEY] ==1601        ids[1][webdriver.WebElement.ELEMENT_KEY]) {1602      return true;1603    }16041605    var command = new webdriver.Command(1606        webdriver.CommandName.ELEMENT_EQUALS);1607    command.setParameter('other', b);1608    return a.schedule_(command, 'webdriver.WebElement.equals()');1609  });1610};161116121613/**1614 * @return {!webdriver.WebDriver} The parent driver for this instance.1615 */1616webdriver.WebElement.prototype.getDriver = function() {1617  return this.driver_;1618};161916201621/**1622 * @return {!webdriver.promise.Promise.<webdriver.WebElement.Id>} A promise1623 *     that resolves to this element's JSON representation as defined by the1624 *     WebDriver wire protocol.1625 * @see http://code.google.com/p/selenium/wiki/JsonWireProtocol1626 */1627webdriver.WebElement.prototype.toWireValue = function() {1628  return this.id_;1629};163016311632/**1633 * Schedules a command that targets this element with the parent WebDriver1634 * instance. Will ensure this element's ID is included in the command parameters1635 * under the "id" key.1636 * @param {!webdriver.Command} command The command to schedule.1637 * @param {string} description A description of the command for debugging.1638 * @return {!webdriver.promise.Promise.<T>} A promise that will be resolved1639 *     with the command result.1640 * @template T1641 * @see webdriver.WebDriver.prototype.schedule1642 * @private1643 */1644webdriver.WebElement.prototype.schedule_ = function(command, description) {1645  command.setParameter('id', this.id_);1646  return this.driver_.schedule(command, description);1647};164816491650/**1651 * Schedule a command to find a descendant of this element. If the element1652 * cannot be found, a {@code bot.ErrorCode.NO_SUCH_ELEMENT} result will1653 * be returned by the driver. Unlike other commands, this error cannot be1654 * suppressed. In other words, scheduling a command to find an element doubles1655 * as an assert that the element is present on the page. To test whether an1656 * element is present on the page, use {@code #isElementPresent} instead.1657 *1658 * <p>The search criteria for an element may be defined using one of the1659 * factories in the {@link webdriver.By} namespace, or as a short-hand1660 * {@link webdriver.By.Hash} object. For example, the following two statements1661 * are equivalent:1662 * <code><pre>1663 * var e1 = element.findElement(By.id('foo'));1664 * var e2 = element.findElement({id:'foo'});1665 * </pre></code>1666 *1667 * <p>You may also provide a custom locator function, which takes as input1668 * this WebDriver instance and returns a {@link webdriver.WebElement}, or a1669 * promise that will resolve to a WebElement. For example, to find the first1670 * visible link on a page, you could write:1671 * <code><pre>1672 * var link = element.findElement(firstVisibleLink);1673 *1674 * function firstVisibleLink(element) {1675 *   var links = element.findElements(By.tagName('a'));1676 *   return webdriver.promise.filter(links, function(link) {1677 *     return links.isDisplayed();1678 *   }).then(function(visibleLinks) {1679 *     return visibleLinks[0];1680 *   });1681 * }1682 * </pre></code>1683 *1684 * @param {!(webdriver.Locator|webdriver.By.Hash|Function)} locator The1685 *     locator strategy to use when searching for the element.1686 * @return {!webdriver.WebElement} A WebElement that can be used to issue1687 *     commands against the located element. If the element is not found, the1688 *     element will be invalidated and all scheduled commands aborted.1689 */1690webdriver.WebElement.prototype.findElement = function(locator) {1691  locator = webdriver.Locator.checkLocator(locator);1692  var id;1693  if (goog.isFunction(locator)) {1694    id = this.driver_.findElementInternal_(locator, this);1695  } else {1696    var command = new webdriver.Command(1697        webdriver.CommandName.FIND_CHILD_ELEMENT).1698        setParameter('using', locator.using).1699        setParameter('value', locator.value);1700    id = this.schedule_(command, 'WebElement.findElement(' + locator + ')');1701  }1702  return new webdriver.WebElement(this.driver_, id);1703};170417051706/**1707 * Schedules a command to test if there is at least one descendant of this1708 * element that matches the given search criteria.1709 *1710 * @param {!(webdriver.Locator|webdriver.By.Hash|Function)} locator The1711 *     locator strategy to use when searching for the element.1712 * @return {!webdriver.promise.Promise.<boolean>} A promise that will be1713 *     resolved with whether an element could be located on the page.1714 */1715webdriver.WebElement.prototype.isElementPresent = function(locator) {1716  return this.findElements(locator).then(function(result) {1717    return !!result.length;1718  });1719};172017211722/**1723 * Schedules a command to find all of the descendants of this element that1724 * match the given search criteria.1725 *1726 * @param {!(webdriver.Locator|webdriver.By.Hash|Function)} locator The1727 *     locator strategy to use when searching for the elements.1728 * @return {!webdriver.promise.Promise.<!Array.<!webdriver.WebElement>>} A1729 *     promise that will resolve to an array of WebElements.1730 */1731webdriver.WebElement.prototype.findElements = function(locator) {1732  locator = webdriver.Locator.checkLocator(locator);1733  if (goog.isFunction(locator)) {1734    return this.driver_.findElementsInternal_(locator, this);1735  } else {1736    var command = new webdriver.Command(1737        webdriver.CommandName.FIND_CHILD_ELEMENTS).1738        setParameter('using', locator.using).1739        setParameter('value', locator.value);1740    return this.schedule_(command, 'WebElement.findElements(' + locator + ')');1741  }1742};174317441745/**1746 * Schedules a command to click on this element.1747 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved1748 *     when the click command has completed.1749 */1750webdriver.WebElement.prototype.click = function() {1751  return this.schedule_(1752      new webdriver.Command(webdriver.CommandName.CLICK_ELEMENT),1753      'WebElement.click()');1754};175517561757/**1758 * Schedules a command to type a sequence on the DOM element represented by this1759 * instance.1760 * <p/>1761 * Modifier keys (SHIFT, CONTROL, ALT, META) are stateful; once a modifier is1762 * processed in the keysequence, that key state is toggled until one of the1763 * following occurs:1764 * <ul>1765 * <li>The modifier key is encountered again in the sequence. At this point the1766 * state of the key is toggled (along with the appropriate keyup/down events).1767 * </li>1768 * <li>The {@code webdriver.Key.NULL} key is encountered in the sequence. When1769 * this key is encountered, all modifier keys current in the down state are1770 * released (with accompanying keyup events). The NULL key can be used to1771 * simulate common keyboard shortcuts:1772 * <code><pre>1773 *     element.sendKeys("text was",1774 *                      webdriver.Key.CONTROL, "a", webdriver.Key.NULL,1775 *                      "now text is");1776 *     // Alternatively:1777 *     element.sendKeys("text was",1778 *                      webdriver.Key.chord(webdriver.Key.CONTROL, "a"),1779 *                      "now text is");1780 * </pre></code></li>1781 * <li>The end of the keysequence is encountered. When there are no more keys1782 * to type, all depressed modifier keys are released (with accompanying keyup1783 * events).1784 * </li>1785 * </ul>1786 * <strong>Note:</strong> On browsers where native keyboard events are not yet1787 * supported (e.g. Firefox on OS X), key events will be synthesized. Special1788 * punctionation keys will be synthesized according to a standard QWERTY en-us1789 * keyboard layout.1790 *1791 * @param {...string} var_args The sequence of keys to1792 *     type. All arguments will be joined into a single sequence (var_args is1793 *     permitted for convenience).1794 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved1795 *     when all keys have been typed.1796 */1797webdriver.WebElement.prototype.sendKeys = function(var_args) {1798  // Coerce every argument to a string. This protects us from users that1799  // ignore the jsdoc and give us a number (which ends up causing problems on1800  // the server, which requires strings).1801  var keys = webdriver.promise.fullyResolved(goog.array.slice(arguments, 0)).1802      then(function(args) {1803        return goog.array.map(goog.array.slice(args, 0), function(key) {1804          return key + '';1805        });1806      });1807  return this.schedule_(1808      new webdriver.Command(webdriver.CommandName.SEND_KEYS_TO_ELEMENT).1809          setParameter('value', keys),1810      'WebElement.sendKeys(' + keys + ')');1811};181218131814/**1815 * Schedules a command to query for the tag/node name of this element.1816 * @return {!webdriver.promise.Promise.<string>} A promise that will be1817 *     resolved with the element's tag name.1818 */1819webdriver.WebElement.prototype.getTagName = function() {1820  return this.schedule_(1821      new webdriver.Command(webdriver.CommandName.GET_ELEMENT_TAG_NAME),1822      'WebElement.getTagName()');1823};182418251826/**1827 * Schedules a command to query for the computed style of the element1828 * represented by this instance. If the element inherits the named style from1829 * its parent, the parent will be queried for its value.  Where possible, color1830 * values will be converted to their hex representation (e.g. #00ff00 instead of1831 * rgb(0, 255, 0)).1832 * <p/>1833 * <em>Warning:</em> the value returned will be as the browser interprets it, so1834 * it may be tricky to form a proper assertion.1835 *1836 * @param {string} cssStyleProperty The name of the CSS style property to look1837 *     up.1838 * @return {!webdriver.promise.Promise.<string>} A promise that will be1839 *     resolved with the requested CSS value.1840 */1841webdriver.WebElement.prototype.getCssValue = function(cssStyleProperty) {1842  var name = webdriver.CommandName.GET_ELEMENT_VALUE_OF_CSS_PROPERTY;1843  return this.schedule_(1844      new webdriver.Command(name).1845          setParameter('propertyName', cssStyleProperty),1846      'WebElement.getCssValue(' + cssStyleProperty + ')');1847};184818491850/**1851 * Schedules a command to query for the value of the given attribute of the1852 * element. Will return the current value, even if it has been modified after1853 * the page has been loaded. More exactly, this method will return the value of1854 * the given attribute, unless that attribute is not present, in which case the1855 * value of the property with the same name is returned. If neither value is1856 * set, null is returned (for example, the "value" property of a textarea1857 * element). The "style" attribute is converted as best can be to a1858 * text representation with a trailing semi-colon. The following are deemed to1859 * be "boolean" attributes and will return either "true" or null:1860 *1861 * <p>async, autofocus, autoplay, checked, compact, complete, controls, declare,1862 * defaultchecked, defaultselected, defer, disabled, draggable, ended,1863 * formnovalidate, hidden, indeterminate, iscontenteditable, ismap, itemscope,1864 * loop, multiple, muted, nohref, noresize, noshade, novalidate, nowrap, open,1865 * paused, pubdate, readonly, required, reversed, scoped, seamless, seeking,1866 * selected, spellcheck, truespeed, willvalidate1867 *1868 * <p>Finally, the following commonly mis-capitalized attribute/property names1869 * are evaluated as expected:1870 * <ul>1871 *   <li>"class"1872 *   <li>"readonly"1873 * </ul>1874 * @param {string} attributeName The name of the attribute to query.1875 * @return {!webdriver.promise.Promise.<?string>} A promise that will be1876 *     resolved with the attribute's value. The returned value will always be1877 *     either a string or null.1878 */1879webdriver.WebElement.prototype.getAttribute = function(attributeName) {1880  return this.schedule_(1881      new webdriver.Command(webdriver.CommandName.GET_ELEMENT_ATTRIBUTE).1882          setParameter('name', attributeName),1883      'WebElement.getAttribute(' + attributeName + ')');1884};188518861887/**1888 * Get the visible (i.e. not hidden by CSS) innerText of this element, including1889 * sub-elements, without any leading or trailing whitespace.1890 * @return {!webdriver.promise.Promise.<string>} A promise that will be1891 *     resolved with the element's visible text.1892 */1893webdriver.WebElement.prototype.getText = function() {1894  return this.schedule_(1895      new webdriver.Command(webdriver.CommandName.GET_ELEMENT_TEXT),1896      'WebElement.getText()');1897};189818991900/**1901 * Schedules a command to compute the size of this element's bounding box, in1902 * pixels.1903 * @return {!webdriver.promise.Promise.<{width: number, height: number}>} A1904 *     promise that will be resolved with the element's size as a1905 *     {@code {width:number, height:number}} object.1906 */1907webdriver.WebElement.prototype.getSize = function() {1908  return this.schedule_(1909      new webdriver.Command(webdriver.CommandName.GET_ELEMENT_SIZE),1910      'WebElement.getSize()');1911};191219131914/**1915 * Schedules a command to compute the location of this element in page space.1916 * @return {!webdriver.promise.Promise.<{x: number, y: number}>} A promise that1917 *     will be resolved to the element's location as a1918 *     {@code {x:number, y:number}} object.1919 */1920webdriver.WebElement.prototype.getLocation = function() {1921  return this.schedule_(1922      new webdriver.Command(webdriver.CommandName.GET_ELEMENT_LOCATION),1923      'WebElement.getLocation()');1924};192519261927/**1928 * Schedules a command to query whether the DOM element represented by this1929 * instance is enabled, as dicted by the {@code disabled} attribute.1930 * @return {!webdriver.promise.Promise.<boolean>} A promise that will be1931 *     resolved with whether this element is currently enabled.1932 */1933webdriver.WebElement.prototype.isEnabled = function() {1934  return this.schedule_(1935      new webdriver.Command(webdriver.CommandName.IS_ELEMENT_ENABLED),1936      'WebElement.isEnabled()');1937};193819391940/**1941 * Schedules a command to query whether this element is selected.1942 * @return {!webdriver.promise.Promise.<boolean>} A promise that will be1943 *     resolved with whether this element is currently selected.1944 */1945webdriver.WebElement.prototype.isSelected = function() {1946  return this.schedule_(1947      new webdriver.Command(webdriver.CommandName.IS_ELEMENT_SELECTED),1948      'WebElement.isSelected()');1949};195019511952/**1953 * Schedules a command to submit the form containing this element (or this1954 * element if it is a FORM element). This command is a no-op if the element is1955 * not contained in a form.1956 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved1957 *     when the form has been submitted.1958 */1959webdriver.WebElement.prototype.submit = function() {1960  return this.schedule_(1961      new webdriver.Command(webdriver.CommandName.SUBMIT_ELEMENT),1962      'WebElement.submit()');1963};196419651966/**1967 * Schedules a command to clear the {@code value} of this element. This command1968 * has no effect if the underlying DOM element is neither a text INPUT element1969 * nor a TEXTAREA element.1970 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved1971 *     when the element has been cleared.1972 */1973webdriver.WebElement.prototype.clear = function() {1974  return this.schedule_(1975      new webdriver.Command(webdriver.CommandName.CLEAR_ELEMENT),1976      'WebElement.clear()');1977};197819791980/**1981 * Schedules a command to test whether this element is currently displayed.1982 * @return {!webdriver.promise.Promise.<boolean>} A promise that will be1983 *     resolved with whether this element is currently visible on the page.1984 */1985webdriver.WebElement.prototype.isDisplayed = function() {1986  return this.schedule_(1987      new webdriver.Command(webdriver.CommandName.IS_ELEMENT_DISPLAYED),1988      'WebElement.isDisplayed()');1989};199019911992/**1993 * Schedules a command to retrieve the outer HTML of this element.1994 * @return {!webdriver.promise.Promise.<string>} A promise that will be1995 *     resolved with the element's outer HTML.1996 */1997webdriver.WebElement.prototype.getOuterHtml = function() {1998  return this.driver_.executeScript(function() {1999    var element = arguments[0];2000    if ('outerHTML' in element) {2001      return element.outerHTML;2002    } else {2003      var div = element.ownerDocument.createElement('div');2004      div.appendChild(element.cloneNode(true));2005      return div.innerHTML;2006    }2007  }, this);2008};200920102011/**2012 * Schedules a command to retrieve the inner HTML of this element.2013 * @return {!webdriver.promise.Promise.<string>} A promise that will be2014 *     resolved with the element's inner HTML.2015 */2016webdriver.WebElement.prototype.getInnerHtml = function() {2017  return this.driver_.executeScript('return arguments[0].innerHTML', this);2018};2019202020212022/**2023 * Represents a modal dialog such as {@code alert}, {@code confirm}, or2024 * {@code prompt}. Provides functions to retrieve the message displayed with2025 * the alert, accept or dismiss the alert, and set the response text (in the2026 * case of {@code prompt}).2027 * @param {!webdriver.WebDriver} driver The driver controlling the browser this2028 *     alert is attached to.2029 * @param {!(string|webdriver.promise.Promise.<string>)} text Either the2030 *     message text displayed with this alert, or a promise that will be2031 *     resolved to said text.2032 * @constructor2033 * @extends {webdriver.promise.Deferred}2034 */2035webdriver.Alert = function(driver, text) {2036  goog.base(this, null, driver.controlFlow());20372038  /** @private {!webdriver.WebDriver} */2039  this.driver_ = driver;20402041  // This class is responsible for resolving itself; delete the resolve and2042  // reject methods so they may not be accessed by consumers of this class.2043  var fulfill = goog.partial(this.fulfill, this);2044  var reject = this.reject;2045  delete this.promise;2046  delete this.fulfill;2047  delete this.reject;20482049  /** @private {!webdriver.promise.Promise.<string>} */2050  this.text_ = webdriver.promise.when(text);20512052  // Make sure this instance is resolved when its displayed text is.2053  this.text_.then(fulfill, reject);2054};2055goog.inherits(webdriver.Alert, webdriver.promise.Deferred);205620572058/**2059 * Retrieves the message text displayed with this alert. For instance, if the2060 * alert were opened with alert("hello"), then this would return "hello".2061 * @return {!webdriver.promise.Promise.<string>} A promise that will be2062 *     resolved to the text displayed with this alert.2063 */2064webdriver.Alert.prototype.getText = function() {2065  return this.text_;2066};206720682069/**2070 * Accepts this alert.2071 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved2072 *     when this command has completed.2073 */2074webdriver.Alert.prototype.accept = function() {2075  return this.driver_.schedule(2076      new webdriver.Command(webdriver.CommandName.ACCEPT_ALERT),2077      'WebDriver.switchTo().alert().accept()');2078};207920802081/**2082 * Dismisses this alert.2083 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved2084 *     when this command has completed.2085 */2086webdriver.Alert.prototype.dismiss = function() {2087  return this.driver_.schedule(2088      new webdriver.Command(webdriver.CommandName.DISMISS_ALERT),2089      'WebDriver.switchTo().alert().dismiss()');2090};209120922093/**2094 * Sets the response text on this alert. This command will return an error if2095 * the underlying alert does not support response text (e.g. window.alert and2096 * window.confirm).2097 * @param {string} text The text to set.2098 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved2099 *     when this command has completed.2100 */2101webdriver.Alert.prototype.sendKeys = function(text) {2102  return this.driver_.schedule(2103      new webdriver.Command(webdriver.CommandName.SET_ALERT_TEXT).2104          setParameter('text', text),2105      'WebDriver.switchTo().alert().sendKeys(' + text + ')');2106};2107210821092110/**2111 * An error returned to indicate that there is an unhandled modal dialog on the2112 * current page.2113 * @param {string} message The error message.2114 * @param {!webdriver.Alert} alert The alert handle.2115 * @constructor2116 * @extends {bot.Error}2117 */2118webdriver.UnhandledAlertError = function(message, alert) {2119  goog.base(this, bot.ErrorCode.MODAL_DIALOG_OPENED, message);21202121  /** @private {!webdriver.Alert} */2122  this.alert_ = alert;2123};2124goog.inherits(webdriver.UnhandledAlertError, bot.Error);212521262127/**2128 * @return {!webdriver.Alert} The open alert.2129 */2130webdriver.UnhandledAlertError.prototype.getAlert = function() {2131  return this.alert_;2132};