import path from 'node:path'
import { EventEmitter } from 'node:events'
import { expect, test, vi, beforeEach } from 'vitest'
import type { CDPSession } from 'puppeteer-core/lib/esm/puppeteer/api/CDPSession.js'
import type { Page } from 'puppeteer-core/lib/esm/puppeteer/api/Page.js'
import Auditor from '../src/auditor.js'
import CommandHandler from '../src/commands.js'
vi.mock('@wdio/logger', () => import(path.join(process.cwd(), '__mocks__', '@wdio/logger')))
vi.mock('../src/utils', () => ({
readIOStream: vi.fn().mockReturnValue('foobar'),
sumByKey: vi.fn().mockReturnValue('foobar')
}))
vi.mock('../src/auditor', () => {
const updateCommandsMock = vi.fn()
return {
default: class {
traceEvents: any
logs: any
updateCommands = updateCommandsMock
constructor (traceEvents: any, logs: any) {
this.traceEvents = traceEvents
this.logs = logs
}
}
}
})
vi.mock('../src/gatherer/coverage', () => {
const instances: any[] = []
return {
default: class {
getCoverageReport = vi.fn()
init = vi.fn()
constructor () {
instances.push(this)
}
}
}
})
const pageMock = {
setCacheEnabled: vi.fn(),
emulate: vi.fn(),
on: vi.fn(),
tracing: {
start: vi.fn(),
stop: vi.fn()
}
}
const sessionMock = {
on: vi.fn(),
emit: vi.fn(),
send: vi.fn()
}
const driverMock = {}
const options = {}
const browser: any = {
addCommand: vi.fn(),
emit: vi.fn()
}
beforeEach(() => {
pageMock.on.mockClear()
pageMock.tracing.start.mockClear()
pageMock.tracing.stop.mockClear()
sessionMock.on.mockClear()
sessionMock.send.mockClear()
browser.addCommand.mockReset()
})
test('initialization', async () => {
const handler = new CommandHandler(
sessionMock as any,
pageMock as any,
driverMock as any,
{
coverageReporter: {
enable: true
}
},
browser as any
)
await handler._initCommand()
expect(browser.addCommand.mock.calls).toHaveLength(8)
expect(sessionMock.on).toBeCalled()
expect(pageMock.on).toBeCalled()
expect(handler['_session']?.send).toBeCalledWith('Network.enable')
expect(handler['_session']?.send).toBeCalledWith('Runtime.enable')
expect(handler['_session']?.send).toBeCalledWith('Page.enable')
handler['_devtoolsGatherer'] = { onMessage: vi.fn() } as any
handler['_propagateWSEvents']({ method: 'foo', params: 'bar' })
expect(handler['_devtoolsGatherer']?.onMessage).toBeCalledTimes(1)
expect(handler['_devtoolsGatherer']?.onMessage).toBeCalledWith({ method:'foo', params: 'bar' })
expect((handler['_browser'] as any).emit).toBeCalledTimes(1)
expect((handler['_browser'] as any).emit).toBeCalledWith('foo', 'bar')
})
test('getTraceLogs', () => {
const commander = new CommandHandler(
sessionMock as unknown as CDPSession,
pageMock as unknown as Page,
driverMock as any,
options as any,
browser
)
commander['_traceEvents'] = [{ foo: 'bar' }] as any
expect(commander.getTraceLogs()).toEqual([{ foo: 'bar' }])
})
test('startTracing', () => {
const handler = new CommandHandler(
sessionMock as any,
pageMock as any,
driverMock as any,
options as any,
browser as any
)
handler.startTracing()
expect(handler['_isTracing']).toBe(true)
expect(handler.startTracing.bind(handler)).toThrow()
expect(pageMock.tracing.start).toBeCalledTimes(1)
})
test('endTracing', async () => {
pageMock.tracing.stop.mockResolvedValue(Buffer.from('{ "traceEvents": "foobar" }'))
const handler = new CommandHandler(
sessionMock as any,
pageMock as any,
driverMock as any,
options as any,
browser as any
)
handler['_isTracing'] = true
const traceEvents = await handler.endTracing()
expect(pageMock.tracing.stop).toBeCalledTimes(1)
expect(traceEvents).toEqual({ traceEvents: 'foobar' })
expect(handler['_isTracing']).toBe(false)
})
test('endTracing throws if not tracing', async () => {
const handler = new CommandHandler(
sessionMock as any,
pageMock as any,
driverMock as any,
options as any,
browser as any
)
const err = await handler.endTracing().catch((err) => err)
expect(err.message).toContain('No tracing was initiated')
})
test('endTracing throws if parsing of trace events fails', async () => {
pageMock.tracing.stop.mockResolvedValue(Buffer.from('{ "traceEven'))
const handler = new CommandHandler(
sessionMock as any,
pageMock as any,
driverMock as any,
options as any,
browser as any
)
handler['_isTracing'] = true
const err = await handler.endTracing().catch((err) => err)
expect(err.message).toContain("Couldn't parse trace events")
})
test('getPageWeight', () => {
const handler = new CommandHandler(
sessionMock as any,
pageMock as any,
driverMock as any,
options as any,
browser as any
)
handler['_networkHandler'].requestTypes = {
Document: { size: 23343, encoded: 7674, count: 1 },
Image: { size: 53479, encoded: 53479, count: 6 },
Other: { size: 0, encoded: 0, count: 1 }
}
const { pageWeight, transferred, requestCount, details } = handler.getPageWeight()
expect(pageWeight).toBe('foobar')
expect(transferred).toBe('foobar')
expect(requestCount).toBe('foobar')
expect(details).toEqual(handler['_networkHandler'].requestTypes)
})
test('beforeCmd', () => {
const handler = new CommandHandler(
sessionMock as any,
pageMock as any,
driverMock as any,
options as any,
browser as any
)
handler['_traceGatherer'] = { startTracing: vi.fn() } as any
handler.setThrottlingProfile = vi.fn()
handler['_networkThrottling'] = 'offline'
handler['_cpuThrottling'] = 2
handler['_cacheEnabled'] = true
handler._beforeCmd()
expect(handler['_traceGatherer']?.startTracing).toBeCalledTimes(0)
handler['_shouldRunPerformanceAudits'] = true
handler._beforeCmd()
expect(handler['_traceGatherer']?.startTracing).toBeCalledTimes(0)
handler._beforeCmd('foobar')
expect(handler['_traceGatherer']?.startTracing).toBeCalledTimes(0)
handler._beforeCmd('navigateTo', ['some page'])
expect(handler['_traceGatherer']?.startTracing).toBeCalledTimes(1)
expect(handler['_traceGatherer']?.startTracing).toBeCalledWith('some page')
expect(handler.setThrottlingProfile).toBeCalledWith('offline', 2, true)
handler._beforeCmd('url', ['next page'])
expect(handler['_traceGatherer']?.startTracing).toBeCalledTimes(2)
expect(handler['_traceGatherer']?.startTracing).toBeCalledWith('next page')
expect(handler.setThrottlingProfile).toBeCalledWith('offline', 2, true)
handler._beforeCmd('click', ['some other page'])
expect(handler['_traceGatherer']?.startTracing).toBeCalledTimes(3)
expect(handler['_traceGatherer']?.startTracing).toBeCalledWith('click transition')
})
test('afterCmd', () => {
const handler = new CommandHandler(
sessionMock as any,
pageMock as any,
driverMock as any,
options as any,
browser as any
)
handler['_traceGatherer'] = { once: vi.fn() } as any
handler._afterCmd()
expect(handler['_traceGatherer']?.once).toBeCalledTimes(0)
handler['_traceGatherer']['isTracing'] = true
handler._afterCmd()
expect(handler['_traceGatherer']?.once).toBeCalledTimes(0)
handler._afterCmd('foobar')
expect(handler['_traceGatherer']?.once).toBeCalledTimes(0)
handler._afterCmd('navigateTo')
expect(handler['_traceGatherer']?.once).toBeCalledTimes(3)
handler._afterCmd('url')
expect(handler['_traceGatherer']?.once).toBeCalledTimes(6)
handler._afterCmd('click')
expect(handler['_traceGatherer']?.once).toBeCalledTimes(9)
})
test('afterCmd: should create a new auditor instance and should update the browser commands', () => {
const handler = new CommandHandler(
sessionMock as any,
pageMock as any,
driverMock as any,
options as any,
browser as any
)
handler['_traceGatherer'] = new EventEmitter() as any
handler['_traceGatherer']['isTracing'] = true
handler['_devtoolsGatherer'] = { getLogs: vi.fn() } as any
handler['_browser'] = 'some browser' as any
handler._afterCmd('url')
handler['_traceGatherer']?.emit('tracingComplete', { some: 'events' })
const auditor = new Auditor()
expect(auditor.updateCommands).toBeCalledWith('some browser')
})
test('afterCmd: should update browser commands even if failed', () => {
const handler = new CommandHandler(
sessionMock as any,
pageMock as any,
driverMock as any,
options as any,
browser as any
)
handler['_traceGatherer'] = new EventEmitter() as any
handler['_traceGatherer']['isTracing'] = true
handler['_devtoolsGatherer'] = { getLogs: vi.fn() } as any
handler['_browser'] = 'some browser' as any
handler._afterCmd('url')
handler['_traceGatherer']?.emit('tracingError', new Error('boom'))
const auditor = new Auditor()
expect(auditor.updateCommands).toBeCalledWith('some browser', expect.any(Function))
})
test('afterCmd: should continue with command after tracingFinished was emitted', async () => {
const handler = new CommandHandler(
sessionMock as any,
pageMock as any,
driverMock as any,
options as any,
browser as any
)
handler['_traceGatherer'] = new EventEmitter() as any
handler['_traceGatherer']['isTracing'] = true
handler.setThrottlingProfile = vi.fn()
const start = Date.now()
setTimeout(() => handler['_traceGatherer']?.emit('tracingFinished'), 100)
await handler._afterCmd('navigateTo')
expect(Date.now() - start).toBeGreaterThan(98)
expect(handler.setThrottlingProfile).toBeCalledWith('online', 0, true)
})
test('enablePerformanceAudits: applies some default values', () => {
const handler = new CommandHandler(
sessionMock as any,
pageMock as any,
driverMock as any,
options as any,
browser as any
)
handler.enablePerformanceAudits()
expect(handler['_networkThrottling']).toBe('online')
expect(handler['_cpuThrottling']).toBe(0)
expect(handler['_cacheEnabled']).toBe(false)
expect(handler['_formFactor']).toBe('desktop')
})
test('enablePerformanceAudits: applies some custom values', () => {
const handler = new CommandHandler(
sessionMock as any,
pageMock as any,
driverMock as any,
options as any,
browser as any
)
handler.enablePerformanceAudits({
networkThrottling: 'Regular 2G',
cpuThrottling: 42,
cacheEnabled: true,
formFactor: 'mobile'
})
expect(handler['_networkThrottling']).toBe('Regular 2G')
expect(handler['_cpuThrottling']).toBe(42)
expect(handler['_cacheEnabled']).toBe(true)
expect(handler['_formFactor']).toBe('mobile')
})
test('disablePerformanceAudits', () => {
const handler = new CommandHandler(
sessionMock as any,
pageMock as any,
driverMock as any,
options as any,
browser as any
)
handler.enablePerformanceAudits({
networkThrottling: 'Regular 2G',
cpuThrottling: 42,
cacheEnabled: true,
formFactor: 'mobile'
})
handler.disablePerformanceAudits()
expect(handler['_shouldRunPerformanceAudits']).toBe(false)
})
test('setThrottlingProfile', async () => {
const handler = new CommandHandler(
sessionMock as any,
pageMock as any,
driverMock as any,
options as any,
browser as any
)
await handler.setThrottlingProfile('GPRS', 42, true)
expect(pageMock.setCacheEnabled).toBeCalledWith(true)
expect(sessionMock.send).toBeCalledWith('Emulation.setCPUThrottlingRate', { rate: 42 })
expect(sessionMock.send).toBeCalledWith('Network.emulateNetworkConditions', {
downloadThroughput: 6400,
latency: 500,
offline: false,
uploadThroughput: 2560
})
pageMock.setCacheEnabled.mockClear()
sessionMock.send.mockClear()
await handler.setThrottlingProfile()
expect(pageMock.setCacheEnabled).toBeCalledWith(false)
expect(sessionMock.send).toBeCalledWith('Emulation.setCPUThrottlingRate', { rate: 0 })
expect(sessionMock.send).toBeCalledWith('Network.emulateNetworkConditions', {
downloadThroughput: -1,
latency: 0,
offline: false,
uploadThroughput: -1
})
})