🏡 index : quine-global/webdriverio.git

import path from 'node:path'
import { vi, describe, it, expect, beforeEach } from 'vitest'
import webdriverMonad from '../src/monad.js'
import logger from '@wdio/logger'
const log = logger('webdriver')

let prototype: any

vi.mock('@wdio/logger', () => import(path.join(process.cwd(), '__mocks__', '@wdio/logger')))

beforeEach(() => {
    prototype = {
        scope: { value: '' },
        someFunc: { value: vi.fn().mockImplementation((arg) => `result-${arg.toString()}`) }
    }
})

const sessionId = 'c5fa4320-07d5-48f5-b7c2-922d4405e17f'

describe('monad', () => {
    it('should be able to initialize client with prototype with commands', () => {
        const modifier = vi.fn()
        const monad = webdriverMonad({ baseUrl: 'option' }, (client: any) => {
            modifier()
            return client
        }, prototype)
        const client = monad(sessionId)

        expect(client.sessionId).toBe(sessionId)
        expect(client.options).toEqual({ baseUrl: 'option' })
        expect(client.commandList).toHaveLength(1)
        expect(client.commandList[0]).toBe('someFunc')

        client.someFunc(123)
        expect(prototype.someFunc.value.mock.calls).toHaveLength(1)
        expect(prototype.someFunc.value.mock.calls[0][0]).toBe(123)
        expect(client.constructor.name).toBe('Browser')
    })

    it('should allow to set element scope name', () => {
        prototype.scope.value = 'element'
        const monad = webdriverMonad({}, (client: any) => client, prototype)
        const client = monad(sessionId)
        expect(client.constructor.name).toBe('Element')
    })

    it('should allow to extend base prototype', () => {
        const monad = webdriverMonad({}, (client: any) => client, prototype)
        const commandWrapperMock = vi.fn().mockImplementation((name, fn) => fn)
        const client = monad(sessionId, commandWrapperMock)
        const fn = () => 'bar'

        client.addCommand('foo', fn)
        expect(client.foo()).toBe('bar')
    })

    it('should allow to overwrite command in base prototype', () => {
        const monad = webdriverMonad({}, (client: any) => client, { ...prototype })
        const commandWrapperMock = vi.fn().mockImplementation((name, fn) => fn)
        const client = monad(sessionId, commandWrapperMock)
        const fn = () => 'bar'

        client.overwriteCommand('someFunc', fn)
        expect(client.someFunc()).toBe('bar')
    })

    it('should throw if there is no command to be overwritten', () => {
        const monad = webdriverMonad({}, (client: any) => client, { ...prototype })
        const commandWrapperMock = vi.fn().mockImplementation((name, fn) => fn)
        const client = monad(sessionId, commandWrapperMock)
        const fn = () => 'bar'

        expect(() => client.overwriteCommand('someFunc2', fn))
            .toThrow('overwriteCommand: no command to be overwritten: someFunc2')
    })

    it('should add element commands to the __propertiesObject__ cache', () => {
        const monad = webdriverMonad({}, (client: any) => client, prototype)
        const client = monad(sessionId)

        const func = function (x: number, y: number) { return x + y }

        client.addCommand('myCustomElementCommand', func, true)
        expect(typeof client.__propertiesObject__.myCustomElementCommand).toBe('object')
        expect(client.__propertiesObject__.myCustomElementCommand.value).toBe(func)
    })

    it('should add element commands for override to the __propertiesObject__.__elementOverrides__ cache', () => {
        const monad = webdriverMonad({}, (client: any) => client, { ...prototype })
        const client = monad(sessionId)

        const func = function (x: number, y: number) { return x + y }

        client.overwriteCommand('someFunc', func, true)
        expect(client.__propertiesObject__.__elementOverrides__.value.someFunc(2, 3)).toBe(5)
    })

    it('should add element commands to the __propertiesObject__ cache in multiremote', () => {
        const monad = webdriverMonad({}, (client: any) => client, prototype)
        const client = monad(sessionId)
        const instances = { foo: { __propertiesObject__: { myCustomElementCommand: { value: undefined } } } }

        const func = function (x: number, y: number) { return x + y }

        client.addCommand('myCustomElementCommand', func, true, undefined, instances)
        expect(typeof instances.foo.__propertiesObject__.myCustomElementCommand).toBe('object')
        expect(instances.foo.__propertiesObject__.myCustomElementCommand.value).toBe(func)
    })

    it('should add element commands for override to the __propertiesObject__.__elementOverrides__ cache in multiremote', () => {
        const monad = webdriverMonad({}, (client: any) => client, { ...prototype })
        const client = monad(sessionId)
        const instances = {
            foo: {
                __propertiesObject__: {
                    __elementOverrides__: {
                        value: {
                            someFunc: (x: number, y: number) => x - y
                        }
                    }
                }
            }
        }

        const func = function (x: number, y: number) { return x + y }

        client.overwriteCommand('someFunc', func, true, undefined, instances)
        expect(instances.foo.__propertiesObject__.__elementOverrides__.value.someFunc(4, 5)).toBe(9)
    })

    it('allows to use custom command wrapper', () => {
        const monad = webdriverMonad({}, (client: any) => client, prototype)
        const client = monad(sessionId, (commandName: string, commandFn: Function) => {
            return (...args: any[]) => {
                return `${commandName}(${args.join(', ')}) = ${commandFn(...args)}`
            }
        })
        expect(client.someFunc(123)).toBe('someFunc(123) = result-123')
    })

    it('should allow empty prototype object', () => {
        const monad = webdriverMonad({}, (client: any) => client)
        const client = monad(sessionId)
        expect(client.commandList).toHaveLength(0)
    })

    it('should be ok without modifier', () => {
        const monad = webdriverMonad({})
        const client = monad(sessionId)
        expect(client.commandList).toHaveLength(0)
    })

    describe('given custom command', () => {
        let client: any
        beforeEach(() => {
            const monad = webdriverMonad({}, (client: any) => client, prototype)
            const commandWrapperMock = vi.fn().mockImplementation((_name, fn) => fn)
            client = monad(sessionId, commandWrapperMock)
            client.emit = vi.fn()
        })
        describe('when custom command is a function', () => {
            const fn = vi.fn().mockImplementation(() => 'command result')
            const customCommandName = 'customFunctionCommand'
            beforeEach(() => {
                client.addCommand('customFunctionCommand', fn)
            })

            it('should return result when running custom command as a function and emit command and result', () => {
                const result = client.customFunctionCommand()

                expect(result).toBe('command result')
                expect(client.emit).toHaveBeenNthCalledWith(1, 'command', { command: customCommandName, body: [] })
                expect(client.emit).toHaveBeenNthCalledWith(2, 'result', { command: customCommandName, result: { value: 'command result' } })
                expect(client.emit).toHaveBeenCalledTimes(2)
            })

            it('should throw and emit result when throwing an error', () => {
                const error1 = new Error('command error')
                fn.mockImplementation(() => {throw error1})

                expect(() => client.customFunctionCommand()).toThrowError('command error')
                expect(client.emit).toHaveBeenNthCalledWith(1, 'command', { command: customCommandName, body: [] })
                expect(client.emit).toHaveBeenNthCalledWith(2, 'result', { command: customCommandName, result: { error: error1 } })
                expect(client.emit).toHaveBeenCalledTimes(2)
            })
        })
        describe('when custom command is a Promise', () => {
            const expectedResult = 'command promise result'
            const customCommandName = 'customPromiseCommand'
            const fn = vi.fn().mockImplementation(async () => Promise.resolve(expectedResult))
            beforeEach(() => {
                client.addCommand(customCommandName, fn)
            })

            it('should return result when running custom command and emit command + result', async () => {
                const result = await client.customPromiseCommand()

                expect(result).toBe(expectedResult)
                expect(client.emit).toHaveBeenNthCalledWith(1, 'command', { command: customCommandName, body: [] })
                expect(client.emit).toHaveBeenNthCalledWith(2, 'result', { command: customCommandName, result: { value: expectedResult }, name: customCommandName })
                expect(client.emit).toHaveBeenCalledTimes(2)
            })

            it('should reject promise and emit result when throwing an error', async () => {
                const error2 = new Error('command promise error')
                fn.mockImplementation(async () => {throw error2})

                const promise = client.customPromiseCommand()

                expect(client.emit).toHaveBeenNthCalledWith(1, 'command', { command: customCommandName, body: [],  })
                await expect(promise).rejects.toThrowError('command promise error')
                expect(client.emit).toHaveBeenNthCalledWith(2, 'result', { command: customCommandName, result: { error: error2 } })
                expect(client.emit).toHaveBeenCalledTimes(2)
            })
        })
    })

    describe('given element scope', () => {
        let monad: any
        let ElementCtor: any

        beforeEach(() => {
            monad = webdriverMonad({}, (client: any) => client, { scope: { value: 'element' } })
            const elementInstance = monad('some-session-id')
            ElementCtor = elementInstance.constructor
        })

        it('should log command and result for element scope', async () => {
            const browser = monad(sessionId)

            // Add a command that returns an instance of ElementCtor
            browser.addCommand('myElementCommand', async function () {
                // @ts-ignore
                const element = new ElementCtor()
                element.elementId = 'abc123'
                return element
            })

            // Call the command
            await browser.myElementCommand()
            expect(log.info).toHaveBeenCalledWith('COMMAND', 'myElementCommand()')
            expect(log.info).toHaveBeenCalledWith('RESULT', 'WebdriverIO.Element<abc123>')
        })
    })

    describe('given browser scope', () => {
        let monad: any
        let BrowserCtor: any

        beforeEach(() => {
            monad = webdriverMonad({}, (client: any) => client, { scope: { value: 'browser' } })
            const browserInstance = monad('some-session-id')
            BrowserCtor = browserInstance.constructor
        })

        it('should log command and result for element scope', async () => {
            const browser = monad(sessionId)

            // Add a command that returns an instance of ElementCtor
            browser.addCommand('myElementCommand', async function () {
                // @ts-ignore
                const browser = new BrowserCtor()
                return browser
            })

            // Call the command
            await browser.myElementCommand()
            expect(log.info).toHaveBeenCalledWith('COMMAND', 'myElementCommand()')
            expect(log.info).toHaveBeenCalledWith('RESULT', 'WebdriverIO.Browser')
        })
    })
})