🏡 index : quine-global/webdriverio.git

import path from 'node:path'
import {
    RUNNER,
    SUITES,
    SUITE_UIDS,
    SUITES_NO_TESTS,
    SUITES_WITH_TEST_RETRY,
    SUITES_WITH_DATA_TABLE,
    SUITES_MULTIPLE_ERRORS,
    SUITES_WITH_DOC_STRING,
    SUITES_NO_TESTS_WITH_HOOK_ERROR,
    SUITES_WITH_RETRIES
} from './__fixtures__/testdata.js'
import { State } from '../src/types.js'
import SpecReporter from '../src/index.js'
import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
import { runnerEnd } from '../../wdio-allure-reporter/tests/__fixtures__/runner.js'

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

const reporter = new SpecReporter({})

const defaultCaps = { browserName: 'loremipsum', version: 50, platform: 'Windows 10', sessionId: 'foobar' }
const fakeSessionId = 'ba86cbcb70774ef8a0757c1702c3bdf9'
const getRunnerConfig = (config: any = {}) => {
    return Object.assign(JSON.parse(JSON.stringify(RUNNER)), {
        capabilities: config.capabilities || defaultCaps,
        config,
        sessionId: fakeSessionId,
        isMultiremote: Boolean(config.isMultiremote)
    })
}

