import fs from 'node:fs' import path from 'node:path' import { fileURLToPath } from 'node:url' import { expect, test as base, type Page, type TestInfo } from '@playwright/test' const screenshotDirectory = fileURLToPath(new URL('../artifacts/screenshots/', import.meta.url)) const safeName = (value: string) => value .normalize('NFKC') .replace(/[^\p{L}\p{N}._-]+/gu, '-') .replace(/^-+|-+$/g, '') .slice(0, 120) const stableTestName = (testInfo: TestInfo) => { const fileStem = path.basename(testInfo.file, path.extname(testInfo.file)) return safeName(`${testInfo.project.name}--${fileStem}--${testInfo.title}`).slice(0, 110) || 'unnamed-test' } export async function captureScreenshot(page: Page, testInfo: TestInfo, label: string) { fs.mkdirSync(screenshotDirectory, { recursive: true }) const testName = stableTestName(testInfo) const artifactName = safeName(label).slice(0, 70) || 'success' const screenshotPath = path.join(screenshotDirectory, `${testName}--${artifactName}.png`) await page.screenshot({ path: screenshotPath, fullPage: true, animations: 'disabled', caret: 'hide', }) await testInfo.attach(`success-${artifactName}`, { path: screenshotPath, contentType: 'image/png' }) return screenshotPath } export const test = base test.beforeEach(async ({}, testInfo) => { fs.mkdirSync(screenshotDirectory, { recursive: true }) const prefix = `${stableTestName(testInfo)}--` for (const filename of fs.readdirSync(screenshotDirectory)) { if (filename.startsWith(prefix) && filename.endsWith('.png')) { fs.rmSync(path.join(screenshotDirectory, filename), { force: true }) } } }) test.afterEach(async ({ page }, testInfo) => { if (testInfo.status === testInfo.expectedStatus || page.isClosed()) return fs.mkdirSync(screenshotDirectory, { recursive: true }) const title = safeName(testInfo.titlePath.join('--')) || 'failed-test' const filename = `${title}--retry-${testInfo.retry}--${Date.now()}.png` const screenshotPath = path.join(screenshotDirectory, filename) try { await page.screenshot({ path: screenshotPath, fullPage: true }) await testInfo.attach('failure-screenshot', { path: screenshotPath, contentType: 'image/png' }) } catch (error) { await testInfo.attach('failure-screenshot-error', { body: Buffer.from(String(error)), contentType: 'text/plain', }) } }) export { expect }