feat: add AI-powered visual regression testing infrastructure
- 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
This commit is contained in:
84
tests/scripts/capture-screenshots.js
Normal file
84
tests/scripts/capture-screenshots.js
Normal file
@@ -0,0 +1,84 @@
|
||||
const { chromium } = require('playwright');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const VIEWPORTS = {
|
||||
mobile: { width: 375, height: 667 },
|
||||
tablet: { width: 768, height: 1024 },
|
||||
desktop: { width: 1280, height: 720 },
|
||||
};
|
||||
|
||||
function slugify(pagePath) {
|
||||
return pagePath.replace(/^\//, '').replace(/[^a-zA-Z0-9_-]/g, '_') || 'home';
|
||||
}
|
||||
|
||||
async function capturePage(page, pagePath, viewportName, outputDir) {
|
||||
const fullUrl = `${process.env.TARGET_URL}${pagePath}`;
|
||||
const filename = `${slugify(pagePath)}_${viewportName}.png`;
|
||||
const filepath = path.join(outputDir, filename);
|
||||
|
||||
await page.setViewportSize(VIEWPORTS[viewportName]);
|
||||
await page.goto(fullUrl, { waitUntil: 'networkidle', timeout: 30000 });
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: filepath, fullPage: false });
|
||||
|
||||
return { pagePath, viewport: viewportName, file: filename, saved: true };
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const mode = process.argv[2];
|
||||
if (!['baseline', 'current'].includes(mode)) {
|
||||
console.error('Usage: node capture-screenshots.js <baseline|current>');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const targetUrl = process.env.TARGET_URL;
|
||||
const pages = (process.env.PAGES || '/').split(',').map(p => p.trim()).filter(p => /^\/[A-Za-z0-9._\/-]*$/.test(p));
|
||||
if (pages.length === 0) {
|
||||
console.error('No valid pages in PAGES environment variable');
|
||||
process.exit(1);
|
||||
}
|
||||
const outputDir = mode === 'baseline'
|
||||
? (process.env.BASELINE_DIR || './screenshots/baseline')
|
||||
: (process.env.CURRENT_DIR || './screenshots/current');
|
||||
|
||||
if (!targetUrl) {
|
||||
console.error('TARGET_URL environment variable is required');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
fs.mkdirSync(outputDir, { recursive: true });
|
||||
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
const context = await browser.newContext();
|
||||
const page = await context.newPage();
|
||||
const results = [];
|
||||
|
||||
try {
|
||||
for (const pagePath of pages) {
|
||||
for (const vpName of Object.keys(VIEWPORTS)) {
|
||||
try {
|
||||
const result = await capturePage(page, pagePath, vpName, outputDir);
|
||||
results.push(result);
|
||||
console.log(`Captured ${pagePath} @ ${vpName} -> ${result.file}`);
|
||||
} catch (err) {
|
||||
results.push({ pagePath, viewport: vpName, error: err.message, saved: false });
|
||||
console.error(`Failed ${pagePath} @ ${vpName}: ${err.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
|
||||
const report = { mode, targetUrl, pages, outputDir, results };
|
||||
const reportPath = path.join(outputDir, '..', `${mode}-capture-report.json`);
|
||||
fs.mkdirSync(path.dirname(reportPath), { recursive: true });
|
||||
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
|
||||
console.log(`Report saved to ${reportPath}`);
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error('Fatal error:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user