// Licensed to the Software Freedom Conservancy (SFC) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The SFC licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. /** * @fileoverview Defines common conditions for use with * {@link webdriver.WebDriver#wait WebDriver wait}. * * Sample usage: * * driver.get('http://www.google.com/ncr'); * * var query = driver.wait(until.elementLocated(By.name('q'))); * query.sendKeys('webdriver\n'); * * driver.wait(until.titleIs('webdriver - Google Search')); * * To define a custom condition, simply call WebDriver.wait with a function * that will eventually return a truthy-value (neither null, undefined, false, * 0, or the empty string): * * driver.wait(function() { * return driver.getTitle().then(function(title) { * return title === 'webdriver - Google Search'; * }); * }, 1000); */ 'use strict'; const by = require('./by'); const By = require('./by').By; const error = require('./error'); const webdriver = require('./webdriver'), Condition = webdriver.Condition, WebElementCondition = webdriver.WebElementCondition; /** * Creates a condition that will wait until the input driver is able to switch * to the designated frame. The target frame may be specified as * * 1. a numeric index into * [window.frames](https://developer.mozilla.org/en-US/docs/Web/API/Window.frames) * for the currently selected frame. * 2. a {@link ./webdriver.WebElement}, which must reference a FRAME or IFRAME * element on the current page. * 3. a locator which may be used to first locate a FRAME or IFRAME on the * current page before attempting to switch to it. * * Upon successful resolution of this condition, the driver will be left * focused on the new frame. * * @param {!(number|./webdriver.WebElement|By| * function(!./webdriver.WebDriver): !./webdriver.WebElement)} frame * The frame identifier. * @return {!Condition} A new condition. */ exports.ableToSwitchToFrame = function ableToSwitchToFrame(frame) { var condition; if (typeof frame === 'number' || frame instanceof webdriver.WebElement) { condition = attemptToSwitchFrames; } else { condition = function(driver) { let locator = /** @type {!(By|Function)} */(frame); return driver.findElements(locator).then(function(els) { if (els.length) { return attemptToSwitchFrames(driver, els[0]); } }); }; } return new Condition('to be able to switch to frame', condition); function attemptToSwitchFrames(driver, frame) { return driver.switchTo().frame(frame).then( function() { return true; }, function(e) { if (!(e instanceof error.NoSuchFrameError)) { throw e; } }); } }; /** * Creates a condition that waits for an alert to be opened. Upon success, the * returned promise will be fulfilled with the handle for the opened alert. * * @return {!Condition} The new condition. */ exports.alertIsPresent = function alertIsPresent() { return new Condition('for alert to be present', function(driver) { return driver.switchTo().alert().catch(function(e) { if (!(e instanceof error.NoSuchAlertError // XXX: Workaround for GeckoDriver error `TypeError: can't convert null // to object`. For more details, see // https://github.com/SeleniumHQ/selenium/pull/2137 || (e instanceof error.WebDriverError && e.message === `can't convert null to object`) )) { throw e; } }); }); }; /** * Creates a condition that will wait for the current page's title to match the * given value. * * @param {string} title The expected page title. * @return {!Condition} The new condition. */ exports.titleIs = function titleIs(title) { return new Condition( 'for title to be ' + JSON.stringify(title), function(driver) { return driver.getTitle().then(function(t) { return t === title; }); }); }; /** * Creates a condition that will wait for the current page's title to contain * the given substring. * * @param {string} substr The substring that should be present in the page * title. * @return {!Condition} The new condition. */ exports.titleContains = function titleContains(substr) { return new Condition( 'for title to contain ' + JSON.stringify(substr), function(driver) { return driver.getTitle().then(function(title) { return title.indexOf(substr) !== -1; }); }); }; /** * Creates a condition that will wait for the current page's title to match the * given regular expression. * * @param {!RegExp} regex The regular expression to test against. * @return {!Condition} The new condition. */ exports.titleMatches = function titleMatches(regex) { return new Condition('for title to match ' + regex, function(driver) { return driver.getTitle().then(function(title) { return regex.test(title); }); }); }; /** * Creates a condition that will wait for the current page's url to match the * given value. * * @param {string} url The expected page url. * @return {!Condition} The new condition. */ exports.urlIs = function urlIs(url) { return new Condition( 'for URL to be ' + JSON.stringify(url), function(driver) { return driver.getCurrentUrl().then(function(u) { return u === url; }); }); }; /** * Creates a condition that will wait for the current page's url to contain * the given substring. * * @param {string} substrUrl The substring that should be present in the current * URL. * @return {!Condition} The new condition. */ exports.urlContains = function urlContains(substrUrl) { return new Condition( 'for URL to contain ' + JSON.stringify(substrUrl), function(driver) { return driver.getCurrentUrl().then(function(url) { return url.indexOf(substrUrl) !== -1; }); }); }; /** * Creates a condition that will wait for the current page's url to match the * given regular expression. * * @param {!RegExp} regex The regular expression to test against. * @return {!Condition} The new condition. */ exports.urlMatches = function urlMatches(regex) { return new Condition('for URL to match ' + regex, function(driver) { return driver.getCurrentUrl().then(function(url) { return regex.test(url); }); }); }; /** * Creates a condition that will loop until an element is * {@link ./webdriver.WebDriver#findElement found} with the given locator. * * @param {!(By|Function)} locator The locator to use. * @return {!WebElementCondition} The new condition. */ exports.elementLocated = function elementLocated(locator) { locator = by.checkedLocator(locator); let locatorStr = typeof locator === 'function' ? 'by function()' : locator + ''; return new WebElementCondition('for element to be located ' + locatorStr, function(driver) { return driver.findElements(locator).then(function(elements) { return elements[0]; }); }); }; /** * Creates a condition that will loop until at least one element is * {@link ./webdriver.WebDriver#findElement found} with the given locator. * * @param {!(By|Function)} locator The locator to use. * @return {!Condition>} The new * condition. */ exports.elementsLocated = function elementsLocated(locator) { locator = by.checkedLocator(locator); let locatorStr = typeof locator === 'function' ? 'by function()' : locator + ''; return new Condition( 'for at least one element to be located ' + locatorStr, function(driver) { return driver.findElements(locator).then(function(elements) { return elements.length > 0 ? elements : null; }); }); }; /** * Creates a condition that will wait for the given element to become stale. An * element is considered stale once it is removed from the DOM, or a new page * has loaded. * * @param {!./webdriver.WebElement} element The element that should become stale. * @return {!Condition} The new condition. */ exports.stalenessOf = function stalenessOf(element) { return new Condition('element to become stale', function() { return element.getTagName().then( function() { return false; }, function(e) { if (e instanceof error.StaleElementReferenceError) { return true; } throw e; }); }); }; /** * Creates a condition that will wait for the given element to become visible. * * @param {!./webdriver.WebElement} element The element to test. * @return {!WebElementCondition} The new condition. * @see ./webdriver.WebDriver#isDisplayed */ exports.elementIsVisible = function elementIsVisible(element) { return new WebElementCondition('until element is visible', function() { return element.isDisplayed().then(v => v ? element : null); }); }; /** * Creates a condition that will wait for the given element to be in the DOM, * yet not visible to the user. * * @param {!./webdriver.WebElement} element The element to test. * @return {!WebElementCondition} The new condition. * @see ./webdriver.WebDriver#isDisplayed */ exports.elementIsNotVisible = function elementIsNotVisible(element) { return new WebElementCondition('until element is not visible', function() { return element.isDisplayed().then(v => v ? null : element); }); }; /** * Creates a condition that will wait for the given element to be enabled. * * @param {!./webdriver.WebElement} element The element to test. * @return {!WebElementCondition} The new condition. * @see webdriver.WebDriver#isEnabled */ exports.elementIsEnabled = function elementIsEnabled(element) { return new WebElementCondition('until element is enabled', function() { return element.isEnabled().then(v => v ? element : null); }); }; /** * Creates a condition that will wait for the given element to be disabled. * * @param {!./webdriver.WebElement} element The element to test. * @return {!WebElementCondition} The new condition. * @see webdriver.WebDriver#isEnabled */ exports.elementIsDisabled = function elementIsDisabled(element) { return new WebElementCondition('until element is disabled', function() { return element.isEnabled().then(v => v ? null : element); }); }; /** * Creates a condition that will wait for the given element to be selected. * @param {!./webdriver.WebElement} element The element to test. * @return {!WebElementCondition} The new condition. * @see webdriver.WebDriver#isSelected */ exports.elementIsSelected = function elementIsSelected(element) { return new WebElementCondition('until element is selected', function() { return element.isSelected().then(v => v ? element : null); }); }; /** * Creates a condition that will wait for the given element to be deselected. * * @param {!./webdriver.WebElement} element The element to test. * @return {!WebElementCondition} The new condition. * @see webdriver.WebDriver#isSelected */ exports.elementIsNotSelected = function elementIsNotSelected(element) { return new WebElementCondition('until element is not selected', function() { return element.isSelected().then(v => v ? null : element); }); }; /** * Creates a condition that will wait for the given element's * {@link webdriver.WebDriver#getText visible text} to match the given * {@code text} exactly. * * @param {!./webdriver.WebElement} element The element to test. * @param {string} text The expected text. * @return {!WebElementCondition} The new condition. * @see webdriver.WebDriver#getText */ exports.elementTextIs = function elementTextIs(element, text) { return new WebElementCondition('until element text is', function() { return element.getText().then(t => t === text ? element : null); }); }; /** * Creates a condition that will wait for the given element's * {@link webdriver.WebDriver#getText visible text} to contain the given * substring. * * @param {!./webdriver.WebElement} element The element to test. * @param {string} substr The substring to search for. * @return {!WebElementCondition} The new condition. * @see webdriver.WebDriver#getText */ exports.elementTextContains = function elementTextContains(element, substr) { return new WebElementCondition('until element text contains', function() { return element.getText() .then(t => t.indexOf(substr) != -1 ? element : null); }); }; /** * Creates a condition that will wait for the given element's * {@link webdriver.WebDriver#getText visible text} to match a regular * expression. * * @param {!./webdriver.WebElement} element The element to test. * @param {!RegExp} regex The regular expression to test against. * @return {!WebElementCondition} The new condition. * @see webdriver.WebDriver#getText */ exports.elementTextMatches = function elementTextMatches(element, regex) { return new WebElementCondition('until element text matches', function() { return element.getText().then(t => regex.test(t) ? element : null); }); };