// 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 { StubError, assertIsInstance, assertIsStubError, throwStubError } = require('./testutil') const error = require('selenium-webdriver/lib/error') const logging = require('selenium-webdriver/lib/logging') const promise = require('selenium-webdriver/lib/promise') const until = require('selenium-webdriver/lib/until') const { Alert, AlertPromise, WebDriver, WebElement, WebElementPromise } = require('selenium-webdriver/lib/webdriver') const { By } = require('selenium-webdriver/lib/by') const { Capabilities } = require('selenium-webdriver/lib/capabilities') const { Name } = require('selenium-webdriver/lib/command') const { Session } = require('selenium-webdriver/lib/session') const assert = require('node:assert') const CName = Name const SESSION_ID = 'test_session_id' const fail = (msg) => assert.fail(msg) describe('WebDriver', function () { const LOG = logging.getLogger('webdriver.test') function defer() { let d = {} let promise = new Promise((resolve, reject) => { Object.assign(d, { resolve, reject }) }) d.promise = promise return d } function expectedError(ctor, message) { return function (e) { assertIsInstance(ctor, e) assert.strictEqual(message, e.message) } } class Expectation { constructor(executor, name, opt_parameters) { this.executor_ = executor this.name_ = name this.times_ = 1 this.sessionId_ = SESSION_ID this.check_ = null this.toDo_ = null this.withParameters(opt_parameters || {}) } anyTimes() { this.times_ = Infinity return this } times(n) { this.times_ = n return this } withParameters(parameters) { this.parameters_ = parameters if (this.name_ !== CName.NEW_SESSION) { this.parameters_['sessionId'] = this.sessionId_ } return this } andReturn(code, opt_value) { this.toDo_ = function (command) { LOG.info('executing ' + command.getName() + '; returning ' + code) return Promise.resolve(opt_value !== void 0 ? opt_value : null) } return this } andReturnSuccess(opt_value) { this.toDo_ = function (command) { LOG.info('executing ' + command.getName() + '; returning success') return Promise.resolve(opt_value !== void 0 ? opt_value : null) } return this } andReturnError(error) { if (typeof error === 'number') { throw Error('need error type') } this.toDo_ = function (command) { LOG.info('executing ' + command.getName() + '; returning failure') return Promise.reject(error) } return this } expect(name, opt_parameters) { this.end() return this.executor_.expect(name, opt_parameters) } end() { if (!this.toDo_) { this.andReturnSuccess(null) } return this.executor_ } execute(command) { assert.deepStrictEqual(command.getParameters(), this.parameters_) return this.toDo_(command) } } class FakeExecutor { constructor() { this.commands_ = new Map() } execute(command) { let expectations = this.commands_.get(command.getName()) if (!expectations || !expectations.length) { assert.fail('unexpected command: ' + command.getName()) return } let next = expectations[0] let result = next.execute(command) if (next.times_ !== Infinity) { next.times_ -= 1 if (!next.times_) { expectations.shift() } } return result } expect(commandName, opt_parameters) { if (!this.commands_.has(commandName)) { this.commands_.set(commandName, []) } let e = new Expectation(this, commandName, opt_parameters) this.commands_.get(commandName).push(e) return e } createDriver(opt_session) { let session = opt_session || new Session(SESSION_ID, {}) return new WebDriver(session, this) } } ///////////////////////////////////////////////////////////////////////////// // // Tests // ///////////////////////////////////////////////////////////////////////////// describe('testCreateSession', function () { it('happyPathWithCapabilitiesHashObject', function () { let aSession = new Session(SESSION_ID, { browserName: 'firefox' }) let executor = new FakeExecutor() .expect(CName.NEW_SESSION) .withParameters({ capabilities: { alwaysMatch: { browserName: 'firefox' }, firstMatch: [{}], }, }) .andReturnSuccess(aSession) .end() const driver = WebDriver.createSession(executor, { browserName: 'firefox', }) return driver.getSession().then((v) => assert.strictEqual(v, aSession)) }) it('happy Path With Capabilities Instance', function () { let aSession = new Session(SESSION_ID, { browserName: 'firefox' }) let executor = new FakeExecutor() .expect(CName.NEW_SESSION) .withParameters({ capabilities: { alwaysMatch: { 'moz:debuggerAddress': true, browserName: 'firefox', }, firstMatch: [{}], }, }) .andReturnSuccess(aSession) .end() const driver = WebDriver.createSession(executor, Capabilities.firefox()) return driver.getSession().then((v) => assert.strictEqual(v, aSession)) }) it('drops non-W3C capability names from W3C capabilities', function () { let aSession = new Session(SESSION_ID, { browserName: 'firefox' }) let executor = new FakeExecutor() .expect(CName.NEW_SESSION) .withParameters({ capabilities: { alwaysMatch: { browserName: 'firefox' }, firstMatch: [{}], }, }) .andReturnSuccess(aSession) .end() const driver = WebDriver.createSession(executor, { browserName: 'firefox', foo: 'bar', }) return driver.getSession().then((v) => assert.strictEqual(v, aSession)) }) it('failsToCreateSession', function () { let executor = new FakeExecutor() .expect(CName.NEW_SESSION) .withParameters({ capabilities: { alwaysMatch: { browserName: 'firefox' }, firstMatch: [{}], }, }) .andReturnError(new StubError()) .end() const driver = WebDriver.createSession(executor, { browserName: 'firefox', }) return driver.getSession().then(fail, assertIsStubError) }) it('invokes quit callback if it fails to create a session', function () { let called = false let executor = new FakeExecutor() .expect(CName.NEW_SESSION) .withParameters({ capabilities: { alwaysMatch: { browserName: 'firefox' }, firstMatch: [{}], }, }) .andReturnError(new StubError()) .end() let driver = WebDriver.createSession(executor, { browserName: 'firefox' }, () => (called = true)) return driver.getSession().then(fail, (err) => { assert.ok(called) assertIsStubError(err) }) }) }) it('testDoesNotExecuteCommandIfSessionDoesNotResolve', function () { const session = Promise.reject(new StubError()) return new FakeExecutor() .createDriver(session) .getTitle() .then((_) => assert.fail('should have failed'), assertIsStubError) }) it('testCommandReturnValuesArePassedToFirstCallback', function () { let executor = new FakeExecutor().expect(CName.GET_TITLE).andReturnSuccess('Google Search').end() const driver = executor.createDriver() return driver.getTitle().then((title) => assert.strictEqual('Google Search', title)) }) it('testStopsCommandExecutionWhenAnErrorOccurs', function () { let e = new error.NoSuchWindowError('window not found') let executor = new FakeExecutor() .expect(CName.SWITCH_TO_WINDOW) .withParameters({ name: 'foo', handle: 'foo', }) .andReturnError(e) .end() let driver = executor.createDriver() return driver .switchTo() .window('foo') .then( (_) => driver.getTitle(), // mock should blow if this gets executed (v) => assert.strictEqual(v, e), ) }) it('testReportsErrorWhenExecutingCommandsAfterExecutingAQuit', function () { let executor = new FakeExecutor().expect(CName.QUIT).end() let verifyError = expectedError( error.NoSuchSessionError, 'This driver instance does not have a valid session ID ' + '(did you call WebDriver.quit()?) and may no longer be used.', ) let driver = executor.createDriver() return driver .quit() .then((_) => driver.get('http://www.google.com')) .then(assert.fail, verifyError) }) describe('returningAPromise', function () { it('fromACallback', function () { let executor = new FakeExecutor() .expect(CName.GET_TITLE) .expect(CName.GET_CURRENT_URL) .andReturnSuccess('http://www.google.com') .end() const driver = executor.createDriver() return driver .getTitle() .then(function () { return driver.getCurrentUrl() }) .then(function (value) { assert.strictEqual('http://www.google.com', value) }) }) it('fromAnErrbackSuppressesTheError', function () { let executor = new FakeExecutor() .expect(CName.SWITCH_TO_WINDOW, { name: 'foo', handle: 'foo', }) .andReturnError(new StubError()) .expect(CName.GET_CURRENT_URL) .andReturnSuccess('http://www.google.com') .end() const driver = executor.createDriver() return driver .switchTo() .window('foo') .catch(function (e) { assertIsStubError(e) return driver.getCurrentUrl() }) .then((url) => assert.strictEqual('http://www.google.com', url)) }) }) describe('WebElementPromise', function () { let driver = new FakeExecutor().createDriver() it('resolvesWhenUnderlyingElementDoes', function () { let el = new WebElement(driver, { ELEMENT: 'foo' }) return new WebElementPromise(driver, Promise.resolve(el)).then((e) => assert.strictEqual(e, el)) }) it('resolvesBeforeCallbacksOnWireValueTrigger', function () { const el = defer() const element = new WebElementPromise(driver, el.promise) const messages = [] let steps = [ element.then((_) => messages.push('element resolved')), element.getId().then((_) => messages.push('wire value resolved')), ] el.resolve(new WebElement(driver, { ELEMENT: 'foo' })) return Promise.all(steps).then(function () { assert.deepStrictEqual(['element resolved', 'wire value resolved'], messages) }) }) it('isRejectedIfUnderlyingIdIsRejected', function () { let element = new WebElementPromise(driver, Promise.reject(new StubError())) return element.then(fail, assertIsStubError) }) }) describe('executeScript', function () { it('nullReturnValue', function () { let executor = new FakeExecutor() .expect(CName.EXECUTE_SCRIPT) .withParameters({ script: 'return document.body;', args: [], }) .andReturnSuccess(null) .end() const driver = executor.createDriver() return driver.executeScript('return document.body;').then((result) => assert.strictEqual(null, result)) }) it('primitiveReturnValue', function () { let executor = new FakeExecutor() .expect(CName.EXECUTE_SCRIPT) .withParameters({ script: 'return document.body;', args: [], }) .andReturnSuccess(123) .end() const driver = executor.createDriver() return driver.executeScript('return document.body;').then((result) => assert.strictEqual(123, result)) }) it('webElementReturnValue', function () { const json = WebElement.buildId('foo') let executor = new FakeExecutor() .expect(CName.EXECUTE_SCRIPT) .withParameters({ script: 'return document.body;', args: [], }) .andReturnSuccess(json) .end() const driver = executor.createDriver() return driver .executeScript('return document.body;') .then((element) => element.getId()) .then((id) => assert.strictEqual(id, 'foo')) }) it('arrayReturnValue', function () { const json = [WebElement.buildId('foo')] let executor = new FakeExecutor() .expect(CName.EXECUTE_SCRIPT) .withParameters({ script: 'return document.body;', args: [], }) .andReturnSuccess(json) .end() const driver = executor.createDriver() return driver .executeScript('return document.body;') .then(function (array) { assert.strictEqual(1, array.length) return array[0].getId() }) .then((id) => assert.strictEqual('foo', id)) }) it('objectReturnValue', function () { const json = { foo: WebElement.buildId('foo') } let executor = new FakeExecutor() .expect(CName.EXECUTE_SCRIPT) .withParameters({ script: 'return document.body;', args: [], }) .andReturnSuccess(json) .end() const driver = executor.createDriver() return driver .executeScript('return document.body;') .then((obj) => obj['foo'].getId()) .then((id) => assert.strictEqual(id, 'foo')) }) it('scriptAsFunction', function () { let executor = new FakeExecutor() .expect(CName.EXECUTE_SCRIPT) .withParameters({ script: 'return (' + function () {} + ').apply(null, arguments);', args: [], }) .andReturnSuccess(null) .end() const driver = executor.createDriver() return driver.executeScript(function () {}) }) it('simpleArgumentConversion', function () { let executor = new FakeExecutor() .expect(CName.EXECUTE_SCRIPT) .withParameters({ script: 'return 1;', args: ['abc', 123, true, [123, { foo: 'bar' }]], }) .andReturnSuccess(null) .end() const driver = executor.createDriver() return driver.executeScript('return 1;', 'abc', 123, true, [123, { foo: 'bar' }]) }) it('webElementArgumentConversion', function () { const elementJson = WebElement.buildId('fefifofum') let executor = new FakeExecutor() .expect(CName.EXECUTE_SCRIPT) .withParameters({ script: 'return 1;', args: [elementJson], }) .andReturnSuccess(null) .end() const driver = executor.createDriver() return driver.executeScript('return 1;', new WebElement(driver, 'fefifofum')) }) it('webElementPromiseArgumentConversion', function () { const elementJson = WebElement.buildId('bar') let executor = new FakeExecutor() .expect(CName.FIND_ELEMENT, { using: 'css selector', value: '*[id="foo"]', }) .andReturnSuccess(elementJson) .expect(CName.EXECUTE_SCRIPT) .withParameters({ script: 'return 1;', args: [elementJson], }) .andReturnSuccess(null) .end() const driver = executor.createDriver() const element = driver.findElement(By.id('foo')) return driver.executeScript('return 1;', element) }) it('argumentConversion', function () { const elementJson = WebElement.buildId('fefifofum') let executor = new FakeExecutor() .expect(CName.EXECUTE_SCRIPT) .withParameters({ script: 'return 1;', args: ['abc', 123, true, elementJson, [123, { foo: 'bar' }]], }) .andReturnSuccess(null) .end() const driver = executor.createDriver() const element = new WebElement(driver, 'fefifofum') return driver.executeScript('return 1;', 'abc', 123, true, element, [123, { foo: 'bar' }]) }) it('scriptReturnsAnError', function () { let executor = new FakeExecutor() .expect(CName.EXECUTE_SCRIPT) .withParameters({ script: 'throw Error(arguments[0]);', args: ['bam'], }) .andReturnError(new StubError()) .end() const driver = executor.createDriver() return driver.executeScript('throw Error(arguments[0]);', 'bam').then(fail, assertIsStubError) }) it('failsIfArgumentIsARejectedPromise', function () { let executor = new FakeExecutor() const arg = Promise.reject(new StubError()) arg.catch(function () {}) // Suppress default handler. const driver = executor.createDriver() return driver.executeScript(function () {}, arg).then(fail, assertIsStubError) }) }) describe('executeAsyncScript', function () { it('failsIfArgumentIsARejectedPromise', function () { const arg = Promise.reject(new StubError()) arg.catch(function () {}) // Suppress default handler. const driver = new FakeExecutor().createDriver() return driver.executeAsyncScript(function () {}, arg).then(fail, assertIsStubError) }) }) describe('findElement', function () { it('elementNotFound', function () { let executor = new FakeExecutor() .expect(CName.FIND_ELEMENT, { using: 'css selector', value: '*[id="foo"]', }) .andReturnError(new StubError()) .end() const driver = executor.createDriver() return driver.findElement(By.id('foo')).then(assert.fail, assertIsStubError) }) it('elementNotFoundInACallback', function () { let executor = new FakeExecutor() .expect(CName.FIND_ELEMENT, { using: 'css selector', value: '*[id="foo"]', }) .andReturnError(new StubError()) .end() const driver = executor.createDriver() return Promise.resolve() .then((_) => driver.findElement(By.id('foo'))) .then(assert.fail, assertIsStubError) }) it('elementFound', function () { let executor = new FakeExecutor() .expect(CName.FIND_ELEMENT, { using: 'css selector', value: '*[id="foo"]', }) .andReturnSuccess(WebElement.buildId('bar')) .expect(CName.CLICK_ELEMENT, { id: WebElement.buildId('bar') }) .andReturnSuccess() .end() const driver = executor.createDriver() const element = driver.findElement(By.id('foo')) return element.click() }) it('canUseElementInCallback', function () { let executor = new FakeExecutor() .expect(CName.FIND_ELEMENT, { using: 'css selector', value: '*[id="foo"]', }) .andReturnSuccess(WebElement.buildId('bar')) .expect(CName.CLICK_ELEMENT, { id: WebElement.buildId('bar') }) .andReturnSuccess() .end() const driver = executor.createDriver() return driver.findElement(By.id('foo')).then((e) => e.click()) }) it('byJs', function () { let executor = new FakeExecutor() .expect(CName.EXECUTE_SCRIPT, { script: 'return document.body', args: [], }) .andReturnSuccess(WebElement.buildId('bar')) .expect(CName.CLICK_ELEMENT, { id: WebElement.buildId('bar') }) .end() const driver = executor.createDriver() return driver.findElement(By.js('return document.body')).then((e) => e.click()) }) it('byJs_returnsNonWebElementValue', function () { let executor = new FakeExecutor() .expect(CName.EXECUTE_SCRIPT, { script: 'return 123', args: [] }) .andReturnSuccess(123) .end() const driver = executor.createDriver() return driver.findElement(By.js('return 123')).then(assert.fail, function (e) { assertIsInstance(TypeError, e) assert.strictEqual('Custom locator did not return a WebElement', e.message) }) }) it('byJs_canPassArguments', function () { const script = 'return document.getElementsByTagName(arguments[0]);' let executor = new FakeExecutor() .expect(CName.EXECUTE_SCRIPT, { script: script, args: ['div'], }) .andReturnSuccess(WebElement.buildId('one')) .end() const driver = executor.createDriver() return driver.findElement(By.js(script, 'div')) }) it('customLocator', function () { let executor = new FakeExecutor() .expect(CName.FIND_ELEMENTS, { using: 'css selector', value: '.a' }) .andReturnSuccess([WebElement.buildId('foo'), WebElement.buildId('bar')]) .expect(CName.CLICK_ELEMENT, { id: WebElement.buildId('foo') }) .andReturnSuccess() .end() const driver = executor.createDriver() const element = driver.findElement(function (d) { assert.strictEqual(driver, d) return d.findElements(By.className('a')) }) return element.click() }) it('customLocatorThrowsIfresultIsNotAWebElement', function () { const driver = new FakeExecutor().createDriver() return driver .findElement((_) => 1) .then(assert.fail, function (e) { assertIsInstance(TypeError, e) assert.strictEqual('Custom locator did not return a WebElement', e.message) }) }) }) describe('findElements', function () { it('returnsMultipleElements', function () { const ids = ['foo', 'bar', 'baz'] let executor = new FakeExecutor() .expect(CName.FIND_ELEMENTS, { using: 'css selector', value: '.a' }) .andReturnSuccess(ids.map(WebElement.buildId)) .end() const driver = executor.createDriver() return driver .findElements(By.className('a')) .then(function (elements) { return Promise.all( elements.map(function (e) { assert.ok(e instanceof WebElement) return e.getId() }), ) }) .then((actual) => assert.deepStrictEqual(ids, actual)) }) it('byJs', function () { const ids = ['foo', 'bar', 'baz'] let executor = new FakeExecutor() .expect(CName.EXECUTE_SCRIPT, { script: 'return document.getElementsByTagName("div");', args: [], }) .andReturnSuccess(ids.map(WebElement.buildId)) .end() const driver = executor.createDriver() return driver .findElements(By.js('return document.getElementsByTagName("div");')) .then(function (elements) { return Promise.all( elements.map(function (e) { assert.ok(e instanceof WebElement) return e.getId() }), ) }) .then((actual) => assert.deepStrictEqual(ids, actual)) }) it('byJs_filtersOutNonWebElementResponses', function () { const ids = ['foo', 'bar', 'baz'] const json = [ WebElement.buildId(ids[0]), 123, 'a', false, WebElement.buildId(ids[1]), { 'not a web element': 1 }, WebElement.buildId(ids[2]), ] let executor = new FakeExecutor() .expect(CName.EXECUTE_SCRIPT, { script: 'return document.getElementsByTagName("div");', args: [], }) .andReturnSuccess(json) .end() const driver = executor.createDriver() return driver .findElements(By.js('return document.getElementsByTagName("div");')) .then(function (elements) { return Promise.all( elements.map(function (e) { assert.ok(e instanceof WebElement) return e.getId() }), ) }) .then((actual) => assert.deepStrictEqual(ids, actual)) }) it('byJs_convertsSingleWebElementResponseToArray', function () { let executor = new FakeExecutor() .expect(CName.EXECUTE_SCRIPT, { script: 'return document.getElementsByTagName("div");', args: [], }) .andReturnSuccess(WebElement.buildId('foo')) .end() const driver = executor.createDriver() return driver .findElements(By.js('return document.getElementsByTagName("div");')) .then(function (elements) { return Promise.all( elements.map(function (e) { assert.ok(e instanceof WebElement) return e.getId() }), ) }) .then((actual) => assert.deepStrictEqual(['foo'], actual)) }) it('byJs_canPassScriptArguments', function () { const script = 'return document.getElementsByTagName(arguments[0]);' let executor = new FakeExecutor() .expect(CName.EXECUTE_SCRIPT, { script: script, args: ['div'], }) .andReturnSuccess([WebElement.buildId('one'), WebElement.buildId('two')]) .end() const driver = executor.createDriver() return driver .findElements(By.js(script, 'div')) .then(function (elements) { return Promise.all( elements.map(function (e) { assert.ok(e instanceof WebElement) return e.getId() }), ) }) .then((actual) => assert.deepStrictEqual(['one', 'two'], actual)) }) }) describe('sendKeys', function () { it('convertsVarArgsIntoStrings_simpleArgs', function () { let executor = new FakeExecutor() .expect(CName.SEND_KEYS_TO_ELEMENT, { id: WebElement.buildId('one'), text: '12abc3', value: '12abc3'.split(''), }) .andReturnSuccess() .end() const driver = executor.createDriver() const element = new WebElement(driver, 'one') return element.sendKeys(1, 2, 'abc', 3) }) it('sendKeysWithEmojiRepresentedByPairOfCodePoints', function () { let executor = new FakeExecutor() .expect(CName.SEND_KEYS_TO_ELEMENT, { id: WebElement.buildId('one'), text: '\uD83D\uDE00', value: ['\uD83D\uDE00'], }) .andReturnSuccess() .end() const driver = executor.createDriver() const element = new WebElement(driver, 'one') return element.sendKeys('\uD83D\uDE00') }) it('convertsVarArgsIntoStrings_promisedArgs', function () { let executor = new FakeExecutor() .expect(CName.FIND_ELEMENT, { using: 'css selector', value: '*[id="foo"]', }) .andReturnSuccess(WebElement.buildId('one')) .expect(CName.SEND_KEYS_TO_ELEMENT, { id: WebElement.buildId('one'), text: 'abc123def', value: 'abc123def'.split(''), }) .andReturnSuccess() .end() const driver = executor.createDriver() const element = driver.findElement(By.id('foo')) return element.sendKeys(Promise.resolve('abc'), 123, Promise.resolve('def')) }) it('sendKeysWithAFileDetector', function () { let executor = new FakeExecutor() .expect(CName.FIND_ELEMENT, { using: 'css selector', value: '*[id="foo"]', }) .andReturnSuccess(WebElement.buildId('one')) .expect(CName.SEND_KEYS_TO_ELEMENT, { id: WebElement.buildId('one'), text: 'modified/path', value: 'modified/path'.split(''), }) .andReturnSuccess() .end() let driver = executor.createDriver() let handleFile = function (d, path) { assert.strictEqual(driver, d) assert.strictEqual(path, 'original/path') return Promise.resolve('modified/path') } driver.setFileDetector({ handleFile }) return driver.findElement(By.id('foo')).sendKeys('original/', 'path') }) it('sendKeysWithAFileDetector_handlerError', function () { let executor = new FakeExecutor() .expect(CName.FIND_ELEMENT, { using: 'css selector', value: '*[id="foo"]', }) .andReturnSuccess(WebElement.buildId('one')) .expect(CName.SEND_KEYS_TO_ELEMENT, { id: WebElement.buildId('one'), text: 'original/path', value: 'original/path'.split(''), }) .andReturnSuccess() .end() let driver = executor.createDriver() let handleFile = function (d, path) { assert.strictEqual(driver, d) assert.strictEqual(path, 'original/path') return Promise.reject('unhandled file error') } driver.setFileDetector({ handleFile }) return driver.findElement(By.id('foo')).sendKeys('original/', 'path') }) }) describe('switchTo()', function () { describe('window', function () { it('should return a resolved promise when the window is found', function () { let executor = new FakeExecutor() .expect(CName.SWITCH_TO_WINDOW) .withParameters({ name: 'foo', handle: 'foo', }) .andReturnSuccess() .end() return executor.createDriver().switchTo().window('foo') }) it('should propagate exceptions', function () { let e = new error.NoSuchWindowError('window not found') let executor = new FakeExecutor() .expect(CName.SWITCH_TO_WINDOW) .withParameters({ name: 'foo', handle: 'foo', }) .andReturnError(e) .end() return executor .createDriver() .switchTo() .window('foo') .then(assert.fail, (v) => assert.strictEqual(v, e)) }) }) }) describe('elementEquality', function () { it('isReflexive', function () { const a = new WebElement(new FakeExecutor().createDriver(), 'foo') return WebElement.equals(a, a).then(assert.ok) }) it('failsIfAnInputElementCouldNotBeFound', function () { let id = Promise.reject(new StubError()) const driver = new FakeExecutor().createDriver() const a = new WebElement(driver, 'foo') const b = new WebElementPromise(driver, id) return WebElement.equals(a, b).then(fail, assertIsStubError) }) }) describe('waiting', function () { it('on a condition that always returns true', function () { let executor = new FakeExecutor() let driver = executor.createDriver() let count = 0 function condition() { count++ return true } return driver.wait(condition, 1).then(() => assert.strictEqual(1, count)) }) it('on a simple counting condition', function () { let executor = new FakeExecutor() let driver = executor.createDriver() let count = 0 function condition() { return ++count === 3 } return driver.wait(condition, 250).then(() => assert.strictEqual(3, count)) }) it('on a condition that returns a promise that resolves to true after a short timeout', function () { let executor = new FakeExecutor() let driver = executor.createDriver() let count = 0 function condition() { count += 1 return new Promise((resolve) => { setTimeout(() => resolve(true), 50) }) } return driver.wait(condition, 75).then(() => assert.strictEqual(1, count)) }) it('on a condition that returns a promise', function () { let executor = new FakeExecutor() let driver = executor.createDriver() let count = 0 function condition() { count += 1 return new Promise((resolve) => { setTimeout(() => resolve(count === 3), 25) }) } return driver.wait(condition, 100, null, 25).then(() => assert.strictEqual(3, count)) }) it('fails if condition throws', function () { let executor = new FakeExecutor() let driver = executor.createDriver() return driver.wait(throwStubError, 0, 'goes boom').then(fail, assertIsStubError) }) it('fails if condition returns a rejected promise', function () { let executor = new FakeExecutor() let driver = executor.createDriver() function condition() { return new Promise((_, reject) => reject(new StubError())) } return driver.wait(condition, 0, 'goes boom').then(fail, assertIsStubError) }) it('supports message function if condition exceeds timeout', function () { let executor = new FakeExecutor() let driver = executor.createDriver() let message = () => 'goes boom' return driver .wait(() => false, 0.001, message) .then(fail, (e) => { assert.ok(/^goes boom\nWait timed out after \d+ms$/.test(e.message)) }) }) it('handles if the message function throws an error after a condition exceeds timeout', function () { let executor = new FakeExecutor() let driver = executor.createDriver() let message = () => { throw new Error('message function error') } return driver .wait(() => false, 0.001, message) .then(fail, (e) => { assert.ok(/^message function error\nWait timed out after \d+ms$/.test(e.message)) }) }) it('supports message function if condition returns a rejected promise', function () { let executor = new FakeExecutor() let driver = executor.createDriver() let condition = new Promise((res) => setTimeout(res, 100)) let message = () => 'goes boom' return driver.wait(condition, 1, message).then(fail, (e) => { assert.ok(/^goes boom\nTimed out waiting for promise to resolve after \d+ms$/.test(e.message)) }) }) it('handles if the message function returns an error after a rejected promise', function () { let executor = new FakeExecutor() let driver = executor.createDriver() let condition = new Promise((res) => setTimeout(res, 100)) let message = () => { throw new Error('message function error') } return driver.wait(condition, 1, message).then(fail, (e) => { assert.ok(/^message function error\nTimed out waiting for promise to resolve after \d+ms$/.test(e.message)) }) }) it('waits forever on a zero timeout', function () { let done = false setTimeout(() => (done = true), 150) let executor = new FakeExecutor() let driver = executor.createDriver() let waitResult = driver.wait(() => done, 0) return driver .sleep(75) .then(function () { assert.ok(!done) return driver.sleep(100) }) .then(function () { assert.ok(done) return waitResult }) }) it('waits forever if timeout omitted', function () { let done = false setTimeout(() => (done = true), 150) let executor = new FakeExecutor() let driver = executor.createDriver() let waitResult = driver.wait(() => done) return driver .sleep(75) .then(function () { assert.ok(!done) return driver.sleep(100) }) .then(function () { assert.ok(done) return waitResult }) }) it('times out when timer expires', function () { let executor = new FakeExecutor() let driver = executor.createDriver() let count = 0 let wait = driver.wait( function () { count += 1 let ms = count === 2 ? 65 : 5 return promise.delayed(ms).then(function () { return false }) }, 60, 'counting to 3', ) return wait.then(fail, function (e) { assert.strictEqual(2, count) assert.ok(e instanceof error.TimeoutError, 'Unexpected error: ' + e) assert.ok(/^counting to 3\nWait timed out after \d+ms$/.test(e.message)) }) }) it('requires condition to be a promise or function', function () { let executor = new FakeExecutor() let driver = executor.createDriver() assert.throws(() => driver.wait(1234, 0)) }) it('promise that does not resolve before timeout', function () { let d = defer() let executor = new FakeExecutor() let driver = executor.createDriver() return driver.wait(d.promise, 5).then(fail, (e) => { assert.ok(e instanceof error.TimeoutError, 'Unexpected error: ' + e) assert.ok( /Timed out waiting for promise to resolve after \d+ms/.test(e.message), 'unexpected error message: ' + e.message, ) }) }) it('unbounded wait on promise resolution', function () { let messages = [] let d = defer() let executor = new FakeExecutor() let driver = executor.createDriver() let waitResult = driver.wait(d.promise).then(function (value) { messages.push('b') assert.strictEqual(1234, value) }) setTimeout(() => messages.push('a'), 5) return driver .sleep(10) .then(function () { assert.deepStrictEqual(['a'], messages) d.resolve(1234) return waitResult }) .then(function () { assert.deepStrictEqual(['a', 'b'], messages) }) }) describe('supports custom wait functions', function () { it('waitSucceeds', function () { let executor = new FakeExecutor() .expect(CName.FIND_ELEMENTS, { using: 'css selector', value: '*[id="foo"]', }) .andReturnSuccess([]) .times(2) .expect(CName.FIND_ELEMENTS, { using: 'css selector', value: '*[id="foo"]', }) .andReturnSuccess([WebElement.buildId('bar')]) .end() const driver = executor.createDriver() return driver.wait( function () { return driver.findElements(By.id('foo')).then((els) => els.length > 0) }, 200, null, 25, ) }) it('waitTimesout_timeoutCaught', function () { let executor = new FakeExecutor() .expect(CName.FIND_ELEMENTS, { using: 'css selector', value: '*[id="foo"]', }) .andReturnSuccess([]) .anyTimes() .end() const driver = executor.createDriver() return driver .wait(function () { return driver.findElements(By.id('foo')).then((els) => els.length > 0) }, 25) .then(fail, function (e) { assert.strictEqual('Wait timed out after ', e.message.substring(0, 'Wait timed out after '.length)) }) }) }) describe('supports condition objects', function () { it('wait succeeds', function () { let executor = new FakeExecutor() .expect(CName.FIND_ELEMENTS, { using: 'css selector', value: '*[id="foo"]', }) .andReturnSuccess([]) .times(2) .expect(CName.FIND_ELEMENTS, { using: 'css selector', value: '*[id="foo"]', }) .andReturnSuccess([WebElement.buildId('bar')]) .end() let driver = executor.createDriver() return driver.wait(until.elementLocated(By.id('foo')), 200, null, 25) }) it('wait times out', function () { let executor = new FakeExecutor() .expect(CName.FIND_ELEMENTS, { using: 'css selector', value: '*[id="foo"]', }) .andReturnSuccess([]) .anyTimes() .end() let driver = executor.createDriver() return driver .wait(until.elementLocated(By.id('foo')), 5) .then(fail, (err) => assert.ok(err instanceof error.TimeoutError)) }) }) describe('supports promise objects', function () { it('wait succeeds', function () { let promise = new Promise((resolve) => { setTimeout(() => resolve(1), 10) }) let driver = new FakeExecutor().createDriver() return driver.wait(promise, 200).then((v) => assert.strictEqual(v, 1)) }) it('wait times out', function () { let promise = new Promise(() => { /* never resolves */ }) let driver = new FakeExecutor().createDriver() return driver.wait(promise, 5).then(fail, (err) => assert.ok(err instanceof error.TimeoutError)) }) it('wait fails if promise is rejected', function () { let err = Error('boom') let driver = new FakeExecutor().createDriver() return driver.wait(Promise.reject(err), 5).then(fail, (e) => assert.strictEqual(e, err)) }) }) it('fails if not supported condition type provided', function () { let driver = new FakeExecutor().createDriver() assert.throws(() => driver.wait({}, 5), TypeError) }) }) describe('alert handling', function () { it('alertResolvesWhenPromisedTextResolves', function () { let driver = new FakeExecutor().createDriver() let deferredText = defer() let alert = new AlertPromise(driver, deferredText.promise) deferredText.resolve(new Alert(driver, 'foo')) return alert.getText().then((text) => assert.strictEqual(text, 'foo')) }) it('cannotSwitchToAlertThatIsNotPresent', function () { let e = new error.NoSuchAlertError() let executor = new FakeExecutor().expect(CName.GET_ALERT_TEXT).andReturnError(e).end() return executor .createDriver() .switchTo() .alert() .then(assert.fail, (v) => assert.strictEqual(v, e)) }) it('commandsFailIfAlertNotPresent', function () { let e = new error.NoSuchAlertError() let executor = new FakeExecutor().expect(CName.GET_ALERT_TEXT).andReturnError(e).end() const driver = executor.createDriver() const alert = driver.switchTo().alert() const expectError = (v) => assert.strictEqual(v, e) return alert .getText() .then(fail, expectedError) .then(() => alert.accept()) .then(fail, expectedError) .then(() => alert.dismiss()) .then(fail, expectError) .then(() => alert.sendKeys('hi')) .then(fail, expectError) }) }) it('testFetchingLogs', function () { let executor = new FakeExecutor() .expect(CName.GET_LOG, { type: 'browser' }) .andReturnSuccess([ { level: 'INFO', message: 'hello', timestamp: 1234 }, { level: 'DEBUG', message: 'abc123', timestamp: 5678 }, ]) .end() const driver = executor.createDriver() return driver .manage() .logs() .get('browser') .then(function (entries) { assert.strictEqual(2, entries.length) assert.ok(entries[0] instanceof logging.Entry) assert.strictEqual(logging.Level.INFO.value, entries[0].level.value) assert.strictEqual('hello', entries[0].message) assert.strictEqual(1234, entries[0].timestamp) assert.ok(entries[1] instanceof logging.Entry) assert.strictEqual(logging.Level.DEBUG.value, entries[1].level.value) assert.strictEqual('abc123', entries[1].message) assert.strictEqual(5678, entries[1].timestamp) }) }) it('testCommandsFailIfInitialSessionCreationFailed', function () { const session = Promise.reject(new StubError()) const driver = new FakeExecutor().createDriver(session) const navigateResult = driver.get('some-url').then(fail, assertIsStubError) const quitResult = driver.quit().then(fail, assertIsStubError) return Promise.all([navigateResult, quitResult]) }) it('testWebElementCommandsFailIfInitialDriverCreationFailed', function () { const session = Promise.reject(new StubError()) const driver = new FakeExecutor().createDriver(session) return driver.findElement(By.id('foo')).click().then(fail, assertIsStubError) }) it('testWebElementCommansFailIfElementCouldNotBeFound', function () { let e = new error.NoSuchElementError('Unable to find element') let executor = new FakeExecutor() .expect(CName.FIND_ELEMENT, { using: 'css selector', value: '*[id="foo"]', }) .andReturnError(e) .end() const driver = executor.createDriver() return driver .findElement(By.id('foo')) .click() .then(fail, (v) => assert.strictEqual(v, e)) }) it('testCannotFindChildElementsIfParentCouldNotBeFound', function () { let e = new error.NoSuchElementError('Unable to find element') let executor = new FakeExecutor() .expect(CName.FIND_ELEMENT, { using: 'css selector', value: '*[id="foo"]', }) .andReturnError(e) .end() const driver = executor.createDriver() return driver .findElement(By.id('foo')) .findElement(By.id('bar')) .findElement(By.id('baz')) .then(fail, (v) => assert.strictEqual(v, e)) }) describe('actions()', function () { describe('move()', function () { it('no origin', function () { let executor = new FakeExecutor() .expect(CName.ACTIONS, { actions: [ { type: 'pointer', id: 'default mouse', parameters: { pointerType: 'mouse', }, actions: [ { duration: 100, origin: 'viewport', type: 'pointerMove', x: 0, y: 125, altitudeAngle: 0, azimuthAngle: 0, width: 0, height: 0, pressure: 0, tangentialPressure: 0, tiltX: 0, tiltY: 0, twist: 0, }, ], }, ], }) .andReturnSuccess() .end() let driver = executor.createDriver() return driver.actions().move({ x: 0, y: 125 }).perform() }) it('origin = element', function () { let executor = new FakeExecutor() .expect(CName.FIND_ELEMENT, { using: 'css selector', value: '*[id="foo"]', }) .andReturnSuccess(WebElement.buildId('abc123')) .expect(CName.ACTIONS, { actions: [ { type: 'pointer', id: 'default mouse', parameters: { pointerType: 'mouse', }, actions: [ { duration: 100, origin: WebElement.buildId('abc123'), type: 'pointerMove', x: 0, y: 125, altitudeAngle: 0, azimuthAngle: 0, width: 0, height: 0, pressure: 0, tangentialPressure: 0, tiltX: 0, tiltY: 0, twist: 0, }, ], }, ], }) .end() let driver = executor.createDriver() let element = driver.findElement(By.id('foo')) return driver.actions().move({ x: 0, y: 125, origin: element }).perform() }) }) }) describe('manage()', function () { describe('setTimeouts()', function () { describe('throws if no timeouts are specified', function () { let driver before(() => (driver = new FakeExecutor().createDriver())) it('; no arguments', function () { assert.throws(() => driver.manage().setTimeouts(), TypeError) }) it('; ignores unrecognized timeout keys', function () { assert.throws(() => driver.manage().setTimeouts({ foo: 123 }), TypeError) }) it('; ignores positional arguments', function () { assert.throws(() => driver.manage().setTimeouts(1234, 56), TypeError) }) }) describe('throws timeout is not a number, null, or undefined', () => { let driver before(() => (driver = new FakeExecutor().createDriver())) function checkError(e) { return e instanceof TypeError && /expected "(script|pageLoad|implicit)" to be a number/.test(e.message) } it('script', function () { assert.throws(() => driver.manage().setTimeouts({ script: 'abc' }), checkError) }) it('pageLoad', function () { assert.throws(() => driver.manage().setTimeouts({ pageLoad: 'abc' }), checkError) }) it('implicit', function () { assert.throws(() => driver.manage().setTimeouts({ implicit: 'abc' }), checkError) }) }) it('can set multiple timeouts', function () { let executor = new FakeExecutor() .expect(CName.SET_TIMEOUT, { script: 1, pageLoad: 2, implicit: 3 }) .andReturnSuccess() .end() let driver = executor.createDriver() return driver.manage().setTimeouts({ script: 1, pageLoad: 2, implicit: 3 }) }) it('falls back to legacy wire format if W3C version fails', () => { let executor = new FakeExecutor() .expect(CName.SET_TIMEOUT, { implicit: 3 }) .andReturnError(Error('oops')) .expect(CName.SET_TIMEOUT, { type: 'implicit', ms: 3 }) .andReturnSuccess() .end() let driver = executor.createDriver() return driver.manage().setTimeouts({ implicit: 3 }) }) }) }) })