// 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. 'use strict' const assert = require('node:assert') const By = require('selenium-webdriver/lib/by').By const CommandName = require('selenium-webdriver/lib/command').Name const error = require('selenium-webdriver/lib/error') const until = require('selenium-webdriver/lib/until') const webdriver = require('selenium-webdriver/lib/webdriver') const WebElement = webdriver.WebElement describe('until', function () { let driver, executor class TestExecutor { constructor() { this.handlers_ = {} } on(cmd, handler) { this.handlers_[cmd] = handler return this } execute(cmd) { let self = this return Promise.resolve().then(function () { if (!self.handlers_[cmd.getName()]) { throw new error.UnknownCommandError(cmd.getName()) } return self.handlers_[cmd.getName()](cmd) }) } } function fail(opt_msg) { throw new assert.AssertionError({ message: opt_msg }) } beforeEach(function setUp() { executor = new TestExecutor() driver = new webdriver.WebDriver('session-id', executor) }) describe('ableToSwitchToFrame', function () { it('failsFastForNonSwitchErrors', function () { let e = Error('boom') executor.on(CommandName.SWITCH_TO_FRAME, function () { throw e }) return driver.wait(until.ableToSwitchToFrame(0), 100).then(fail, (e2) => assert.strictEqual(e2, e)) }) const ELEMENT_ID = 'some-element-id' const ELEMENT_INDEX = 1234 function onSwitchFrame(expectedId) { if (typeof expectedId === 'string') { expectedId = WebElement.buildId(expectedId) } else { assert.strictEqual(typeof expectedId, 'number', 'must be string or number') } return (cmd) => { assert.deepStrictEqual(cmd.getParameter('id'), expectedId, 'frame ID not specified') return true } } it('byIndex', function () { executor.on(CommandName.SWITCH_TO_FRAME, onSwitchFrame(ELEMENT_INDEX)) return driver.wait(until.ableToSwitchToFrame(ELEMENT_INDEX), 100) }) it('byWebElement', function () { executor.on(CommandName.SWITCH_TO_FRAME, onSwitchFrame(ELEMENT_ID)) var el = new webdriver.WebElement(driver, ELEMENT_ID) return driver.wait(until.ableToSwitchToFrame(el), 100) }) it('byWebElementPromise', function () { executor.on(CommandName.SWITCH_TO_FRAME, onSwitchFrame(ELEMENT_ID)) var el = new webdriver.WebElementPromise(driver, Promise.resolve(new webdriver.WebElement(driver, ELEMENT_ID))) return driver.wait(until.ableToSwitchToFrame(el), 100) }) it('byLocator', function () { executor.on(CommandName.FIND_ELEMENTS, () => [WebElement.buildId(ELEMENT_ID)]) executor.on(CommandName.SWITCH_TO_FRAME, onSwitchFrame(ELEMENT_ID)) return driver.wait(until.ableToSwitchToFrame(By.id('foo')), 100) }) it('byLocator_elementNotInitiallyFound', function () { let foundResponses = [[], [], [WebElement.buildId(ELEMENT_ID)]] executor.on(CommandName.FIND_ELEMENTS, () => foundResponses.shift()) executor.on(CommandName.SWITCH_TO_FRAME, onSwitchFrame(ELEMENT_ID)) return driver .wait(until.ableToSwitchToFrame(By.id('foo')), 2000) .then(() => assert.deepStrictEqual(foundResponses, [])) }) it('timesOutIfNeverAbletoSwitchFrames', function () { var count = 0 executor.on(CommandName.SWITCH_TO_FRAME, function () { count += 1 throw new error.NoSuchFrameError() }) return driver.wait(until.ableToSwitchToFrame(0), 100).then(fail, function (e) { assert.ok(count > 0) assert.ok(e.message.startsWith('Waiting to be able to switch to frame'), 'Wrong message: ' + e.message) }) }) }) describe('alertIsPresent', function () { it('failsFastForNonAlertSwitchErrors', function () { return driver.wait(until.alertIsPresent(), 100).then(fail, function (e) { assert.ok(e instanceof error.UnknownCommandError) assert.strictEqual(e.message, CommandName.GET_ALERT_TEXT) }) }) it('waitsForAlert', function () { var count = 0 executor .on(CommandName.GET_ALERT_TEXT, function () { if (count++ < 3) { throw new error.NoSuchAlertError() } else { return true } }) .on(CommandName.DISMISS_ALERT, () => true) return driver.wait(until.alertIsPresent(), 1000).then(function (alert) { assert.strictEqual(count, 4) return alert.dismiss() }) }) // TODO: Remove once GeckoDriver doesn't throw this unwanted error. // See https://github.com/SeleniumHQ/selenium/pull/2137 describe('workaround for GeckoDriver', function () { it('doesNotFailWhenCannotConvertNullToObject', function () { var count = 0 executor .on(CommandName.GET_ALERT_TEXT, function () { if (count++ < 3) { throw new error.WebDriverError(`can't convert null to object`) } else { return true } }) .on(CommandName.DISMISS_ALERT, () => true) return driver.wait(until.alertIsPresent(), 1000).then(function (alert) { assert.strictEqual(count, 4) return alert.dismiss() }) }) it('keepsRaisingRegularWebdriverError', function () { var webDriverError = new error.WebDriverError() executor.on(CommandName.GET_ALERT_TEXT, function () { throw webDriverError }) return driver.wait(until.alertIsPresent(), 1000).then( function () { throw new Error('driver did not fail against WebDriverError') }, function (error) { assert.strictEqual(error, webDriverError) }, ) }) }) }) it('testUntilTitleIs', function () { var titles = ['foo', 'bar', 'baz'] executor.on(CommandName.GET_TITLE, () => titles.shift()) return driver.wait(until.titleIs('bar'), 3000).then(function () { assert.deepStrictEqual(titles, ['baz']) }) }) it('testUntilTitleContains', function () { var titles = ['foo', 'froogle', 'google'] executor.on(CommandName.GET_TITLE, () => titles.shift()) return driver.wait(until.titleContains('oogle'), 3000).then(function () { assert.deepStrictEqual(titles, ['google']) }) }) it('testUntilTitleMatches', function () { var titles = ['foo', 'froogle', 'aaaabc', 'aabbbc', 'google'] executor.on(CommandName.GET_TITLE, () => titles.shift()) return driver.wait(until.titleMatches(/^a{2,3}b+c$/), 3000).then(function () { assert.deepStrictEqual(titles, ['google']) }) }) it('testUntilUrlIs', function () { var urls = ['http://www.foo.com', 'https://boo.com', 'http://docs.yes.com'] executor.on(CommandName.GET_CURRENT_URL, () => urls.shift()) return driver.wait(until.urlIs('https://boo.com'), 3000).then(function () { assert.deepStrictEqual(urls, ['http://docs.yes.com']) }) }) it('testUntilUrlContains', function () { var urls = ['http://foo.com', 'https://groups.froogle.com', 'http://google.com'] executor.on(CommandName.GET_CURRENT_URL, () => urls.shift()) return driver.wait(until.urlContains('oogle.com'), 3000).then(function () { assert.deepStrictEqual(urls, ['http://google.com']) }) }) it('testUntilUrlMatches', function () { var urls = ['foo', 'froogle', 'aaaabc', 'aabbbc', 'google'] executor.on(CommandName.GET_CURRENT_URL, () => urls.shift()) return driver.wait(until.urlMatches(/^a{2,3}b+c$/), 3000).then(function () { assert.deepStrictEqual(urls, ['google']) }) }) it('testUntilElementLocated', function () { var responses = [[], [WebElement.buildId('abc123'), WebElement.buildId('foo')], ['end']] executor.on(CommandName.FIND_ELEMENTS, () => responses.shift()) let element = driver.wait(until.elementLocated(By.id('quux')), 2000) assert.ok(element instanceof webdriver.WebElementPromise) return element.getId().then(function (id) { assert.deepStrictEqual(responses, [['end']]) assert.strictEqual(id, 'abc123') }) }) describe('untilElementLocated, elementNeverFound', function () { function runNoElementFoundTest(locator, locatorStr) { executor.on(CommandName.FIND_ELEMENTS, () => []) function expectedFailure() { fail('expected condition to timeout') } return driver.wait(until.elementLocated(locator), 100).then(expectedFailure, function (error) { var expected = 'Waiting for element to be located ' + locatorStr var lines = error.message.split(/\n/, 2) assert.strictEqual(lines[0], expected) let regex = /^Wait timed out after \d+ms$/ assert.ok(regex.test(lines[1]), `Lines <${lines[1]}> does not match ${regex}`) }) } it('byLocator', function () { return runNoElementFoundTest(By.id('quux'), 'By(css selector, *[id="quux"])') }) it('byHash', function () { return runNoElementFoundTest({ id: 'quux' }, 'By(css selector, *[id="quux"])') }) it('byFunction', function () { return runNoElementFoundTest(function () {}, 'by function()') }) }) it('testUntilElementsLocated', function () { var responses = [[], [WebElement.buildId('abc123'), WebElement.buildId('foo')], ['end']] executor.on(CommandName.FIND_ELEMENTS, () => responses.shift()) return driver .wait(until.elementsLocated(By.id('quux')), 2000) .then(function (els) { return Promise.all(els.map((e) => e.getId())) }) .then(function (ids) { assert.deepStrictEqual(responses, [['end']]) assert.strictEqual(ids.length, 2) assert.strictEqual(ids[0], 'abc123') assert.strictEqual(ids[1], 'foo') }) }) describe('untilElementsLocated, noElementsFound', function () { function runNoElementsFoundTest(locator, locatorStr) { executor.on(CommandName.FIND_ELEMENTS, () => []) function expectedFailure() { fail('expected condition to timeout') } return driver.wait(until.elementsLocated(locator), 100).then(expectedFailure, function (error) { var expected = 'Waiting for at least one element to be located ' + locatorStr var lines = error.message.split(/\n/, 2) assert.strictEqual(lines[0], expected) let regex = /^Wait timed out after \d+ms$/ assert.ok(regex.test(lines[1]), `Lines <${lines[1]}> does not match ${regex}`) }) } it('byLocator', function () { return runNoElementsFoundTest(By.id('quux'), 'By(css selector, *[id="quux"])') }) it('byHash', function () { return runNoElementsFoundTest({ id: 'quux' }, 'By(css selector, *[id="quux"])') }) it('byFunction', function () { return runNoElementsFoundTest(function () {}, 'by function()') }) }) it('testUntilStalenessOf', function () { let count = 0 executor.on(CommandName.GET_ELEMENT_TAG_NAME, function () { while (count < 3) { count += 1 return 'body' } throw new error.StaleElementReferenceError('now stale') }) var el = new webdriver.WebElement(driver, { ELEMENT: 'foo' }) return driver.wait(until.stalenessOf(el), 2000).then(() => assert.strictEqual(count, 3)) }) describe('element state conditions', function () { function runElementStateTest(predicate, command, responses, _var_args) { let original = new webdriver.WebElement(driver, 'foo') let predicateArgs = [original] if (arguments.length > 3) { predicateArgs = predicateArgs.concat(arguments[1]) command = arguments[2] responses = arguments[3] } assert.ok(responses.length > 1) responses = responses.concat(['end']) executor.on(command, () => responses.shift()) let result = driver.wait(predicate.apply(null, predicateArgs), 2000) assert.ok(result instanceof webdriver.WebElementPromise) return result .then(function (value) { assert.ok(value instanceof webdriver.WebElement) assert.ok(!(value instanceof webdriver.WebElementPromise)) return value.getId() }) .then(function (id) { assert.strictEqual('foo', id) assert.deepStrictEqual(responses, ['end']) }) } it('elementIsVisible', function () { return runElementStateTest(until.elementIsVisible, CommandName.IS_ELEMENT_DISPLAYED, [false, false, true]) }) it('elementIsNotVisible', function () { return runElementStateTest(until.elementIsNotVisible, CommandName.IS_ELEMENT_DISPLAYED, [true, true, false]) }) it('elementIsEnabled', function () { return runElementStateTest(until.elementIsEnabled, CommandName.IS_ELEMENT_ENABLED, [false, false, true]) }) it('elementIsDisabled', function () { return runElementStateTest(until.elementIsDisabled, CommandName.IS_ELEMENT_ENABLED, [true, true, false]) }) it('elementIsSelected', function () { return runElementStateTest(until.elementIsSelected, CommandName.IS_ELEMENT_SELECTED, [false, false, true]) }) it('elementIsNotSelected', function () { return runElementStateTest(until.elementIsNotSelected, CommandName.IS_ELEMENT_SELECTED, [true, true, false]) }) it('elementTextIs', function () { return runElementStateTest(until.elementTextIs, 'foobar', CommandName.GET_ELEMENT_TEXT, [ 'foo', 'fooba', 'foobar', ]) }) it('elementTextContains', function () { return runElementStateTest(until.elementTextContains, 'bar', CommandName.GET_ELEMENT_TEXT, [ 'foo', 'foobaz', 'foobarbaz', ]) }) it('elementTextMatches', function () { return runElementStateTest(until.elementTextMatches, /fo+bar{3}/, CommandName.GET_ELEMENT_TEXT, [ 'foo', 'foobar', 'fooobarrr', ]) }) }) describe('WebElementCondition', function () { it('fails if wait completes with a non-WebElement value', function () { let result = driver.wait(new webdriver.WebElementCondition('testing', () => 123), 1000) return result.then( () => assert.fail('expected to fail'), function (e) { assert.ok(e instanceof TypeError) assert.strictEqual('WebElementCondition did not resolve to a WebElement: ' + '[object Number]', e.message) }, ) }) }) })