- /eroeffnung (noindex, unlinked): opening hero, offers embedded from app.kantarci-markt.de without title and app button, app store buttons, visit section with route, hours and contact placeholders. - The height script on the embed asks the app for its height once it runs (it is deferred and missed the first report) and scrolls to the offer popup when the app reports it outside the viewport. - audit.mjs takes CMS_HOST_RULES, because the app allows framing only from localhost while the server listens on 127.0.0.1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
61 lines
3.3 KiB
JavaScript
61 lines
3.3 KiB
JavaScript
import lighthouse from 'lighthouse';
|
|
import desktopConfig from 'lighthouse/core/config/desktop-config.js';
|
|
import { chromium } from 'playwright';
|
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
import net from 'node:net';
|
|
|
|
const origin = process.env.CMS_URL || 'http://127.0.0.1:8080';
|
|
// Eine Startseite ist nicht die Website. Geprüft wird, was CMS_PATHS nennt - mehrere Adressen mit
|
|
// Komma getrennt; ohne Angabe die Startseite.
|
|
const paths = (process.env.CMS_PATHS || '/').split(',').map(p => p.trim()).filter(Boolean);
|
|
const output = process.env.CMS_AUDIT_DIR || 'reports/lighthouse';
|
|
const preflight = await fetch(origin);
|
|
if (!preflight.ok || /noindex/i.test(preflight.headers.get('x-robots-tag') || '')) {
|
|
throw new Error('Audit requires an indexable HTTP 200 page in production mode. Check APP_ENV and APP_DEBUG.');
|
|
}
|
|
await mkdir(output, { recursive: true });
|
|
const portProbe = net.createServer();
|
|
await new Promise(resolve => portProbe.listen(0, '127.0.0.1', resolve));
|
|
const port = portProbe.address().port;
|
|
await new Promise(resolve => portProbe.close(resolve));
|
|
// CMS_HOST_RULES, etwa "MAP localhost 127.0.0.1": für eingebettete Seiten, die das Einbetten nur
|
|
// von localhost erlauben, während der Server auf 127.0.0.1 lauscht (Kantarci-Markt-App).
|
|
const chrome = await chromium.launch({ args: [`--remote-debugging-port=${port}`, ...(process.env.CMS_HOST_RULES ? [`--host-resolver-rules=${process.env.CMS_HOST_RULES}`] : [])] });
|
|
const summary = [];
|
|
try {
|
|
for (const path of paths) {
|
|
const label = '/' === path ? 'home' : path.replace(/^\//, '').replace(/\//g, '-');
|
|
for (const profile of ['mobile', 'desktop']) {
|
|
const runs = [];
|
|
for (let run = 1; run <= 3; run++) {
|
|
const result = await lighthouse(origin + ('/' === path ? '' : path), {
|
|
port,
|
|
output: 'json',
|
|
logLevel: 'error',
|
|
}, profile === 'desktop' ? desktopConfig : undefined);
|
|
const lhr = result.lhr;
|
|
if (lhr.configSettings.formFactor !== profile) {
|
|
throw new Error(`Requested ${profile}, measured ${lhr.configSettings.formFactor}`);
|
|
}
|
|
await writeFile(`${output}/${label}-${profile}-${run}.json`, result.report);
|
|
runs.push(lhr);
|
|
console.log(label, profile, run, Object.fromEntries(Object.entries(lhr.categories).map(([key, value]) => [key, value.score])));
|
|
}
|
|
const scores = {};
|
|
for (const category of Object.keys(runs[0].categories)) {
|
|
const values = runs.map(r => r.categories[category].score).filter(v => v !== null).sort((a, b) => a - b);
|
|
scores[category] = values.length ? values[Math.floor(values.length / 2)] : null;
|
|
}
|
|
summary.push({ path, profile, scores, lighthouseVersion: runs[0].lighthouseVersion, settings: runs[0].configSettings, environment: runs[0].environment, note: 'Local prototype only. No customer-site acceptance or field-data claim.' });
|
|
}
|
|
}
|
|
await writeFile(`${output}/summary.json`, JSON.stringify(summary, null, 2));
|
|
for (const row of summary) {
|
|
console.log(`${row.path} ${row.profile}: ${Object.entries(row.scores).map(([k, v]) => k + ' ' + Math.round(v * 100)).join(', ')}`);
|
|
}
|
|
const passed = summary.every(r => r.scores.performance >= .95 && ['accessibility', 'best-practices', 'seo'].every(c => r.scores[c] === 1) && (r.scores['agentic-browsing'] === undefined || r.scores['agentic-browsing'] === 1));
|
|
if (!passed) process.exitCode = 1;
|
|
} finally {
|
|
await chrome.close();
|
|
}
|