🏡 index : quine-global/webdriverio.git

import { describe, it, beforeEach, expect, vi, afterEach } from 'vitest'
import path from 'node:path'
import WebDriver from 'webdriver'
import logger from '@wdio/logger'
import { validateConfig } from '@wdio/config'

import detectBackend from '../src/utils/detectBackend.js'
import { remote, multiremote, attach, Key, SevereServiceError } from '../src/index.js'

vi.mock('../src/utils/detectBackend', () => ({ default: vi.fn() }))
vi.mock('@wdio/logger', () => import(path.join(process.cwd(), '__mocks__', '@wdio/logger')))
vi.mock('webdriver', () => {
    const client = {
        sessionId: 'foobar-123',
        options: {},
        addCommand: vi.fn(),
        overwriteCommand: vi.fn(),
        strategies: new Map(),
        isWebDriver: true,
        capabilities: { webSocketUrl: 'ws://' },
        on: vi.fn()
    }
    const newSessionMock = vi.fn()
    newSessionMock.mockReturnValue(new Promise((resolve) => resolve(client)))
    newSessionMock.mockImplementation((params, cb) => {
        const result = cb(client, params)
        // @ts-ignore mock feature
        if (params.test_multiremote) {
            result.options = { logLevel: 'error' }
        }
        return result
    })
    const attachToSessionMock = vi.fn().mockReturnValue(client)
    return {
        DEFAULTS: {},
        default: class WebDriverMock {
            static newSession = newSessionMock
            static attachToSession = attachToSessionMock
        }
    }
})

vi.mock('@wdio/config', () => {
    const validateConfigMock = {
        validateConfig: vi.fn((_, args) => args),
        detectBackend: vi.fn(),
    }
    return validateConfigMock
})

vi.mock('http', () => {
    let response = { statusCode: 404 }
    const reqCall = { on: vi.fn(), end: vi.fn() }
    return {
        default: {
            request: vi.fn().mockImplementation((url, cb) => {
                cb(response)
                return reqCall
            }),
            setResonse: (res: any) => (response = res),
            Agent: vi.fn()
        }
    }
})

