🏡 index : quine-global/webdriverio.git

import path from 'node:path'
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
// @ts-ignore mock feature
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'
// @ts-expect-error mock feature
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'
}

// @ts-expect-error
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', () => {
            // @ts-ignore
            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()
    })
})