veza/apps/web/scripts/visual-diff.js
senke be7d7b02cc feat(e2e): Playwright + pixelmatch stack for pixel-perfect visual regression
- playwright.config.visual.ts: dedicated config, viewport 1280x720, Chromium only,
  snapshots in e2e/tests/visual/__snapshots__
- e2e/tests/visual/visual-regression.spec.ts: login, register, dashboard (full/header/sidebar),
  player bar, playlists, 404, mobile/tablet viewports; dark theme + reduceMotion
- scripts/visual-diff.js: optional pixelmatch script to generate diff image from two PNGs
- docs/VISUAL_TESTING_STRATEGY.md: strategy, commands, CI, workflow
- npm scripts: test:visual, test:visual:update, test:visual:report
- deps: pixelmatch, pngjs; @playwright/test aligned to 1.58.1
- baseline snapshots added for login, dashboard, playlists, 404, viewports

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-07 20:01:30 +01:00

63 lines
2 KiB
JavaScript

#!/usr/bin/env node
/**
* Generate a pixel diff image between two PNGs using pixelmatch.
* Usage: node scripts/visual-diff.js <expected.png> <actual.png> [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 <expected.png> <actual.png> [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);
}