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:
83
tests/scripts/link-checker.js
Normal file
83
tests/scripts/link-checker.js
Normal file
@@ -0,0 +1,83 @@
|
||||
const { chromium } = require('playwright');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const TARGET_URL = process.env.TARGET_URL;
|
||||
const PAGES = (process.env.PAGES || '/').split(',').map(p => p.trim());
|
||||
const REPORTS_DIR = process.env.REPORTS_DIR || './reports';
|
||||
|
||||
async function collectLinks(page, pagePath) {
|
||||
const fullUrl = `${TARGET_URL}${pagePath}`;
|
||||
await page.goto(fullUrl, { waitUntil: 'networkidle', timeout: 30000 });
|
||||
return page.evaluate(() => Array.from(document.querySelectorAll('a[href]'))
|
||||
.map(a => a.href));
|
||||
}
|
||||
|
||||
async function checkLink(context, url) {
|
||||
try {
|
||||
const resp = await context.request.fetch(url, { method: 'HEAD', timeout: 15000 });
|
||||
return { url, status: resp.status(), ok: resp.ok() };
|
||||
} catch (err) {
|
||||
return { url, status: 0, ok: false, error: err.message };
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
if (!TARGET_URL) {
|
||||
console.error('TARGET_URL environment variable is required');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
const context = await browser.newContext();
|
||||
const results = [];
|
||||
|
||||
try {
|
||||
for (const pagePath of PAGES) {
|
||||
const page = await context.newPage();
|
||||
try {
|
||||
const links = await collectLinks(page, pagePath);
|
||||
await page.close();
|
||||
const checked = [];
|
||||
for (const url of [...new Set(links)]) {
|
||||
if (url.startsWith('mailto:') || url.startsWith('tel:')) continue;
|
||||
checked.push(await checkLink(context, url));
|
||||
}
|
||||
const broken = checked.filter(c => !c.ok);
|
||||
results.push({ pagePath, total: checked.length, broken: broken.length, brokenLinks: broken });
|
||||
console.log(`${pagePath}: ${checked.length} links, ${broken.length} broken`);
|
||||
} catch (err) {
|
||||
results.push({ pagePath, error: err.message, total: 0, broken: 0, brokenLinks: [] });
|
||||
console.error(`Failed ${pagePath}: ${err.message}`);
|
||||
} finally {
|
||||
await page.close().catch(() => {});
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
|
||||
const totalBroken = results.reduce((s, r) => s + r.broken, 0);
|
||||
const report = {
|
||||
timestamp: new Date().toISOString(),
|
||||
targetUrl: TARGET_URL,
|
||||
pages: PAGES,
|
||||
summary: { totalPages: results.length, totalBroken, hasBroken: totalBroken > 0 },
|
||||
results,
|
||||
};
|
||||
|
||||
fs.mkdirSync(REPORTS_DIR, { recursive: true });
|
||||
const reportPath = path.join(REPORTS_DIR, 'link-check-report.json');
|
||||
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
|
||||
console.log(`Report saved to ${reportPath}`);
|
||||
|
||||
if (totalBroken > 0) {
|
||||
console.error(`Found ${totalBroken} broken links`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error('Fatal error:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user