import path from 'node:path'
import logger from '@wdio/logger'
import { describe, expect, it, vi, afterEach, beforeEach } from 'vitest'
import { executeHooksWithArgs } from '@wdio/utils'
import { ConfigParser } from '@wdio/config/node'
import { attach } from 'webdriverio'
import { _setGlobal } from '@wdio/globals'
import { setOptions, SnapshotService, SoftAssertionService } from 'expect-webdriverio'
import WDIORunner from '../src/index.js'
vi.mock('fs/promises', async (orig) => ({
...(await orig()) as any,
default: { writeFile: vi.fn() }
}))
vi.mock('util')
vi.mock('expect-webdriverio', () => ({
setOptions: vi.fn(),
expect: vi.fn(),
SoftAssertionService: vi.fn(),
SnapshotService: {
initiate: vi.fn().mockReturnValue({
results: ['foobar']
})
}
}))
vi.mock('webdriverio', () => import(path.join(process.cwd(), '__mocks__', 'webdriverio')))
vi.mock('@wdio/utils', () => import(path.join(process.cwd(), '__mocks__', '@wdio/utils')))
vi.mock('@wdio/logger', () => import(path.join(process.cwd(), '__mocks__', '@wdio/logger')))
vi.mock('@wdio/globals', () => ({
_setGlobal: vi.fn()
}))
type BrowserObject = WebdriverIO.Browser
type MultiRemoteBrowserObject = WebdriverIO.MultiRemoteBrowser
describe('wdio-runner', () => {
beforeEach(() => {
vi.mocked(_setGlobal).mockClear()
vi.mocked(setOptions).mockClear()
process.send = vi.fn()
})
describe('endSession', () => {
it('should work normally when called after framework run', async () => {
const hook = vi.fn()
const runner = new WDIORunner()
runner['_shutdown'] = vi.fn()
runner['_browser'] = {
deleteSession: vi.fn(),
sessionId: '123',
config: { afterSession: [hook] }
} as unknown as BrowserObject
runner['_config'] = { logLevel: 'info', afterSession: [hook] } as any
await runner.endSession()
expect(executeHooksWithArgs).toBeCalledWith(
'afterSession',
[hook],
[{ logLevel: 'info', afterSession: [hook] }, {}, undefined])
expect(runner['_browser'].deleteSession).toBeCalledTimes(1)
expect(!runner['_browser'].sessionId).toBe(true)
expect(runner['_shutdown']).toBeCalledTimes(0)
})
it('should do nothing when triggered by run method without session', async () => {
const hook = vi.fn()
const runner = new WDIORunner()
runner['_shutdown'] = vi.fn()
await runner.endSession()
expect(hook).toBeCalledTimes(0)
})
it('should work normally when called after framework run in multiremote', async () => {
const hook = vi.fn()
const runner = new WDIORunner()
runner['_isMultiremote'] = true
runner['_shutdown'] = vi.fn()
runner['_browser'] = {
deleteSession: vi.fn(),
instances: ['foo', 'bar'],
getInstance: vi.fn().mockReturnValue({
sessionId: '123',
}),
capabilities: {
foo: {
browserName: 'Chrome'
},
bar: {
browserName: 'Chrome'
}
},
config: { afterSession: [hook] }
} as any
runner['_config'] = { logLevel: 'error', afterSession: [hook] } as any
await runner.endSession()
expect(executeHooksWithArgs).toBeCalledWith(
'afterSession',
[hook],
[{ logLevel: 'error', afterSession: [hook] }, { foo: undefined, bar: undefined }, undefined])
expect(runner['_browser']!.deleteSession).toBeCalledTimes(1)
expect(!(runner['_browser'] as unknown as MultiRemoteBrowserObject).getInstance('foo').sessionId).toBe(true)
expect(!(runner['_browser'] as unknown as MultiRemoteBrowserObject).getInstance('bar').sessionId).toBe(true)
expect(runner['_shutdown']).toBeCalledTimes(0)
})
it('should do nothing when triggered by run method without session in multiremote', async () => {
const hook = vi.fn()
const runner = new WDIORunner()
runner['_isMultiremote'] = true
runner['_shutdown'] = vi.fn()
await runner.endSession()
expect(hook).toBeCalledTimes(0)
})
})
describe('run', () => {
afterEach(() => {
vi.spyOn(ConfigParser.prototype, 'initialize').mockRestore()
})
it('should fail if log file is corrupted', async () => {
vi.spyOn(ConfigParser.prototype, 'initialize').mockRejectedValueOnce(new Error('ups'))
const runner = new WDIORunner()
runner['_shutdown'] = vi.fn()
await runner.run({ args: {}, configFile: '/foo/bar' } as any)
expect(runner['_shutdown']).toBeCalledWith(1, undefined, true)
})
it('should auto compile if args are given', async () => {
const config: any = {
reporters: [],
before: [],
beforeSession: [],
framework: 'testWithFailures',
runner: 'local'
}
const runner = new WDIORunner()
runner['_shutdown'] = vi.fn()
vi.spyOn(ConfigParser.prototype, 'getConfig').mockReturnValue(config)
await runner.run({ configFile: '/foo/bar', args: {}, caps: [] } as any)
expect(runner['_shutdown']).toBeCalledWith(1, undefined, true)
})
it('should fail if init session fails', async () => {
const runner = new WDIORunner()
const beforeSession = vi.fn()
const before = vi.fn()
const caps = { browserName: '123' }
const specs = ['foobar']
const config:any = {
reporters: [],
before: [before],
beforeSession: [beforeSession],
framework: 'testWithFailures',
runner: 'local'
}
vi.spyOn(ConfigParser.prototype, 'getConfig').mockReturnValue(config)
vi.spyOn(ConfigParser.prototype, 'addConfigFile').mockReturnValue()
runner['_shutdown'] = vi.fn()
const stubBrowser = {
capabilities: { browserName: 'chrome' },
options: {}
}
runner['_initSession'] = vi.fn().mockReturnValue(stubBrowser)
await runner.run({
args: { reporters: [] },
cid: '0-0',
retries: 2,
caps,
specs,
configFile: '/foo/bar'
})
expect(runner['_shutdown']).toBeCalledWith(123, 2)
expect(executeHooksWithArgs).toBeCalledWith('beforeSession', [beforeSession], [config, {
browserName: '123'
}, ['foobar'], '0-0'])
expect(executeHooksWithArgs).toBeCalledWith('before', config.before, [caps, specs, stubBrowser])
expect(runner['_reporter']?.caps).toEqual({ browserName: 'chrome' })
})
it('should return failures count', async () => {
const runner = new WDIORunner()
const config: any = {
framework: 'testNoFailures',
reporters: [],
beforeSession: [],
runner: 'local'
}
vi.spyOn(ConfigParser.prototype, 'getConfig').mockReturnValue(config)
runner['_initSession'] = vi.fn().mockReturnValue({ options: { capabilities: {} } })
const failures = await runner.run({ args: {}, caps: {}, configFile: '/bar/foo' } as any)
expect(failures).toBe(0)
})
it('should not call browser url if args watch', async () => {
const runner = new WDIORunner()
const config: any = {
framework: 'testNoFailures',
reporters: [],
beforeSession: [],
runner: 'local'
}
vi.spyOn(ConfigParser.prototype, 'getConfig').mockReturnValue(config)
runner['_browser'] = { url: vi.fn(url => url) } as unknown as BrowserObject
runner['_startSession'] = vi.fn().mockReturnValue({ })
runner['_initSession'] = vi.fn().mockReturnValue({ options: { capabilities: {} } })
const failures = await runner.run({ args: { watch: true }, caps: {}, configFile: '/foo/bar' } as any)
expect(failures).toBe(0)
expect(runner['_browser']?.url).not.toBeCalled()
})
it('should attach snapshot service to service list', async () => {
const runner = new WDIORunner()
const config: any = {
framework: 'testNoFailures',
reporters: [],
beforeSession: [],
runner: 'local',
updateSnapshots: 'do it',
resolveSnapshotPath: 'resolve me'
}
vi.spyOn(ConfigParser.prototype, 'getConfig').mockReturnValue(config)
const addServiceSpy = vi.spyOn(ConfigParser.prototype, 'addService')
runner['_browser'] = { url: vi.fn(url => url) } as unknown as BrowserObject
runner['_startSession'] = vi.fn().mockReturnValue({ })
runner['_initSession'] = vi.fn().mockReturnValue({ options: { capabilities: {} } })
await runner.run({ args: { watch: true }, caps: {}, configFile: '/foo/bar' } as any)
expect(addServiceSpy).toBeCalledWith({
results: ['foobar']
})
expect(SnapshotService.initiate).toBeCalledWith({
updateState: 'do it',
resolveSnapshotPath: 'resolve me'
})
expect(process.send).toBeCalledWith({
origin: 'worker',
name: 'snapshot',
content: ['foobar']
})
})
it('should set failures to 1 in case of error', async () => {
const runner = new WDIORunner()
const config:any = {
framework: 'testThrows',
reporters: [],
beforeSession: [],
runner: 'local'
}
vi.spyOn(ConfigParser.prototype, 'getConfig').mockReturnValue(config)
runner['_initSession'] = vi.fn().mockReturnValue({ options: { capabilities: {} } })
runner.emit = vi.fn()
const failures = await runner.run({ args: {}, caps: {}, configFile: '/foo/bar' } as any)
expect(failures).toBe(1)
expect(vi.mocked(runner.emit).mock.calls[0])
.toEqual(['error', new Error('framework testThrows failed')])
})
it('should return if sigintWasCalled', async () => {
const runner = new WDIORunner()
const caps = { browserName: '123' }
const specs = ['foobar']
const config: any = {
framework: 'testThrows',
reporters: [],
beforeSession: [],
runner: 'local'
}
vi.spyOn(ConfigParser.prototype, 'getConfig').mockReturnValue(config)
runner['_shutdown'] = vi.fn()
runner.endSession = vi.fn()
runner['_initSession'] = vi.fn().mockReturnValue({})
runner['_sigintWasCalled'] = true
await runner.run({
args: { reporters: [] },
cid: '0-0',
caps,
specs,
configFile: '/foo/bar',
retries: 0
})
expect(runner.endSession).toBeCalledTimes(1)
expect(runner['_shutdown']).toBeCalledWith(0, 0, true)
})
it('should not initSession if there are no tests to run', async () => {
const runner = new WDIORunner()
const config: any = {
framework: 'testNoTests',
reporters: [],
beforeSession: [],
runner: 'local'
}
vi.spyOn(ConfigParser.prototype, 'getConfig').mockReturnValue(config)
runner['_shutdown'] = vi.fn().mockImplementation((arg) => arg)
runner['_initSession'] = vi.fn()
expect(await runner.run({ args: {}, caps: {}, configFile: '/foo/bar' } as any)).toBe(0)
expect(runner['_shutdown']).toBeCalledWith(0, undefined, true)
expect(runner['_initSession']).not.toBeCalled()
})
it('should shutdown if session was not created', async () => {
const runner = new WDIORunner()
const caps = { browserName: '123' }
const specs = ['foobar']
const config: any = {
framework: 'testNoFailures',
reporters: [],
beforeSession: [],
after: 'foobar',
runner: 'local'
}
vi.spyOn(ConfigParser.prototype, 'getConfig').mockReturnValue(config)
runner['_shutdown'] = vi.fn().mockReturnValue('_shutdown')
runner.endSession = vi.fn()
runner['_initSession'] = vi.fn().mockReturnValue(null)
expect(await runner.run({
args: { reporters: [] },
cid: '0-0',
caps,
specs,
configFile: '/foo/bar',
retries: 0
})).toBe('_shutdown')
expect(runner['_shutdown']).toBeCalledWith(1, 0, true)
expect(executeHooksWithArgs).toBeCalledWith(
'after',
'foobar',
[1, caps, specs]
)
expect(runner['_reporter']?.caps).toEqual(caps)
})
})
describe('_initSession', () => {
it('should register browser to global scope', async () => {
const runner = new WDIORunner()
const browser = await runner['_initSession'](
{ hostname: 'foobar', waitforTimeout: 1, waitforInterval: 2 } as any,
[{ browserName: 'chrome1' }] as any
)
expect(_setGlobal).toBeCalledWith('browser', browser, undefined)
expect(_setGlobal).toBeCalledWith('driver', browser, undefined)
expect(_setGlobal).toBeCalledWith('expect', expect.any(Function), undefined)
expect(_setGlobal).toBeCalledWith('$', expect.any(Function), undefined)
expect(_setGlobal).toBeCalledWith('$$', expect.any(Function), undefined)
expect(setOptions).toBeCalledTimes(1)
expect(setOptions).toBeCalledWith({
afterAssertion: expect.any(Function),
beforeAssertion: expect.any(Function),
wait: 1,
interval: 2
})
})
it('should register before and after command listener', async () => {
const reporter = { emit: vi.fn() }
const runner = new WDIORunner()
runner['_reporter'] = reporter as any
const browser = await runner['_initSession'](
{ hostname: 'foobar' } as any,
[{ browserName: 'chrome' }] as any
)
const beforeListener = vi.mocked(browser!.on).mock.calls[0]
expect(beforeListener[0]).toBe('command')
beforeListener[1]({ foo: 'bar' })
expect(reporter.emit).toBeCalledWith(
'client:beforeCommand',
{ foo: 'bar', sessionId: 'fakeid' })
reporter.emit.mockClear()
const afterListener = vi.mocked(browser!.on).mock.calls[1]
expect(afterListener[0]).toBe('result')
afterListener[1]({ bar: 'foo' })
expect(reporter.emit).toBeCalledWith(
'client:afterCommand',
{ bar: 'foo', sessionId: 'fakeid' })
})
it('should return null if initiating session fails', async () => {
global.throwRemoteCall = true
const runner = new WDIORunner()
runner.emit = vi.fn()
const browser = await runner['_initSession'](
{ hostname: 'foobar' } as any,
[{ browserName: 'chrome' }] as any
)
expect(browser).toBe(undefined)
})
afterEach(() => {
delete global.throwRemoteCall
})
})
describe('_startSession', () => {
it('can start a session', async () => {
const runner = new WDIORunner()
const browser = await runner['_startSession']({} as any, {} as any)
expect(typeof browser?.deleteSession).toBe('function')
expect(_setGlobal).toBeCalledTimes(3)
expect(setOptions).toBeCalledTimes(1)
})
it('transfers custom commands from old instance to new one', async () => {
const runner = new WDIORunner()
runner['_browser'] = {
customCommands: [[1, 2, 3]],
overwrittenCommands: [[3, 2, 1]]
} as any
const browser = await runner['_startSession']({} as any, {} as any)
expect(browser?.addCommand).toBeCalledWith(1, 2, 3)
expect(browser?.overwriteCommand).toBeCalledWith(3, 2, 1)
})
})
describe('_shutdown', () => {
it('should emit exit', async () => {
const runner = new WDIORunner()
runner['_browser'] = {} as unknown as BrowserObject
runner['_reporter'] = {
waitForSync: vi.fn().mockReturnValue(Promise.resolve()),
emit: vi.fn()
} as any
runner.emit = vi.fn()
expect(await runner['_shutdown'](123, 123)).toBe(123)
expect(runner['_reporter']!.waitForSync).toBeCalledTimes(1)
expect(runner.emit).toBeCalledWith('exit', 1)
})
it('should emit exit when reporter unsync', async () => {
const log = logger('wdio-runner')
vi.spyOn(log, 'error').mockImplementation((string) => string)
const runner = new WDIORunner()
runner['_browser'] = {} as unknown as BrowserObject
runner['_reporter'] = {
waitForSync: vi.fn().mockReturnValue(Promise.reject('foo')),
emit: vi.fn()
} as any
runner.emit = vi.fn()
expect(await runner['_shutdown'](123, 123)).toBe(123)
expect(runner['_reporter']!.waitForSync).toBeCalledTimes(1)
expect(runner.emit).toBeCalledWith('exit', 1)
expect(log.error).toHaveBeenCalledWith('foo')
})
it('should emit runner:start if the initialization failed', async () => {
const runner = new WDIORunner()
runner['_configParser'] = { getCapabilities: vi.fn().mockReturnValue([{ browserName: 'safari' }]) } as any
runner['_browser'] = {} as unknown as BrowserObject
runner['_caps'] = { 'browserName': 'safari' }
runner['_reporter'] = {
waitForSync: vi.fn().mockReturnValue(Promise.resolve()),
emit: vi.fn()
} as any
const reporter = runner['_reporter'] as any
runner.emit = vi.fn()
const args = [
['runner:start', {
'capabilities': { 'browserName': 'safari' },
'cid': undefined,
'config': undefined,
'instanceOptions': {},
'isMultiremote': false,
'retry': 0,
'specs': undefined
}],
['runner:end', { 'cid': undefined, 'failures': 123, 'retries': 123 }]
]
expect(await runner['_shutdown'](123, 123, true)).toBe(123)
expect(reporter.emit.mock.calls).toEqual(args)
expect(runner.emit).toBeCalledWith('exit', 1)
})
})
afterEach(() => {
vi.mocked(executeHooksWithArgs).mockClear()
vi.mocked(attach).mockClear()
delete process.send
})
})