- Add docker-compose.web-testing.yml with vlmkit service - Add 5 test scripts: capture, compare, pipeline, console-monitor, link-checker - Add vrt.config.json with 3 viewports + maskSelectors - Add package.json with @mizchi/vlmkit and Node >=24 - Add tests/README.md with VRT documentation - Update .gitignore for test artifacts Based on APAW issue #144 / milestone #106
108 lines
3.5 KiB
JavaScript
108 lines
3.5 KiB
JavaScript
const pixelmatch = require('pixelmatch');
|
|
const { PNG } = require('pngjs');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const THRESHOLD = parseFloat(process.env.PIXELMATCH_THRESHOLD) || 0.05;
|
|
const BASELINE_DIR = process.env.BASELINE_DIR || './screenshots/baseline';
|
|
const CURRENT_DIR = process.env.CURRENT_DIR || './screenshots/current';
|
|
const DIFF_DIR = process.env.DIFF_DIR || './screenshots/diffs';
|
|
const REPORTS_DIR = process.env.REPORTS_DIR || './reports';
|
|
|
|
function readPNG(filepath) {
|
|
const data = fs.readFileSync(filepath);
|
|
return PNG.sync.read(data);
|
|
}
|
|
|
|
function compareImages(baselinePath, currentPath, diffPath) {
|
|
const img1 = readPNG(baselinePath);
|
|
const img2 = readPNG(currentPath);
|
|
|
|
const { width, height } = img1;
|
|
const diff = new PNG({ width, height });
|
|
|
|
const mismatchedPixels = pixelmatch(img1.data, img2.data, diff.data, width, height, {
|
|
threshold: THRESHOLD,
|
|
});
|
|
|
|
const totalPixels = width * height;
|
|
const mismatchPercent = (mismatchedPixels / totalPixels) * 100;
|
|
|
|
if (mismatchedPixels > 0) {
|
|
fs.mkdirSync(path.dirname(diffPath), { recursive: true });
|
|
fs.writeFileSync(diffPath, PNG.sync.write(diff));
|
|
}
|
|
|
|
return { mismatchedPixels, totalPixels, mismatchPercent, diffCreated: mismatchedPixels > 0 };
|
|
}
|
|
|
|
function findPNGFiles(dir) {
|
|
if (!fs.existsSync(dir)) return [];
|
|
return fs.readdirSync(dir).filter(f => f.endsWith('.png'));
|
|
}
|
|
|
|
async function main() {
|
|
const baselineFiles = findPNGFiles(BASELINE_DIR);
|
|
const currentFiles = findPNGFiles(CURRENT_DIR);
|
|
|
|
if (baselineFiles.length === 0) {
|
|
console.error(`No baseline screenshots found in ${BASELINE_DIR}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
if (currentFiles.length === 0) {
|
|
console.error(`No current screenshots found in ${CURRENT_DIR}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
const allFiles = [...new Set([...baselineFiles, ...currentFiles])];
|
|
const comparisons = [];
|
|
|
|
for (const file of allFiles) {
|
|
const baselinePath = path.join(BASELINE_DIR, file);
|
|
const currentPath = path.join(CURRENT_DIR, file);
|
|
|
|
if (!fs.existsSync(baselinePath)) {
|
|
comparisons.push({ file, status: 'missing_baseline' });
|
|
continue;
|
|
}
|
|
if (!fs.existsSync(currentPath)) {
|
|
comparisons.push({ file, status: 'missing_current' });
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
const diffPath = path.join(DIFF_DIR, file);
|
|
const result = compareImages(baselinePath, currentPath, diffPath);
|
|
comparisons.push({ file, status: result.mismatchedPixels === 0 ? 'pass' : 'fail', ...result });
|
|
} catch (err) {
|
|
comparisons.push({ file, status: 'error', error: err.message });
|
|
}
|
|
}
|
|
|
|
const passed = comparisons.filter(c => c.status === 'pass').length;
|
|
const failed = comparisons.filter(c => c.status === 'fail').length;
|
|
const errors = comparisons.filter(c => c.status === 'error').length;
|
|
const missing = comparisons.filter(c => c.status === 'missing_baseline' || c.status === 'missing_current').length;
|
|
|
|
const report = {
|
|
timestamp: new Date().toISOString(),
|
|
threshold: THRESHOLD,
|
|
summary: { total: comparisons.length, passed, failed, errors, missing },
|
|
comparisons,
|
|
};
|
|
|
|
fs.mkdirSync(REPORTS_DIR, { recursive: true });
|
|
const reportPath = path.join(REPORTS_DIR, 'visual-test-report.json');
|
|
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
|
|
console.log(`Report saved to ${reportPath}`);
|
|
console.log(`Passed: ${passed}, Failed: ${failed}, Errors: ${errors}, Missing: ${missing}`);
|
|
|
|
if (failed > 0 || errors > 0) process.exit(1);
|
|
}
|
|
|
|
main().catch(err => {
|
|
console.error('Fatal error:', err);
|
|
process.exit(1);
|
|
});
|