import { appendFileSync, mkdirSync, writeFileSync } from 'node:fs'; import { dirname, resolve } from 'node:path'; import type { Page, TestInfo } from '@playwright/test'; function safeName(value: string) { return value.replace(/[^a-zA-Z0-9._-]+/g, '_'); } function evidenceRoot(testInfo: TestInfo) { return resolve(testInfo.project.outputDir, '..', 'evidence'); } export async function captureEvidence(page: Page, testInfo: TestInfo, label: string) { const root = evidenceRoot(testInfo); const stem = safeName(`${testInfo.file}-${testInfo.title}-${label}`); const screenshotPath = resolve(root, `${stem}.png`); const domPath = resolve(root, `${stem}.html`); const urlPath = resolve(root, `${stem}.url.txt`); const manifestPath = resolve(root, `manifest.jsonl`); mkdirSync(dirname(screenshotPath), { recursive: true }); await page.screenshot({ path: screenshotPath, fullPage: true }); writeFileSync(domPath, await page.content(), 'utf8'); writeFileSync(urlPath, page.url(), 'utf8'); appendFileSync(manifestPath, JSON.stringify({ testFile: testInfo.file, title: testInfo.title, label, url: page.url(), screenshotPath, domPath, urlPath, timestamp: new Date().toISOString() }) + '\n', 'utf8'); await testInfo.attach(`screenshot:${label}`, { path: screenshotPath, contentType: 'image/png' }); await testInfo.attach(`dom:${label}`, { path: domPath, contentType: 'text/html' }); await testInfo.attach(`url:${label}`, { path: urlPath, contentType: 'text/plain' }); return { screenshotPath, domPath, urlPath }; }