import { Page } from '@playwright/test'; import * as fs from 'fs'; import * as path from 'path'; /** * Screenshot Helper Utilities * Provides consistent screenshot capture with metadata */ export interface ScreenshotMetadata { path: string; context: string; timestamp: string; viewport: { width: number; height: number; }; url: string; testName?: string; } /** * Capture screenshot with context metadata * * @param page - Playwright page object * @param name - Screenshot name (will be kebab-cased) * @param context - Description of what the screenshot shows * @returns Metadata about the captured screenshot */ export async function captureWithContext( page: Page, name: string, context: string ): Promise { const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const viewport = page.viewportSize() || { width: 1280, height: 720 }; const url = page.url(); // Ensure screenshots directory exists const screenshotDir = path.join(process.cwd(), 'screenshots', 'current'); if (!fs.existsSync(screenshotDir)) { fs.mkdirSync(screenshotDir, { recursive: true }); } // Generate filename const filename = `${name}-${timestamp}.png`; const screenshotPath = path.join(screenshotDir, filename); // Wait for network idle before capturing await page.waitForLoadState('networkidle'); // Capture screenshot await page.screenshot({ path: screenshotPath, fullPage: true, }); // Create metadata const metadata: ScreenshotMetadata = { path: screenshotPath, context, timestamp: new Date().toISOString(), viewport, url, testName: process.env.PLAYWRIGHT_TEST_NAME, }; // Save metadata alongside screenshot const metadataPath = screenshotPath.replace('.png', '.json'); fs.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2)); console.log(`šŸ“ø Screenshot captured: ${filename}`); console.log(` Context: ${context}`); return metadata; } /** * Capture element screenshot with context * * @param page - Playwright page object * @param selector - Element selector * @param name - Screenshot name * @param context - Description */ export async function captureElement( page: Page, selector: string, name: string, context: string ): Promise { const element = page.locator(selector); await element.waitFor({ state: 'visible' }); const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const viewport = page.viewportSize() || { width: 1280, height: 720 }; const url = page.url(); const screenshotDir = path.join(process.cwd(), 'screenshots', 'current'); if (!fs.existsSync(screenshotDir)) { fs.mkdirSync(screenshotDir, { recursive: true }); } const filename = `${name}-element-${timestamp}.png`; const screenshotPath = path.join(screenshotDir, filename); await element.screenshot({ path: screenshotPath, }); const metadata: ScreenshotMetadata = { path: screenshotPath, context: `${context} (element: ${selector})`, timestamp: new Date().toISOString(), viewport, url, testName: process.env.PLAYWRIGHT_TEST_NAME, }; const metadataPath = screenshotPath.replace('.png', '.json'); fs.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2)); console.log(`šŸ“ø Element screenshot captured: ${filename}`); return metadata; } /** * Capture comparison screenshots (before/after) * * @param page - Playwright page object * @param name - Base name for screenshots * @param actionCallback - Action to perform between screenshots */ export async function captureComparison( page: Page, name: string, actionCallback: () => Promise ): Promise<{ before: ScreenshotMetadata; after: ScreenshotMetadata }> { const before = await captureWithContext(page, `${name}-before`, 'State before action'); await actionCallback(); const after = await captureWithContext(page, `${name}-after`, 'State after action'); return { before, after }; } /** * Capture screenshots across multiple viewports * * @param page - Playwright page object * @param name - Base name for screenshots * @param viewports - Array of viewport configurations */ export async function captureViewports( page: Page, name: string, viewports: Array<{ name: string; width: number; height: number }> ): Promise { const screenshots: ScreenshotMetadata[] = []; for (const viewport of viewports) { await page.setViewportSize({ width: viewport.width, height: viewport.height }); // Wait for responsive changes to settle await page.waitForTimeout(500); const metadata = await captureWithContext( page, `${name}-${viewport.name}`, `${viewport.width}x${viewport.height} viewport` ); screenshots.push(metadata); } return screenshots; } /** * Generate screenshot manifest * Collects all screenshots and their metadata into a single manifest file */ export function generateManifest(): void { const screenshotDir = path.join(process.cwd(), 'screenshots', 'current'); if (!fs.existsSync(screenshotDir)) { console.log('No screenshots directory found'); return; } const files = fs.readdirSync(screenshotDir); const metadataFiles = files.filter((f) => f.endsWith('.json')); const manifest = metadataFiles.map((file) => { const content = fs.readFileSync(path.join(screenshotDir, file), 'utf-8'); return JSON.parse(content); }); const manifestPath = path.join(screenshotDir, 'manifest.json'); fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2)); console.log(`\nšŸ“‹ Screenshot manifest generated: ${manifestPath}`); console.log(` Total screenshots: ${manifest.length}`); } /** * Compare screenshot with baseline * * @param currentPath - Path to current screenshot * @param baselinePath - Path to baseline screenshot * @param diffPath - Path to save diff image * @param threshold - Difference threshold (0-1, default 0.2 = 20%) */ export async function compareWithBaseline( currentPath: string, baselinePath: string, diffPath: string, threshold: number = 0.2 ): Promise<{ match: boolean; diffPercentage: number }> { // Note: This requires pixelmatch or Playwright's built-in comparison // For now, this is a placeholder showing the interface console.log(`šŸ” Comparing screenshots:`); console.log(` Current: ${currentPath}`); console.log(` Baseline: ${baselinePath}`); // Implementation would use Playwright's toHaveScreenshot comparison // or a library like pixelmatch for pixel-level comparison return { match: true, diffPercentage: 0, }; }