import path from 'node:path'
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest'
import { executeHooksWithArgs, executeAsync, wrapCommand } from '../src/shim.js'
const globalAny: any = global
vi.mock('@wdio/logger', () => import(path.join(process.cwd(), '__mocks__', '@wdio/logger')))
beforeEach(() => {
globalAny.browser = {} as WebdriverIO.Browser
})
afterEach(() => {
delete globalAny.browser
})
describe('executeHooksWithArgs', () => {
it('multiple hooks, multiple args', async () => {
const hookHoge = () => { return 'hoge' }
const hookFuga = () => { return 'fuga' }
const argHoge = { hoge: 'hoge' }
const argFuga = { fuga: 'fuga' }
const res = await executeHooksWithArgs('hookName', [hookHoge, hookFuga], [argHoge, argFuga])
expect(res).toEqual(['hoge', 'fuga'])
})
it('one hook, one arg', async () => {
const hook = () => { return 'hoge' }
const arg = { hoge: 'hoge' }
const res = await executeHooksWithArgs('hookName', hook, arg)
expect(res).toHaveLength(1)
expect(res).toContain('hoge')
})
it('with error', async () => {
const hook = () => { throw new Error('Hoge') }
const res = await executeHooksWithArgs('hookName', hook, [])
expect(res).toHaveLength(1)
expect(res).toEqual([new Error('Hoge')])
})
it('return promise with error', async () => {
const hook = () => {
return new Promise(() => { throw new Error('Hoge') })
}
const res = await executeHooksWithArgs('hookName', hook, [])
expect(res).toHaveLength(1)
expect(res).toEqual([new Error('Hoge')])
})
it('async functions', async () => {
const hookHoge = () => {
return new Promise(reject => setTimeout(reject, 5, new Error('Hoge')))
}
const hookFuga = async () => new Promise(resolve => setTimeout(resolve, 10, 'fuga'))
const res = await executeHooksWithArgs('hookName', [hookHoge, hookFuga], [])
expect(res).toEqual([new Error('Hoge'), 'fuga'])
})
})
describe('executeAsync', () => {
it('should pass with default values and fn returning synchronous value', async () => {
const result = await executeAsync.call({}, async () => 'foo', { limit: 0, attempts: 0 })
expect(result).toEqual('foo')
})
it('should pass when optional arguments are passed', async () => {
const result = await executeAsync.call({}, async (arg: unknown) => arg, { limit: 1, attempts: 0 }, ['foo'])
expect(result).toEqual('foo')
})
it('should reject if fn throws error directly', async () => {
let error
const fn = () => { throw new Error('foo') }
try {
await executeAsync.call({}, fn, { limit: 0, attempts: 0 })
} catch (err: any) {
error = err
}
expect(error.message).toEqual('foo')
})
it('should repeat if fn throws error directly and repeatTest provided', async () => {
let counter = 3
const scope = { wdioRetries: undefined }
const repeatTest = { limit: counter, attempts: 0 }
const result = await executeAsync.call(scope, () => {
if (counter > 0) {
counter--
throw new Error('foo')
}
return Promise.resolve(true)
}, repeatTest)
expect(result).toEqual(true)
expect(counter).toEqual(0)
expect(repeatTest).toEqual({ limit: 3, attempts: 3 })
expect(scope.wdioRetries).toEqual(3)
})
it('should repeat if fn rejects and repeatTest provided', async () => {
let counter = 3
const scope = { wdioRetries: undefined }
const repeatTest = { limit: counter, attempts: 0 }
const result = await executeAsync.call(scope, () => {
if (counter > 0) {
counter--
return Promise.reject(new Error('foo'))
}
return Promise.resolve(true)
}, repeatTest)
expect(result).toEqual(true)
expect(counter).toEqual(0)
expect(repeatTest).toEqual({ limit: 3, attempts: 3 })
expect(scope.wdioRetries).toEqual(3)
})
})
describe('wrapCommand', () => {
it('should not run a command hook in command hook', async () => {
const rawCommand = vi.fn().mockReturnValue(Promise.resolve('Yayy!'))
const commandA = wrapCommand('foobar', rawCommand)
const commandB = wrapCommand('barfoo', rawCommand)
const scope: any = {
options: {
beforeCommand: vi.fn(),
afterCommand: vi.fn().mockImplementation(
() => commandB.call(scope, 123))
}
}
expect(await commandA.call(scope, true, false, '!!')).toBe('Yayy!')
expect(scope.options!.beforeCommand).toBeCalledTimes(1)
expect(scope.options!.afterCommand).toBeCalledTimes(1)
expect(rawCommand).toBeCalledTimes(2)
})
it('throws an error if command fails', async () => {
const rawCommand = vi.fn().mockReturnValue(
Promise.reject(new Error('Uppsi!')))
const commandA = wrapCommand('foobar', rawCommand)
const commandB = wrapCommand('barfoo', rawCommand)
const scope: any = {
options: {
beforeCommand: vi.fn(),
afterCommand: vi.fn().mockImplementation(
() => commandB.call(scope, 123))
}
}
const error = await commandA.call(scope, true, false, '!!')
.catch((err: Error) => err)
expect((error as Error).message).toBe('Uppsi!')
expect(scope.options!.beforeCommand).toBeCalledTimes(1)
expect(scope.options!.afterCommand).toBeCalledTimes(1)
expect(rawCommand).toBeCalledTimes(2)
})
it('allows to chain element promises', async () => {
const rawCommand = vi.fn()
const scope: any = {
options: {
beforeCommand: vi.fn(),
afterCommand: vi.fn()
},
getTagName: vi.fn().mockResolvedValue('Yayy'),
$: rawCommand
}
rawCommand.mockReturnValue(Promise.resolve(scope))
const commandA = wrapCommand('$', rawCommand)
expect(await commandA.call(scope, 'bar')
.$('foo')
.getTagName()
).toBe('Yayy')
expect(scope.$).toBeCalledTimes(2)
expect(scope.$).toBeCalledWith('bar')
expect(scope.$).toBeCalledWith('foo')
expect(scope.getTagName).toBeCalledTimes(1)
})
it('allows to chain element promises for custom command', async () => {
const rawCommand = vi.fn()
const scope: any = {
options: {
beforeCommand: vi.fn(),
afterCommand: vi.fn()
},
getTagName: vi.fn().mockResolvedValue('Yayy'),
user$: rawCommand
}
rawCommand.mockReturnValue(Promise.resolve(scope))
const commandB = wrapCommand('user$', rawCommand)
expect(await commandB.call(scope, 'bar')
.user$('foo')
.getTagName()).toBe('Yayy')
expect(scope.user$).toBeCalledTimes(2)
expect(scope.user$).toBeCalledWith('bar')
expect(scope.user$).toBeCalledWith('foo')
expect(scope.getTagName).toBeCalledTimes(1)
})
it('allows to access indexed element', async () => {
const rawCommand$ = vi.fn()
const rawCommand$$ = vi.fn()
const scope: (i: number) => any = (i) => ({
options: {
beforeCommand: vi.fn(),
afterCommand: vi.fn()
},
getTagName: vi.fn().mockResolvedValue('Yayy' + i),
$: rawCommand$,
$$: rawCommand$$
})
rawCommand$.mockResolvedValue(scope(0))
rawCommand$$.mockReturnValue([
Promise.resolve(scope(0)),
Promise.resolve(scope(1)),
Promise.resolve(scope(2))
])
const commandA = wrapCommand('$', rawCommand$)
expect(await commandA.call(scope(0))
.$('foo')
.$$('bar')[2]
.getTagName()
).toBe('Yayy2')
expect(await commandA.call(scope(0))
.$('foo')
.$$('bar')[2]
.$('barfoo')
.getTagName()
).toBe('Yayy0')
expect(rawCommand$$).toBeCalledTimes(2)
expect(rawCommand$$).toBeCalledWith('bar')
})
it('offers array methods on elements', async () => {
const rawCommand$ = vi.fn()
const rawCommand$$ = vi.fn()
const scope: (i: number) => any = (i) => ({
options: {
beforeCommand: vi.fn(),
afterCommand: vi.fn()
},
getTagName: vi.fn().mockResolvedValue('Yayy' + i),
$: rawCommand$,
$$: rawCommand$$
})
rawCommand$.mockResolvedValue(scope(0))
rawCommand$$.mockReturnValue([
Promise.resolve(scope(0)),
Promise.resolve(scope(1)),
Promise.resolve(scope(2))
])
const commandA = wrapCommand('$', rawCommand$)
expect(await commandA.call(scope(0))
.$('foo')
.$$('bar')
.map((el: any) => el.getTagName())
).toEqual(['Yayy0', 'Yayy1', 'Yayy2'])
})
it('can access element properties', async () => {
const scope: any = {
options: {
beforeCommand: vi.fn(),
afterCommand: vi.fn()
},
selector: 'foobar'
}
const rawCommand = vi.fn().mockReturnValue(Promise.resolve(scope))
const commandA = wrapCommand('$', rawCommand)
expect(await commandA.call(scope).selector).toBe('foobar')
})
it('can iterate over elements asynchronously', async () => {
const options = {
beforeCommand: vi.fn(),
afterCommand: vi.fn()
}
const scope: any = [{
selector: 'foobarA',
options
}, {
selector: 'foobarB',
options
}, {
selector: 'foobarC',
options
}]
scope.options = options
const rawCommand = vi.fn().mockReturnValue(Promise.resolve(scope))
const commandA = wrapCommand('$$', rawCommand).bind(scope) as unknown as (sel: string) => Promise<any>[]
const expectedResults = ['foobarA', 'foobarB', 'foobarC']
let i = 0
for await (const elem of commandA('selector')) {
expect(expectedResults[i++]).toBe(elem.selector)
}
})
it('throws an error if iterating through a non array', async () => {
expect.assertions(1)
const options = {
beforeCommand: vi.fn(),
afterCommand: vi.fn()
}
const scope: any = {
selector: 'foobarA',
options
}
scope.options = options
const rawCommand = vi.fn().mockReturnValue(Promise.resolve(scope))
const commandA = wrapCommand('$', rawCommand).bind(scope) as unknown as (sel: string) => Promise<any>[]
try {
for await (const elem of commandA('selector')) {
console.log(elem)
}
} catch (err: any) {
expect(err.message).toBe('Can not iterate over non array')
}
})
})