# 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} A promise that will be resolved with258 *     the command result.259 */260webdriver.WebDriver.prototype.schedule = function(command, description) {261  var self = this;262263  checkHasNotQuit();264  command.setParameter('sessionId', this.session_);265266  var flow = this.flow_;267  return flow.execute(function() {268    // A call to WebDriver.quit() may have been scheduled in the same event269    // loop as this |command|, which would prevent us from detecting that the270    // driver has quit above.  Therefore, we need to make another quick check.271    // We still check above so we can fail as early as possible.272    checkHasNotQuit();273    return webdriver.WebDriver.executeCommand_(self.executor_, command);274  }, description).then(function(response) {275    try {276      bot.response.checkResponse(response);277    } catch (ex) {278      var value = response['value'];279      if (ex.code === bot.ErrorCode.MODAL_DIALOG_OPENED) {280        var text = value && value['alert'] ? value['alert']['text'] : '';281        throw new webdriver.UnhandledAlertError(ex.message,282            new webdriver.Alert(self, text));283      }284      throw ex;285    }286    return webdriver.WebDriver.fromWireValue_(self, response['value']);287  });288289  function checkHasNotQuit() {290    if (!self.session_) {291      throw new Error('This driver instance does not have a valid session ID ' +292                      '(did you call WebDriver.quit()?) and may no longer be ' +293                      'used.');294    }295  }296};297298299// ----------------------------------------------------------------------------300// Client command functions:301// ----------------------------------------------------------------------------302303304/**305 * @return {!webdriver.promise.Promise} A promise for this client's session.306 */307webdriver.WebDriver.prototype.getSession = function() {308  return webdriver.promise.when(this.session_);309};310311312/**313 * @return {!webdriver.promise.Promise} A promise that will resolve with the314 *     this instance's capabilities.315 */316webdriver.WebDriver.prototype.getCapabilities = function() {317  return webdriver.promise.when(this.session_, function(session) {318    return session.getCapabilities();319  });320};321322323/**324 * Schedules a command to quit the current session. After calling quit, this325 * instance will be invalidated and may no longer be used to issue commands326 * against the browser.327 * @return {!webdriver.promise.Promise} A promise that will be resolved when328 *     the command has completed.329 */330webdriver.WebDriver.prototype.quit = function() {331  var result = this.schedule(332      new webdriver.Command(webdriver.CommandName.QUIT),333      'WebDriver.quit()');334  // Delete our session ID when the quit command finishes; this will allow us to335  // throw an error when attemnpting to use a driver post-quit.336  return result.thenFinally(goog.bind(function() {337    delete this.session_;338  }, this));339};340341342/**343 * Creates a new action sequence using this driver. The sequence will not be344 * scheduled for execution until {@link webdriver.ActionSequence#perform} is345 * called. Example:346 * <pre><code>347 *   driver.actions().348 *       mouseDown(element1).349 *       mouseMove(element2).350 *       mouseUp().351 *       perform();352 * </code></pre>353 * @return {!webdriver.ActionSequence} A new action sequence for this instance.354 */355webdriver.WebDriver.prototype.actions = function() {356  return new webdriver.ActionSequence(this);357};358359360/**361 * Schedules a command to execute JavaScript in the context of the currently362 * selected frame or window. The script fragment will be executed as the body363 * of an anonymous function. If the script is provided as a function object,364 * that function will be converted to a string for injection into the target365 * window.366 *367 * Any arguments provided in addition to the script will be included as script368 * arguments and may be referenced using the {@code arguments} object.369 * Arguments may be a boolean, number, string, or {@code webdriver.WebElement}.370 * Arrays and objects may also be used as script arguments as long as each item371 * adheres to the types previously mentioned.372 *373 * The script may refer to any variables accessible from the current window.374 * Furthermore, the script will execute in the window's context, thus375 * {@code document} may be used to refer to the current document. Any local376 * variables will not be available once the script has finished executing,377 * though global variables will persist.378 *379 * If the script has a return value (i.e. if the script contains a return380 * statement), then the following steps will be taken for resolving this381 * functions return value:382 * <ul>383 * <li>For a HTML element, the value will resolve to a384 *     {@code webdriver.WebElement}</li>385 * <li>Null and undefined return values will resolve to null</li>386 * <li>Booleans, numbers, and strings will resolve as is</li>387 * <li>Functions will resolve to their string representation</li>388 * <li>For arrays and objects, each member item will be converted according to389 *     the rules above</li>390 * </ul>391 *392 * @param {!(string|Function)} script The script to execute.393 * @param {...*} var_args The arguments to pass to the script.394 * @return {!webdriver.promise.Promise} A promise that will resolve to the395 *    scripts return value.396 */397webdriver.WebDriver.prototype.executeScript = function(script, var_args) {398  if (goog.isFunction(script)) {399    script = 'return (' + script + ').apply(null, arguments);';400  }401  return this.schedule(402      new webdriver.Command(webdriver.CommandName.EXECUTE_SCRIPT).403          setParameter('script', script).404          setParameter('args', goog.array.slice(arguments, 1)),405      'WebDriver.executeScript()');406};407408409/**410 * Schedules a command to execute asynchronous JavaScript in the context of the411 * currently selected frame or window. The script fragment will be executed as412 * the body of an anonymous function. If the script is provided as a function413 * object, that function will be converted to a string for injection into the414 * target window.415 *416 * Any arguments provided in addition to the script will be included as script417 * arguments and may be referenced using the {@code arguments} object.418 * Arguments may be a boolean, number, string, or {@code webdriver.WebElement}.419 * Arrays and objects may also be used as script arguments as long as each item420 * adheres to the types previously mentioned.421 *422 * Unlike executing synchronous JavaScript with423 * {@code webdriver.WebDriver.prototype.executeScript}, scripts executed with424 * this function must explicitly signal they are finished by invoking the425 * provided callback. This callback will always be injected into the426 * executed function as the last argument, and thus may be referenced with427 * {@code arguments[arguments.length - 1]}. The following steps will be taken428 * for resolving this functions return value against the first argument to the429 * script's callback function:430 * <ul>431 * <li>For a HTML element, the value will resolve to a432 *     {@code webdriver.WebElement}</li>433 * <li>Null and undefined return values will resolve to null</li>434 * <li>Booleans, numbers, and strings will resolve as is</li>435 * <li>Functions will resolve to their string representation</li>436 * <li>For arrays and objects, each member item will be converted according to437 *     the rules above</li>438 * </ul>439 *440 * Example #1: Performing a sleep that is synchronized with the currently441 * selected window:442 * <code><pre>443 * var start = new Date().getTime();444 * driver.executeAsyncScript(445 *     'window.setTimeout(arguments[arguments.length - 1], 500);').446 *     then(function() {447 *       console.log('Elapsed time: ' + (new Date().getTime() - start) + ' ms');448 *     });449 * </pre></code>450 *451 * Example #2: Synchronizing a test with an AJAX application:452 * <code><pre>453 * var button = driver.findElement(By.id('compose-button'));454 * button.click();455 * driver.executeAsyncScript(456 *     'var callback = arguments[arguments.length - 1];' +457 *     'mailClient.getComposeWindowWidget().onload(callback);');458 * driver.switchTo().frame('composeWidget');459 * driver.findElement(By.id('to')).sendKEys('dog@example.com');460 * </pre></code>461 *462 * Example #3: Injecting a XMLHttpRequest and waiting for the result. In this463 * example, the inject script is specified with a function literal. When using464 * this format, the function is converted to a string for injection, so it465 * should not reference any symbols not defined in the scope of the page under466 * test.467 * <code><pre>468 * driver.executeAsyncScript(function() {469 *   var callback = arguments[arguments.length - 1];470 *   var xhr = new XMLHttpRequest();471 *   xhr.open("GET", "/resource/data.json", true);472 *   xhr.onreadystatechange = function() {473 *     if (xhr.readyState == 4) {474 *       callback(xhr.resposneText);475 *     }476 *   }477 *   xhr.send('');478 * }).then(function(str) {479 *   console.log(JSON.parse(str)['food']);480 * });481 * </pre></code>482 *483 * @param {!(string|Function)} script The script to execute.484 * @param {...*} var_args The arguments to pass to the script.485 * @return {!webdriver.promise.Promise} A promise that will resolve to the486 *    scripts return value.487 */488webdriver.WebDriver.prototype.executeAsyncScript = function(script, var_args) {489  if (goog.isFunction(script)) {490    script = 'return (' + script + ').apply(null, arguments);';491  }492  return this.schedule(493      new webdriver.Command(webdriver.CommandName.EXECUTE_ASYNC_SCRIPT).494          setParameter('script', script).495          setParameter('args', goog.array.slice(arguments, 1)),496      'WebDriver.executeScript()');497};498499500/**501 * Schedules a command to execute a custom function.502 * @param {!Function} fn The function to execute.503 * @param {Object=} opt_scope The object in whose scope to execute the function.504 * @param {...*} var_args Any arguments to pass to the function.505 * @return {!webdriver.promise.Promise} A promise that will be resolved with the506 *     function's result.507 */508webdriver.WebDriver.prototype.call = function(fn, opt_scope, var_args) {509  var args = goog.array.slice(arguments, 2);510  var flow = this.flow_;511  return flow.execute(function() {512    return webdriver.promise.fullyResolved(args).then(function(args) {513      return fn.apply(opt_scope, args);514    });515  }, 'WebDriver.call(' + (fn.name || 'function') + ')');516};517518519/**520 * Schedules a command to wait for a condition to hold, as defined by some521 * user supplied function. If any errors occur while evaluating the wait, they522 * will be allowed to propagate.523 * @param {function():boolean} fn The function to evaluate as a wait condition.524 * @param {number} timeout How long to wait for the condition to be true.525 * @param {string=} opt_message An optional message to use if the wait times526 *     out.527 * @return {!webdriver.promise.Promise} A promise that will be resolved when the528 *     wait condition has been satisfied.529 */530webdriver.WebDriver.prototype.wait = function(fn, timeout, opt_message) {531  return this.flow_.wait(fn, timeout, opt_message);532};533534535/**536 * Schedules a command to make the driver sleep for the given amount of time.537 * @param {number} ms The amount of time, in milliseconds, to sleep.538 * @return {!webdriver.promise.Promise} A promise that will be resolved when the539 *     sleep has finished.540 */541webdriver.WebDriver.prototype.sleep = function(ms) {542  return this.flow_.timeout(ms, 'WebDriver.sleep(' + ms + ')');543};544545546/**547 * Schedules a command to retrieve they current window handle.548 * @return {!webdriver.promise.Promise} A promise that will be resolved with the549 *     current window handle.550 */551webdriver.WebDriver.prototype.getWindowHandle = function() {552  return this.schedule(553      new webdriver.Command(webdriver.CommandName.GET_CURRENT_WINDOW_HANDLE),554      'WebDriver.getWindowHandle()');555};556557558/**559 * Schedules a command to retrieve the current list of available window handles.560 * @return {!webdriver.promise.Promise} A promise that will be resolved with an561 *     array of window handles.562 */563webdriver.WebDriver.prototype.getAllWindowHandles = function() {564  return this.schedule(565      new webdriver.Command(webdriver.CommandName.GET_WINDOW_HANDLES),566      'WebDriver.getAllWindowHandles()');567};568569570/**571 * Schedules a command to retrieve the current page's source. The page source572 * returned is a representation of the underlying DOM: do not expect it to be573 * formatted or escaped in the same way as the response sent from the web574 * server.575 * @return {!webdriver.promise.Promise} A promise that will be resolved with the576 *     current page source.577 */578webdriver.WebDriver.prototype.getPageSource = function() {579  return this.schedule(580      new webdriver.Command(webdriver.CommandName.GET_PAGE_SOURCE),581      'WebDriver.getAllWindowHandles()');582};583584585/**586 * Schedules a command to close the current window.587 * @return {!webdriver.promise.Promise} A promise that will be resolved when588 *     this command has completed.589 */590webdriver.WebDriver.prototype.close = function() {591  return this.schedule(new webdriver.Command(webdriver.CommandName.CLOSE),592                       'WebDriver.close()');593};594595596/**597 * Schedules a command to navigate to the given URL.598 * @param {string} url The fully qualified URL to open.599 * @return {!webdriver.promise.Promise} A promise that will be resolved when the600 *     document has finished loading.601 */602webdriver.WebDriver.prototype.get = function(url) {603  return this.navigate().to(url);604};605606607/**608 * Schedules a command to retrieve the URL of the current page.609 * @return {!webdriver.promise.Promise} A promise that will be resolved with the610 *     current URL.611 */612webdriver.WebDriver.prototype.getCurrentUrl = function() {613  return this.schedule(614      new webdriver.Command(webdriver.CommandName.GET_CURRENT_URL),615      'WebDriver.getCurrentUrl()');616};617618619/**620 * Schedules a command to retrieve the current page's title.621 * @return {!webdriver.promise.Promise} A promise that will be resolved with the622 *     current page's title.623 */624webdriver.WebDriver.prototype.getTitle = function() {625  return this.schedule(new webdriver.Command(webdriver.CommandName.GET_TITLE),626                       'WebDriver.getTitle()');627};628629630/**631 * Schedule a command to find an element on the page. If the element cannot be632 * found, a {@link bot.ErrorCode.NO_SUCH_ELEMENT} result will be returned633 * by the driver. Unlike other commands, this error cannot be suppressed. In634 * other words, scheduling a command to find an element doubles as an assert635 * that the element is present on the page. To test whether an element is636 * present on the page, use {@link #isElementPresent} instead.637 *638 * <p>The search criteria for an element may be defined using one of the639 * factories in the {@link webdriver.By} namespace, or as a short-hand640 * {@link webdriver.By.Hash} object. For example, the following two statements641 * are equivalent:642 * <code><pre>643 * var e1 = driver.findElement(By.id('foo'));644 * var e2 = driver.findElement({id:'foo'});645 * </pre></code>646 *647 * <p>You may also provide a custom locator function, which takes as input648 * this WebDriver instance and returns a {@link webdriver.WebElement}, or a649 * promise that will resolve to a WebElement. For example, to find the first650 * visible link on a page, you could write:651 * <code><pre>652 * var link = driver.findElement(firstVisibleLink);653 *654 * function firstVisibleLink(driver) {655 *   var links = driver.findElements(By.tagName('a'));656 *   return webdriver.promise.filter(links, function(link) {657 *     return links.isDisplayed();658 *   }).then(function(visibleLinks) {659 *     return visibleLinks[0];660 *   });661 * }662 * </pre></code>663 *664 * <p>When running in the browser, a WebDriver cannot manipulate DOM elements665 * directly; it may do so only through a {@link webdriver.WebElement} reference.666 * This function may be used to generate a WebElement from a DOM element. A667 * reference to the DOM element will be stored in a known location and this668 * driver will attempt to retrieve it through {@link #executeScript}. If the669 * element cannot be found (eg, it belongs to a different document than the670 * one this instance is currently focused on), a671 * {@link bot.ErrorCode.NO_SUCH_ELEMENT} error will be returned.672 *673 * @param {!(webdriver.Locator|webdriver.By.Hash|Element|Function)} locator The674 *     locator to use.675 * @return {!webdriver.WebElement} A WebElement that can be used to issue676 *     commands against the located element. If the element is not found, the677 *     element will be invalidated and all scheduled commands aborted.678 */679webdriver.WebDriver.prototype.findElement = function(locator) {680  var id;681  if ('nodeType' in locator && 'ownerDocument' in locator) {682    var element = /** @type {!Element} */ (locator);683    id = this.findDomElement_(element).684        then(function(elements) {685          if (!elements.length) {686            throw new bot.Error(bot.ErrorCode.NO_SUCH_ELEMENT,687                'Unable to locate element. Is WebDriver focused on its ' +688                    'ownerDocument\'s frame?');689          }690          return elements[0];691        });692  } else {693    locator = webdriver.Locator.checkLocator(locator);694    if (goog.isFunction(locator)) {695      id = this.findElementInternal_(locator, this);696    } else {697      var command = new webdriver.Command(webdriver.CommandName.FIND_ELEMENT).698          setParameter('using', locator.using).699          setParameter('value', locator.value);700      id = this.schedule(command, 'WebDriver.findElement(' + locator + ')');701    }702  }703  return new webdriver.WebElement(this, id);704};705706707/**708 * @param {!Function} locatorFn The locator function to use.709 * @param {!(webdriver.WebDriver|webdriver.WebElement)} context The search710 *     context.711 * @return {!webdriver.promise.Promise.<!webdriver.WebElement>} A712 *     promise that will resolve to a list of WebElements.713 * @private714 */715webdriver.WebDriver.prototype.findElementInternal_ = function(716    locatorFn, context) {717  return this.call(goog.partial(locatorFn, context)).then(function(result) {718    if (goog.isArray(result)) {719      result = result[0];720    }721    if (!(result instanceof webdriver.WebElement)) {722      throw new TypeError('Custom locator did not return a WebElement');723    }724    return result;725  });726};727728729/**730 * Locates a DOM element so that commands may be issued against it using the731 * {@link webdriver.WebElement} class. This is accomplished by storing a732 * reference to the element in an object on the element's ownerDocument.733 * {@link #executeScript} will then be used to create a WebElement from this734 * reference. This requires this driver to currently be focused on the735 * ownerDocument's window+frame.736737 * @param {!Element} element The element to locate.738 * @return {!webdriver.promise.Promise} A promise that will be resolved739 *     with the located WebElement.740 * @private741 */742webdriver.WebDriver.prototype.findDomElement_ = function(element) {743  var doc = element.ownerDocument;744  var store = doc['$webdriver$'] = doc['$webdriver$'] || {};745  var id = Math.floor(Math.random() * goog.now()).toString(36);746  store[id] = element;747  element[id] = id;748749  function cleanUp() {750    delete store[id];751  }752753  function lookupElement(id) {754    var store = document['$webdriver$'];755    if (!store) {756      return null;757    }758759    var element = store[id];760    if (!element || element[id] !== id) {761      return [];762    }763    return [element];764  }765766  return this.executeScript(lookupElement, id).767      then(function(value) {768        cleanUp();769        if (value.length && !(value[0] instanceof webdriver.WebElement)) {770          throw new Error('JS locator script result was not a WebElement');771        }772        return value;773      }, cleanUp);774};775776777/**778 * Schedules a command to test if an element is present on the page.779 *780 * <p>If given a DOM element, this function will check if it belongs to the781 * document the driver is currently focused on. Otherwise, the function will782 * test if at least one element can be found with the given search criteria.783 *784 * @param {!(webdriver.Locator|webdriver.By.Hash|Element|785 *           Function)} locatorOrElement The locator to use, or the actual786 *     DOM element to be located by the server.787 * @return {!webdriver.promise.Promise.<boolean>} A promise that will resolve788 *     with whether the element is present on the page.789 */790webdriver.WebDriver.prototype.isElementPresent = function(locatorOrElement) {791  var findElement =792      ('nodeType' in locatorOrElement && 'ownerDocument' in locatorOrElement) ?793          this.findDomElement_(/** @type {!Element} */ (locatorOrElement)) :794          this.findElements.apply(this, arguments);795  return findElement.then(function(result) {796    return !!result.length;797  });798};799800801/**802 * Schedule a command to search for multiple elements on the page.803 *804 * @param {!(webdriver.Locator|webdriver.By.Hash|Function)} locator The locator805 *     strategy to use when searching for the element.806 * @return {!webdriver.promise.Promise.<!Array.<!webdriver.WebElement>>} A807 *     promise that will resolve to an array of WebElements.808 */809webdriver.WebDriver.prototype.findElements = function(locator) {810  locator = webdriver.Locator.checkLocator(locator);811  if (goog.isFunction(locator)) {812    return this.findElementsInternal_(locator, this);813  } else {814    var command = new webdriver.Command(webdriver.CommandName.FIND_ELEMENTS).815        setParameter('using', locator.using).816        setParameter('value', locator.value);817    return this.schedule(command, 'WebDriver.findElements(' + locator + ')');818  }819};820821822/**823 * @param {!Function} locatorFn The locator function to use.824 * @param {!(webdriver.WebDriver|webdriver.WebElement)} context The search825 *     context.826 * @return {!webdriver.promise.Promise.<!Array.<!webdriver.WebElement>>} A827 *     promise that will resolve to an array of WebElements.828 * @private829 */830webdriver.WebDriver.prototype.findElementsInternal_ = function(831    locatorFn, context) {832  return this.call(goog.partial(locatorFn, context)).then(function(result) {833    if (result instanceof webdriver.WebElement) {834      return [result];835    }836837    if (!goog.isArray(result)) {838      return [];839    }840841    return goog.array.filter(result, function(item) {842      return item instanceof webdriver.WebElement;843    });844  });845};846847848/**849 * Schedule a command to take a screenshot. The driver makes a best effort to850 * return a screenshot of the following, in order of preference:851 * <ol>852 *   <li>Entire page853 *   <li>Current window854 *   <li>Visible portion of the current frame855 *   <li>The screenshot of the entire display containing the browser856 * </ol>857 *858 * @return {!webdriver.promise.Promise} A promise that will be resolved to the859 *     screenshot as a base-64 encoded PNG.860 */861webdriver.WebDriver.prototype.takeScreenshot = function() {862  return this.schedule(new webdriver.Command(webdriver.CommandName.SCREENSHOT),863      'WebDriver.takeScreenshot()');864};865866867/**868 * @return {!webdriver.WebDriver.Options} The options interface for this869 *     instance.870 */871webdriver.WebDriver.prototype.manage = function() {872  return new webdriver.WebDriver.Options(this);873};874875876/**877 * @return {!webdriver.WebDriver.Navigation} The navigation interface for this878 *     instance.879 */880webdriver.WebDriver.prototype.navigate = function() {881  return new webdriver.WebDriver.Navigation(this);882};883884885/**886 * @return {!webdriver.WebDriver.TargetLocator} The target locator interface for887 *     this instance.888 */889webdriver.WebDriver.prototype.switchTo = function() {890  return new webdriver.WebDriver.TargetLocator(this);891};892893894895/**896 * Interface for navigating back and forth in the browser history.897 * @param {!webdriver.WebDriver} driver The parent driver.898 * @constructor899 */900webdriver.WebDriver.Navigation = function(driver) {901902  /** @private {!webdriver.WebDriver} */903  this.driver_ = driver;904};905906907/**908 * Schedules a command to navigate to a new URL.909 * @param {string} url The URL to navigate to.910 * @return {!webdriver.promise.Promise} A promise that will be resolved when the911 *     URL has been loaded.912 */913webdriver.WebDriver.Navigation.prototype.to = function(url) {914  return this.driver_.schedule(915      new webdriver.Command(webdriver.CommandName.GET).916          setParameter('url', url),917      'WebDriver.navigate().to(' + url + ')');918};919920921/**922 * Schedules a command to move backwards in the browser history.923 * @return {!webdriver.promise.Promise} A promise that will be resolved when the924 *     navigation event has completed.925 */926webdriver.WebDriver.Navigation.prototype.back = function() {927  return this.driver_.schedule(928      new webdriver.Command(webdriver.CommandName.GO_BACK),929      'WebDriver.navigate().back()');930};931932933/**934 * Schedules a command to move forwards in the browser history.935 * @return {!webdriver.promise.Promise} A promise that will be resolved when the936 *     navigation event has completed.937 */938webdriver.WebDriver.Navigation.prototype.forward = function() {939  return this.driver_.schedule(940      new webdriver.Command(webdriver.CommandName.GO_FORWARD),941      'WebDriver.navigate().forward()');942};943944945/**946 * Schedules a command to refresh the current page.947 * @return {!webdriver.promise.Promise} A promise that will be resolved when the948 *     navigation event has completed.949 */950webdriver.WebDriver.Navigation.prototype.refresh = function() {951  return this.driver_.schedule(952      new webdriver.Command(webdriver.CommandName.REFRESH),953      'WebDriver.navigate().refresh()');954};955956957958/**959 * Provides methods for managing browser and driver state.960 * @param {!webdriver.WebDriver} driver The parent driver.961 * @constructor962 */963webdriver.WebDriver.Options = function(driver) {964965  /** @private {!webdriver.WebDriver} */966  this.driver_ = driver;967};968969970/**971 * Schedules a command to add a cookie.972 * @param {string} name The cookie name.973 * @param {string} value The cookie value.974 * @param {string=} opt_path The cookie path.975 * @param {string=} opt_domain The cookie domain.976 * @param {boolean=} opt_isSecure Whether the cookie is secure.977 * @param {(number|!Date)=} opt_expiry When the cookie expires. If specified as978 *     a number, should be in milliseconds since midnight, January 1, 1970 UTC.979 * @return {!webdriver.promise.Promise} A promise that will be resolved when the980 *     cookie has been added to the page.981 */982webdriver.WebDriver.Options.prototype.addCookie = function(983    name, value, opt_path, opt_domain, opt_isSecure, opt_expiry) {984  // We do not allow '=' or ';' in the name.985  if (/[;=]/.test(name)) {986    throw Error('Invalid cookie name "' + name + '"');987  }988989  // We do not allow ';' in value.990  if (/;/.test(value)) {991    throw Error('Invalid cookie value "' + value + '"');992  }993994  var cookieString = name + '=' + value +995      (opt_domain ? ';domain=' + opt_domain : '') +996      (opt_path ? ';path=' + opt_path : '') +997      (opt_isSecure ? ';secure' : '');998999  var expiry;1000  if (goog.isDef(opt_expiry)) {1001    var expiryDate;1002    if (goog.isNumber(opt_expiry)) {1003      expiryDate = new Date(opt_expiry);1004    } else {1005      expiryDate = /** @type {!Date} */ (opt_expiry);1006      opt_expiry = expiryDate.getTime();1007    }1008    cookieString += ';expires=' + expiryDate.toUTCString();1009    // Convert from milliseconds to seconds.1010    expiry = Math.floor(/** @type {number} */ (opt_expiry) / 1000);1011  }10121013  return this.driver_.schedule(1014      new webdriver.Command(webdriver.CommandName.ADD_COOKIE).1015          setParameter('cookie', {1016            'name': name,1017            'value': value,1018            'path': opt_path,1019            'domain': opt_domain,1020            'secure': !!opt_isSecure,1021            'expiry': expiry1022          }),1023      'WebDriver.manage().addCookie(' + cookieString + ')');1024};102510261027/**1028 * Schedules a command to delete all cookies visible to the current page.1029 * @return {!webdriver.promise.Promise} A promise that will be resolved when all1030 *     cookies have been deleted.1031 */1032webdriver.WebDriver.Options.prototype.deleteAllCookies = function() {1033  return this.driver_.schedule(1034      new webdriver.Command(webdriver.CommandName.DELETE_ALL_COOKIES),1035      'WebDriver.manage().deleteAllCookies()');1036};103710381039/**1040 * Schedules a command to delete the cookie with the given name. This command is1041 * a no-op if there is no cookie with the given name visible to the current1042 * page.1043 * @param {string} name The name of the cookie to delete.1044 * @return {!webdriver.promise.Promise} A promise that will be resolved when the1045 *     cookie has been deleted.1046 */1047webdriver.WebDriver.Options.prototype.deleteCookie = function(name) {1048  return this.driver_.schedule(1049      new webdriver.Command(webdriver.CommandName.DELETE_COOKIE).1050          setParameter('name', name),1051      'WebDriver.manage().deleteCookie(' + name + ')');1052};105310541055/**1056 * Schedules a command to retrieve all cookies visible to the current page.1057 * Each cookie will be returned as a JSON object as described by the WebDriver1058 * wire protocol.1059 * @return {!webdriver.promise.Promise} A promise that will be resolved with the1060 *     cookies visible to the current page.1061 * @see http://code.google.com/p/selenium/wiki/JsonWireProtocol#Cookie_JSON_Object1062 */1063webdriver.WebDriver.Options.prototype.getCookies = function() {1064  return this.driver_.schedule(1065      new webdriver.Command(webdriver.CommandName.GET_ALL_COOKIES),1066      'WebDriver.manage().getCookies()');1067};106810691070/**1071 * Schedules a command to retrieve the cookie with the given name. Returns null1072 * if there is no such cookie. The cookie will be returned as a JSON object as1073 * described by the WebDriver wire protocol.1074 * @param {string} name The name of the cookie to retrieve.1075 * @return {!webdriver.promise.Promise} A promise that will be resolved with the1076 *     named cookie, or {@code null} if there is no such cookie.1077 * @see http://code.google.com/p/selenium/wiki/JsonWireProtocol#Cookie_JSON_Object1078 */1079webdriver.WebDriver.Options.prototype.getCookie = function(name) {1080  return this.getCookies().then(function(cookies) {1081    return goog.array.find(cookies, function(cookie) {1082      return cookie && cookie['name'] == name;1083    });1084  });1085};108610871088/**1089 * @return {!webdriver.WebDriver.Logs} The interface for managing driver1090 *     logs.1091 */1092webdriver.WebDriver.Options.prototype.logs = function() {1093  return new webdriver.WebDriver.Logs(this.driver_);1094};109510961097/**1098 * @return {!webdriver.WebDriver.Timeouts} The interface for managing driver1099 *     timeouts.1100 */1101webdriver.WebDriver.Options.prototype.timeouts = function() {1102  return new webdriver.WebDriver.Timeouts(this.driver_);1103};110411051106/**1107 * @return {!webdriver.WebDriver.Window} The interface for managing the1108 *     current window.1109 */1110webdriver.WebDriver.Options.prototype.window = function() {1111  return new webdriver.WebDriver.Window(this.driver_);1112};1113111411151116/**1117 * An interface for managing timeout behavior for WebDriver instances.1118 * @param {!webdriver.WebDriver} driver The parent driver.1119 * @constructor1120 */1121webdriver.WebDriver.Timeouts = function(driver) {11221123  /** @private {!webdriver.WebDriver} */1124  this.driver_ = driver;1125};112611271128/**1129 * Specifies the amount of time the driver should wait when searching for an1130 * element if it is not immediately present.1131 * <p/>1132 * When searching for a single element, the driver should poll the page1133 * until the element has been found, or this timeout expires before failing1134 * with a {@code bot.ErrorCode.NO_SUCH_ELEMENT} error. When searching1135 * for multiple elements, the driver should poll the page until at least one1136 * element has been found or this timeout has expired.1137 * <p/>1138 * Setting the wait timeout to 0 (its default value), disables implicit1139 * waiting.1140 * <p/>1141 * Increasing the implicit wait timeout should be used judiciously as it1142 * will have an adverse effect on test run time, especially when used with1143 * slower location strategies like XPath.1144 *1145 * @param {number} ms The amount of time to wait, in milliseconds.1146 * @return {!webdriver.promise.Promise} A promise that will be resolved when the1147 *     implicit wait timeout has been set.1148 */1149webdriver.WebDriver.Timeouts.prototype.implicitlyWait = function(ms) {1150  return this.driver_.schedule(1151      new webdriver.Command(webdriver.CommandName.IMPLICITLY_WAIT).1152          setParameter('ms', ms < 0 ? 0 : ms),1153      'WebDriver.manage().timeouts().implicitlyWait(' + ms + ')');1154};115511561157/**1158 * Sets the amount of time to wait, in milliseconds, for an asynchronous script1159 * to finish execution before returning an error. If the timeout is less than or1160 * equal to 0, the script will be allowed to run indefinitely.1161 *1162 * @param {number} ms The amount of time to wait, in milliseconds.1163 * @return {!webdriver.promise.Promise} A promise that will be resolved when the1164 *     script timeout has been set.1165 */1166webdriver.WebDriver.Timeouts.prototype.setScriptTimeout = function(ms) {1167  return this.driver_.schedule(1168      new webdriver.Command(webdriver.CommandName.SET_SCRIPT_TIMEOUT).1169          setParameter('ms', ms < 0 ? 0 : ms),1170      'WebDriver.manage().timeouts().setScriptTimeout(' + ms + ')');1171};117211731174/**1175 * Sets the amount of time to wait for a page load to complete before returning1176 * an error.  If the timeout is negative, page loads may be indefinite.1177 * @param {number} ms The amount of time to wait, in milliseconds.1178 * @return {!webdriver.promise.Promise} A promise that will be resolved when1179 *     the timeout has been set.1180 */1181webdriver.WebDriver.Timeouts.prototype.pageLoadTimeout = function(ms) {1182  return this.driver_.schedule(1183      new webdriver.Command(webdriver.CommandName.SET_TIMEOUT).1184          setParameter('type', 'page load').1185          setParameter('ms', ms),1186      'WebDriver.manage().timeouts().pageLoadTimeout(' + ms + ')');1187};1188118911901191/**1192 * An interface for managing the current window.1193 * @param {!webdriver.WebDriver} driver The parent driver.1194 * @constructor1195 */1196webdriver.WebDriver.Window = function(driver) {11971198  /** @private {!webdriver.WebDriver} */1199  this.driver_ = driver;1200};120112021203/**1204 * Retrieves the window's current position, relative to the top left corner of1205 * the screen.1206 * @return {!webdriver.promise.Promise} A promise that will be resolved with the1207 *     window's position in the form of a {x:number, y:number} object literal.1208 */1209webdriver.WebDriver.Window.prototype.getPosition = function() {1210  return this.driver_.schedule(1211      new webdriver.Command(webdriver.CommandName.GET_WINDOW_POSITION).1212          setParameter('windowHandle', 'current'),1213      'WebDriver.manage().window().getPosition()');1214};121512161217/**1218 * Repositions the current window.1219 * @param {number} x The desired horizontal position, relative to the left side1220 *     of the screen.1221 * @param {number} y The desired vertical position, relative to the top of the1222 *     of the screen.1223 * @return {!webdriver.promise.Promise} A promise that will be resolved when the1224 *     command has completed.1225 */1226webdriver.WebDriver.Window.prototype.setPosition = function(x, y) {1227  return this.driver_.schedule(1228      new webdriver.Command(webdriver.CommandName.SET_WINDOW_POSITION).1229          setParameter('windowHandle', 'current').1230          setParameter('x', x).1231          setParameter('y', y),1232      'WebDriver.manage().window().setPosition(' + x + ', ' + y + ')');1233};123412351236/**1237 * Retrieves the window's current size.1238 * @return {!webdriver.promise.Promise} A promise that will be resolved with the1239 *     window's size in the form of a {width:number, height:number} object1240 *     literal.1241 */1242webdriver.WebDriver.Window.prototype.getSize = function() {1243  return this.driver_.schedule(1244      new webdriver.Command(webdriver.CommandName.GET_WINDOW_SIZE).1245          setParameter('windowHandle', 'current'),1246      'WebDriver.manage().window().getSize()');1247};124812491250/**1251 * Resizes the current window.1252 * @param {number} width The desired window width.1253 * @param {number} height The desired window height.1254 * @return {!webdriver.promise.Promise} A promise that will be resolved when the1255 *     command has completed.1256 */1257webdriver.WebDriver.Window.prototype.setSize = function(width, height) {1258  return this.driver_.schedule(1259      new webdriver.Command(webdriver.CommandName.SET_WINDOW_SIZE).1260          setParameter('windowHandle', 'current').1261          setParameter('width', width).1262          setParameter('height', height),1263      'WebDriver.manage().window().setSize(' + width + ', ' + height + ')');1264};126512661267/**1268 * Maximizes the current window.1269 * @return {!webdriver.promise.Promise} A promise that will be resolved when the1270 *     command has completed.1271 */1272webdriver.WebDriver.Window.prototype.maximize = function() {1273  return this.driver_.schedule(1274      new webdriver.Command(webdriver.CommandName.MAXIMIZE_WINDOW).1275          setParameter('windowHandle', 'current'),1276      'WebDriver.manage().window().maximize()');1277};127812791280/**1281 * Interface for managing WebDriver log records.1282 * @param {!webdriver.WebDriver} driver The parent driver.1283 * @constructor1284 */1285webdriver.WebDriver.Logs = function(driver) {12861287  /** @private {!webdriver.WebDriver} */1288  this.driver_ = driver;1289};129012911292/**1293 * Fetches available log entries for the given type.1294 *1295 * <p/>Note that log buffers are reset after each call, meaning that1296 * available log entries correspond to those entries not yet returned for a1297 * given log type. In practice, this means that this call will return the1298 * available log entries since the last call, or from the start of the1299 * session.1300 *1301 * @param {!webdriver.logging.Type} type The desired log type.1302 * @return {!webdriver.promise.Promise.<!Array.<!webdriver.logging.Entry>>} A1303 *   promise that will resolve to a list of log entries for the specified1304 *   type.1305 */1306webdriver.WebDriver.Logs.prototype.get = function(type) {1307  return this.driver_.schedule(1308      new webdriver.Command(webdriver.CommandName.GET_LOG).1309          setParameter('type', type),1310      'WebDriver.manage().logs().get(' + type + ')').1311      then(function(entries) {1312        return goog.array.map(entries, function(entry) {1313          if (!(entry instanceof webdriver.logging.Entry)) {1314            return new webdriver.logging.Entry(1315                entry['level'], entry['message'], entry['timestamp']);1316          }1317          return entry;1318        });1319      });1320};132113221323/**1324 * Retrieves the log types available to this driver.1325 * @return {!webdriver.promise.Promise.<!Array.<!webdriver.logging.Type>>} A1326 *     promise that will resolve to a list of available log types.1327 */1328webdriver.WebDriver.Logs.prototype.getAvailableLogTypes = function() {1329  return this.driver_.schedule(1330      new webdriver.Command(webdriver.CommandName.GET_AVAILABLE_LOG_TYPES),1331      'WebDriver.manage().logs().getAvailableLogTypes()');1332};1333133413351336/**1337 * An interface for changing the focus of the driver to another frame or window.1338 * @param {!webdriver.WebDriver} driver The parent driver.1339 * @constructor1340 */1341webdriver.WebDriver.TargetLocator = function(driver) {13421343  /** @private {!webdriver.WebDriver} */1344  this.driver_ = driver;1345};134613471348/**1349 * Schedules a command retrieve the {@code document.activeElement} element on1350 * the current document, or {@code document.body} if activeElement is not1351 * available.1352 * @return {!webdriver.WebElement} The active element.1353 */1354webdriver.WebDriver.TargetLocator.prototype.activeElement = function() {1355  var id = this.driver_.schedule(1356      new webdriver.Command(webdriver.CommandName.GET_ACTIVE_ELEMENT),1357      'WebDriver.switchTo().activeElement()');1358  return new webdriver.WebElement(this.driver_, id);1359};136013611362/**1363 * Schedules a command to switch focus of all future commands to the first frame1364 * on the page.1365 * @return {!webdriver.promise.Promise} A promise that will be resolved when the1366 *     driver has changed focus to the default content.1367 */1368webdriver.WebDriver.TargetLocator.prototype.defaultContent = function() {1369  return this.driver_.schedule(1370      new webdriver.Command(webdriver.CommandName.SWITCH_TO_FRAME).1371          setParameter('id', null),1372      'WebDriver.switchTo().defaultContent()');1373};137413751376/**1377 * Schedules a command to switch the focus of all future commands to another1378 * frame on the page.1379 * <p/>1380 * If the frame is specified by a number, the command will switch to the frame1381 * by its (zero-based) index into the {@code window.frames} collection.1382 * <p/>1383 * If the frame is specified by a string, the command will select the frame by1384 * its name or ID. To select sub-frames, simply separate the frame names/IDs by1385 * dots. As an example, "main.child" will select the frame with the name "main"1386 * and then its child "child".1387 * <p/>1388 * If the specified frame can not be found, the deferred result will errback1389 * with a {@code bot.ErrorCode.NO_SUCH_FRAME} error.1390 * @param {string|number} nameOrIndex The frame locator.1391 * @return {!webdriver.promise.Promise} A promise that will be resolved when the1392 *     driver has changed focus to the specified frame.1393 */1394webdriver.WebDriver.TargetLocator.prototype.frame = function(nameOrIndex) {1395  return this.driver_.schedule(1396      new webdriver.Command(webdriver.CommandName.SWITCH_TO_FRAME).1397          setParameter('id', nameOrIndex),1398      'WebDriver.switchTo().frame(' + nameOrIndex + ')');1399};140014011402/**1403 * Schedules a command to switch the focus of all future commands to another1404 * window. Windows may be specified by their {@code window.name} attribute or1405 * by its handle (as returned by {@code webdriver.WebDriver#getWindowHandles}).1406 * <p/>1407 * If the specificed window can not be found, the deferred result will errback1408 * with a {@code bot.ErrorCode.NO_SUCH_WINDOW} error.1409 * @param {string} nameOrHandle The name or window handle of the window to1410 *     switch focus to.1411 * @return {!webdriver.promise.Promise} A promise that will be resolved when the1412 *     driver has changed focus to the specified window.1413 */1414webdriver.WebDriver.TargetLocator.prototype.window = function(nameOrHandle) {1415  return this.driver_.schedule(1416      new webdriver.Command(webdriver.CommandName.SWITCH_TO_WINDOW).1417          setParameter('name', nameOrHandle),1418      'WebDriver.switchTo().window(' + nameOrHandle + ')');1419};142014211422/**1423 * Schedules a command to change focus to the active alert dialog. This command1424 * will return a {@link bot.ErrorCode.NO_MODAL_DIALOG_OPEN} error if a modal1425 * dialog is not currently open.1426 * @return {!webdriver.Alert} The open alert.1427 */1428webdriver.WebDriver.TargetLocator.prototype.alert = function() {1429  var text = this.driver_.schedule(1430      new webdriver.Command(webdriver.CommandName.GET_ALERT_TEXT),1431      'WebDriver.switchTo().alert()');1432  return new webdriver.Alert(this.driver_, text);1433};143414351436/**1437 * Simulate pressing many keys at once in a "chord". Takes a sequence of1438 * {@link webdriver.Key}s or strings, appends each of the values to a string,1439 * and adds the chord termination key ({@link webdriver.Key.NULL}) and returns1440 * the resultant string.1441 *1442 * Note: when the low-level webdriver key handlers see Keys.NULL, active1443 * modifier keys (CTRL/ALT/SHIFT/etc) release via a keyup event.1444 *1445 * @param {...string} var_args The key sequence to concatenate.1446 * @return {string} The null-terminated key sequence.1447 * @see http://code.google.com/p/webdriver/issues/detail?id=791448 */1449webdriver.Key.chord = function(var_args) {1450  var sequence = goog.array.reduce(1451      goog.array.slice(arguments, 0),1452      function(str, key) {1453        return str + key;1454      }, '');1455  sequence += webdriver.Key.NULL;1456  return sequence;1457};145814591460//////////////////////////////////////////////////////////////////////////////1461//1462//  webdriver.WebElement1463//1464//////////////////////////////////////////////////////////////////////////////1465146614671468/**1469 * Represents a DOM element. WebElements can be found by searching from the1470 * document root using a {@code webdriver.WebDriver} instance, or by searching1471 * under another {@code webdriver.WebElement}:1472 * <pre><code>1473 *   driver.get('http://www.google.com');1474 *   var searchForm = driver.findElement(By.tagName('form'));1475 *   var searchBox = searchForm.findElement(By.name('q'));1476 *   searchBox.sendKeys('webdriver');1477 * </code></pre>1478 *1479 * The WebElement is implemented as a promise for compatibility with the promise1480 * API. It will always resolve itself when its internal state has been fully1481 * resolved and commands may be issued against the element. This can be used to1482 * catch errors when an element cannot be located on the page:1483 * <pre><code>1484 *   driver.findElement(By.id('not-there')).then(function(element) {1485 *     alert('Found an element that was not expected to be there!');1486 *   }, function(error) {1487 *     alert('The element was not found, as expected');1488 *   });1489 * </code></pre>1490 *1491 * @param {!webdriver.WebDriver} driver The parent WebDriver instance for this1492 *     element.1493 * @param {!(string|webdriver.promise.Promise)} id Either the opaque ID for the1494 *     underlying DOM element assigned by the server, or a promise that will1495 *     resolve to that ID or another WebElement.1496 * @constructor1497 * @extends {webdriver.promise.Deferred}1498 */1499webdriver.WebElement = function(driver, id) {1500  webdriver.promise.Deferred.call(this, null, driver.controlFlow());15011502  /**1503   * The parent WebDriver instance for this element.1504   * @private {!webdriver.WebDriver}1505   */1506  this.driver_ = driver;15071508  // This class is responsible for resolving itself; delete the resolve and1509  // reject methods so they may not be accessed by consumers of this class.1510  var fulfill = goog.partial(this.fulfill, this);1511  var reject = this.reject;1512  delete this.promise;1513  delete this.fulfill;1514  delete this.reject;15151516  /**1517   * A promise that resolves to the JSON representation of this WebElement's1518   * ID, as defined by the WebDriver wire protocol.1519   * @private {!webdriver.promise.Promise}1520   * @see http://code.google.com/p/selenium/wiki/JsonWireProtocol1521   */1522  this.id_ = webdriver.promise.when(id, function(id) {1523    if (id instanceof webdriver.WebElement) {1524      return id.id_;1525    } else if (goog.isDef(id[webdriver.WebElement.ELEMENT_KEY])) {1526      return id;1527    }15281529    var json = {};1530    json[webdriver.WebElement.ELEMENT_KEY] = id;1531    return json;1532  });15331534  // This WebElement should not be resolved until its ID has been1535  // fully resolved.1536  this.id_.then(fulfill, reject);1537};1538goog.inherits(webdriver.WebElement, webdriver.promise.Deferred);153915401541/**1542 * The property key used in the wire protocol to indicate that a JSON object1543 * contains the ID of a WebElement.1544 * @type {string}1545 * @const1546 */1547webdriver.WebElement.ELEMENT_KEY = 'ELEMENT';154815491550/**1551 * Compares to WebElements for equality.1552 * @param {!webdriver.WebElement} a A WebElement.1553 * @param {!webdriver.WebElement} b A WebElement.1554 * @return {!webdriver.promise.Promise} A promise that will be resolved to1555 *     whether the two WebElements are equal.1556 */1557webdriver.WebElement.equals = function(a, b) {1558  if (a == b) {1559    return webdriver.promise.fulfilled(true);1560  }1561  return webdriver.promise.fullyResolved([a.id_, b.id_]).then(function(ids) {1562    // If the two element's have the same ID, they should be considered1563    // equal. Otherwise, they may still be equivalent, but we'll need to1564    // ask the server to check for us.1565    if (ids[0][webdriver.WebElement.ELEMENT_KEY] ==1566        ids[1][webdriver.WebElement.ELEMENT_KEY]) {1567      return true;1568    }15691570    var command = new webdriver.Command(1571        webdriver.CommandName.ELEMENT_EQUALS);1572    command.setParameter('other', b);1573    return a.schedule_(command, 'webdriver.WebElement.equals()');1574  });1575};157615771578/**1579 * @return {!webdriver.WebDriver} The parent driver for this instance.1580 */1581webdriver.WebElement.prototype.getDriver = function() {1582  return this.driver_;1583};158415851586/**1587 * @return {!webdriver.promise.Promise} A promise that resolves to this1588 *     element's JSON representation as defined by the WebDriver wire protocol.1589 * @see http://code.google.com/p/selenium/wiki/JsonWireProtocol1590 */1591webdriver.WebElement.prototype.toWireValue = function() {1592  return this.id_;1593};159415951596/**1597 * Schedules a command that targets this element with the parent WebDriver1598 * instance. Will ensure this element's ID is included in the command parameters1599 * under the "id" key.1600 * @param {!webdriver.Command} command The command to schedule.1601 * @param {string} description A description of the command for debugging.1602 * @return {!webdriver.promise.Promise} A promise that will be resolved with1603 *     the command result.1604 * @see webdriver.WebDriver.prototype.schedule1605 * @private1606 */1607webdriver.WebElement.prototype.schedule_ = function(command, description) {1608  command.setParameter('id', this.id_);1609  return this.driver_.schedule(command, description);1610};161116121613/**1614 * Schedule a command to find a descendant of this element. If the element1615 * cannot be found, a {@code bot.ErrorCode.NO_SUCH_ELEMENT} result will1616 * be returned by the driver. Unlike other commands, this error cannot be1617 * suppressed. In other words, scheduling a command to find an element doubles1618 * as an assert that the element is present on the page. To test whether an1619 * element is present on the page, use {@code #isElementPresent} instead.1620 *1621 * <p>The search criteria for an element may be defined using one of the1622 * factories in the {@link webdriver.By} namespace, or as a short-hand1623 * {@link webdriver.By.Hash} object. For example, the following two statements1624 * are equivalent:1625 * <code><pre>1626 * var e1 = element.findElement(By.id('foo'));1627 * var e2 = element.findElement({id:'foo'});1628 * </pre></code>1629 *1630 * <p>You may also provide a custom locator function, which takes as input1631 * this WebDriver instance and returns a {@link webdriver.WebElement}, or a1632 * promise that will resolve to a WebElement. For example, to find the first1633 * visible link on a page, you could write:1634 * <code><pre>1635 * var link = element.findElement(firstVisibleLink);1636 *1637 * function firstVisibleLink(element) {1638 *   var links = element.findElements(By.tagName('a'));1639 *   return webdriver.promise.filter(links, function(link) {1640 *     return links.isDisplayed();1641 *   }).then(function(visibleLinks) {1642 *     return visibleLinks[0];1643 *   });1644 * }1645 * </pre></code>1646 *1647 * @param {!(webdriver.Locator|webdriver.By.Hash|Function)} locator The1648 *     locator strategy to use when searching for the element.1649 * @return {!webdriver.WebElement} A WebElement that can be used to issue1650 *     commands against the located element. If the element is not found, the1651 *     element will be invalidated and all scheduled commands aborted.1652 */1653webdriver.WebElement.prototype.findElement = function(locator) {1654  locator = webdriver.Locator.checkLocator(locator);1655  var id;1656  if (goog.isFunction(locator)) {1657    id = this.driver_.findElementInternal_(locator, this);1658  } else {1659    var command = new webdriver.Command(1660        webdriver.CommandName.FIND_CHILD_ELEMENT).1661        setParameter('using', locator.using).1662        setParameter('value', locator.value);1663    id = this.schedule_(command, 'WebElement.findElement(' + locator + ')');1664  }1665  return new webdriver.WebElement(this.driver_, id);1666};166716681669/**1670 * Schedules a command to test if there is at least one descendant of this1671 * element that matches the given search criteria.1672 *1673 * @param {!(webdriver.Locator|webdriver.By.Hash|Function)} locator The1674 *     locator strategy to use when searching for the element.1675 * @return {!webdriver.promise.Promise.<boolean>} A promise that will be1676 *     resolved with whether an element could be located on the page.1677 */1678webdriver.WebElement.prototype.isElementPresent = function(locator) {1679  return this.findElements(locator).then(function(result) {1680    return !!result.length;1681  });1682};168316841685/**1686 * Schedules a command to find all of the descendants of this element that1687 * match the given search criteria.1688 *1689 * @param {!(webdriver.Locator|webdriver.By.Hash|Function)} locator The1690 *     locator strategy to use when searching for the elements.1691 * @return {!webdriver.promise.Promise.<!Array.<!webdriver.WebElement>>} A1692 *     promise that will resolve to an array of WebElements.1693 */1694webdriver.WebElement.prototype.findElements = function(locator) {1695  locator = webdriver.Locator.checkLocator(locator);1696  if (goog.isFunction(locator)) {1697    return this.driver_.findElementsInternal_(locator, this);1698  } else {1699    var command = new webdriver.Command(1700        webdriver.CommandName.FIND_CHILD_ELEMENTS).1701        setParameter('using', locator.using).1702        setParameter('value', locator.value);1703    return this.schedule_(command, 'WebElement.findElements(' + locator + ')');1704  }1705};170617071708/**1709 * Schedules a command to click on this element.1710 * @return {!webdriver.promise.Promise} A promise that will be resolved when1711 *     the click command has completed.1712 */1713webdriver.WebElement.prototype.click = function() {1714  return this.schedule_(1715      new webdriver.Command(webdriver.CommandName.CLICK_ELEMENT),1716      'WebElement.click()');1717};171817191720/**1721 * Schedules a command to type a sequence on the DOM element represented by this1722 * instance.1723 * <p/>1724 * Modifier keys (SHIFT, CONTROL, ALT, META) are stateful; once a modifier is1725 * processed in the keysequence, that key state is toggled until one of the1726 * following occurs:1727 * <ul>1728 * <li>The modifier key is encountered again in the sequence. At this point the1729 * state of the key is toggled (along with the appropriate keyup/down events).1730 * </li>1731 * <li>The {@code webdriver.Key.NULL} key is encountered in the sequence. When1732 * this key is encountered, all modifier keys current in the down state are1733 * released (with accompanying keyup events). The NULL key can be used to1734 * simulate common keyboard shortcuts:1735 * <code><pre>1736 *     element.sendKeys("text was",1737 *                      webdriver.Key.CONTROL, "a", webdriver.Key.NULL,1738 *                      "now text is");1739 *     // Alternatively:1740 *     element.sendKeys("text was",1741 *                      webdriver.Key.chord(webdriver.Key.CONTROL, "a"),1742 *                      "now text is");1743 * </pre></code></li>1744 * <li>The end of the keysequence is encountered. When there are no more keys1745 * to type, all depressed modifier keys are released (with accompanying keyup1746 * events).1747 * </li>1748 * </ul>1749 * <strong>Note:</strong> On browsers where native keyboard events are not yet1750 * supported (e.g. Firefox on OS X), key events will be synthesized. Special1751 * punctionation keys will be synthesized according to a standard QWERTY en-us1752 * keyboard layout.1753 *1754 * @param {...string} var_args The sequence of keys to1755 *     type. All arguments will be joined into a single sequence (var_args is1756 *     permitted for convenience).1757 * @return {!webdriver.promise.Promise} A promise that will be resolved when all1758 *     keys have been typed.1759 */1760webdriver.WebElement.prototype.sendKeys = function(var_args) {1761  // Coerce every argument to a string. This protects us from users that1762  // ignore the jsdoc and give us a number (which ends up causing problems on1763  // the server, which requires strings).1764  var keys = webdriver.promise.fullyResolved(goog.array.slice(arguments, 0)).1765      then(function(args) {1766        return goog.array.map(goog.array.slice(args, 0), function(key) {1767          return key + '';1768        });1769      });1770  return this.schedule_(1771      new webdriver.Command(webdriver.CommandName.SEND_KEYS_TO_ELEMENT).1772          setParameter('value', keys),1773      'WebElement.sendKeys(' + keys + ')');1774};177517761777/**1778 * Schedules a command to query for the tag/node name of this element.1779 * @return {!webdriver.promise.Promise} A promise that will be resolved with the1780 *     element's tag name.1781 */1782webdriver.WebElement.prototype.getTagName = function() {1783  return this.schedule_(1784      new webdriver.Command(webdriver.CommandName.GET_ELEMENT_TAG_NAME),1785      'WebElement.getTagName()');1786};178717881789/**1790 * Schedules a command to query for the computed style of the element1791 * represented by this instance. If the element inherits the named style from1792 * its parent, the parent will be queried for its value.  Where possible, color1793 * values will be converted to their hex representation (e.g. #00ff00 instead of1794 * rgb(0, 255, 0)).1795 * <p/>1796 * <em>Warning:</em> the value returned will be as the browser interprets it, so1797 * it may be tricky to form a proper assertion.1798 *1799 * @param {string} cssStyleProperty The name of the CSS style property to look1800 *     up.1801 * @return {!webdriver.promise.Promise} A promise that will be resolved with the1802 *     requested CSS value.1803 */1804webdriver.WebElement.prototype.getCssValue = function(cssStyleProperty) {1805  var name = webdriver.CommandName.GET_ELEMENT_VALUE_OF_CSS_PROPERTY;1806  return this.schedule_(1807      new webdriver.Command(name).1808          setParameter('propertyName', cssStyleProperty),1809      'WebElement.getCssValue(' + cssStyleProperty + ')');1810};181118121813/**1814 * Schedules a command to query for the value of the given attribute of the1815 * element. Will return the current value, even if it has been modified after1816 * the page has been loaded. More exactly, this method will return the value of1817 * the given attribute, unless that attribute is not present, in which case the1818 * value of the property with the same name is returned. If neither value is1819 * set, null is returned (for example, the "value" property of a textarea1820 * element). The "style" attribute is converted as best can be to a1821 * text representation with a trailing semi-colon. The following are deemed to1822 * be "boolean" attributes and will return either "true" or null:1823 *1824 * <p>async, autofocus, autoplay, checked, compact, complete, controls, declare,1825 * defaultchecked, defaultselected, defer, disabled, draggable, ended,1826 * formnovalidate, hidden, indeterminate, iscontenteditable, ismap, itemscope,1827 * loop, multiple, muted, nohref, noresize, noshade, novalidate, nowrap, open,1828 * paused, pubdate, readonly, required, reversed, scoped, seamless, seeking,1829 * selected, spellcheck, truespeed, willvalidate1830 *1831 * <p>Finally, the following commonly mis-capitalized attribute/property names1832 * are evaluated as expected:1833 * <ul>1834 *   <li>"class"1835 *   <li>"readonly"1836 * </ul>1837 * @param {string} attributeName The name of the attribute to query.1838 * @return {!webdriver.promise.Promise} A promise that will be resolved with the1839 *     attribute's value. The returned value will always be either a string or1840 *     null.1841 */1842webdriver.WebElement.prototype.getAttribute = function(attributeName) {1843  return this.schedule_(1844      new webdriver.Command(webdriver.CommandName.GET_ELEMENT_ATTRIBUTE).1845          setParameter('name', attributeName),1846      'WebElement.getAttribute(' + attributeName + ')');1847};184818491850/**1851 * Get the visible (i.e. not hidden by CSS) innerText of this element, including1852 * sub-elements, without any leading or trailing whitespace.1853 * @return {!webdriver.promise.Promise} A promise that will be resolved with the1854 *     element's visible text.1855 */1856webdriver.WebElement.prototype.getText = function() {1857  return this.schedule_(1858      new webdriver.Command(webdriver.CommandName.GET_ELEMENT_TEXT),1859      'WebElement.getText()');1860};186118621863/**1864 * Schedules a command to compute the size of this element's bounding box, in1865 * pixels.1866 * @return {!webdriver.promise.Promise} A promise that will be resolved with the1867 *     element's size as a {@code {width:number, height:number}} object.1868 */1869webdriver.WebElement.prototype.getSize = function() {1870  return this.schedule_(1871      new webdriver.Command(webdriver.CommandName.GET_ELEMENT_SIZE),1872      'WebElement.getSize()');1873};187418751876/**1877 * Schedules a command to compute the location of this element in page space.1878 * @return {!webdriver.promise.Promise} A promise that will be resolved to the1879 *     element's location as a {@code {x:number, y:number}} object.1880 */1881webdriver.WebElement.prototype.getLocation = function() {1882  return this.schedule_(1883      new webdriver.Command(webdriver.CommandName.GET_ELEMENT_LOCATION),1884      'WebElement.getLocation()');1885};188618871888/**1889 * Schedules a command to query whether the DOM element represented by this1890 * instance is enabled, as dicted by the {@code disabled} attribute.1891 * @return {!webdriver.promise.Promise} A promise that will be resolved with1892 *     whether this element is currently enabled.1893 */1894webdriver.WebElement.prototype.isEnabled = function() {1895  return this.schedule_(1896      new webdriver.Command(webdriver.CommandName.IS_ELEMENT_ENABLED),1897      'WebElement.isEnabled()');1898};189919001901/**1902 * Schedules a command to query whether this element is selected.1903 * @return {!webdriver.promise.Promise} A promise that will be resolved with1904 *     whether this element is currently selected.1905 */1906webdriver.WebElement.prototype.isSelected = function() {1907  return this.schedule_(1908      new webdriver.Command(webdriver.CommandName.IS_ELEMENT_SELECTED),1909      'WebElement.isSelected()');1910};191119121913/**1914 * Schedules a command to submit the form containing this element (or this1915 * element if it is a FORM element). This command is a no-op if the element is1916 * not contained in a form.1917 * @return {!webdriver.promise.Promise} A promise that will be resolved when1918 *     the form has been submitted.1919 */1920webdriver.WebElement.prototype.submit = function() {1921  return this.schedule_(1922      new webdriver.Command(webdriver.CommandName.SUBMIT_ELEMENT),1923      'WebElement.submit()');1924};192519261927/**1928 * Schedules a command to clear the {@code value} of this element. This command1929 * has no effect if the underlying DOM element is neither a text INPUT element1930 * nor a TEXTAREA element.1931 * @return {!webdriver.promise.Promise} A promise that will be resolved when1932 *     the element has been cleared.1933 */1934webdriver.WebElement.prototype.clear = function() {1935  return this.schedule_(1936      new webdriver.Command(webdriver.CommandName.CLEAR_ELEMENT),1937      'WebElement.clear()');1938};193919401941/**1942 * Schedules a command to test whether this element is currently displayed.1943 * @return {!webdriver.promise.Promise} A promise that will be resolved with1944 *     whether this element is currently visible on the page.1945 */1946webdriver.WebElement.prototype.isDisplayed = function() {1947  return this.schedule_(1948      new webdriver.Command(webdriver.CommandName.IS_ELEMENT_DISPLAYED),1949      'WebElement.isDisplayed()');1950};195119521953/**1954 * Schedules a command to retrieve the outer HTML of this element.1955 * @return {!webdriver.promise.Promise} A promise that will be resolved with1956 *     the element's outer HTML.1957 */1958webdriver.WebElement.prototype.getOuterHtml = function() {1959  return this.driver_.executeScript(function() {1960    var element = arguments[0];1961    if ('outerHTML' in element) {1962      return element.outerHTML;1963    } else {1964      var div = element.ownerDocument.createElement('div');1965      div.appendChild(element.cloneNode(true));1966      return div.innerHTML;1967    }1968  }, this);1969};197019711972/**1973 * Schedules a command to retrieve the inner HTML of this element.1974 * @return {!webdriver.promise.Promise} A promise that will be resolved with the1975 *     element's inner HTML.1976 */1977webdriver.WebElement.prototype.getInnerHtml = function() {1978  return this.driver_.executeScript('return arguments[0].innerHTML', this);1979};1980198119821983/**1984 * Represents a modal dialog such as {@code alert}, {@code confirm}, or1985 * {@code prompt}. Provides functions to retrieve the message displayed with1986 * the alert, accept or dismiss the alert, and set the response text (in the1987 * case of {@code prompt}).1988 * @param {!webdriver.WebDriver} driver The driver controlling the browser this1989 *     alert is attached to.1990 * @param {!(string|webdriver.promise.Promise)} text Either the message text1991 *     displayed with this alert, or a promise that will be resolved to said1992 *     text.1993 * @constructor1994 * @extends {webdriver.promise.Deferred}1995 */1996webdriver.Alert = function(driver, text) {1997  goog.base(this, null, driver.controlFlow());19981999  /** @private {!webdriver.WebDriver} */2000  this.driver_ = driver;20012002  // This class is responsible for resolving itself; delete the resolve and2003  // reject methods so they may not be accessed by consumers of this class.2004  var fulfill = goog.partial(this.fulfill, this);2005  var reject = this.reject;2006  delete this.promise;2007  delete this.fulfill;2008  delete this.reject;20092010  /** @private {!webdriver.promise.Promise} */2011  this.text_ = webdriver.promise.when(text);20122013  // Make sure this instance is resolved when its displayed text is.2014  this.text_.then(fulfill, reject);2015};2016goog.inherits(webdriver.Alert, webdriver.promise.Deferred);201720182019/**2020 * Retrieves the message text displayed with this alert. For instance, if the2021 * alert were opened with alert("hello"), then this would return "hello".2022 * @return {!webdriver.promise.Promise} A promise that will be resolved to the2023 *     text displayed with this alert.2024 */2025webdriver.Alert.prototype.getText = function() {2026  return this.text_;2027};202820292030/**2031 * Accepts this alert.2032 * @return {!webdriver.promise.Promise} A promise that will be resolved when2033 *     this command has completed.2034 */2035webdriver.Alert.prototype.accept = function() {2036  return this.driver_.schedule(2037      new webdriver.Command(webdriver.CommandName.ACCEPT_ALERT),2038      'WebDriver.switchTo().alert().accept()');2039};204020412042/**2043 * Dismisses this alert.2044 * @return {!webdriver.promise.Promise} A promise that will be resolved when2045 *     this command has completed.2046 */2047webdriver.Alert.prototype.dismiss = function() {2048  return this.driver_.schedule(2049      new webdriver.Command(webdriver.CommandName.DISMISS_ALERT),2050      'WebDriver.switchTo().alert().dismiss()');2051};205220532054/**2055 * Sets the response text on this alert. This command will return an error if2056 * the underlying alert does not support response text (e.g. window.alert and2057 * window.confirm).2058 * @param {string} text The text to set.2059 * @return {!webdriver.promise.Promise} A promise that will be resolved when2060 *     this command has completed.2061 */2062webdriver.Alert.prototype.sendKeys = function(text) {2063  return this.driver_.schedule(2064      new webdriver.Command(webdriver.CommandName.SET_ALERT_TEXT).2065          setParameter('text', text),2066      'WebDriver.switchTo().alert().sendKeys(' + text + ')');2067};2068206920702071/**2072 * An error returned to indicate that there is an unhandled modal dialog on the2073 * current page.2074 * @param {string} message The error message.2075 * @param {!webdriver.Alert} alert The alert handle.2076 * @constructor2077 * @extends {bot.Error}2078 */2079webdriver.UnhandledAlertError = function(message, alert) {2080  goog.base(this, bot.ErrorCode.MODAL_DIALOG_OPENED, message);20812082  /** @private {!webdriver.Alert} */2083  this.alert_ = alert;2084};2085goog.inherits(webdriver.UnhandledAlertError, bot.Error);208620872088/**2089 * @return {!webdriver.Alert} The open alert.2090 */2091webdriver.UnhandledAlertError.prototype.getAlert = function() {2092  return this.alert_;2093};