🏡 index : quine-global/webdriverio.git

import path from 'node:path'
import { expect, test, vi, beforeEach } from 'vitest'
import type { Protocol } from 'devtools-protocol'
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 type { HTTPRequest } from 'puppeteer-core/lib/esm/puppeteer/api/HTTPRequest.js'

import TraceGatherer from '../../src/gatherer/trace.js'
import { FRAME_LOAD_START_TIMEOUT, CLICK_TRANSITION } from '../../src/constants.js'
import type { GathererDriver } from '../../src/types.js'

import TRACELOG from '../__fixtures__/tracelog.json' with { type: 'json' }

vi.mock('lighthouse/lighthouse-core/fraggle-rock/gather/session')
vi.mock('lighthouse/lighthouse-core/gather/driver/wait-for-condition')
vi.mock('@wdio/logger', () => import(path.join(process.cwd(), '__mocks__', '@wdio/logger')))

let traceGatherer: TraceGatherer

const pageMock = {
    on: vi.fn(),
    evaluateOnNewDocument: vi.fn()
}

const sessionMock = {
    on: vi.fn(),
    emit: vi.fn(),
    send: vi.fn(),
    off: vi.fn(),
    _connection: {
        _transport: {
            _ws: { addEventListener: vi.fn() }
        }
    }
}

const frame = {
    id: '123',
    loaderId: 'foobar123',
    url: 'http://foobar.com'
}

const driver = {
    beginTrace: vi.fn(),
    endTrace: vi.fn().mockReturnValue(Promise.resolve(TRACELOG))
} as unknown as GathererDriver

vi.useFakeTimers()
vi.spyOn(global, 'setTimeout')

beforeEach(() => {
    pageMock.on.mockClear()
    pageMock.evaluateOnNewDocument.mockClear()
    sessionMock.on.mockClear()
    traceGatherer = new TraceGatherer(
        sessionMock as unknown as CDPSession,
        pageMock as unknown as Page,
        driver
    )
    traceGatherer.emit = vi.fn()
    ;(traceGatherer['_driver'] as any).beginTrace.mockClear()
    vi.mocked(driver.beginTrace).mockClear()
    vi.mocked(driver.endTrace).mockClear()
})

test('should register eventlisteners for network monitor', () => {
    const statusMonitor = { dispatch: vi.fn() }
    traceGatherer['_networkStatusMonitor'] = statusMonitor as any
    for (const fn of Object.values(traceGatherer['_networkListeners'])) {
        fn({ some: 'params' })
    }
    expect(statusMonitor.dispatch.mock.calls)
        .toMatchSnapshot()
})

test('onFrameLoadFail', () => {
    traceGatherer.onFrameLoadFail({
        frame: () => undefined
    } as unknown as HTTPRequest)
    expect(traceGatherer['_failingFrameLoadIds']).toEqual([])
    traceGatherer.onFrameLoadFail({
        frame: () => ({ url: () => '123' })
    } as unknown as HTTPRequest)
    expect(traceGatherer['_failingFrameLoadIds']).toEqual(['123'])
})

test('getter isTracing', () => {
    expect(traceGatherer.isTracing).toBe(false)

    traceGatherer['_traceStart'] = Date.now()
    expect(traceGatherer.isTracing).toBe(true)
})

test('finishTracing', async () => {
    traceGatherer['_pageLoadDetected'] = true
    traceGatherer['_networkStatusMonitor'] = {} as any
    traceGatherer['_traceStart'] = 123
    traceGatherer['_frameId'] = 'true'
    traceGatherer['_loaderId'] = 'true'
    traceGatherer['_pageUrl'] = 'http://some.url'

    traceGatherer.finishTracing()

    expect(traceGatherer['_pageLoadDetected']).toBe(false)
    expect(traceGatherer['_networkStatusMonitor']).toBe(undefined)
    expect(traceGatherer['_traceStart']).toBe(undefined)
    expect(traceGatherer['_frameId']).toBe(undefined)
    expect(traceGatherer['_loaderId']).toBe(undefined)
    expect(traceGatherer['_pageUrl']).toBe(undefined)

    expect(sessionMock.off).toHaveBeenCalledTimes(7)
    expect(traceGatherer.emit).toBeCalledWith('tracingFinished')
})

test('onFrameNavigated', () => {
    expect(traceGatherer['_frameId']).toBe(undefined)
    expect(traceGatherer['_loaderId']).toBe(undefined)
    expect(traceGatherer['_pageUrl']).toBe(undefined)
    traceGatherer['_traceStart'] = Date.now()

    traceGatherer.onFrameNavigated({ frame } as unknown as Protocol.Page.FrameNavigatedEvent)
    expect(traceGatherer.emit).toBeCalledWith('tracingStarted', '123')

    expect(traceGatherer['_frameId']).toBe('123')
    expect(traceGatherer['_loaderId']).toBe('foobar123')
    expect(traceGatherer['_pageUrl']).toBe('http://foobar.com')
})

test('onFrameNavigated: should not start if frame was detected', () => {
    traceGatherer['_frameId'] = '123'
    traceGatherer.onFrameNavigated({ frame } as unknown as Protocol.Page.FrameNavigatedEvent)
    expect(traceGatherer.emit).toHaveBeenCalledTimes(0)
})

