This commit is contained in:
seb
2026-10-07 22:26:40 +02:00
parent 3d1601595e
commit 878f558a3a
3 changed files with 45 additions and 14 deletions

View File

@@ -28,7 +28,7 @@ npm run rig:compare
| `test/collect.mjs` | Fixture collector (mints JWT via dashback env + signing lib) | | `test/collect.mjs` | Fixture collector (mints JWT via dashback env + signing lib) |
| `test/dummyBackend.mjs` | Always-authed stub serving `test/fixtures/snapshot.json` + `images/` | | `test/dummyBackend.mjs` | Always-authed stub serving `test/fixtures/snapshot.json` + `images/` |
| `test/cdp.mjs` | Zero-dep CDP client (fresh page target per shot, settle wait, PNG capture) | | `test/cdp.mjs` | Zero-dep CDP client (fresh page target per shot, settle wait, PNG capture) |
| `test/capture.mjs` | Orchestrates dummy + vite + Chrome | | `test/capture.mjs` | Builds prod bundle, serves via preview + dummy, drives Chrome |
| `test/compare.mjs` | Zero-dep PNG decoder + comparator (0.1% tolerance, red diff PNGs) | | `test/compare.mjs` | Zero-dep PNG decoder + comparator (0.1% tolerance, red diff PNGs) |
| `test/fixtures/` | Committed snapshot + binary thumbs (2.5 MiB) | | `test/fixtures/` | Committed snapshot + binary thumbs (2.5 MiB) |
| `test/screenshots/reference/` | Committed reference PNGs (7.5 MiB) | | `test/screenshots/reference/` | Committed reference PNGs (7.5 MiB) |

View File

