import type { CDPSession, Page } from '@playwright/test' interface PerfSnapshot { RecalcStyleCount: number RecalcStyleDuration: number LayoutCount: number LayoutDuration: number TaskDuration: number JSHeapUsedSize: number Timestamp: number } export interface PerfMeasurement { name: string durationMs: number styleRecalcs: number styleRecalcDurationMs: number layouts: number layoutDurationMs: number taskDurationMs: number heapDeltaBytes: number } export class PerformanceHelper { private cdp: CDPSession | null = null private snapshot: PerfSnapshot | null = null constructor(private readonly page: Page) {} async init(): Promise { this.cdp = await this.page.context().newCDPSession(this.page) await this.cdp.send('Performance.enable') } async dispose(): Promise { this.snapshot = null if (this.cdp) { try { await this.cdp.send('Performance.disable') } finally { await this.cdp.detach() this.cdp = null } } } private async getSnapshot(): Promise { if (!this.cdp) throw new Error('PerformanceHelper not initialized') const { metrics } = (await this.cdp.send('Performance.getMetrics')) as { metrics: { name: string; value: number }[] } function get(name: string): number { return metrics.find((m) => m.name === name)?.value ?? 0 } return { RecalcStyleCount: get('RecalcStyleCount'), RecalcStyleDuration: get('RecalcStyleDuration'), LayoutCount: get('LayoutCount'), LayoutDuration: get('LayoutDuration'), TaskDuration: get('TaskDuration'), JSHeapUsedSize: get('JSHeapUsedSize'), Timestamp: get('Timestamp') } } async startMeasuring(): Promise { if (this.snapshot) { throw new Error( 'Measurement already in progress — call stopMeasuring() first' ) } this.snapshot = await this.getSnapshot() } async stopMeasuring(name: string): Promise { if (!this.snapshot) throw new Error('Call startMeasuring() first') const after = await this.getSnapshot() const before = this.snapshot this.snapshot = null function delta(key: keyof PerfSnapshot): number { return after[key] - before[key] } return { name, durationMs: delta('Timestamp') * 1000, styleRecalcs: delta('RecalcStyleCount'), styleRecalcDurationMs: delta('RecalcStyleDuration') * 1000, layouts: delta('LayoutCount'), layoutDurationMs: delta('LayoutDuration') * 1000, taskDurationMs: delta('TaskDuration') * 1000, heapDeltaBytes: delta('JSHeapUsedSize') } } }