Path: blob/trunk/javascript/selenium-webdriver/lib/until.js
2884 views
// Licensed to the Software Freedom Conservancy (SFC) under one1// or more contributor license agreements. See the NOTICE file2// distributed with this work for additional information3// regarding copyright ownership. The SFC licenses this file4// to you under the Apache License, Version 2.0 (the5// "License"); you may not use this file except in compliance6// with the License. You may obtain a copy of the License at7//8// http://www.apache.org/licenses/LICENSE-2.09//10// Unless required by applicable law or agreed to in writing,11// software distributed under the License is distributed on an12// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY13// KIND, either express or implied. See the License for the14// specific language governing permissions and limitations15// under the License.1617/**18* @fileoverview Defines common conditions for use with19* {@link webdriver.WebDriver#wait WebDriver wait}.20*21* Sample usage:22*23* driver.get('http://www.google.com/ncr');24*25* var query = driver.wait(until.elementLocated(By.name('q')));26* query.sendKeys('webdriver\n');27*28* driver.wait(until.titleIs('webdriver - Google Search'));29*30* To define a custom condition, simply call WebDriver.wait with a function31* that will eventually return a truthy-value (neither null, undefined, false,32* 0, or the empty string):33*34* driver.wait(function() {35* return driver.getTitle().then(function(title) {36* return title === 'webdriver - Google Search';37* });38* }, 1000);39*/4041'use strict'4243const by = require('./by')44const error = require('./error')45const webdriver = require('./webdriver')46const Condition = webdriver.Condition47const WebElementCondition = webdriver.WebElementCondition4849/**50* Creates a condition that will wait until the input driver is able to switch51* to the designated frame. The target frame may be specified as52*53* 1. a numeric index into54* [window.frames](https://developer.mozilla.org/en-US/docs/Web/API/Window.frames)55* for the currently selected frame.56* 2. a {@link ./webdriver.WebElement}, which must reference a FRAME or IFRAME57* element on the current page.58* 3. a locator which may be used to first locate a FRAME or IFRAME on the59* current page before attempting to switch to it.60*61* Upon successful resolution of this condition, the driver will be left62* focused on the new frame.63*64* @param {!(number|./webdriver.WebElement|By|65* function(!./webdriver.WebDriver): !./webdriver.WebElement)} frame66* The frame identifier.67* @return {!Condition<boolean>} A new condition.68*/69function ableToSwitchToFrame(frame) {70let condition71if (typeof frame === 'number' || frame instanceof webdriver.WebElement) {72condition = (driver) => attemptToSwitchFrames(driver, frame)73} else {74condition = function (driver) {75let locator = /** @type {!(By|Function)} */ (frame)76return driver.findElements(locator).then(function (els) {77if (els.length) {78return attemptToSwitchFrames(driver, els[0])79}80})81}82}8384return new Condition('to be able to switch to frame', condition)8586function attemptToSwitchFrames(driver, frame) {87return driver88.switchTo()89.frame(frame)90.then(91function () {92return true93},94function (e) {95if (!(e instanceof error.NoSuchFrameError)) {96throw e97}98},99)100}101}102103/**104* Creates a condition that waits for an alert to be opened. Upon success, the105* returned promise will be fulfilled with the handle for the opened alert.106*107* @return {!Condition<!./webdriver.Alert>} The new condition.108*/109function alertIsPresent() {110return new Condition('for alert to be present', function (driver) {111return driver112.switchTo()113.alert()114.catch(function (e) {115if (116!(117e instanceof error.NoSuchAlertError ||118// XXX: Workaround for GeckoDriver error `TypeError: can't convert null119// to object`. For more details, see120// https://github.com/SeleniumHQ/selenium/pull/2137121(e instanceof error.WebDriverError && e.message === `can't convert null to object`)122)123) {124throw e125}126})127})128}129130/**131* Creates a condition that will wait for the current page's title to match the132* given value.133*134* @param {string} title The expected page title.135* @return {!Condition<boolean>} The new condition.136*/137function titleIs(title) {138return new Condition('for title to be ' + JSON.stringify(title), function (driver) {139return driver.getTitle().then(function (t) {140return t === title141})142})143}144145/**146* Creates a condition that will wait for the current page's title to contain147* the given substring.148*149* @param {string} substr The substring that should be present in the page150* title.151* @return {!Condition<boolean>} The new condition.152*/153function titleContains(substr) {154return new Condition('for title to contain ' + JSON.stringify(substr), function (driver) {155return driver.getTitle().then(function (title) {156return title.indexOf(substr) !== -1157})158})159}160161/**162* Creates a condition that will wait for the current page's title to match the163* given regular expression.164*165* @param {!RegExp} regex The regular expression to test against.166* @return {!Condition<boolean>} The new condition.167*/168function titleMatches(regex) {169return new Condition('for title to match ' + regex, function (driver) {170return driver.getTitle().then(function (title) {171return regex.test(title)172})173})174}175176/**177* Creates a condition that will wait for the current page's url to match the178* given value.179*180* @param {string} url The expected page url.181* @return {!Condition<boolean>} The new condition.182*/183function urlIs(url) {184return new Condition('for URL to be ' + JSON.stringify(url), function (driver) {185return driver.getCurrentUrl().then(function (u) {186return u === url187})188})189}190191/**192* Creates a condition that will wait for the current page's url to contain193* the given substring.194*195* @param {string} substrUrl The substring that should be present in the current196* URL.197* @return {!Condition<boolean>} The new condition.198*/199function urlContains(substrUrl) {200return new Condition('for URL to contain ' + JSON.stringify(substrUrl), function (driver) {201return driver.getCurrentUrl().then(function (url) {202return url && url.includes(substrUrl)203})204})205}206207/**208* Creates a condition that will wait for the current page's url to match the209* given regular expression.210*211* @param {!RegExp} regex The regular expression to test against.212* @return {!Condition<boolean>} The new condition.213*/214function urlMatches(regex) {215return new Condition('for URL to match ' + regex, function (driver) {216return driver.getCurrentUrl().then(function (url) {217return regex.test(url)218})219})220}221222/**223* Creates a condition that will loop until an element is224* {@link ./webdriver.WebDriver#findElement found} with the given locator.225*226* @param {!(By|Function)} locator The locator to use.227* @return {!WebElementCondition} The new condition.228*/229function elementLocated(locator) {230locator = by.checkedLocator(locator)231let locatorStr = typeof locator === 'function' ? 'by function()' : locator + ''232return new WebElementCondition('for element to be located ' + locatorStr, function (driver) {233return driver.findElements(locator).then(function (elements) {234return elements[0]235})236})237}238239/**240* Creates a condition that will loop until at least one element is241* {@link ./webdriver.WebDriver#findElement found} with the given locator.242*243* @param {!(By|Function)} locator The locator to use.244* @return {!Condition<!Array<!./webdriver.WebElement>>} The new245* condition.246*/247function elementsLocated(locator) {248locator = by.checkedLocator(locator)249let locatorStr = typeof locator === 'function' ? 'by function()' : locator + ''250return new Condition('for at least one element to be located ' + locatorStr, function (driver) {251return driver.findElements(locator).then(function (elements) {252return elements.length > 0 ? elements : null253})254})255}256257/**258* Creates a condition that will wait for the given element to become stale. An259* element is considered stale once it is removed from the DOM, or a new page260* has loaded.261*262* @param {!./webdriver.WebElement} element The element that should become stale.263* @return {!Condition<boolean>} The new condition.264*/265function stalenessOf(element) {266return new Condition('element to become stale', function () {267return element.getTagName().then(268function () {269return false270},271function (e) {272if (e instanceof error.StaleElementReferenceError) {273return true274}275throw e276},277)278})279}280281/**282* Creates a condition that will wait for the given element to become visible.283*284* @param {!./webdriver.WebElement} element The element to test.285* @return {!WebElementCondition} The new condition.286* @see ./webdriver.WebDriver#isDisplayed287*/288function elementIsVisible(element) {289return new WebElementCondition('until element is visible', function () {290return element.isDisplayed().then((v) => (v ? element : null))291})292}293294/**295* Creates a condition that will wait for the given element to be in the DOM,296* yet not visible to the user.297*298* @param {!./webdriver.WebElement} element The element to test.299* @return {!WebElementCondition} The new condition.300* @see ./webdriver.WebDriver#isDisplayed301*/302function elementIsNotVisible(element) {303return new WebElementCondition('until element is not visible', function () {304return element.isDisplayed().then((v) => (v ? null : element))305})306}307308/**309* Creates a condition that will wait for the given element to be enabled.310*311* @param {!./webdriver.WebElement} element The element to test.312* @return {!WebElementCondition} The new condition.313* @see webdriver.WebDriver#isEnabled314*/315function elementIsEnabled(element) {316return new WebElementCondition('until element is enabled', function () {317return element.isEnabled().then((v) => (v ? element : null))318})319}320321/**322* Creates a condition that will wait for the given element to be disabled.323*324* @param {!./webdriver.WebElement} element The element to test.325* @return {!WebElementCondition} The new condition.326* @see webdriver.WebDriver#isEnabled327*/328function elementIsDisabled(element) {329return new WebElementCondition('until element is disabled', function () {330return element.isEnabled().then((v) => (v ? null : element))331})332}333334/**335* Creates a condition that will wait for the given element to be selected.336* @param {!./webdriver.WebElement} element The element to test.337* @return {!WebElementCondition} The new condition.338* @see webdriver.WebDriver#isSelected339*/340function elementIsSelected(element) {341return new WebElementCondition('until element is selected', function () {342return element.isSelected().then((v) => (v ? element : null))343})344}345346/**347* Creates a condition that will wait for the given element to be deselected.348*349* @param {!./webdriver.WebElement} element The element to test.350* @return {!WebElementCondition} The new condition.351* @see webdriver.WebDriver#isSelected352*/353function elementIsNotSelected(element) {354return new WebElementCondition('until element is not selected', function () {355return element.isSelected().then((v) => (v ? null : element))356})357}358359/**360* Creates a condition that will wait for the given element's361* {@link webdriver.WebDriver#getText visible text} to match the given362* {@code text} exactly.363*364* @param {!./webdriver.WebElement} element The element to test.365* @param {string} text The expected text.366* @return {!WebElementCondition} The new condition.367* @see webdriver.WebDriver#getText368*/369function elementTextIs(element, text) {370return new WebElementCondition('until element text is', function () {371return element.getText().then((t) => (t === text ? element : null))372})373}374375/**376* Creates a condition that will wait for the given element's377* {@link webdriver.WebDriver#getText visible text} to contain the given378* substring.379*380* @param {!./webdriver.WebElement} element The element to test.381* @param {string} substr The substring to search for.382* @return {!WebElementCondition} The new condition.383* @see webdriver.WebDriver#getText384*/385function elementTextContains(element, substr) {386return new WebElementCondition('until element text contains', function () {387return element.getText().then((t) => (t.indexOf(substr) != -1 ? element : null))388})389}390391/**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 {!WebElementCondition} The new condition.399* @see webdriver.WebDriver#getText400*/401function elementTextMatches(element, regex) {402return new WebElementCondition('until element text matches', function () {403return element.getText().then((t) => (regex.test(t) ? element : null))404})405}406407// PUBLIC API408409module.exports = {410elementTextMatches,411elementTextContains,412elementTextIs,413elementIsNotSelected,414elementIsSelected,415elementIsDisabled,416ableToSwitchToFrame,417elementIsEnabled,418elementIsNotVisible,419elementIsVisible,420stalenessOf,421elementsLocated,422elementLocated,423urlMatches,424urlContains,425urlIs,426titleMatches,427titleContains,428alertIsPresent,429titleIs,430}431432433