#!/usr/bin/env node /** * Generate a pixel diff image between two PNGs using pixelmatch. * Usage: node scripts/visual-diff.js [diff-output.png] [threshold] * * When Playwright fails a visual test it writes: * - expected: test-results/.../expected-*.png * - actual: test-results/.../actual-*.png * You can run this script to get a single diff image (pixels that differ in red). */ import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; import pixelmatch from 'pixelmatch'; import { PNG } from 'pngjs'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const expectedPath = process.argv[2]; const actualPath = process.argv[3]; const diffOutputPath = process.argv[4] || path.join(path.dirname(actualPath || ''), 'diff.png'); const threshold = parseFloat(process.argv[5] || '0.1', 10); if (!expectedPath || !actualPath) { console.error('Usage: node scripts/visual-diff.js [diff-output.png] [threshold]'); process.exit(1); } function loadPng(filePath) { const data = fs.readFileSync(filePath); return PNG.sync.read(data); } try { const imgExpected = loadPng(expectedPath); const imgActual = loadPng(actualPath); if (imgExpected.width !== imgActual.width || imgExpected.height !== imgActual.height) { console.error('Image dimensions differ:', imgExpected.width, 'x', imgExpected.height, 'vs', imgActual.width, 'x', imgActual.height); process.exit(1); } const { width, height } = imgExpected; const diff = new PNG({ width, height }); const numDiffPixels = pixelmatch( imgExpected.data, imgActual.data, diff.data, width, height, { threshold } ); fs.mkdirSync(path.dirname(diffOutputPath), { recursive: true }); fs.writeFileSync(diffOutputPath, PNG.sync.write(diff)); console.log(`Diff pixels: ${numDiffPixels}`); console.log(`Diff image written: ${diffOutputPath}`); process.exit(numDiffPixels > 0 ? 1 : 0); } catch (err) { console.error(err); process.exit(1); }