u
This commit is contained in:
@@ -28,7 +28,7 @@ npm run rig:compare
|
||||
| `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/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/fixtures/` | Committed snapshot + binary thumbs (2.5 MiB) |
|
||||
| `test/screenshots/reference/` | Committed reference PNGs (7.5 MiB) |
|
||||
|
||||
@@ -4,15 +4,21 @@
|
||||
* Usage:
|
||||
* node test/capture.mjs [--out test/screenshots/current]
|
||||
*
|
||||
* Flow: spawns the dummy backend (default :4218) + `vite --port 4229` with
|
||||
* RIG_API_TARGET pointing at the dummy, then drives a persistent headless
|
||||
* Chrome (remote-debugging port) via CDP: one fresh page target per shot,
|
||||
* Flow: builds the production bundle (`vite build`), serves `dist/` via
|
||||
* `vite preview` (default :4229) with RIG_API_TARGET pointing at the dummy
|
||||
* 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.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
import { spawn } from 'node:child_process';
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
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.stderr.on('data', (d) => {
|
||||
const text = String(d);
|
||||
// vite proxy logs are noise during capture; keep errors.
|
||||
if (tag === 'vite' && text.includes('[vite-proxy]')) return;
|
||||
// preview proxy logs are noise during capture; keep errors.
|
||||
if ((tag === 'preview' || tag === 'vite') && text.includes('[vite-proxy]')) return;
|
||||
process.stderr.write(`[${tag}] ${d}`);
|
||||
});
|
||||
child.stdout.unref?.();
|
||||
@@ -136,19 +142,38 @@ async function main() {
|
||||
}
|
||||
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');
|
||||
await waitForPort(DUMMY_PORT);
|
||||
|
||||
const vite = startProc(
|
||||
const server = startProc(
|
||||
'npx',
|
||||
['vite', '--port', String(VITE_PORT), '--strictPort'],
|
||||
['vite', 'preview', '--port', String(VITE_PORT), '--strictPort'],
|
||||
{ RIG_API_TARGET: `http://localhost:${DUMMY_PORT}` },
|
||||
'vite',
|
||||
'preview',
|
||||
);
|
||||
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 });
|
||||
const chrome = startProc(
|
||||
CHROME,
|
||||
@@ -187,15 +212,21 @@ async function main() {
|
||||
}
|
||||
} finally {
|
||||
await stopProc(chrome, 'chrome');
|
||||
await stopProc(vite, 'vite');
|
||||
await stopProc(server, 'preview');
|
||||
await stopProc(dummy, 'dummy');
|
||||
// Remove the throwaway Chrome profile (SW + caches).
|
||||
try {
|
||||
fs.rmSync(profile, { recursive: true, force: true });
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
if (failed > 0) {
|
||||
console.error(`[capture] ${failed} shots failed`);
|
||||
process.exit(1);
|
||||
}
|
||||
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.
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
2
test/fixtures/snapshot.json
vendored
2
test/fixtures/snapshot.json
vendored
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"meta": {
|
||||
"collectedAt": "2026-10-07T19:27:26.707Z",
|
||||
"collectedAt": "2026-10-07T20:26:08.334Z",
|
||||
"backend": "http://localhost:4208",
|
||||
"companyId": 1,
|
||||
"googleClientId": "928121624463-m9du45ek2d5f6p9cke8qpnf89ii9no20.apps.googleusercontent.com"
|
||||
|
||||
Reference in New Issue
Block a user