🏡 index : quine-global/webdriverio.git

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()

        // @ts-expect-error
        expect(reporter['_reporters'][0].options.logFile)
            .toBe('/barfoo.log')
        // @ts-expect-error
        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)
        // @ts-ignore
        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()

        // @ts-ignore test reporter param
        setTimeout(() => (reporter['_reporters'][0].inSync = true), 100)
        // @ts-ignore test reporter param
        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()

        // @ts-ignore test reporter param
        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()
    })
})