SHA256
54 lines
3.4 KiB
JavaScript
54 lines
3.4 KiB
JavaScript
// Development-only side-by-side evidence. This deliberately asserts no pixel parity.
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { createRequire } from 'node:module';
|
|
import { createHash } from 'node:crypto';
|
|
|
|
const [modulePath, nativeDirectory, pwaDirectory, destination] = process.argv.slice(2);
|
|
if (!destination) throw new Error('Usage: node compare-native-pwa.mjs PLAYWRIGHT_MODULE NATIVE_DIR PWA_DIR OUTPUT_DIR');
|
|
const { chromium } = createRequire(import.meta.url)(path.resolve(modulePath));
|
|
const viewport = JSON.parse(fs.readFileSync(path.join(nativeDirectory, 'viewport.json')));
|
|
if (viewport.fontScale !== 1) throw new Error('This CSS reference comparison requires fontScale 1');
|
|
const cssWidth = viewport.widthPx / viewport.density;
|
|
const cssHeight = viewport.heightPx / viewport.density;
|
|
const reference = JSON.parse(fs.readFileSync(path.join(pwaDirectory, 'measurements.json')));
|
|
if (!reference.screens.every(screen => screen.viewport.width === cssWidth && screen.viewport.height === cssHeight)) {
|
|
throw new Error('PWA and native content viewports do not match');
|
|
}
|
|
function image(directory, name) {
|
|
const bytes = fs.readFileSync(path.join(directory, name));
|
|
if (bytes.subarray(0, 8).toString('hex') !== '89504e470d0a1a0a' ||
|
|
bytes.readUInt32BE(16) !== viewport.widthPx || bytes.readUInt32BE(20) !== viewport.heightPx) {
|
|
throw new Error(`Invalid or mismatched PNG: ${name}`);
|
|
}
|
|
return { uri: `data:image/png;base64,${bytes.toString('base64')}`, sha256: createHash('sha256').update(bytes).digest('hex') };
|
|
}
|
|
fs.mkdirSync(destination, { recursive: true });
|
|
const browser = await chromium.launch({ headless: true, args: process.getuid?.() === 0 ? ['--no-sandbox'] : [] });
|
|
const records = [];
|
|
try {
|
|
const page = await browser.newPage({ viewport: { width: viewport.widthPx * 2, height: viewport.heightPx + 48 }, deviceScaleFactor: 1 });
|
|
await page.route('**/*', route => route.abort());
|
|
for (const name of ['login', 'dashboard', 'tasks', 'notes', 'shopping']) {
|
|
for (const theme of ['light', 'dark']) {
|
|
const native = image(nativeDirectory, `${name}-${theme}.png`);
|
|
const pwa = image(pwaDirectory, `${name}-${theme}-${cssWidth}.png`);
|
|
await page.setContent(`<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src data:; style-src 'unsafe-inline'">
|
|
<style>body{margin:0;background:#eee;font:20px sans-serif}main{display:flex}section{width:${viewport.widthPx}px}
|
|
header{height:48px;display:grid;place-items:center}img{display:block;width:100%}</style>
|
|
<main><section><header>PWA 2.78.0 · ${name} · ${theme}</header><img src="${pwa.uri}"></section>
|
|
<section><header>Android nativ · ${name} · ${theme} · keine 1:1-Abnahme</header><img src="${native.uri}"></section></main>`);
|
|
await page.locator('img').evaluateAll(images => Promise.all(images.map(image => image.decode())));
|
|
const file = `${name}-${theme}-comparison.png`;
|
|
await page.screenshot({ path: path.join(destination, file) });
|
|
records.push({ file, nativeSha256: native.sha256, pwaSha256: pwa.sha256 });
|
|
}
|
|
}
|
|
} finally {
|
|
await browser.close();
|
|
}
|
|
fs.writeFileSync(path.join(destination, 'comparison.json'), JSON.stringify({
|
|
status: 'Side-by-side evidence only; no visual acceptance', viewport, upstreamRevision: reference.upstreamRevision, records,
|
|
}, null, 2) + '\n');
|
|
console.log(`Wrote ${records.length} matched-viewport comparisons; no parity assertion.`);
|