test('onFrameNavigated: should not start if a subframe was detected', () => {
    const subFrame = Object.assign({}, frame, { parentId: 122 })
    traceGatherer.onFrameNavigated({ frame: subFrame } as unknown as Protocol.Page.FrameNavigatedEvent)
    expect(traceGatherer.emit).toHaveBeenCalledTimes(0)
})

test('onFrameNavigated: should not start if url is not supported', () => {
    const subFrame = Object.assign({}, frame, { url: 'data:,foobar' })
    traceGatherer.onFrameNavigated({ frame: subFrame } as unknown as Protocol.Page.FrameNavigatedEvent)
    expect(traceGatherer.emit).toHaveBeenCalledTimes(0)
})

test('onFrameNavigated: should not start if tracing is not started', () => {
    traceGatherer['_traceStart'] = undefined
    traceGatherer.finishTracing = vi.fn()
    traceGatherer['_failingFrameLoadIds'].push('123')
    traceGatherer.onFrameNavigated({ frame } as unknown as Protocol.Page.FrameNavigatedEvent)
    expect(traceGatherer.emit).toHaveBeenCalledTimes(0)
    expect(traceGatherer.finishTracing).toHaveBeenCalledTimes(0)
})

test('onFrameNavigated: should cancel trace if page load failed', () => {
    traceGatherer['_traceStart'] = Date.now()
    traceGatherer.finishTracing = vi.fn()
    traceGatherer['_failingFrameLoadIds'].push('http://foobar.com')
    traceGatherer.onFrameNavigated({ frame } as unknown as Protocol.Page.FrameNavigatedEvent)
    expect(traceGatherer.emit).toHaveBeenCalledWith('tracingError', expect.any(Error))
    expect(traceGatherer.finishTracing).toHaveBeenCalledTimes(1)
})

test('onFrameNavigated: can detect page load', () => {
    traceGatherer['_clickTraceTimeout'] = 123 as unknown as NodeJS.Timeout
    traceGatherer['_traceStart'] = Date.now()

    expect(traceGatherer['_pageLoadDetected']).toBe(false)
    traceGatherer.onFrameNavigated({ frame } as unknown as Protocol.Page.FrameNavigatedEvent)
    expect(traceGatherer['_pageLoadDetected']).toBe(true)
})

test('startTracing', async () => {
    await traceGatherer.startTracing('http://foobar.com')

    expect(sessionMock.on).toHaveBeenCalledTimes(7)
    expect(pageMock.evaluateOnNewDocument).toHaveBeenCalledTimes(1)
    expect((traceGatherer['_driver'] as any).beginTrace).toHaveBeenCalledTimes(1)
})

test('startTracing: registers timeout for click events', async () => {
    traceGatherer.finishTracing = vi.fn()

    await traceGatherer.startTracing(CLICK_TRANSITION)
    vi.advanceTimersByTime(FRAME_LOAD_START_TIMEOUT + 10)
    expect((traceGatherer['_driver'] as any).beginTrace).toHaveBeenCalledTimes(1)
    expect(traceGatherer.finishTracing).toHaveBeenCalledTimes(1)
})

test('completeTracing', async () => {
    traceGatherer.finishTracing = vi.fn()
    await traceGatherer.completeTracing()
    expect(traceGatherer.finishTracing).toHaveBeenCalledTimes(1)
    expect(traceGatherer.emit).toBeCalledWith('tracingComplete', expect.any(Object))
})

test('completeTracing: in failure case', async () => {
    traceGatherer.finishTracing = vi.fn()
    ;(traceGatherer['_driver'] as any).endTrace.mockReturnValue(Promise.reject(new Error('boom')))
    await traceGatherer.completeTracing()
    expect(traceGatherer.finishTracing).toHaveBeenCalledTimes(1)
    expect(traceGatherer.emit).toBeCalledWith('tracingError', expect.any(Error))
})

test('onLoadEventFired', async () => {
    await traceGatherer.onLoadEventFired()
    traceGatherer['_traceStart'] = Date.now()
    traceGatherer.completeTracing = vi.fn().mockReturnValue(Promise.resolve('yeahh'))

    vi.advanceTimersByTime(15000)
    await traceGatherer.onLoadEventFired()
    expect(traceGatherer.completeTracing).toHaveBeenCalledTimes(1)
})

/**
 * ToDo(Christian): unskip test
 */
test.skip('onLoadEventFired: using min trace time', async () => {
    traceGatherer['_traceStart'] = Date.now() - 9700
    traceGatherer.completeTracing = vi.fn().mockReturnValue(Promise.resolve('yeahh'))

    const doneCb = vi.fn()
    traceGatherer.waitForMaxTimeout = vi.fn().mockReturnValue(new Promise((resolve) => {
        vi.advanceTimersByTime(150)
        resolve(doneCb)
    }))

    await traceGatherer.onLoadEventFired()
    expect(doneCb).toBeCalledTimes(1)
})

test('waitForMaxTimeout', async () => {
    traceGatherer.completeTracing = vi.fn()
    const done = traceGatherer.waitForMaxTimeout(200)
    vi.advanceTimersByTime(200)

    ;(await done)()
    expect(traceGatherer.completeTracing).toHaveBeenCalledTimes(1)
})