describe('SpecReporter', () => {
    let tmpReporter: SpecReporter

    beforeEach(() => {
        tmpReporter = new SpecReporter({})
    })

    describe('on create', () => {
        it('should verify initial properties', () => {
            expect(reporter['_suiteUids'].size).toBe(0)
            expect(reporter['_indents']).toBe(0)
            expect(reporter['_suiteIndents']).toEqual({})
            expect(reporter['_stateCounts']).toEqual({
                passed: 0,
                failed: 0,
                skipped: 0,
                pending: 0,
                retried: 0
            })
        })
    })

    describe('onSuiteStart', () => {
        beforeAll(() => {
            reporter.onSuiteStart(Object.values(SUITES)[0] as any)
        })

        it('should add to suiteUids', () => {
            expect(reporter['_suiteUids'].size).toBe(1)
            expect([...reporter['_suiteUids'].values()][0]).toBe('Foo test1')
        })

        it('should increase suiteIndents', () => {
            expect(reporter['_suiteIndents']['Foo test1']).toBe(1)
        })
    })

    describe('onHookEnd', () => {
        it('should increase stateCount failures if hook failed', () => {
            expect(tmpReporter['_stateCounts'].failed).toBe(0)
            tmpReporter.onHookEnd({} as any)
            expect(tmpReporter['_stateCounts'].failed).toBe(0)
            tmpReporter.onHookEnd({ error: new Error('boom!') } as any)
            expect(tmpReporter['_stateCounts'].failed).toBe(1)
        })
    })

    describe('getEventsToReport', () => {
        it('should return all tests and hook errors to report', () => {
            expect(tmpReporter.getEventsToReport({
                tests: [{ type: 'test', title: '1' }, { type: 'test', title: '2' }],
                hooks: [{}],
                hooksAndTests: [{}, { type: 'test', title: '11' }, {}, { type: 'test', title: '22' }, {}]
            } as any)).toEqual([{ type: 'test', title: '11' }, { type: 'test', title: '22' }])
            expect(tmpReporter.getEventsToReport({
                tests: [{ type: 'test', title: '1' }, { type: 'test', title: '2' }],
                hooks: [{ error: 1 }, {}, { error: 2 }],
                hooksAndTests: [{}, { error: 11 }, {}, { type: 'test', title: '33' }, {}, { error: 22 }, {}]
            } as any)).toEqual([{ error: 11 }, { type: 'test', title: '33' }, { error: 22 }])
        })
    })

    describe('onTestPass', () => {
        beforeAll(() => {
            reporter.onTestPass({
                title:'test1',
                state:State.PASSED
            } as any)
        })

        it('should increase stateCounts.passed by 1', () => {
            expect(reporter['_stateCounts'].passed).toBe(1)
        })
    })

    describe('onTestFail', () => {
        beforeAll(() => {
            reporter.onTestFail({
                title:'test1',
                state:State.FAILED
            } as any)
        })

        it('should increase stateCounts.failed by 1', () => {
            expect(reporter['_stateCounts'].failed).toBe(1)
        })
    })

    describe('onTestSkip', () => {
        beforeAll(() => {
            reporter.onTestSkip({
                title:'test1',
                state:State.SKIPPED,
                pendingReason: 'some random reason'
            } as any)
        })

        it('should have a pending reason', () => {
            expect(reporter['_pendingReasons'][0]).toBe('some random reason')
        })

        it('should increase stateCounts.skipped by 1', () => {
            expect(reporter['_stateCounts'].skipped).toBe(1)
        })
    })

    describe('onSuiteEnd', () => {
        beforeAll(() => {
            reporter.onSuiteEnd()
        })

        it('should decrease indents', () => {
            expect(reporter['_indents']).toBe(0)
        })
    })

    describe('onRunnerEnd', () => {
        it('should call printReport method', () => {
            reporter.printReport = vi.fn()
            reporter.onRunnerEnd(RUNNER as any)

            expect(vi.mocked(reporter.printReport).mock.calls.length).toBe(1)
            expect(vi.mocked(reporter.printReport).mock.calls[0][0]).toEqual(RUNNER)
        })
    })

    describe('printReport', () => {
        let printReporter: any = null

        beforeEach(() => {
            printReporter = new SpecReporter({})
            printReporter.write = vi.fn()
            printReporter.runnerStat = {
                instanceOptions: {
                    [fakeSessionId]: {}
                }
            } as any
        })

        describe('with normal setup', () => {
            beforeEach(() => {
                printReporter['_suiteUids'] = SUITE_UIDS
                printReporter.suites = SUITES
                printReporter['_stateCounts'] = {
                    passed: 4,
                    failed: 1,
                    skipped: 1,
                }
            })

            it('should print the report to the console', () => {
                const runner = getRunnerConfig({ hostname: 'localhost' })
                printReporter.printReport(runner)
                expect(printReporter.write.mock.calls).toMatchSnapshot()
            })

            it('should print link to Sauce Labs job details page for VDC', () => {
                const options = {
                    hostname: 'ondemand.saucelabs.com',
                    user: 'foobar',
                    key: '123'
                }
                const runner = getRunnerConfig({})
                printReporter.runnerStat.instanceOptions[fakeSessionId] = options
                printReporter.printReport(runner)
                expect(printReporter.write.mock.calls).toMatchSnapshot()
            })

            it('should print link to Sauce Labs job details page for RDC', () => {
                const options = {
                    hostname: 'ondemand.saucelabs.com',
                    user: 'foobar',
                    key: '123'
                }
                const runner = getRunnerConfig({
                    capabilities: {
                        browserName: 'safari',
                        ['appium:deviceName']: 'udid-serial-of-device',
                        ['appium:platformVersion']: '14.3',
                        ['appium:platformName']: 'iOS',
                        testobject_test_report_url: ' https://app.eu-central-1.saucelabs.com/tests/c752c683e0874da4b1dad593ce6645b2'
                    },
                    sessionId: 'c752c683e0874da4b1dad593ce6645b2',
                })
                printReporter.runnerStat.instanceOptions[fakeSessionId] = options
                printReporter.printReport(runner)
                expect(printReporter.write.mock.calls).toMatchSnapshot()
            })

            it('should print jobs of all instance when run with multiremote', () => {
                const options = {
                    hostname: 'ondemand.saucelabs.com',
                    user: 'foobar',
                    key: '123'
                }
                const runner = getRunnerConfig({
                    capabilities: {
                        browserA: { browserName: 'chrome' },
                        browserB: { browserName: 'firefox' }
                    },
                    isMultiremote: true
                })
                printReporter.runnerStat.instanceOptions['foobar'] = options
                printReporter.runnerStat.instanceOptions['barfoo'] = options
                printReporter.printReport(runner)
                expect(printReporter.write.mock.calls).toMatchSnapshot()
            })

            it('should print link to Sauce Labs job details page if run with Sauce Connect (w3c)', () => {
                const runner = getRunnerConfig({
                    capabilities: {
                        ...defaultCaps,
                        'sauce:options': 'foobar'
                    },
                    sessionId: fakeSessionId
                })
                printReporter.runnerStat.instanceOptions[fakeSessionId] = {
                    hostname: 'ondemand.us-west-1.saucelabs.com',
                    user: 'foobar',
                    key: '123'
                }
                printReporter.printReport(runner)
                expect(printReporter.write.mock.calls).toMatchSnapshot()
            })

            it('should print link to Sauce Labs job details page if run with Sauce Connect (jsonwp)', () => {
                const runner = getRunnerConfig({
                    capabilities: {
                        tunnelName: 'foobar',
                        ...defaultCaps
                    },
                    sessionId: fakeSessionId
                })
                printReporter.runnerStat.instanceOptions[fakeSessionId] = {
                    hostname: 'ondemand.saucelabs.com',
                    user: 'foobar',
                    key: '123'
                }
                printReporter.printReport(runner)
                expect(printReporter.write.mock.calls).toMatchSnapshot()
            })

            it('should print link to Sauce Labs for many regions', () => {
                printReporter.runnerStat.instanceOptions[fakeSessionId] = {
                    hostname: 'ondemand.eu-central-1.saucelabs.com',
                    user: 'foobar',
                    key: '123'
                }
                printReporter.printReport(getRunnerConfig({}))
                expect(printReporter.write.mock.calls).toMatchSnapshot()

                printReporter.write.mockClear()

                printReporter.runnerStat.instanceOptions[fakeSessionId] = {
                    hostname: 'ondemand.us-east-4.saucelabs.com',
                    user: 'foobar',
                    key: '123'
                }
                printReporter.printReport(getRunnerConfig({}))
                expect(printReporter.write.mock.calls).toMatchSnapshot()
            })
        })

        describe('with disabled sharable Sauce report links', () => {
            const options = { sauceLabsSharableLinks: false }
            beforeEach(() => {
                tmpReporter = new SpecReporter(options)
                // tmpReporter.suiteUids = SUITE_UIDS
                // tmpReporter.suites = SUITES
                // tmpReporter.stateCounts = {
                //     passed: 4,
                //     failed: 1,
                //     skipped: 1,
                // }
                tmpReporter.write = vi.fn()
            })

            it('should print the default Sauce Labs job details page link', () => {
                const runner = getRunnerConfig({
                    hostname: 'ondemand.saucelabs.com',
                    user: 'foobar',
                    key: '123',
                })
                tmpReporter.printReport(runner)
                expect(vi.mocked(tmpReporter.write).mock.calls).toMatchSnapshot()
            })
        })

        it('should print report for suites with no tests but failed hooks', () => {
            printReporter['_suiteUids'] = SUITE_UIDS
            printReporter.suites = SUITES_NO_TESTS_WITH_HOOK_ERROR

            const runner = getRunnerConfig({
                capabilities: {},
                hostname: 'localhost'
            })
            printReporter.printReport(runner)

            expect(printReporter.write.mock.calls.length).toBe(1)
            expect(printReporter.write.mock.calls[0][0]).toContain('a failed hook')
        })

        it('should not print the report because there are no tests', () => {
            printReporter['_suiteUids'] = SUITE_UIDS
            printReporter.suites = SUITES_NO_TESTS

            printReporter.printReport(getRunnerConfig())

            expect(printReporter.write.mock.calls.length).toBe(0)
        })

        it('should print a report even if session could not be created', () => {
            printReporter['_suiteUids'] = []
            printReporter.suites = {}
            const runner = getRunnerConfig()
            runner.error = 'No tests found'

            printReporter.printReport(runner)
            expect(printReporter.write.mock.calls).toMatchSnapshot()
        })
    })

    describe('getHeaderDisplay', () => {
        it('should validate header output', () => {
            const result = reporter.getHeaderDisplay(getRunnerConfig() as any)
            expect(result).toMatchSnapshot()
        })

        it('should list multiple specs', () => {
            const config = getRunnerConfig() as any
            config.specs.push('/foo/bar/loo.js', '/bar/foo/baz.js')
            const result = reporter.getHeaderDisplay(config)
            expect(result).toMatchSnapshot()
        })

        it('should validate header output in multiremote', () => {
            const result = tmpReporter.getHeaderDisplay(
                getRunnerConfig({
                    capabilities: {
                        browserA: { browserName: 'chrome' },
                        browserB: { browserName: 'firefox' }
                    },
                    isMultiremote: true,
                }))

            expect(result).toMatchSnapshot()
        })
    })

    describe('getResultDisplay', () => {
        it('should validate the result output with tests', () => {
            tmpReporter.getOrderedSuites = vi.fn(() => Object.values(SUITES)) as any
            const result = tmpReporter.getResultDisplay()
            expect(result).toMatchSnapshot()
        })

        it('should validate the result output with no tests', () => {
            tmpReporter.getOrderedSuites = vi.fn(() => Object.values(SUITES_NO_TESTS)) as any
            const result = tmpReporter.getResultDisplay()
            expect(result.length).toBe(0)
        })

        it('should print data tables', () => {
            tmpReporter.getOrderedSuites = vi.fn(() => Object.values(SUITES_WITH_DATA_TABLE)) as any
            const result = tmpReporter.getResultDisplay()
            expect(result).toMatchSnapshot()
        })

        it('should not print if argument is a single line doc string', () => {
            tmpReporter.getOrderedSuites = vi.fn(() => {
                const suites = Object.values(JSON.parse(JSON.stringify(SUITES_WITH_DATA_TABLE))) as any[]
                suites[0].hooksAndTests[0].argument = 'some different format'
                return suites
            })
            const result = tmpReporter.getResultDisplay()
            expect(result).toMatchSnapshot()
        })

        it('should print multiple lines doc string', () => {
            tmpReporter.getOrderedSuites = vi.fn(() => {
                return Object.values(JSON.parse(JSON.stringify(SUITES_WITH_DOC_STRING))) as any[]
            })
            const result = tmpReporter.getResultDisplay()
            expect(result).toMatchSnapshot()
        })

        it('should print if the doc string is a blank string', () => {
            tmpReporter.getOrderedSuites = vi.fn(() => {
                const suites = Object.values(JSON.parse(JSON.stringify(SUITES_WITH_DOC_STRING))) as any[]
                suites[0].hooksAndTests[0].argument = ''
                suites[0].hooksAndTests[1].argument = ''
                return suites
            })
            const result = tmpReporter.getResultDisplay()
            expect(result).toMatchSnapshot()
        })

        it('should not print if the argument is undefined', () => {
            tmpReporter.getOrderedSuites = vi.fn(() => {
                const suites = Object.values(JSON.parse(JSON.stringify(SUITES_WITH_DATA_TABLE))) as any[]
                suites[0].hooksAndTests[0].argument = undefined
                return suites
            })
            const result = tmpReporter.getResultDisplay()
            expect(result).toMatchSnapshot()
        })

        it('should not print if data table is empty', () => {
            tmpReporter.getOrderedSuites = vi.fn(() => {
                const suites = Object.values(JSON.parse(JSON.stringify(SUITES_WITH_DATA_TABLE))) as any[]
                suites[0].hooksAndTests[0].argument.rows = []
                return suites
            })

            const result = tmpReporter.getResultDisplay()
            expect(result).toMatchSnapshot()
        })
    })

    describe('getCountDisplay', () => {
        it('should return only passing counts', () => {
            tmpReporter['_stateCounts'].passed = 2
            const result = tmpReporter.getCountDisplay('5')

            expect(result.length).toBe(1)
            expect(result[0]).toBe('green 2 passing 5')
        })

        it('should return passing and failing counts', () => {
            tmpReporter['_stateCounts'].passed = 2
            tmpReporter['_stateCounts'].failed = 1
            const result = tmpReporter.getCountDisplay('5')

            expect(result.length).toBe(2)
            expect(result[0]).toBe('green 2 passing 5')
            expect(result[1]).toBe('red 1 failing')
        })

        it('should return failing and skipped counts', () => {
            tmpReporter['_stateCounts'].failed = 1
            tmpReporter['_stateCounts'].skipped = 2
            const result = tmpReporter.getCountDisplay('5')

            expect(result.length).toBe(2)
            expect(result[0]).toBe('red 1 failing 5')
            expect(result[1]).toBe('cyan 2 skipped')
        })

        it('should only display skipped with duration', () => {
            tmpReporter['_stateCounts'].skipped = 2
            const result = tmpReporter.getCountDisplay('5')

            expect(result.length).toBe(1)
            expect(result[0]).toBe('cyan 2 skipped 5')
        })
    })

    describe('getFailureDisplay', () => {
        it('should return failing results', () => {
            tmpReporter.getOrderedSuites = vi.fn(() => Object.values(SUITES)) as any
            const result = tmpReporter.getFailureDisplay()

            expect(result.length).toBe(7)
            expect(result[0]).toBe('')
            expect(result[1]).toBe('1) Bar test a failed test')
            expect(result[2]).toBe('red expected foo to equal bar')
            expect(result[3]).toBe('gray Failed test stack trace')
        })

        it('should return no results', () => {
            tmpReporter.getOrderedSuites = vi.fn(() => Object.values(SUITES_NO_TESTS)) as any
            const result = tmpReporter.getFailureDisplay()

            expect(result.length).toBe(0)
        })

        it('should return mutliple failing results if they exist', () => {
            tmpReporter.getOrderedSuites = vi.fn(() => Object.values(SUITES_MULTIPLE_ERRORS)) as any
            const result = tmpReporter.getFailureDisplay()
            expect(result.length).toBe(6)
            expect(result[0]).toBe('')
            expect(result[1]).toBe('1) Bar test a test with two failures')
            expect(result[2]).toBe('red expected the party on the first part to be the party on the first part')
            expect(result[3]).toBe('gray First failed stack trace')
            expect(result[4]).toBe('red expected the party on the second part to be the party on the second part')
            expect(result[5]).toBe('gray Second failed stack trace')
        })
    })

    describe('getOrderedSuites', () => {
        it('should return the suites in order based on uids', () => {
            tmpReporter['_suiteUids'] = new Set(['5', '3', '8'])
            tmpReporter.suites = { '3': { uid: 3 }, '5': { uid: 5 } } as any

            const result = tmpReporter.getOrderedSuites()

            expect(result.length).toBe(2)
            expect(result[0]).toEqual({ uid: 5 })
            expect(result[1]).toEqual({ uid: 3 })

            expect(tmpReporter['_orderedSuites'].length).toBe(2)
            expect(tmpReporter['_orderedSuites'][0]).toEqual({ uid: 5 })
            expect(tmpReporter['_orderedSuites'][1]).toEqual({ uid: 3 })
        })

        it('should return the cached ordered suites', () => {
            tmpReporter['_orderedSuites'] = ['foo', 'bar'] as any
            const result = tmpReporter.getOrderedSuites()

            expect(result.length).toBe(2)
            expect(result[0]).toBe('foo')
            expect(result[1]).toBe('bar')
        })

        it('should return no suites', () => {
            expect(tmpReporter.getOrderedSuites().length).toBe(0)
        })

        it('should propagate root suite hook errors', () => {
            tmpReporter['_suiteUids'] = new Set(['5', '3', '8'])
            tmpReporter.suites = { '3': { uid: 3 }, '5': { uid: 5 } } as any
            tmpReporter.currentSuites = [{
                hooks: [{
                    type: 'hook',
                    title: '"before all" hook in "{root}"',
                    state: State.FAILED
                }, {
                    type: 'hook',
                    title: '"after all" hook in "{root}"',
                    state: undefined
                }, {
                    type: 'hook',
                    title: '"after all" hook in "{root}"',
                    state: State.FAILED
                }]
            }] as any
            const result = tmpReporter.getOrderedSuites()
            expect(result.length).toBe(4)
            expect(result[0].hooks.length).toBe(1)
            expect(result[3].hooks.length).toBe(1)
        })
    })

    describe('indent', () => {
        const uid = '123'

        it('should not indent', () => {
            tmpReporter['_suiteIndents'][uid] = 0
            const result = tmpReporter.indent(uid)

            expect(result).toBe('')
        })

        it('should indent', () => {
            tmpReporter['_suiteIndents'][uid] = 3
            const result = tmpReporter.indent(uid)

            expect(result).toBe('        ')
        })
    })

    describe('getSymbol', () => {
        it('should get the checkbox symbol', () => {
            expect(tmpReporter.getSymbol(State.PASSED)).toBe('✓')
        })

        it('should get the x symbol', () => {
            expect(tmpReporter.getSymbol(State.FAILED)).toBe('✖')
        })

        it('should get the - symbol', () => {
            expect(tmpReporter.getSymbol(State.SKIPPED)).toBe('-')
        })

        it('should get the ? symbol', () => {
            expect(tmpReporter.getSymbol()).toBe('?')
        })
    })

    describe('custom getSymbol', () => {
        const options = { symbols: { passed: 'Y', failed: 'N' } }
        beforeEach(() => {
            tmpReporter = new SpecReporter(options)
        })

        it('should get new passed symbol', () => {
            expect(tmpReporter.getSymbol(State.PASSED)).toBe(options.symbols.passed)
        })

        it('should get new failed symbol', () => {
            expect(tmpReporter.getSymbol(State.FAILED)).toBe(options.symbols.failed)
        })

        it('should get the skipped symbol that is not set', () => {
            expect(tmpReporter.getSymbol(State.SKIPPED)).toBe('-')
        })
    })

    describe('add console logs', () => {
        const options = { addConsoleLogs: true }

        it('should add console log to report for passing test', () => {
            tmpReporter = new SpecReporter(options)
            tmpReporter.onSuiteStart(Object.values(SUITES)[0] as any)
            tmpReporter.onTestStart()
            tmpReporter['_orderedSuites'] = Object.values(SUITES) as any
            tmpReporter['_consoleOutput']='Printing to console spec'
            tmpReporter.onTestPass({
                title:'test1',
                state:State.PASSED
            } as any)
            expect(tmpReporter.getResultDisplay().toString()).toContain('Printing to console spec')
            tmpReporter.onSuiteEnd()
            tmpReporter.onRunnerEnd(runnerEnd())
        })

        it('should add console logs to report for failing test', () => {
            tmpReporter = new SpecReporter(options)
            tmpReporter.onSuiteStart(Object.values(SUITES)[0] as any)
            tmpReporter.onTestStart()
            tmpReporter['_orderedSuites'] = Object.values(SUITES) as any
            tmpReporter['_consoleOutput']='Printing to console spec'
            tmpReporter.onTestFail({
                title:'test1',
                state:State.FAILED
            } as any)
            expect(tmpReporter.getResultDisplay().toString()).toContain('Printing to console spec')
            tmpReporter.onSuiteEnd()
            tmpReporter.onRunnerEnd(runnerEnd())
        })

        it('should add console logs to report for skipping test', () => {
            tmpReporter = new SpecReporter(options)
            tmpReporter.onSuiteStart(Object.values(SUITES)[0] as any)
            tmpReporter.onTestStart()
            tmpReporter['_orderedSuites'] = Object.values(SUITES) as any
            tmpReporter['_consoleOutput']='Printing to console spec'
            tmpReporter.onTestSkip({
                title:'test1',
                state:State.SKIPPED
            } as any)
            expect(tmpReporter.getResultDisplay().toString()).toContain('Printing to console spec')
            tmpReporter.onSuiteEnd()
            tmpReporter.onRunnerEnd(runnerEnd())
        })

        it('should add pending reason to report for skipped tests', () => {
            tmpReporter = new SpecReporter(options)
            tmpReporter.onSuiteStart(Object.values(SUITES)[2] as any)
            tmpReporter.onTestStart()
            tmpReporter['_orderedSuites'] = Object.values(SUITES) as any
            tmpReporter.onTestSkip({
                title:'test1',
                state:State.SKIPPED,
                pendingReason:'some random Reasons'
            } as any)
            expect(tmpReporter.getResultDisplay().toString()).toContain('Pending Reasons')
            tmpReporter.onSuiteEnd()
            tmpReporter.onRunnerEnd(runnerEnd())
        })

        it('should not add webdriver logs to report', () => {
            tmpReporter = new SpecReporter(options)
            tmpReporter.onSuiteStart(Object.values(SUITES)[0] as any)
            tmpReporter['_orderedSuites'] = Object.values(SUITES) as any
            tmpReporter['_consoleOutput']='mwebdriver test log'
            expect(tmpReporter.getResultDisplay().toString()).not.toContain('mwebdriver test log')
            tmpReporter.onSuiteEnd()
            tmpReporter.onRunnerEnd(runnerEnd())
        })
    })

    describe('onlyFailures', () => {
        let printReporter: any = null
        const runner = getRunnerConfig({ hostname: 'localhost' })

        describe('false', () => {
            beforeEach(() => {
                printReporter = new SpecReporter({ onlyFailures: false })
                printReporter.write = vi.fn()
                printReporter['_suiteUids'] = SUITE_UIDS
                printReporter.suites = SUITES
            })

            it('1 failure', () => {
                runner.failures = 1
                printReporter.printReport(runner)

                expect(printReporter['_onlyFailures']).toBe(false)
                expect(printReporter.write.mock.calls).toMatchSnapshot()
            })

            it('0 failures', () => {
                runner.failures = 0
                printReporter.printReport(runner)

                expect(printReporter['_onlyFailures']).toBe(false)
                expect(printReporter.write.mock.calls).toMatchSnapshot()
            })
        })

        describe('true', () => {
            beforeEach(() => {
                printReporter = new SpecReporter({ onlyFailures: true })
                printReporter.write = vi.fn()
                printReporter['_suiteUids'] = SUITE_UIDS
                printReporter.suites = SUITES
            })

            it('1 failure', () => {
                runner.failures = 1
                printReporter.printReport(runner)

                expect(printReporter['_onlyFailures']).toBe(true)
                expect(printReporter.write.mock.calls).toMatchSnapshot()
            })

            it('0 failures', () => {
                runner.failures = 0
                printReporter.printReport(runner)

                expect(printReporter['_onlyFailures']).toBe(true)
                expect(printReporter.write.mock.calls).toMatchSnapshot()
            })
        })
    })

    describe('onRetry', () => {
        let printReporter: any = null
        const runner = getRunnerConfig({ hostname: 'localhost' })

        beforeEach(() => {
            printReporter = new SpecReporter({})
            printReporter.write = vi.fn()
            printReporter['_suiteUids'] = SUITE_UIDS
            printReporter.suites = SUITES_WITH_TEST_RETRY
        })

        it('should group retried test cases', () => {
            runner.failures = 0
            printReporter.printReport(runner)
            expect(printReporter.write.mock.calls).toMatchSnapshot()
        })
    })

    describe('suite retry', () => {
        let printReporter: any = null
        const runner = getRunnerConfig({ hostname: 'localhost' })

        beforeEach(() => {
            printReporter = new SpecReporter({})
            printReporter.write = vi.fn()
            printReporter['_suiteUids'] = SUITE_UIDS
            printReporter.suites = SUITES_WITH_RETRIES
        })

        it('should group retried test suites', () => {
            runner.failures = 0
            printReporter.onTestPass({})
            printReporter.onTestPass({})
            printReporter.onTestFail({})
            printReporter.onSuiteRetry()
            printReporter.onTestPass({})
            printReporter.onTestPass({})
            printReporter.printReport(runner)
            expect(printReporter.write.mock.calls).toMatchSnapshot()
        })
    })

    describe('showPreface', () => {
        let printReporter: SpecReporter = null as any
        const runner = getRunnerConfig({ hostname: 'localhost' })
        it('false', () => {
            printReporter = new SpecReporter({ showPreface: false })
            printReporter.write = vi.fn()
            printReporter['_suiteUids'] = SUITE_UIDS
            printReporter.printReport(runner)

            expect(printReporter['_showPreface']).toBe(false)
            expect(vi.mocked(printReporter.write).mock.calls).toMatchSnapshot()
        })

        it('true', () => {
            printReporter = new SpecReporter({ showPreface: true })
            printReporter.write = vi.fn()
            printReporter['_suiteUids'] = SUITE_UIDS
            printReporter.printReport(runner)

            expect(printReporter['_showPreface']).toBe(true)
            expect(vi.mocked(printReporter.write).mock.calls).toMatchSnapshot()
        })
    })

    describe('getColor', () => {
        it('should get green', () => {
            expect(tmpReporter.getColor(State.PASSED)).toBe('green')
        })

        it('should get red', () => {
            expect(tmpReporter.getColor(State.FAILED)).toBe('red')
        })

        it('should get cyan', () => {
            expect(tmpReporter.getColor(State.SKIPPED)).toBe('cyan')
            expect(tmpReporter.getColor(State.PENDING)).toBe('cyan')
        })

        it('should get null', () => {
            expect(tmpReporter.getColor()).toBe('gray')
        })
    })

    describe('colors in terminal', () => {
        it('should give colors', () => {
            const tmpReporter = new SpecReporter({})
            expect(tmpReporter.setMessageColor('test', State.PASSED)).toEqual('green test')
            expect(tmpReporter.setMessageColor('test', State.FAILED)).toEqual('red test')
            expect(tmpReporter.setMessageColor('test', State.SKIPPED)).toEqual('cyan test')
        })

        it('should not give any color', () => {
            const tmpReporter = new SpecReporter({ color: false })
            expect(tmpReporter.setMessageColor('test', State.PASSED)).toEqual('test')
            expect(tmpReporter.setMessageColor('test', State.FAILED)).toEqual('test')
            expect(tmpReporter.setMessageColor('test', State.SKIPPED)).toEqual('test')
        })
    })

    describe('getEnviromentCombo', () => {
        it('should return Multibrowser as capability if multiremote is used', () => {
            expect(tmpReporter.getEnviromentCombo({
                myBrowser: {
                    browserName: 'chrome',
                    platform: 'Windows 8.1'
                }
            } as any, true, true)).toBe('MultiremoteBrowser on chrome')
        })

        it('should not throw if mutliremote name is "app"', () => {
            expect(tmpReporter.getEnviromentCombo({
                app: {
                    browserName: 'chrome',
                    platform: 'Windows 8.1'
                }
            } as any, true, true)).toBe('MultiremoteBrowser on chrome')
        })

        it('should return verbose desktop combo', () => {
            expect(tmpReporter.getEnviromentCombo({
                browserName: 'chrome',
                version: 50,
                platform: 'Windows 8.1'
            } as any)).toBe('chrome (v50) on Windows 8.1')
        })

        it('should return preface desktop combo', () => {
            expect(tmpReporter.getEnviromentCombo({
                browserName: 'chrome',
                version: 50,
                platform: 'Windows 8.1'
            } as any, false)).toBe('chrome 50 Windows 8.1')
        })

        it('should return verbose mobile combo', () => {
            expect(tmpReporter.getEnviromentCombo({
                ['appium:deviceName']: 'iPhone 6 Plus',
                ['appium:platformVersion']: '9.2',
                ['appium:platformName']: 'iOS'
            })).toBe('iPhone 6 Plus on iOS 9.2')
        })

        it('should return verbose mobile combo', () => {
            expect(tmpReporter.getEnviromentCombo({
                ['appium:deviceName']: 'iPhone 6 Plus',
                ['appium:platformVersion']: '9.2',
                platformName: 'iOS'
            })).toBe('iPhone 6 Plus on iOS 9.2')
        })

        it('should return preface mobile combo', () => {
            expect(tmpReporter.getEnviromentCombo({
                ['appium:deviceName']: 'iPhone 6 Plus',
                ['appium:platformVersion']: '9.2',
                ['appium:platformName']: 'iOS'
            }, false)).toBe('iPhone 6 Plus iOS 9.2')
        })

        it('should return verbose mobile combo executing an app', () => {
            expect(tmpReporter.getEnviromentCombo({
                ['appium:deviceName']: 'iPhone 6 Plus',
                ['appium:platformVersion']: '9.2',
                ['appium:platformName']: 'iOS',
                ['appium:app']: 'sauce-storage:myApp.app'
            })).toBe('iPhone 6 Plus on iOS 9.2 executing myApp.app')
        })

        it('should return preface mobile combo executing an app', () => {
            expect(tmpReporter.getEnviromentCombo({
                ['appium:deviceName']: 'iPhone 6 Plus',
                ['appium:platformVersion']: '9.2',
                ['appium:platformName']: 'iOS',
                ['appium:app']: 'sauce-storage:myApp.app'
            }, true)).toBe('iPhone 6 Plus on iOS 9.2 executing myApp.app')
        })

        it('should return verbose mobile combo executing a browser', () => {
            expect(tmpReporter.getEnviromentCombo({
                ['appium:deviceName']: 'iPhone 6 Plus',
                ['appium:platformVersion']: '9.2',
                ['appium:platformName']: 'iOS',
                browserName: 'Safari'
            })).toBe('iPhone 6 Plus on iOS 9.2 executing Safari')
        })

        it('should return preface mobile combo executing a browser', () => {
            expect(tmpReporter.getEnviromentCombo({
                ['appium:deviceName']: 'iPhone 6 Plus',
                ['appium:platformVersion']: '9.2',
                ['appium:platformName']: 'iOS',
                browserName: 'Safari'
            }, false)).toBe('iPhone 6 Plus iOS 9.2')
        })

        it('should return verbose desktop combo when using BrowserStack capabilities', () => {
            expect(tmpReporter.getEnviromentCombo({
                browser: 'Chrome',
                browser_version: 50,
                os: 'Windows',
                os_version: '10'
            } as any)).toBe('Chrome (v50) on Windows 10')
        })

        it('should return preface desktop combo when using BrowserStack capabilities', () => {
            expect(tmpReporter.getEnviromentCombo({
                browser: 'Chrome',
                browser_version: 50,
                os: 'Windows',
                os_version: '10'
            } as any, false)).toBe('Chrome 50 Windows 10')
        })

        it('should return verbose desktop combo when using BrowserStack capabilities without os', () => {
            expect(tmpReporter.getEnviromentCombo({
                browser: 'Chrome',
                browser_version: 50,
            } as any)).toBe('Chrome (v50) on (unknown)')
        })

        it('should return preface desktop combo when using BrowserStack capabilities without os', () => {
            expect(tmpReporter.getEnviromentCombo({
                browser: 'Chrome',
                browser_version: 50,
            } as any, false)).toBe('Chrome 50 (unknown)')
        })

        it('should return verbose desktop combo when using BrowserStack capabilities without os_version', () => {
            expect(tmpReporter.getEnviromentCombo({
                browser: 'Chrome',
                browser_version: 50,
                os: 'Windows',
            } as any)).toBe('Chrome (v50) on Windows')
        })

        it('should return preface desktop combo when using BrowserStack capabilities without os_version', () => {
            expect(tmpReporter.getEnviromentCombo({
                browser: 'Chrome',
                browser_version: 50,
                os: 'Windows',
            } as any, false)).toBe('Chrome 50 Windows')
        })

        it('should return preface desktop combo when using W3C capabilities', () => {
            expect(tmpReporter.getEnviromentCombo({
                browser: 'Chrome',
                browserVersion: 50,
                platformName: 'Windows',
            } as any, false)).toBe('Chrome 50 Windows')
        })

        it('should return verbose desktop combo without platform', () => {
            expect(tmpReporter.getEnviromentCombo({
                browserName: 'chrome',
                version: 50,
            } as any)).toBe('chrome (v50) on (unknown)')
        })

        it('should return preface desktop combo without platform', () => {
            expect(tmpReporter.getEnviromentCombo({
                browserName: 'chrome',
                version: 50,
            } as any, false)).toBe('chrome 50 (unknown)')
        })

        it('should recognise bundleIds', () => {
            expect(tmpReporter.getEnviromentCombo({
                platformName: 'Mac',
                automationName: 'Mac2',
                'appium:bundleId': 'com.apple.calculator',
                sessionId: '53d1c8fd-23d9-4e81-a94b-011d2e694b9a'
            } as any, false)).toBe('com.apple.calculator Mac')
        })

        it('should recognise appPackage', () => {
            expect(tmpReporter.getEnviromentCombo({
                platformName: 'Android',
                'appium:automationName': 'uiautomator2',
                'appium:appPackage': 'com.example.android',
                'appium:appActivity': '.MainActivity'
            }, false)).toBe('com.example.android Android')
        })

        it('should prefer bundleId over app path', () => {
            expect(tmpReporter.getEnviromentCombo({
                platformName: 'Android',
                'appium:automationName': 'uiautomator2',
                'appium:bundleId': 'com.example.android',
                'appium:appActivity': '.MainActivity',
                'appium:app': '/foo/bar/loo.apk'
            }, false)).toBe('com.example.android Android')
        })

        it('prefers app activity over app path', () => {
            expect(tmpReporter.getEnviromentCombo({
                platformName: 'Android',
                'appium:automationName': 'uiautomator2',
                'appium:appActivity': '.MainActivity',
                'appium:app': '/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/loo.apk'
            }, false)).toBe('.MainActivity Android')
        })

        it('uses file name as app path instead of long path', () => {
            expect(tmpReporter.getEnviromentCombo({
                platformName: 'Android',
                'appium:automationName': 'uiautomator2',
                'appium:app': '/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/loo.apk'
            }, false)).toBe('loo.apk Android')
        })
    })

    describe('add real time report', () => {
        const options = { realtimeReporting: true }

        it('should call printCurrentStats for passing test', () => {
            tmpReporter = new SpecReporter(options)
            vi.spyOn(tmpReporter, 'printCurrentStats')
            tmpReporter.onSuiteStart(Object.values(SUITES)[0] as any)
            tmpReporter.onTestStart()
            tmpReporter['_orderedSuites'] = Object.values(SUITES) as any
            tmpReporter['_consoleOutput']='Printing to console spec'
            tmpReporter.onTestPass({
                title:'test1',
                state:State.PASSED
            } as any)
            expect(tmpReporter.printCurrentStats).toBeCalledWith({
                title:'test1',
                state:State.PASSED
            })
            tmpReporter.onSuiteEnd()
            tmpReporter.onRunnerEnd(runnerEnd())
        })

        it('should call printCurrentStats for falling test', () => {
            tmpReporter = new SpecReporter(options)
            vi.spyOn(tmpReporter, 'printCurrentStats')
            tmpReporter.onSuiteStart(Object.values(SUITES)[0] as any)
            tmpReporter.onTestStart()
            tmpReporter['_orderedSuites'] = Object.values(SUITES) as any
            tmpReporter['_consoleOutput']='Printing to console spec'
            tmpReporter.onTestPass({
                title:'test1',
                state:State.FAILED
            } as any)
            expect(tmpReporter.printCurrentStats).toBeCalledWith({
                title:'test1',
                state:State.FAILED
            })
            tmpReporter.onSuiteEnd()
            tmpReporter.onRunnerEnd(runnerEnd())
        })

        it('should call printCurrentStats skipped test', () => {
            tmpReporter = new SpecReporter(options)
            vi.spyOn(tmpReporter, 'printCurrentStats')
            tmpReporter.onSuiteStart(Object.values(SUITES)[0] as any)
            tmpReporter.onTestStart()
            tmpReporter['_orderedSuites'] = Object.values(SUITES) as any
            tmpReporter['_consoleOutput']='Printing to console spec'
            tmpReporter.onTestPass({
                title:'test1',
                state:State.SKIPPED
            } as any)
            expect(tmpReporter.printCurrentStats).toBeCalledWith({
                title:'test1',
                state:State.SKIPPED
            })
            tmpReporter.onSuiteEnd()
            tmpReporter.onRunnerEnd(runnerEnd())
        })
    })

    it('should call printCurrentStats on Hook complete', () => {
        tmpReporter = new SpecReporter({ realtimeReporting : false })
        vi.spyOn(tmpReporter, 'printCurrentStats')
        tmpReporter.onSuiteStart(Object.values(SUITES)[0] as any)
        tmpReporter.onTestStart()
        tmpReporter['_orderedSuites'] = Object.values(SUITES) as any
        tmpReporter['_consoleOutput']='Printing to console spec'
        tmpReporter.onHookEnd({
            title:'test1',
            state:State.FAILED
        } as any)
        expect(tmpReporter.printCurrentStats).toBeCalledWith({
            title:'test1',
            state:State.FAILED
        })
        tmpReporter.onSuiteEnd()
        tmpReporter.onRunnerEnd(runnerEnd())
    })
})