@@ -4,15 +4,21 @@
* Usage: * Usage:
* node test/capture.mjs [--out test/screenshots/current] * node test/capture.mjs [--out test/screenshots/current]
* *
* Flow: spawns the dummy backend (default :4218) + `vite --port 4229` with * Flow: builds the production bundle (`vite build`), serves `dist/` via
* RIG_API_TARGET pointing at the dummy, then drives a persistent headless * `vite preview` (default :4229) with RIG_API_TARGET pointing at the dummy
* Chrome (remote-debugging port) via CDP: one fresh page target per shot, * backend (default :4218), then drives a persistent headless Chrome
* (remote-debugging port) via CDP: one fresh page target per shot,
* waits for React hydration + data, captures full-page PNGs. No npm deps. * waits for React hydration + data, captures full-page PNGs. No npm deps.
* *
* Production (not dev) so screenshots reflect the shipped app: no HMR client,
* no AutomatInspector, minified bundles. Each run uses a fresh Chrome profile
* so the PWA service worker / caches never leak between runs.
*
* The dummy backend is always-authed, so no login step is needed. * The dummy backend is always-authed, so no login step is needed.
*/ */
import { spawn } from 'node:child_process'; import { spawn } from 'node:child_process';
import fs from 'node:fs'; import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path'; import path from 'node:path';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
import { ROUTES, VIEWPORTS } from './routes.mjs'; import { ROUTES, VIEWPORTS } from './routes.mjs';
@@ -74,8 +80,8 @@ function startProc(cmd, procArgs, env, tag) {
child.stdout.on('data', (d) => process.stdout.write(`[${tag}] ${d}`)); child.stdout.on('data', (d) => process.stdout.write(`[${tag}] ${d}`));
child.stderr.on('data', (d) => { child.stderr.on('data', (d) => {
const text = String(d); const text = String(d);
// vite proxy logs are noise during capture; keep errors. // preview proxy logs are noise during capture; keep errors.
if (tag === 'vite' && text.includes('[vite-proxy]')) return; if ((tag === 'preview' || tag === 'vite') && text.includes('[vite-proxy]')) return;
process.stderr.write(`[${tag}] ${d}`); process.stderr.write(`[${tag}] ${d}`);
}); });
child.stdout.unref?.(); child.stdout.unref?.();
@@ -136,19 +142,38 @@ async function main() {
} }
fs.mkdirSync(OUT_DIR, { recursive: true }); fs.mkdirSync(OUT_DIR, { recursive: true });
// Production bundle first — screenshots must reflect the shipped app.
console.log('[capture] vite build…');
await new Promise((resolve, reject) => {
const build = spawn('npx', ['vite', 'build'], {
cwd: FRONT_ROOT,
env: { ...process.env },
stdio: ['ignore', 'pipe', 'pipe'],
});
build.stdout.on('data', () => process.stdout.write('[build] '));
build.stderr.on('data', (d) => process.stderr.write(`[build] ${d}`));
build.on('exit', (code) => {
if (code === 0) resolve();
else reject(new Error(`vite build exited with ${code}`));
});
build.on('error', reject);
});
const dummy = startProc('node', ['test/dummyBackend.mjs', `--port=${DUMMY_PORT}`], {}, 'dummy'); const dummy = startProc('node', ['test/dummyBackend.mjs', `--port=${DUMMY_PORT}`], {}, 'dummy');
await waitForPort(DUMMY_PORT); await waitForPort(DUMMY_PORT);
const vite = startProc( const server = startProc(
'npx', 'npx',
['vite', '--port', String(VITE_PORT), '--strictPort'], ['vite', 'preview', '--port', String(VITE_PORT), '--strictPort'],
{ RIG_API_TARGET: `http://localhost:${DUMMY_PORT}` }, { RIG_API_TARGET: `http://localhost:${DUMMY_PORT}` },
'vite', 'preview',
); );
await waitForPort(VITE_PORT); await waitForPort(VITE_PORT);
await wait(3000); await wait(2000);
const profile = '/tmp/rig-chrome-profile'; // Fresh profile per run: the production SW registers and caches dist/ —
// never let it leak between reference/compare runs.
const profile = fs.mkdtempSync(path.join(os.tmpdir(), 'rig-chrome-'));
fs.mkdirSync(profile, { recursive: true }); fs.mkdirSync(profile, { recursive: true });
const chrome = startProc( const chrome = startProc(
CHROME, CHROME,
@@ -187,15 +212,21 @@ async function main() {
} }
} finally { } finally {
await stopProc(chrome, 'chrome'); await stopProc(chrome, 'chrome');
await stopProc(vite, 'vite'); await stopProc(server, 'preview');
await stopProc(dummy, 'dummy'); await stopProc(dummy, 'dummy');
// Remove the throwaway Chrome profile (SW + caches).
try {
fs.rmSync(profile, { recursive: true, force: true });
} catch {
// ignore
}
} }
if (failed > 0) { if (failed > 0) {
console.error(`[capture] ${failed} shots failed`); console.error(`[capture] ${failed} shots failed`);
process.exit(1); process.exit(1);
} }
console.log(`[capture] done → ${OUT_DIR}`); console.log(`[capture] done → ${OUT_DIR}`);
// Explicit exit: spawned grandchildren (vite/esbuild, chrome helpers) can // Explicit exit: spawned grandchildren (preview server, chrome helpers) can
// otherwise keep the event loop alive after teardown. // otherwise keep the event loop alive after teardown.
process.exit(0); process.exit(0);
} }

View File

@@ -1,6 +1,6 @@
{ {
"meta": { "meta": {
"collectedAt": "2026-10-07T19:27:26.707Z", "collectedAt": "2026-10-07T20:26:08.334Z",
"backend": "http://localhost:4208", "backend": "http://localhost:4208",
"companyId": 1, "companyId": 1,
"googleClientId": "928121624463-m9du45ek2d5f6p9cke8qpnf89ii9no20.apps.googleusercontent.com" "googleClientId": "928121624463-m9du45ek2d5f6p9cke8qpnf89ii9no20.apps.googleusercontent.com"