blob: f6530a5318a4970566401bc2e91b149c302a3ae6 [file]
// 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 { WebSocketServer } = require('ws')
const { getBidiConnection, closeBidiConnection } = require('selenium-webdriver/lib/bidi_connection')
const { WebDriver } = require('selenium-webdriver/lib/webdriver')
const { Session } = require('selenium-webdriver/lib/session')
function startEchoServer() {
return new Promise((resolve) => {
const server = new WebSocketServer({ port: 0 }, () => {
const { port } = server.address()
resolve({ server, url: `ws://127.0.0.1:${port}` })
})
server.on('connection', (ws) => {
ws.on('message', (data) => {
const { id } = JSON.parse(data.toString())
ws.send(JSON.stringify({ id, result: {} }))
})
})
})
}
describe('bidi_connection', function () {
let server
afterEach(async function () {
if (server !== undefined) {
await new Promise((resolve) => server.close(resolve))
server = undefined
}
})
it('creates exactly one BiDi connection under concurrent first access', async function () {
const started = await startEchoServer()
server = started.server
let capabilitiesCalls = 0
const driver = {
async getCapabilities() {
capabilitiesCalls++
// Yield the event loop before resolving, so concurrent callers race.
await new Promise((resolve) => setTimeout(resolve, 20))
return { map_: new Map([['webSocketUrl', started.url]]) }
},
}
const [a, b, c] = await Promise.all([
getBidiConnection(driver),
getBidiConnection(driver),
getBidiConnection(driver),
])
assert.strictEqual(capabilitiesCalls, 1, 'getCapabilities() should only be called once')
assert.strictEqual(a, b)
assert.strictEqual(b, c)
await closeBidiConnection(driver)
})
it('closeBidiConnection() is a no-op when no connection was ever opened', async function () {
const driver = {
async getCapabilities() {
throw new Error('should not be called')
},
}
await assert.doesNotReject(closeBidiConnection(driver))
})
it('closeBidiConnection() does not reject when the original connection attempt failed', async function () {
const driver = {
async getCapabilities() {
throw new Error('driver does not support BiDi')
},
}
await assert.rejects(getBidiConnection(driver), /does not support BiDi/)
// quit() calls this fire-and-forget (no await/catch at the call site), so
// it must swallow the earlier failure rather than re-throwing it.
await assert.doesNotReject(closeBidiConnection(driver))
})
it('quit() closes the BiDi connection stored outside the driver instance', async function () {
const started = await startEchoServer()
server = started.server
// Resolves only once the server observes the client actually closing the
// socket — if quit() stopped delegating to closeBidiConnection(), this
// would never resolve and the test would time out rather than pass.
const serverSawClose = new Promise((resolve) => {
server.on('connection', (ws) => ws.on('close', () => resolve(true)))
})
const session = new Session('test-session-id', { webSocketUrl: started.url })
const driver = new WebDriver(session, { execute: async () => null })
// Open the connection the same way a composed BiDi module would (via the
// driver, not by calling getBidiConnection/closeBidiConnection directly),
// so there is a real connection for quit() to close. getBidiConnection()
// resolves as soon as the BiDi instance is constructed, not once its
// websocket handshake actually completes — wait for that too, or closing
// immediately can race the handshake and the server never sees 'connection'.
const connection = await getBidiConnection(driver)
await connection.waitForConnection()
await driver.quit()
assert.strictEqual(await serverSawClose, true, "quit() should close the driver's BiDi connection")
})
})