import fs from 'node:fs/promises' import path from 'node:path' import { expect, describe, it, vi, beforeEach, afterEach } from 'vitest' import LocalRunner from '@wdio/local-runner' import libCoverage from 'istanbul-lib-coverage' import libReport from 'istanbul-lib-report' import reports from 'istanbul-reports' import BrowserRunner from '../src/index.js' vi.mock('webdriverio', () => import(path.join(process.cwd(), '__mocks__', 'webdriverio'))) vi.mock('@wdio/logger', () => import(path.join(process.cwd(), '__mocks__', '@wdio/logger'))) vi.mock('@wdio/local-runner') vi.mock('../src/communicator.js', () => ({ ServerWorkerCommunicator: class { coverageMaps = ['coverageMap'] register = vi.fn() } })) vi.mock('../src/vite/server.js', () => ({ ViteServer: class { start = vi.fn().mockResolvedValue(1234) close = vi.fn() onBrowserEvent = vi.fn() config = { server: { port: 1234 } } on = vi.fn() } })) vi.mock('istanbul-lib-coverage', () => ({ default: { createCoverageMap: vi.fn().mockReturnValue({ map: vi.fn() }) } })) vi.mock('istanbul-lib-report', () => ({ default: { createContext: vi.fn().mockReturnValue('context') } })) vi.mock('istanbul-reports', () => ({ default: { create: vi.fn().mockReturnValue({ execute: vi.fn() }) } })) vi.mock('node:fs/promises', async () => { const coverageResult: any = await vi.importActual('./__fixtures__/coverage-summary.json') return { default: { readFile: vi.fn().mockReturnValue(JSON.stringify(coverageResult.default)), access: vi.fn().mockResolvedValue(false), rm: vi.fn() } } }) describe('BrowserRunner', () => { beforeEach(() => { delete process.env.CI }) it('should throw if framework is not Mocha', () => { expect(() => new BrowserRunner({}, {} as any)).toThrow() expect(() => new BrowserRunner({}, { rootDir: '/foo/bar', framework: 'jasmine' } as any)).toThrow() expect(() => new BrowserRunner({}, { rootDir: '/foo/bar', framework: 'mocha' } as any)).not.toThrow() }) it('initialize', async () => { const runner = new BrowserRunner({}, { rootDir: '/foo/bar', framework: 'mocha' } as any) await runner.initialize() expect(fs.rm).toBeCalledWith( path.join('/foo/bar', 'coverage'), { recursive: true } ) }) it('run', async () => { const runner = new BrowserRunner({}, { rootDir: '/foo/bar', framework: 'mocha' } as any) await runner.initialize() const on = vi.fn() vi.mocked(LocalRunner.prototype.run).mockReturnValue({ on } as any) const worker = await runner.run({ caps: { browserName: 'chrome' }, command: 'run', args: {} } as any) expect(worker).toBeDefined() expect(LocalRunner.prototype.run).toBeCalledWith({ args: { baseUrl: 'http://localhost:1234' }, caps: { browserName: 'chrome' }, command: 'run' }) expect(runner['_servers'].size).toBe(1) expect(runner['_servers'].values().next().value!.start).toHaveBeenCalledTimes(1) }) it('modifies runArgs to set allowOrigins', async () => { const runner = new BrowserRunner({}, { rootDir: '/foo/bar', framework: 'mocha' } as any) await runner.initialize() const on = vi.fn() vi.mocked(LocalRunner.prototype.run).mockReturnValue({ on } as any) const runArgs = { caps: { browserName: 'firefox' }, command: 'run', args: {} } as any await runner.run(runArgs) expect(runArgs.caps['wdio:geckodriverOptions'].allowOrigins).toEqual(['http://localhost:1234']) }) it('shutdown', async () => { const runner = new BrowserRunner({}, { rootDir: '/foo/bar', framework: 'mocha' } as any) runner['_generateCoverageReports'] = vi.fn() await runner.initialize() await runner.shutdown() expect(LocalRunner.prototype.shutdown).toBeCalledTimes(1) expect(runner['_generateCoverageReports']).toBeCalledTimes(1) }) describe('_generateCoverageReports', async () => { const logOrig = console.log.bind(console) beforeEach(() => { vi.mocked(libReport.createContext).mockClear() vi.mocked(reports.create).mockClear() console.log = vi.fn() }) afterEach(() => { console.log = logOrig }) it('should do nothing if coverage is not enabled', async () => { const runner = new BrowserRunner({}, { rootDir: '/foo/bar', framework: 'mocha' } as any) expect(await runner['_generateCoverageReports']()).toBe(true) expect(libCoverage.createCoverageMap).toBeCalledTimes(0) }) it('should generate reports', async () => { const runner = new BrowserRunner({ coverage: { enabled: true, } }, { rootDir: '/foo/bar', framework: 'mocha' } as any) expect(await runner['_generateCoverageReports']()).toBe(true) expect(console.log).toBeCalledTimes(0) expect(libCoverage.createCoverageMap).toBeCalledTimes(1) expect(libReport.createContext).toBeCalledTimes(1) expect(reports.create).toBeCalledTimes(4) }) it('should fail if coverage global treshold is not met', async () => { const runner = new BrowserRunner({ coverage: { enabled: true, statements: 90, functions: 70, reporter: 'lcov' } }, { rootDir: '/foo/bar', framework: 'mocha' } as any) expect(await runner['_generateCoverageReports']()).toBe(false) expect(console.log).toBeCalledWith([ 'ERROR: Coverage for functions (50%) does not meet global threshold (70%)', 'ERROR: Coverage for statements (47.82%) does not meet global threshold (90%)' ].join('\n')) expect(reports.create).toBeCalledTimes(2) expect(reports.create).toBeCalledWith('lcov', expect.any(Object)) expect(reports.create).toBeCalledWith('json-summary', expect.any(Object)) }) it('should fail if coverage file based treshold is not met', async () => { const runner = new BrowserRunner({ coverage: { enabled: true, perFile: true, statements: 90, functions: 70, reporter: 'lcov' } }, { rootDir: '/foo/bar', framework: 'mocha' } as any) expect(await runner['_generateCoverageReports']()).toBe(false) expect(console.log).toBeCalledWith([ 'ERROR: Coverage for functions (50%) does not meet threshold (70%) for /components/ReactComponent.jsx', 'ERROR: Coverage for statements (47.82%) does not meet threshold (90%) for /components/ReactComponent.jsx' ].join('\n')) }) it('should return false if generating the report failed', async () => { const runner = new BrowserRunner({ coverage: { enabled: true, perFile: true, statements: 90, functions: 70, reporter: 'lcov' } }, { rootDir: '/foo/bar', framework: 'mocha' } as any) vi.mocked(fs.readFile).mockRejectedValueOnce(new Error('ups')) expect(await runner['_generateCoverageReports']()).toBe(false) expect(console.log).toBeCalledTimes(0) }) }) })