import path from 'node:path'
import { describe, expect, it, vi, test, afterEach } from 'vitest'
import BaseReporter from '../src/reporter.js'
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/config', () => import(path.join(process.cwd(), '__mocks__', '@wdio/config')))
class CustomReporter {
public emit = vi.fn()
public isCustom = true
public inSync = false
constructor (public options: WebdriverIO.ServiceOption) {}
get isSynchronised () {
return this.inSync
}
}
const capability: WebdriverIO.Capabilities = { browserName: 'foo' }
process.send = vi.fn()
describe('BaseReporter', () => {
it('should load all reporters', async () => {
const reporter = new BaseReporter({
outputDir: '/foo/bar',
reporters: [
'dot',
['dot', { foo: 'bar' }]
]
} as WebdriverIO.Config, '0-0', capability)
await reporter.initReporters()
expect(reporter['_reporters']).toHaveLength(2)
})
it('should make "dot" reporter default', async () => {
const reporter = new BaseReporter({
outputDir: '/foo/bar',
reporters: [],
} as WebdriverIO.Config, '0-0', capability)
await reporter.initReporters()
expect(reporter['_reporters']).toHaveLength(1)
expect(reporter['_reporters'][0].constructor.name).toBe('DotReporter')
})
it('getLogFile', async () => {
const reporter = new BaseReporter({
outputDir: '/foo/bar',
reporters: [
'dot',
['dot', { foo: 'bar' }]
]
} as WebdriverIO.Config, '0-0', capability)
await reporter.initReporters()
expect(reporter.getLogFile('foobar'))
.toMatch(/(\\|\/)foo(\\|\/)bar(\\|\/)wdio-0-0-foobar-reporter.log/)
})
it('can set custom logFile property', async () => {
const reporter = new BaseReporter({
outputDir: '/foo/bar',
reporters: [
[CustomReporter, { foo: 'bar', logFile: '/barfoo.log' }],
['dot', { foo: 'bar', logFile: '/foobar.log' }]
],
capabilities: [capability]
} as WebdriverIO.Config, '0-0', capability)
await reporter.initReporters()
expect(reporter['_reporters'][0].options.logFile)
.toBe('/barfoo.log')
expect(reporter['_reporters'][1].options.logFile)
.toBe('/foobar.log')
})
it('should output log file to custom outputDir', async () => {
const reporter = new BaseReporter({
outputDir: '/foo/bar',
reporters: [
['dot', {
foo: 'bar',
outputDir: '/foo/bar/baz'
}]
],
capabilities: [capability]
} as WebdriverIO.Config, '0-0', capability)
await reporter.initReporters()
expect(reporter.getLogFile('dot'))
.toMatch(/(\\|\/)foo(\\|\/)bar(\\|\/)baz(\\|\/)wdio-0-0-dot-reporter.log/)
})
it('should return custom log file name using cid and capabilities', async () => {
const reporter = new BaseReporter({
outputDir: '/foo/bar',
reporters: [
'dot',
['dot', {
foo: 'bar',
outputFileFormat: (options) => {
const { cid, capabilities } = options
expect(cid).toBe('0-0')
expect(capabilities).toBe(capability)
if ('browserName' in capabilities) {
const { browserName } = capabilities
return `wdio-results-${cid}-${browserName}.log`
}
}
}]
]
} as WebdriverIO.Config, '0-0', capability)
await reporter.initReporters()
expect(reporter.getLogFile('dot'))
.toMatch(/(\\|\/)foo(\\|\/)bar(\\|\/)wdio-results-0-0-foo.log/)
})
it('should throw error if outputFileFormat is not a function', async () => {
await expect(async () => {
const reporter = new BaseReporter({
outputDir: '/foo/bar',
reporters: [
'dot',
['dot', {
foo: 'bar',
outputFileFormat: 'foo'
}]
]
} as WebdriverIO.Config, '0-0', capability)
await reporter.initReporters()
}).rejects.toThrow('outputFileFormat must be a function')
})
test('getLogFile returns undefined if outputDir is not defined', () => {
const reporter = new BaseReporter({
reporters: [
'dot',
['dot', { foo: 'bar' }]
]
} as WebdriverIO.Config, '0-0', capability)
expect(reporter.getLogFile('foobar')).toBe(undefined)
})
it('should emit events to all reporters', async () => {
const reporter = new BaseReporter({
outputDir: '/foo/bar',
reporters: [
'dot',
['dot', { foo: 'bar' }]
]
} as WebdriverIO.Config, '0-0', capability)
await reporter.initReporters()
const payload: any = { foo: [1, 2, 3] }
reporter.emit('runner:start', payload)
expect(reporter['_reporters'].map((r) => vi.mocked(r.emit).mock.calls)).toEqual([
[['runner:start', Object.assign(payload, { cid: '0-0' })]],
[['runner:start', Object.assign(payload, { cid: '0-0' })]]
])
expect(process.send).not.toBeCalled()
})
it('should send printFailureMessage on `test:fail`', async () => {
const reporter = new BaseReporter({
outputDir: '/foo/bar',
reporters: [
'dot',
['dot', { foo: 'bar' }]
]
} as WebdriverIO.Config, '0-0', capability)
await reporter.initReporters()
const payload: any = { foo: [1, 2, 3] }
reporter.emit('test:fail', payload)
reporter['_reporters'].forEach((reporter) => {
expect(reporter.emit).toHaveBeenCalledWith('test:fail', { ...payload, cid: '0-0' })
})
expect(process.send).toBeCalledTimes(1)
expect(process.send).toHaveBeenCalledWith(expect.objectContaining({ name: 'printFailureMessage' }))
})
it('should send printFailureMessage on `hook:end`', async () => {
const reporter = new BaseReporter({
outputDir: '/foo/bar',
reporters: [
'dot',
['dot', { foo: 'bar' }]
]
} as WebdriverIO.Config, '0-0', capability)
await reporter.initReporters()
const error = new Error('foobar')
const payload = { foo: [1, 2, 3], error, title: '"before all" hook' }
reporter.emit('hook:end', payload)
reporter['_reporters'].forEach((reporter) => {
expect(reporter.emit).toHaveBeenCalledWith('hook:end', { ...payload, cid: '0-0' })
})
expect(process.send).toBeCalledTimes(1)
expect(process.send).toHaveBeenCalledWith(expect.objectContaining({ name: 'printFailureMessage' }))
})
it('should send printFailureMessage and continue when reporter throws an error', async () => {
const faultyReporter = 'dot'
const workingReporter = ['dot', { foo: 'bar' }]
const reporter = new BaseReporter({
outputDir: '/foo/bar',
reporters: [faultyReporter, workingReporter] } as WebdriverIO.Config, '0-0', capability)
await reporter.initReporters()
const faultyReporterInstance = reporter['_reporters'][0]
const workingReporterInstance = reporter['_reporters'][1]
vi.spyOn(faultyReporterInstance, 'emit').mockImplementation(() => {
throw new Error('Reporter throws an error')
})
const payload: any = { foo: [1] }
reporter.emit('any', payload)
expect(faultyReporterInstance.emit).toBeCalledTimes(1)
expect(workingReporterInstance.emit).toBeCalledTimes(1)
expect(process.send).toBeCalledTimes(1)
expect(process.send).toHaveBeenCalledWith({
'content': {
'cid': '0-0',
'error': {
'message': 'Reporter throws an error',
'stack': expect.stringContaining('Error: Reporter throws an error\n at DotReporter.<anonymous>')
},
'fullTitle': 'reporter DotReporter',
},
'name': 'printFailureMessage',
'origin': 'reporter',
})
})
it('should allow to load custom reporters', async () => {
const reporter = new BaseReporter({
outputDir: '/foo/bar',
reporters: [CustomReporter] as any,
capabilities: [capability]
} as WebdriverIO.Config, '0-0', capability)
await reporter.initReporters()
expect(reporter['_reporters']).toHaveLength(1)
expect(reporter['_reporters'][0].isCustom).toBe(true)
})
it('should allow to write to output directory with custom reporter', async () => {
const reporter = new BaseReporter({
outputDir: '/foo/bar',
reporters: [[CustomReporter, {
outputDir: '/foo/baz/bar'
}]] as any,
capabilities: [capability]
} as WebdriverIO.Config, '0-0', capability)
await reporter.initReporters()
expect(reporter.getLogFile('CustomReporter')).toMatch(/(\\|\/)foo(\\|\/)baz(\\|\/)bar(\\|\/)wdio-0-0-CustomReporter-reporter.log/)
})
it('should throw if reporters are in a wrong format', async () => {
expect.hasAssertions()
try {
const reporter = new BaseReporter({
outputDir: '/foo/bar',
reporters: [{ foo: 'bar' } as any],
capabilities: [capability]
} as WebdriverIO.Config, '0-0', capability)
await reporter.initReporters()
} catch (err: any) {
expect(err.message).toBe('Invalid reporters config')
}
})
it('should have a waitForSync method to allow reporters to sync stuff', async () => {
expect.hasAssertions()
const start = Date.now()
const reporter = new BaseReporter({
outputDir: '/foo/bar',
reporters: [CustomReporter, CustomReporter] as any,
capabilities: [capability]
} as WebdriverIO.Config, '0-0', capability)
await reporter.initReporters()
setTimeout(() => (reporter['_reporters'][0].inSync = true), 100)
setTimeout(() => (reporter['_reporters'][1].inSync = true), 200)
await reporter.waitForSync()
expect(Date.now() - start).toBeGreaterThanOrEqual(199)
})
it('it should fail if waitForSync times out', async () => {
expect.hasAssertions()
const reporter = new BaseReporter({
outputDir: '/foo/bar',
reporters: [CustomReporter] as any,
reporterSyncInterval: 10,
reporterSyncTimeout: 100,
capabilities: [capability]
} as WebdriverIO.Config, '0-0', capability)
await reporter.initReporters()
setTimeout(() => (reporter['_reporters'][0].inSync = true), 112)
await expect(reporter.waitForSync())
.rejects.toEqual(new Error('Some reporters are still unsynced: CustomReporter'))
})
afterEach(() => {
vi.mocked(process.send)!.mockClear()
})
})