import type { MockedFunction } from 'vitest'
import { vi, describe, it, expect, afterEach } from 'vitest'
import * as shim from '../../src/shim.js'
import { testFnWrapper, filterStackTrace } from '../../src/test-framework/testFnWrapper.js'
vi.mock('../../src/shim', () => ({
executeHooksWithArgs: vi.fn(),
executeAsync: async (fn: Function, { limit, attempts }: any, args = []) => fn('async', limit, attempts, ...args),
runSync: null,
}))
const executeHooksWithArgs = shim.executeHooksWithArgs as MockedFunction<any>
describe('testFnWrapper', () => {
const origFn = (mode: string, limit: number, attempts: number, arg: any) => `${mode}: Foo${arg} ${limit} ${attempts}`
const buildArgs = (specFn: Function, retries?: number, beforeFnArgs?: Function, afterFnArgs?: Function) => [
'Foo',
{ specFn, specFnArgs: ['Bar'] },
{ beforeFn: 'beforeFn', beforeFnArgs },
{ afterFn: 'afterFn', afterFnArgs },
'0-9',
retries
]
it('should run fn in async mode if not runSync', async () => {
const args = buildArgs(origFn, undefined, () => [], () => []) as any[]
const result = await testFnWrapper(...args)
expect(result).toBe('async: FooBar 0 0')
expect(executeHooksWithArgs).toBeCalledTimes(2)
expect(executeHooksWithArgs).toBeCalledWith('beforeFoo', 'beforeFn', [])
expect(executeHooksWithArgs).toBeCalledWith('afterFoo', 'afterFn', [{
duration: expect.any(Number),
error: undefined,
result: 'async: FooBar 0 0',
passed: true,
retries: {
limit: 0,
attempts: 0
},
skipped: false
}])
})
it('should run fn in async mode if specFn is async', async () => {
const args = buildArgs(async (...args: any[]) => origFn(...args), 11, () => ['beforeFnArgs'], () => ['afterFnArgs'])
const result = await testFnWrapper(...args)
expect(result).toBe('async: FooBar 11 0')
expect(executeHooksWithArgs).toBeCalledTimes(2)
expect(executeHooksWithArgs).toBeCalledWith('beforeFoo', 'beforeFn', ['beforeFnArgs'])
expect(executeHooksWithArgs).toBeCalledWith('afterFoo', 'afterFn', ['afterFnArgs', {
duration: expect.any(Number),
error: undefined,
result: 'async: FooBar 11 0',
passed: true,
retries: {
limit: 11,
attempts: 0
},
skipped: false,
}])
})
it('should throw on error', async () => {
let expectedError
const args = buildArgs((mode: string, repeatTest: boolean, arg: any) => {
expectedError = new Error(`${mode}: Foo${arg} ${repeatTest}`)
throw expectedError
}, undefined, () => ['beforeFnArgs'], () => ['afterFnArgs'])
let error
try {
await testFnWrapper(...args)
} catch (err) {
error = err
}
expect(error).toBe(expectedError)
expect(executeHooksWithArgs).toBeCalledTimes(2)
expect(executeHooksWithArgs).toBeCalledWith('beforeFoo', 'beforeFn', ['beforeFnArgs'])
expect(executeHooksWithArgs).toBeCalledWith('afterFoo', 'afterFn', ['afterFnArgs', {
duration: expect.any(Number),
error: expectedError,
result: undefined,
passed: false,
retries: {
limit: 0,
attempts: 0
},
skipped: false,
}])
})
it('should not throw on error on skip test', async () => {
const args = buildArgs((mode: string, repeatTest: boolean, arg: any) => {
throw new Error('sync skip; aborting execution')
}, undefined, () => ['beforeFnArgs'], () => ['afterFnArgs'])
let error
try {
await testFnWrapper(...args)
} catch (err) {
error = err
}
expect(error).toBe(undefined)
expect(executeHooksWithArgs).toBeCalledTimes(2)
expect(executeHooksWithArgs).toBeCalledWith('beforeFoo', 'beforeFn', ['beforeFnArgs'])
expect(executeHooksWithArgs).toBeCalledWith('afterFoo', 'afterFn', ['afterFnArgs', {
duration: expect.any(Number),
error: undefined,
result: undefined,
passed: false,
retries: {
limit: 0,
attempts: 0
},
skipped: true,
}])
})
afterEach(() => {
executeHooksWithArgs.mockClear()
})
})
describe('filterStackTrace', () => {
it('should remove internal webdriverio lines only', () => {
const stackTraces = [
{
fullStack : `
at Context.<anonymous> (/foo/bar/baz/example.e2e.js:27:9)
at Context.executeAsync (/foo/bar/baz/node_modules/@wdio/utils/build/shim.js:331:27)
at Context.testFrameworkFnWrapper (/foo/bar/baz/node_modules/@wdio/utils/build/test-framework/testFnWrapper.js:50:32)
at processTicksAndRejections (internal/process/task_queues.js:95:5)
`,
filteredStack : `
at Context.<anonymous> (/foo/bar/baz/example.e2e.js:27:9)
`
},
{
fullStack : `
at implicitWait (/foo/bar/node_modules/webdriverio/build/utils/implicitWait.js:34:19)
at async Element.elementErrorHandlerCallbackFn (/foo/bar/node_modules/webdriverio/build/middlewares.js:20:29)
at async Element.wrapCommandFn (/foo/bar/node_modules/@wdio/utils/build/shim.js:137:29)
at async Element.wrapCommandFn (/foo/bar/node_modules/some/fake/lib/bar.js:137:29)
at async Context.<anonymous> (/foo/bar/test/specs/example.e2e.ts:8:9)
at processTicksAndRejections (internal/process/task_queues.js:95:5)
`,
filteredStack : `
at async Element.wrapCommandFn (/foo/bar/node_modules/some/fake/lib/bar.js:137:29)
at async Context.<anonymous> (/foo/bar/test/specs/example.e2e.ts:8:9)
`
}
]
for (const { fullStack, filteredStack } of stackTraces) {
expect(filteredStack).toBe(filterStackTrace(fullStack))
}
})
it('should remove invalidateCache', () => {
const stacktrace = `
at async Context.<anonymous> (/foo/bar/test/specs/example.e2e.ts:8:9)
at Context.testFrameworkFnWrapper (/foo/bar/baz/testFnWrapper.js?invalidateCache=0.2342342342334:50:32)
`
const filteredStack = `
at async Context.<anonymous> (/foo/bar/test/specs/example.e2e.ts:8:9)
at Context.testFrameworkFnWrapper (/foo/bar/baz/testFnWrapper.js:50:32)
`
expect(filteredStack).toBe(filterStackTrace(stacktrace))
})
})