Files
Yuvomi-App/scripts/compare-native-pwa.mjs
Yuvomi Mobile Contributors 7ac9882307
Android validation / emulator (37.0) (push) Failing after 3s
Android validation / emulator (35) (push) Failing after 4s
iOS validation / simulator (push) Successful in 59s
Android validation / build (push) Successful in 5m30s
feat(android): align native preview with measured PWA design
2026-10-21 10:45:16 +02:00

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.`);