import path from 'node:path'
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
import logger, { logMock } from '@wdio/logger'
import { sessionEnvironmentDetector } from '@wdio/utils'
import { startWebDriver } from '@wdio/utils'
import '../src/browser.js'
import WebDriver, { getPrototype, DEFAULTS, command } from '../src/index.js'
import { initCount } from '../src/bidi/core.js'
import * as utils from '../src/utils.js'
import type { Client } from '../src/types.js'
vi.mock('geckodriver', () => ({ start: vi.fn() }))
vi.mock('@wdio/utils', () => import(path.join(process.cwd(), '__mocks__', '@wdio/utils')))
vi.mock('@wdio/utils/node', () => import(path.join(process.cwd(), '__mocks__', '@wdio/utils/node')))
vi.mock('@wdio/logger', () => import(path.join(process.cwd(), '__mocks__', '@wdio/logger')))
vi.mock('fs')
vi.mock('wait-port')
vi.mock('ws')
vi.mock('fetch')
vi.mock('../src/bidi/core.js', () => {
let initCount = 0
return {
BidiCore: class BidiHandlerMock {
connect = vi.fn().mockResolvedValue({})
constructor () {
++initCount
}
waitForConnected() {
return Promise.resolve()
}
get socket () {
return {
on: vi.fn(),
send: vi.fn(),
close: vi.fn()
}
}
},
initCount: () => initCount
}
})
const sessionOptions = {
protocol: 'http',
hostname: 'localhost',
port: 4444,
path: '/',
sessionId: 'foobar'
}
interface TestClient extends Client {
getUrl (): string
rotateDevice (): void
takeElementScreenshot (): void
getDeviceTime (): void
}
beforeEach(() => {
vi.mocked(sessionEnvironmentDetector).mockClear()
})
describe('WebDriver', () => {
it('exports getPrototype, DEFAULTS', () => {
expect(typeof getPrototype).toBe('function')
expect(typeof DEFAULTS).toBe('object')
expect(typeof command).toBe('function')
})
describe('newSession', () => {
afterEach(() => {
delete process.env.WDIO_LOG_PATH
logMock.error.mockRestore()
logMock.warn.mockRestore()
logMock.info.mockRestore()
logMock.debug.mockRestore()
})
it('should allow to create a new session using jsonwire caps', async () => {
const testDirPath = './logs'
await WebDriver.newSession({
path: '/',
outputDir: testDirPath,
capabilities: { browserName: 'firefox' }
})
expect(fetch).toHaveBeenCalledWith(
expect.objectContaining({ pathname: '/session' }),
expect.objectContaining({ body: JSON.stringify({
capabilities: {
alwaysMatch: {
browserName: 'firefox',
webSocketUrl: true,
unhandledPromptBehavior: 'ignore'
},
firstMatch: [{}]
}
}) })
)
})
it('should allow to create a new session using w3c compliant caps', async () => {
await WebDriver.newSession({
path: '/',
capabilities: {
alwaysMatch: { browserName: 'firefox' },
firstMatch: [{}]
}
})
expect(fetch).toHaveBeenCalledWith(
expect.objectContaining({ pathname: '/session' }),
expect.objectContaining({ body: JSON.stringify({
capabilities: {
alwaysMatch: {
browserName: 'firefox',
webSocketUrl: true,
unhandledPromptBehavior: 'ignore'
},
firstMatch: [{}]
}
}) })
)
expect(vi.mocked(sessionEnvironmentDetector).mock.calls)
.toMatchSnapshot()
})
it('should be possible to skip setting logLevel', async () => {
await WebDriver.newSession({
capabilities: { browserName: 'chrome' },
logLevel: 'info',
logLevels: { webdriver: 'silent' }
})
expect(logger.setLevel).not.toBeCalled()
})
it('should be possible to set logLevel', async () => {
await WebDriver.newSession({
capabilities: { browserName: 'chrome' },
logLevel: 'info'
})
expect(logger.setLevel).toBeCalled()
})
it('propagates capabilities and requestedCapabilities', async () => {
const browser = await WebDriver.newSession({
path: '/',
capabilities: { browserName: 'firefox' }
})
expect(browser.capabilities.browserName).toBe('mockBrowser')
expect((browser.requestedCapabilities as WebdriverIO.Capabilities).browserName).toBe('firefox')
})
it('attaches bidi handler if socket url is given', async () => {
const wid = process.env.WDIO_UNIT_TESTS
delete process.env.WDIO_UNIT_TESTS
vi.mocked(fetch).mockResolvedValueOnce(Response.json({ value: { webSocketUrl: 'ws://foo/bar' } }))
await WebDriver.newSession({
path: '/',
capabilities: { browserName: 'firefox' }
})
expect(initCount()).toBe(1)
process.env.WDIO_UNIT_TESTS = wid
})
it('should call "initiateBidi" with correct arguments', async () => {
const webSocketUrl = 'ws://foo/bar'
const strictSSL = true
const headers = { 'Authorization': 'OAuth 12345' }
vi.spyOn(utils, 'initiateBidi')
vi.mocked(fetch).mockResolvedValueOnce(Response.json({ value: { webSocketUrl } }))
await WebDriver.newSession({
path: '/',
capabilities: { browserName: 'firefox' },
strictSSL,
headers
})
expect(utils.initiateBidi).toHaveBeenCalledWith(webSocketUrl, strictSSL, headers)
})
})
describe('attachToSession', () => {
it('should allow to attach to existing session', async () => {
const client = WebDriver.attachToSession({ ...sessionOptions, logLevel: 'error' }) as unknown as TestClient
await client.getUrl()
expect(fetch).toHaveBeenCalledWith(
expect.objectContaining({ href: 'http://localhost:4444/session/foobar/url' }),
expect.anything()
)
expect(logger.setLevel).toBeCalled()
})
it('should allow to attach to existing session2', async () => {
const client = WebDriver.attachToSession({ ...sessionOptions }) as unknown as TestClient
await client.getUrl()
expect(fetch).toHaveBeenCalledWith(
expect.objectContaining({ href: 'http://localhost:4444/session/foobar/url' }),
expect.anything()
)
expect(logger.setLevel).not.toBeCalled()
})
it('should allow to attach to existing session - W3C', async () => {
const client = WebDriver.attachToSession({ ...sessionOptions }) as unknown as TestClient
await client.getUrl()
expect(client.isChromium).toBeFalsy()
expect(client.isMobile).toBeFalsy()
expect(client.isSauce).toBeFalsy()
expect(client.rotateDevice).toBeFalsy()
expect(client.takeElementScreenshot).toBeTruthy()
expect(client.getDeviceTime).toBeFalsy()
})
it('should allow to attach to existing session - mobile', async () => {
const client = WebDriver.attachToSession({ ...sessionOptions,
isChromium: true,
isMobile: true
}) as unknown as TestClient
await client.getUrl()
expect(client.isChromium).toBe(true)
expect(client.isMobile).toBe(true)
expect(client.rotateDevice).toBeTruthy()
expect(client.takeElementScreenshot).toBeTruthy()
expect(client.getDeviceTime).toBeTruthy()
})
it('it should propagate all environment flags', () => {
const client = WebDriver.attachToSession({ ...sessionOptions,
isW3C: false,
isMobile: false,
isIOS: false,
isAndroid: false,
isChromium: false,
isSauce: false,
isWindowsApp: false,
isMacApp: false
})
expect(client.isW3C).toBe(false)
expect(client.isMobile).toBe(false)
expect(client.isIOS).toBe(false)
expect(client.isAndroid).toBe(false)
expect(client.isChromium).toBe(false)
expect(client.isSauce).toBe(false)
expect(client.isWindowsApp).toBe(false)
expect(client.isMacApp).toBe(false)
const anotherClient = WebDriver.attachToSession({ ...sessionOptions,
isW3C: true,
isMobile: true,
isIOS: true,
isAndroid: true,
isChromium: true,
isSauce: true,
isWindowsApp: true,
isMacApp: true
})
expect(anotherClient.isW3C).toBe(true)
expect(anotherClient.isMobile).toBe(true)
expect(anotherClient.isIOS).toBe(true)
expect(anotherClient.isAndroid).toBe(true)
expect(anotherClient.isChromium).toBe(true)
expect(anotherClient.isSauce).toBe(true)
expect(anotherClient.isWindowsApp).toBe(true)
expect(anotherClient.isMacApp).toBe(true)
})
it('should apply default connection details', () => {
const client = WebDriver.attachToSession({ sessionId: '123', port: 4321 })
expect(client.options.protocol).toBe('http')
expect(client.options.hostname).toBe('localhost')
expect(client.options.port).toBe(4321)
expect(client.options.path).toBe('/')
})
it('should allow to attach to appium session', async () => {
const client = WebDriver.attachToSession({
...sessionOptions,
capabilities: {
'appium:automationName': 'foo',
'platformName': 'ios',
}
}) as unknown as TestClient
expect(client.isMobile).toBe(true)
expect(client.isLocked).toBeTruthy()
expect(client.shake).toBeTruthy()
})
it('should fail attaching to session if sessionId is not given', () => {
expect(() => WebDriver.attachToSession({}))
.toThrow(/sessionId is required/)
})
})
describe('reloadSession', () => {
it('should reload session', async () => {
const session = await WebDriver.newSession({
path: '/',
capabilities: { browserName: 'firefox' }
})
vi.mocked(startWebDriver).mockClear()
await WebDriver.reloadSession(session)
expect(startWebDriver).not.toHaveBeenCalledOnce()
expect(fetch).toHaveBeenCalledTimes(2)
})
it('starts a new driver process if browserName is given', async () => {
vi.mocked(startWebDriver).mockImplementation((params) => {
params.hostname = 'localhost'
params.port = 4444
return { pid: 1234 } as any
})
const session = await WebDriver.newSession({
path: '/',
capabilities: { browserName: 'firefox' }
})
vi.mocked(startWebDriver).mockClear()
await WebDriver.reloadSession(session, { browserName: 'chrome' })
expect(startWebDriver).toHaveBeenCalledOnce()
expect((session.capabilities as WebdriverIO.Capabilities)['wdio:driverPID']).toBe(1234)
})
it('connects to the new remote', async () => {
const session = await WebDriver.newSession({
path: '/',
capabilities: { browserName: 'firefox' }
})
vi.mocked(startWebDriver).mockClear()
await WebDriver.reloadSession(session, { protocol: 'https', hostname: '1.1.1.1', port: 5555, browserName: 'chrome' })
expect(startWebDriver).not.toHaveBeenCalledOnce()
expect(session.options.protocol).toBe('https')
expect(session.options.hostname).toBe('1.1.1.1')
expect(session.options.port).toBe(5555)
await WebDriver.reloadSession(session, { protocol: 'http', hostname: 'localhost', browserName: 'firefox' })
expect(startWebDriver).toHaveBeenCalledOnce()
expect(session.options.protocol).toBe('http')
expect(session.options.hostname).toBe('localhost')
expect((session.requestedCapabilities as WebdriverIO.Capabilities)['browserName']).toBe('firefox')
})
})
it('ensure that WebDriver interface exports protocols and other objects', () => {
expect(WebDriver.WebDriver).not.toBe(undefined)
})
afterEach(() => {
vi.mocked(logger.setLevel).mockClear()
vi.mocked(fetch).mockClear()
vi.mocked(sessionEnvironmentDetector).mockClear()
vi.mocked(startWebDriver).mockClear()
})
})