describe('WebdriverIO module interface', () => {
    beforeEach(() => {
        vi.mocked(WebDriver.newSession).mockClear()
        vi.mocked(WebDriver.attachToSession).mockClear()
        vi.mocked(detectBackend).mockClear()
    })

    it('should provide all exports', () => {
        expect(typeof remote).toBe('function')
        expect(typeof attach).toBe('function')
        expect(typeof multiremote).toBe('function')
        expect(typeof Key).toBe('object')
        expect(typeof SevereServiceError).toBe('function')
    })

    describe('remote function', () => {
        it('creates a webdriver session', async () => {
            const options: any = {
                capabilities: { browserName: 'chrome' },
                logLevel: 'trace'
            }
            const browser = await remote(options)
            expect(browser.sessionId).toBe('foobar-123')
            expect(logger.setLogLevelsConfig).toBeCalledWith(undefined, 'trace')
        })

        it('allows to propagate a modifier', async () => {
            const browser = await remote({
                automationProtocol: 'webdriver',
                capabilities: { browserName: 'chrome' }
            }, (client: any) => {
                client.foobar = 'barfoo'
                return client
            })
            expect(browser.sessionId).toBe('foobar-123')
            // @ts-ignore mock feature
            expect(browser.foobar).toBe('barfoo')
        })

        it('should try to detect the backend', async () => {
            await remote({
                automationProtocol: 'webdriver',
                user: 'foo',
                key: 'bar',
                capabilities: { browserName: 'chrome' }
            })
            expect(detectBackend).toBeCalled()
        })

        it('should attach custom locators to the strategies', async () => {
            const browser = await remote({
                automationProtocol: 'webdriver',
                capabilities: { browserName: 'chrome' }
            })
            const fakeFn = () => { return 'test' as unknown as HTMLElement }

            browser.addLocatorStrategy('test-strat', fakeFn)
            expect(browser.strategies.get('test-strat').toString()).toBe(fakeFn.toString())
        })

        it('throws error if trying to overwrite locator strategy', async () => {
            // @ts-ignore uses expect-webdriverio
            expect.assertions(1)
            const browser = await remote({
                automationProtocol: 'webdriver',
                capabilities: { browserName: 'chrome' }
            })

            try {
                const fakeFn = () => { return 'test' as unknown as HTMLElement }
                browser.addLocatorStrategy('test-strat', fakeFn)
            } catch (error: any) {
                browser.strategies.delete('test-strat')
                expect(error.message).toBe('Strategy test-strat already exists')
            }
        })

        it('should properly create stub instance', async () => {
            vi.mocked(validateConfig).mockReturnValueOnce({
                automationProtocol: './protocol-stub.js'
            })
            const browser = await remote({ capabilities: { browserName: 'chrome' } })

            expect(browser.sessionId).toBeUndefined()
            expect(browser.capabilities).toEqual({
                browserName: 'chrome',
                'goog:chromeOptions': {}
            })

            const flags: any = {}
            Object.entries(browser).forEach(([key, value]) => {
                if (key.startsWith('is')) {
                    flags[key] = value
                }
            })
            expect(flags).toEqual({
                isAndroid: false,
                isChrome: true,
                isChromium: true,
                isFirefox: false,
                isIOS: false,
                isMobile: false,
                isSauce: false,
                isBidi: false,
                isWindowsApp: false,
                isMacApp: false,
            })
        })
    })

    describe('multiremote', () => {
        it('register multiple clients', async () => {
            await multiremote({
                browserA: {
                    // @ts-ignore mock feature
                    test_multiremote: true,
                    automationProtocol: 'webdriver',
                    capabilities: { browserName: 'chrome' }
                },
                browserB: {
                    // @ts-ignore mock feature
                    test_multiremote: true,
                    automationProtocol: 'webdriver',
                    capabilities: { browserName: 'firefox' }
                }
            })
            expect(WebDriver.attachToSession).toBeCalled()
            /**
             * started to be flaky in CI
             */
            // expect(vi.mocked(WebDriver.newSession).mock.calls).toHaveLength(2)
        })

        it('should attach custom locators to the strategies', async () => {
            const driver = await multiremote({
                browserA: {
                    automationProtocol: 'webdriver',
                    // @ts-ignore mock feature
                    test_multiremote: true,
                    capabilities: { browserName: 'chrome' }
                },
                browserB: {
                    automationProtocol: 'webdriver',
                    // @ts-ignore mock feature
                    test_multiremote: true,
                    capabilities: { browserName: 'firefox' }
                }
            })

            const fakeFn = () => { return 'test' as unknown as HTMLElement }
            driver.addLocatorStrategy('test-strat', fakeFn)
            expect(driver.strategies.get('test-strat').toString()).toBe(fakeFn.toString())
        })

        it('throws error if trying to overwrite locator strategy', async () => {
            // @ts-ignore uses expect-webdriverio
            expect.assertions(1)
            const driver = await multiremote({
                // @ts-ignore mock feature
                browserA: { automationProtocol: 'webdriver', test_multiremote: true, capabilities: { browserName: 'chrome' } },
                // @ts-ignore mock feature
                browserB: { automationProtocol: 'webdriver', test_multiremote: true, capabilities: { browserName: 'firefox' } }
            })

            try {
                const fakeFn = () => { return 'test' as unknown as HTMLElement }
                driver.addLocatorStrategy('test-strat', fakeFn)
            } catch (error: any) {
                driver.strategies.delete('test-strat')
                expect(error.message).toBe('Strategy test-strat already exists')
            }
        })
    })

    describe('attach', () => {
        it('attaches', async () => {
            const browser = {
                sessionId: 'foobar',
                capabilities: {
                    browserName: 'chrome',
                    platformName: 'MacOS'
                },
                requestedCapabilities: {
                    browserName: 'chrome'
                }
            }
            await attach(browser)
            expect(WebDriver.attachToSession).toBeCalledTimes(1)
            expect(vi.mocked(WebDriver.attachToSession).mock.calls[0][0]).toMatchSnapshot()
        })

        it('should have defined locatorStrategy', async () => {
            const browser = {
                sessionId: 'foobar',
                capabilities: {
                    browserName: 'chrome',
                    platformName: 'MacOS'
                },
                requestedCapabilities: {
                    browserName: 'chrome'
                }
            }
            const newBrowser = await attach(browser)
            expect(newBrowser).toHaveProperty('addLocatorStrategy')
        })
    })

    afterEach(() => {
        vi.mocked(WebDriver.attachToSession).mockClear()
        vi.mocked(WebDriver.newSession).mockClear()
    })
})