blob: 419fdf6f80212334a2ae87f90ab65699a45c8ef5 [file] [log] [blame]
// 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 })
})
})
})
})