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/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) |
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
2
test/fixtures/snapshot.json
vendored
2
test/fixtures/snapshot.json
vendored
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user