🏡 index : quine-global/webdriverio.git

import path from 'node:path'
import { vi, describe, it, expect, afterEach, beforeEach } from 'vitest'
import WDIOCLInterface from '../src/interface.js'
import { HookError } from '../src/utils.js'
import chalk from 'chalk'

const config = {}
const EMPTY_INTERFACE_MESSAGE_OBJECT = {
    reporter: {},
    debugger: {}
}

vi.mock('chalk')
vi.mock('@wdio/logger', () => import(path.join(process.cwd(), '__mocks__', '@wdio/logger')))

describe('cli interface', () => {
    let wdioClInterface: WDIOCLInterface

    beforeEach(() => {
        global.console.log = vi.fn()
        wdioClInterface = new WDIOCLInterface(config as any, 5)
        wdioClInterface.log = vi.fn().mockImplementation((...args: any[]) => args)
    })

    it('should add jobs', () => {
        wdioClInterface.emit('job:start', { cid: '0-0', hasTests: true })
        expect(wdioClInterface['_jobs'].size).toBe(1)
    })

    it('should mark jobs as pass or failed', () => {
        wdioClInterface.emit('job:start', { cid: '0-0', hasTests: true })
        wdioClInterface.emit('job:start', { cid: '0-1', hasTests: true })
        expect(wdioClInterface.result.finished).toBe(0)
        expect(wdioClInterface.result.passed).toBe(0)
        expect(wdioClInterface.result.failed).toBe(0)
        wdioClInterface.emit('job:end', { cid: '0-0', passed: true })
        expect(wdioClInterface.result.finished).toBe(1)
        expect(wdioClInterface.result.passed).toBe(1)
        expect(wdioClInterface.result.failed).toBe(0)
        wdioClInterface.emit('job:end', { cid: '0-1', passed: false })
        expect(wdioClInterface.result.finished).toBe(2)
        expect(wdioClInterface.result.passed).toBe(1)
        expect(wdioClInterface.result.failed).toBe(1)
    })

    it('should mark jobs as retried when failing', () => {
        wdioClInterface.emit('job:start', { cid: '0-0', hasTests: true })
        expect(wdioClInterface.result.finished).toBe(0)
        expect(wdioClInterface.result.passed).toBe(0)
        expect(wdioClInterface.result.retries).toBe(0)
        expect(wdioClInterface.result.failed).toBe(0)
        wdioClInterface.emit('job:end', { cid: '0-0', passed: false, retries: 1 })
        expect(wdioClInterface.result.finished).toBe(0)
        expect(wdioClInterface.result.passed).toBe(0)
        expect(wdioClInterface.result.retries).toBe(1)
        expect(wdioClInterface.result.failed).toBe(0)
        wdioClInterface.emit('job:start', { cid: '0-0', hasTests: true })
        wdioClInterface.emit('job:end', { cid: '0-0', passed: true, retries: 0 })
        expect(wdioClInterface.result.finished).toBe(1)
        expect(wdioClInterface.result.passed).toBe(1)
        expect(wdioClInterface.result.retries).toBe(1)
        expect(wdioClInterface.result.failed).toBe(0)

        wdioClInterface.emit('job:start', { cid: '0-1', hasTests: true })
        wdioClInterface.emit('job:end', { cid: '0-1', passed: false, retries: 1 })
        expect(wdioClInterface.result.finished).toBe(1)
        expect(wdioClInterface.result.passed).toBe(1)
        expect(wdioClInterface.result.retries).toBe(2)
        expect(wdioClInterface.result.failed).toBe(0)
        wdioClInterface.emit('job:start', { cid: '0-1', hasTests: true })
        wdioClInterface.emit('job:end', { cid: '0-1', passed: false, retries: 0 })
        expect(wdioClInterface.result.finished).toBe(2)
        expect(wdioClInterface.result.passed).toBe(1)
        expect(wdioClInterface.result.retries).toBe(2)
        expect(wdioClInterface.result.failed).toBe(1)
    })

    it('should mark jobs as skipped', () => {
        wdioClInterface.emit('job:start', { cid: '0-0', hasTests: false })
        expect(wdioClInterface.result.finished).toBe(0)
        wdioClInterface.emit('job:end', { cid: '0-0' })
        expect(wdioClInterface.result.finished).toBe(1)
        expect(wdioClInterface.result.passed).toBe(0)
        expect(wdioClInterface.result.failed).toBe(0)
        expect(wdioClInterface.result.retries).toBe(0)
    })

    it('should allow to store reporter messages', () => {
        wdioClInterface.onMessage({
            origin: 'reporter',
            name: 'foo',
            content: '123'
        })
        wdioClInterface.onMessage({
            cid: '0-1',
            origin: 'reporter',
            name: 'foo',
            content: '456'
        })
        expect(wdioClInterface['_messages']).toEqual({
            ...EMPTY_INTERFACE_MESSAGE_OBJECT,
            reporter: { foo: ['123', '456'] }
        })
    })

    it('should print test error', () => {
        wdioClInterface.onTestError = vi.fn()
        wdioClInterface.onMessage({
            origin: 'reporter',
            name: 'printFailureMessage',
            content: 'printFailureMessage'
        })
        expect(wdioClInterface.onTestError).toBeCalledWith('printFailureMessage')
        expect(wdioClInterface['_messages']).toEqual(EMPTY_INTERFACE_MESSAGE_OBJECT)
    })

    it('should trigger job:start event on testFrameworkInit', () => {
        wdioClInterface.emit = vi.fn()
        wdioClInterface.onMessage({
            name: 'testFrameworkInit',
            content: 'content'
        })
        expect(wdioClInterface.emit).toBeCalledWith('job:start', 'content')
    })

    it('should not store any other messages', () => {
        wdioClInterface.printReporters = vi.fn()

        expect(wdioClInterface.onMessage({
            cid: '0-0',
            origin: 'worker',
            name: 'barfoo',
            content: 'foobar'
        })).toEqual(['0-0', 'worker', 'barfoo', 'foobar'])

        expect(wdioClInterface['_messages']).toEqual(EMPTY_INTERFACE_MESSAGE_OBJECT)
        expect(wdioClInterface.printReporters).not.toBeCalled()
    })

    it('should print error message on worker error', () => {
        const err = { message: 'foo', stack: 'bar' }
        wdioClInterface.onMessage({
            cid: '0-0',
            origin: 'worker',
            name: 'error',
            content: err
        })

        expect(wdioClInterface.log).toBeCalledTimes(1)
        expect(wdioClInterface.log).toBeCalledWith('[0-0]', 'white bgRed bold  Error: ', 'foo')
    })

    it('should print message on worker error', () => {
        const err = 'bar'
        wdioClInterface.onMessage({
            cid: '0-0',
            origin: 'worker',
            name: 'error',
            content: err
        })

        expect(wdioClInterface.log).toBeCalledTimes(1)
        expect(wdioClInterface.log).toBeCalledWith('[0-0]', 'white bgRed bold  Error: ', 'bar')
    })

    it('should ignore messages that do not contain a proper origin', () => {
        // @ts-ignore test invalid param
        wdioClInterface.onMessage({ foo: 'bar' })
        expect(wdioClInterface['_messages']).toEqual(EMPTY_INTERFACE_MESSAGE_OBJECT)
    })

    it('should render a debug screen when command was called', () => {
        expect(wdioClInterface.onMessage({
            origin: 'debugger',
            name: 'start',
            params: { introMessage: 'foobar' }
        })).toBe(true)
        expect(wdioClInterface['_inDebugMode']).toBe(true)
    })

    it('should exit from debug screen', () => {
        wdioClInterface['_inDebugMode'] = true
        expect(wdioClInterface.onMessage({
            origin: 'debugger',
            name: 'stop'
        })).toBe(false)
        expect(wdioClInterface['_inDebugMode']).toBe(false)
    })

    describe('setup', () => {
        it('called within constructor', () => {
            wdioClInterface.onStart = vi.fn()
            expect(wdioClInterface.result).toEqual({
                finished: 0,
                passed: 0,
                retries: 0,
                failed: 0
            })
            expect(wdioClInterface['_messages']).toEqual(EMPTY_INTERFACE_MESSAGE_OBJECT)
        })

        it('called explicitly', () => {
            wdioClInterface.onStart = vi.fn()
            wdioClInterface['_messages'] = {
                reporter: {},
                debugger: {}
            }
            wdioClInterface.setup()
            expect(wdioClInterface['_messages']).toEqual(EMPTY_INTERFACE_MESSAGE_OBJECT)
        })
    })

    describe('onStart', () => {
        const scenarios = [{
            inDebugMode: false,
            isWatchMode: false,
            timesCalled: 2
        }, {
            inDebugMode: true,
            isWatchMode: false,
            timesCalled: 3
        }, {
            inDebugMode: false,
            isWatchMode: true,
            timesCalled: 3
        }, {
            inDebugMode: true,
            isWatchMode: true,
            timesCalled: 4
        }]

        scenarios.forEach(scenario => {
            it(`inDebugMode = ${scenario.inDebugMode}, isWatchMode = ${scenario.isWatchMode}`, () => {
                wdioClInterface['_inDebugMode'] = scenario.inDebugMode
                wdioClInterface['_isWatchMode'] = scenario.isWatchMode
                wdioClInterface.onStart()
                expect(wdioClInterface.log).toBeCalledTimes(scenario.timesCalled)
            })
        })
    })

    describe('onJobComplete', () => {
        it('all args with retries', () => {
            expect(wdioClInterface.onJobComplete('cid', {
                caps: { browserName: 'foo' },
                specs: ['bar'],
                hasTests: true
            }, 3, 'msg')).toEqual(['[cid]', 'msg', 'in', 'foo', '- bar', '(3 retries)'])
        })

        it('browser version appears well', ()=>{
            expect(wdioClInterface.onJobComplete('cid', {
                caps: { browserName: 'foo', browserVersion: 'stable' },
                specs: ['bar'],
                hasTests: true
            }, 3, 'msg')).toEqual(['[cid]', 'msg', 'in', 'foo(stable)', '- bar', '(3 retries)'])
        })

        it('platform name appears well', ()=>{
            expect(wdioClInterface.onJobComplete('cid', {
                caps: { browserName: 'foo', platformName: 'mac' },
                specs: ['bar'],
                hasTests: true
            }, 3, 'msg')).toEqual(['[cid]', 'msg', 'in', 'foo', 'on', 'mac', '- bar', '(3 retries)'])

            expect(wdioClInterface.onJobComplete('cid', {
                caps: { browserName: 'foo', 'appium:deviceName': 'iPhone 12' },
                specs: ['bar'],
                hasTests: true
            }, 3, 'msg')).toEqual(['[cid]', 'msg', 'in', 'foo', 'on', 'iPhone 12', '- bar', '(3 retries)'])
        })

        it('job is undefined without retries', () => {
            expect(wdioClInterface.onJobComplete('cid', undefined, 0, 'msg')).toEqual(['[cid]', 'msg'])
        })
    })

    describe('getFilenames', () => {
        it('empty array', () => {
            expect(wdioClInterface.getFilenames([])).toEqual('')
        })

        it('no args', () => {
            expect(wdioClInterface.getFilenames()).toEqual('')
        })

        it('no args', () => {
            expect(wdioClInterface.getFilenames(['foo', 'bar'])).toEqual('- foo, bar')
        })
    })

    describe('onSpec', () => {
        const cid = '0-0'
        const job = {
            caps: { browserName: 'foo' },
            specs: ['bar'],
            hasTests: true
        }
        const retries = 42
        const scenarios = [{
            method: 'onSpecRunning',
            cid,
            job,
            retries: 0,
            message: chalk.bold(chalk.cyan('RUNNING'))
        }, {
            method: 'onSpecRetry',
            cid,
            job,
            retries,
            message: chalk.bold(chalk.yellow('RETRYING'))
        }, {
            method: 'onSpecPass',
            cid,
            job,
            retries,
            message: chalk.bold(chalk.green('PASSED'))
        }, {
            method: 'onSpecFailure',
            cid,
            job,
            retries,
            message: chalk.bold(chalk.red('FAILED'))
        }]

        scenarios.forEach(scenario => {
            it(scenario.method, () => {
                wdioClInterface.onJobComplete = vi.fn()
                wdioClInterface['_jobs'].set(scenario.cid, scenario.job)
                wdioClInterface[scenario.method](scenario.cid, scenario.job, scenario.retries)
                expect(wdioClInterface.onJobComplete).toBeCalledWith(scenario.cid, scenario.job, scenario.retries, scenario.message)
            })
        })

        it('onSpecSkip', () => {
            wdioClInterface.onJobComplete = vi.fn()
            wdioClInterface['_jobs'].set('cid', job)
            wdioClInterface.onSpecSkip(cid, job)
            expect(wdioClInterface.onJobComplete).toBeCalledWith(cid, job, 0, 'SKIPPED', expect.any(Function))
        })

        it('onSpecRetry with delay', () => {
            wdioClInterface.onJobComplete = vi.fn()
            wdioClInterface['_specFileRetriesDelay'] = 2
            wdioClInterface['_jobs'].set('cid', job)
            wdioClInterface.onSpecRetry(cid, job, 3)
            expect(wdioClInterface.onJobComplete).toBeCalledWith(cid, job, 3, chalk.bold(chalk.yellow('RETRYING') + ' after 2s'))
        })
    })

    describe('sigintTrigger', () => {
        const runningErrorMessage = 'Ending WebDriver sessions gracefully'
        it('should print message with jobs', () => {
            wdioClInterface['_jobs'].set('0-0', {
                caps: { browserName: 'foo' },
                specs: ['bar'],
                hasTests: true
            })
            expect((wdioClInterface.sigintTrigger() as any)[0])
                .toContain(runningErrorMessage)
        })

        it('should print message when in watch mode', () => {
            wdioClInterface['_isWatchMode'] = true
            expect((wdioClInterface.sigintTrigger() as any)[0])
                .toContain(runningErrorMessage)
        })

        it('should print message without jobs', () => {
            expect((wdioClInterface.sigintTrigger() as any)[0])
                .toContain('Ended WebDriver sessions gracefully')
        })

        it('should do nothing in debug mode', () => {
            wdioClInterface['_inDebugMode'] = true
            expect(wdioClInterface.sigintTrigger()).toBe(false)
        })
    })

    describe('printReporters', () => {
        it('finalise should print reporters and summary', () => {
            wdioClInterface['_messages'] = {
                reporter: {
                    foo: ['bar']
                },
                debugger: {}
            }
            wdioClInterface.printReporters()
            expect(wdioClInterface['_messages'].reporter).toEqual({})
            expect(wdioClInterface.log).toBeCalledTimes(2)
            expect(vi.mocked(wdioClInterface.log).mock.calls[0][1]).toContain('"foo" Reporter:')
            expect(vi.mocked(wdioClInterface.log).mock.calls[1][0]).toContain('bar')
        })
    })

    describe('finalise', () => {
        it('finalise should print reporters and summary', () => {
            wdioClInterface.printReporters = vi.fn()
            wdioClInterface.printSummary = vi.fn()
            wdioClInterface.finalise()
            expect(wdioClInterface.printReporters).toBeCalledTimes(1)
            expect(wdioClInterface.printSummary).toBeCalledTimes(1)
        })
    })

    describe('printSummary', () => {
        it('retries', () => {
            wdioClInterface.totalWorkerCnt = 2
            wdioClInterface.result.retries = 33
            expect(wdioClInterface.printSummary().some(x => x.includes('yellow 33 retries'))).toBe(true)
        })

        it('failed', () => {
            wdioClInterface.result.failed = 44
            expect(wdioClInterface.printSummary().some(x => x.includes('red 44 failed'))).toBe(true)
        })

        it('skipped', () => {
            wdioClInterface['_skippedSpecs'] = 55
            expect(wdioClInterface.printSummary().some(x => x.includes('gray 55 skipped'))).toBe(true)
        })

        it('percentCompleted', () => {
            wdioClInterface.totalWorkerCnt = 31
            wdioClInterface.result.finished = 13
            const result = Math.round(wdioClInterface.result.finished / wdioClInterface.totalWorkerCnt * 100)
            expect(wdioClInterface.printSummary().some(x => x.includes(result))).toBe(true)
        })

        it('percentCompleted without workers', () => {
            wdioClInterface.totalWorkerCnt = 0
            expect(wdioClInterface.printSummary().some(x => x.includes(0))).toBe(true)
        })
    })

    it('logHookError', () => {
        const err = new HookError('foobar', 'somewhere')
        expect(wdioClInterface.logHookError(err)[0])
            .toContain('red SevereServiceError in "somewhere"')
    })

    describe('onTestError', () => {
        it('onTestError', () => {
            const result = wdioClInterface.onTestError({
                cid: 'CID',
                fullTitle: 'FULL_TITLE',
                name: 'foobar',
                error: {
                    type: 'ERROR_TYPE',
                    message: 'ERROR_MESSAGE'
                }
            })

            expect(result[0]).toContain('CID')
            expect(result[1]).toContain('ERROR_TYPE')
            expect(result[1]).toContain('ERROR_MESSAGE')
            expect(result[1]).toContain('FULL_TITLE')
        })

        it('error with stack trace', () => {
            const result = wdioClInterface.onTestError({
                cid: 'CID',
                fullTitle: 'FULL_TITLE',
                name: 'foobar',
                error: {
                    type: 'ERROR_TYPE',
                    message: 'ERROR_MESSAGE',
                    stack: 'ERROR_STACK',
                },
            })

            expect(result[0]).toContain('CID')
            expect(result[1]).toContain('ERROR_TYPE')
            expect(result[1]).toContain('ERROR_STACK')
            expect(result[1]).toContain('FULL_TITLE')
        })

        it('no error', () => {
            const result = wdioClInterface.onTestError({
                cid: 'CID',
                name: 'foobar',
                fullTitle: 'FULL_TITLE'
            })

            expect(result[1]).toContain('Error')
            expect(result[1]).toContain('Unknown error.')
        })

        it('string error', () => {
            const result = wdioClInterface.onTestError({
                cid: 'CID',
                name: 'foobar',
                fullTitle: 'FULL_TITLE',
                // @ts-ignore simplify test
                error: 'STRING_ERROR'
            })

            expect(result[1]).toContain('Error')
            expect(result[1]).toContain('STRING_ERROR')
        })
    })

    afterEach(() => {
        wdioClInterface['_specFileRetriesDelay'] = 0
        vi.mocked(global.console.log).mockRestore()
    })
})