# lib/webdriver/until.js

    1// Copyright 2014 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 Defines common conditions for use with17 * {@link webdriver.WebDriver#wait WebDriver wait}.18 *19 * <p>Sample usage:20 * <code><pre>21 *   driver.get('http://www.google.com/ncr');22 *23 *   var query = driver.wait(until.elementLocated(By.name('q')));24 *   query.sendKeys('webdriver\n');25 *26 *   driver.wait(until.titleIs('webdriver - Google Search'));27 * </pre></code>28 *29 * <p>To define a custom condition, simply call WebDriver.wait with a function30 * that will eventually return a truthy-value (neither null, undefined, false,31 * 0, or the empty string):32 * <code><pre>33 *   driver.wait(function() {34 *     return driver.getTitle().then(function(title) {35 *       return title === 'webdriver - Google Search';36 *     });37 *   }, 1000);38 * </pre></code>39 */4041goog.provide('webdriver.until');4243goog.require('bot.ErrorCode');44goog.require('goog.array');45goog.require('goog.string');46474849goog.scope(function() {5051var until = webdriver.until;525354/**55 * Defines a condition to 56 * @param {string} message A descriptive error message. Should complete the57 *     sentence "Waiting [...]"58 * @param {function(!webdriver.WebDriver): OUT} fn The condition function to59 *     evaluate on each iteration of the wait loop.60 * @constructor61 * @struct62 * @final63 * @template OUT64 */65until.Condition = function(message, fn) {66  /** @private {string} */67  this.description_ = 'Waiting ' + message;6869  /** @type {function(!webdriver.WebDriver): OUT} */70  this.fn = fn;71};727374/** @return {string} A description of this condition. */75until.Condition.prototype.description = function() {76  return this.description_;77};787980/**81 * Creates a condition that will wait until the input driver is able to switch82 * to the designated frame. The target frame may be specified as:83 * <ol>84 *   <li>A numeric index into {@code window.frames} for the currently selected85 *       frame.86 *   <li>A {@link webdriver.WebElement}, which must reference a FRAME or IFRAME87 *       element on the current page.88 *   <li>A locator which may be used to first locate a FRAME or IFRAME on the89 *       current page before attempting to switch to it.90 * </ol>91 *92 * <p>Upon successful resolution of this condition, the driver will be left93 * focused on the new frame.94 *95 * @param {!(number|webdriver.WebElement|96 *           webdriver.Locator|webdriver.By.Hash|97 *           function(!webdriver.WebDriver): !webdriver.WebElement)} frame98 *     The frame identifier.99 * @return {!until.Condition.<boolean>} A new condition.100 */101until.ableToSwitchToFrame = function(frame) {102  var condition;103  if (goog.isNumber(frame) || frame instanceof webdriver.WebElement) {104    condition = attemptToSwitchFrames;105  } else {106    condition = function(driver) {107      var locator =108          /** @type {!(webdriver.Locator|webdriver.By.Hash|Function)} */(frame);109      return driver.findElements(locator).then(function(els) {110        if (els.length) {111          return attemptToSwitchFrames(driver, els[0]);112        }113      });114    };115  }116117  return new until.Condition('to be able to switch to frame', condition);118119  function attemptToSwitchFrames(driver, frame) {120    return driver.switchTo().frame(frame).then(121        function() { return true; },122        function(e) {123          if (e && e.code !== bot.ErrorCode.NO_SUCH_FRAME) {124            throw e;125          }126        });127  }128};129130131/**132 * Creates a condition that waits for an alert to be opened. Upon success, the133 * returned promise will be fulfilled with the handle for the opened alert.134 *135 * @return {!until.Condition.<!webdriver.Alert>} The new condition.136 */137until.alertIsPresent = function() {138  return new until.Condition('for alert to be present', function(driver) {139    return driver.switchTo().alert().thenCatch(function(e) {140      if (e && e.code !== bot.ErrorCode.NO_SUCH_ALERT) {141        throw e;142      }143    });144  });145};146147148/**149 * Creates a condition that will wait for the current page's title to match the150 * given value.151 *152 * @param {string} title The expected page title.153 * @return {!until.Condition.<boolean>} The new condition.154 */155until.titleIs = function(title) {156  return new until.Condition(157      'for title to be ' + goog.string.quote(title),158      function(driver) {159        return driver.getTitle().then(function(t) {160          return t === title;161        });162      });163};164165166/**167 * Creates a condition that will wait for the current page's title to contain168 * the given substring.169 *170 * @param {string} substr The substring that should be present in the page171 *     title.172 * @return {!until.Condition.<boolean>} The new condition.173 */174until.titleContains = function(substr) {175  return new until.Condition(176      'for title to contain ' + goog.string.quote(substr),177      function(driver) {178        return driver.getTitle().then(function(title) {179          return title.indexOf(substr) !== -1;180        });181      });182};183184185/**186 * Creates a condition that will wait for the current page's title to match the187 * given regular expression.188 *189 * @param {!RegExp} regex The regular expression to test against.190 * @return {!until.Condition.<boolean>} The new condition.191 */192until.titleMatches = function(regex) {193  return new until.Condition('for title to match ' + regex, function(driver) {194    return driver.getTitle().then(function(title) {195      return regex.test(title);196    });197  });198};199200201/**202 * Creates a condition that will loop until an element is203 * {@link webdriver.WebDriver#findElement found} with the given locator.204 *205 * @param {!(webdriver.Locator|webdriver.By.Hash|Function)} locator The locator206 *     to use.207 * @return {!until.Condition.<!webdriver.WebElement>} The new condition.208 */209until.elementLocated = function(locator) {210  var locatorStr = goog.isFunction(locator) ? 'function()' : locator + '';211  return new until.Condition('element to be located by ' + locatorStr,212      function(driver) {213        return driver.findElements(locator).then(function(elements) {214          return elements[0];215        });216      });217};218219220/**221 * Creates a condition that will loop until at least one element is222 * {@link webdriver.WebDriver#findElement found} with the given locator.223 *224 * @param {!(webdriver.Locator|webdriver.By.Hash|Function)} locator The locator225 *     to use.226 * @return {!until.Condition.<!Array.<!webdriver.WebElement>>} The new227 *     condition.228 */229until.elementsLocated = function(locator) {230  var locatorStr = goog.isFunction(locator) ? 'function()' : locator + '';231  return new until.Condition(232      'at least one element to be located by ' + locatorStr,233      function(driver) {234        return driver.findElements(locator).then(function(elements) {235          return elements.length > 0 ? elements : null;236        });237      });238};239240241/**242 * Creates a condition that will wait for the given element to become stale. An243 * element is considered stale once it is removed from the DOM, or a new page244 * has loaded.245 *246 * @param {!webdriver.WebElement} element The element that should become stale.247 * @return {!until.Condition.<boolean>} The new condition.248 */249until.stalenessOf = function(element) {250  return new until.Condition('element to become stale', function() {251    return element.getTagName().then(252        function() { return false; },253        function(e) {254          if (e.code === bot.ErrorCode.STALE_ELEMENT_REFERENCE) {255            return true;256          }257          throw e;258        });259  });260};261262263/**264 * Creates a condition that will wait for the given element to become visible.265 *266 * @param {!webdriver.WebElement} element The element to test.267 * @return {!until.Condition.<boolean>} The new condition.268 * @see webdriver.WebDriver#isDisplayed269 */270until.elementIsVisible = function(element) {271  return new until.Condition('until element is visible', function() {272    return element.isDisplayed();273  });274};275276277/**278 * Creates a condition that will wait for the given element to be in the DOM,279 * yet not visible to the user.280 *281 * @param {!webdriver.WebElement} element The element to test.282 * @return {!until.Condition.<boolean>} The new condition.283 * @see webdriver.WebDriver#isDisplayed284 */285until.elementIsNotVisible = function(element) {286  return new until.Condition('until element is not visible', function() {287    return element.isDisplayed().then(function(v) {288      return !v;289    });290  });291};292293294/**295 * Creates a condition that will wait for the given element to be enabled.296 *297 * @param {!webdriver.WebElement} element The element to test.298 * @return {!until.Condition.<boolean>} The new condition.299 * @see webdriver.WebDriver#isEnabled300 */301until.elementIsEnabled = function(element) {302  return new until.Condition('until element is enabled', function() {303    return element.isEnabled();304  });305};306307308/**309 * Creates a condition that will wait for the given element to be disabled.310 *311 * @param {!webdriver.WebElement} element The element to test.312 * @return {!until.Condition.<boolean>} The new condition.313 * @see webdriver.WebDriver#isEnabled314 */315until.elementIsDisabled = function(element) {316  return new until.Condition('until element is disabled', function() {317    return element.isEnabled().then(function(v) {318      return !v;319    });320  });321};322323324/**325 * Creates a condition that will wait for the given element to be selected.326 * @param {!webdriver.WebElement} element The element to test.327 * @return {!until.Condition.<boolean>} The new condition.328 * @see webdriver.WebDriver#isSelected329 */330until.elementIsSelected = function(element) {331  return new until.Condition('until element is selected', function() {332    return element.isSelected();333  });334};335336337/**338 * Creates a condition that will wait for the given element to be deselected.339 *340 * @param {!webdriver.WebElement} element The element to test.341 * @return {!until.Condition.<boolean>} The new condition.342 * @see webdriver.WebDriver#isSelected343 */344until.elementIsNotSelected = function(element) {345  return new until.Condition('until element is not selected', function() {346    return element.isSelected().then(function(v) {347      return !v;348    });349  });350};351352353/**354 * Creates a condition that will wait for the given element's355 * {@link webdriver.WebDriver#getText visible text} to match the given356 * {@code text} exactly.357 *358 * @param {!webdriver.WebElement} element The element to test.359 * @param {string} text The expected text.360 * @return {!until.Condition.<boolean>} The new condition.361 * @see webdriver.WebDriver#getText362 */363until.elementTextIs = function(element, text) {364  return new until.Condition('until element text is', function() {365    return element.getText().then(function(t) {366      return t === text;367    });368  });369};370371372/**373 * Creates a condition that will wait for the given element's374 * {@link webdriver.WebDriver#getText visible text} to contain the given375 * substring.376 *377 * @param {!webdriver.WebElement} element The element to test.378 * @param {string} substr The substring to search for.379 * @return {!until.Condition.<boolean>} The new condition.380 * @see webdriver.WebDriver#getText381 */382until.elementTextContains = function(element, substr) {383  return new until.Condition('until element text contains', function() {384    return element.getText().then(function(t) {385      return t.indexOf(substr) != -1;386    });387  });388};389390391/**392 * Creates a condition that will wait for the given element's393 * {@link webdriver.WebDriver#getText visible text} to match a regular394 * expression.395 *396 * @param {!webdriver.WebElement} element The element to test.397 * @param {!RegExp} regex The regular expression to test against.398 * @return {!until.Condition.<boolean>} The new condition.399 * @see webdriver.WebDriver#getText400 */401until.elementTextMatches = function(element, regex) {402  return new until.Condition('until element text matches', function() {403    return element.getText().then(function(t) {404      return regex.test(t);405    });406  });407};408});  // goog.scope