/** * Evidence capture for passing tests. * * Playwright keeps screenshots and traces only when something fails, which is * the right default for debugging but useless as a record of what the suite * actually proved. This fixture lets a test mark the moments that matter and * record the data it observed, so a successful run produces a readable report * rather than a row of green ticks. * * await evidence.step('Search results for JFK to LHR', { total: 42, fare: 483.3 }); * * Each call attaches a screenshot plus a JSON blob. `reporters/evidence.ts` * turns those attachments into the report. */ import { Page, TestInfo } from '@playwright/test'; /** Attachment name prefixes the reporter looks for. */ export const SHOT_PREFIX = 'evidence'; export const DATA_PREFIX = 'evidence-data'; export type Facts = Record; export class Evidence { private sequence = 0; constructor( private readonly page: Page, private readonly info: TestInfo, ) {} /** * Records a named moment: a screenshot of the current page, and optionally * the data the test observed there. */ async step(name: string, facts?: Facts): Promise { const seq = this.next(); const shot = await this.page.screenshot({ fullPage: false, animations: 'disabled' }); await this.info.attach(`${SHOT_PREFIX}:${seq}:${name}`, { body: shot, contentType: 'image/png', }); if (facts) { await this.attachFacts(seq, name, facts); } } /** Records data with no screenshot -- for values read across several pages. */ async note(name: string, facts: Facts): Promise { await this.attachFacts(this.next(), name, facts); } /** * Records the whole scrollable page rather than the viewport. Slower, so it * is reserved for the screens worth showing in full. */ async fullPage(name: string, facts?: Facts): Promise { const seq = this.next(); const shot = await this.page.screenshot({ fullPage: true, animations: 'disabled' }); await this.info.attach(`${SHOT_PREFIX}:${seq}:${name}`, { body: shot, contentType: 'image/png', }); if (facts) { await this.attachFacts(seq, name, facts); } } private async attachFacts(seq: string, name: string, facts: Facts): Promise { await this.info.attach(`${DATA_PREFIX}:${seq}:${name}`, { body: Buffer.from(JSON.stringify(facts, null, 2), 'utf8'), contentType: 'application/json', }); } private next(): string { this.sequence += 1; return String(this.sequence).padStart(2, '0'); } }