#!/usr/bin/env node /** * Headless end-to-end checks of the editor, driven over the Chrome DevTools Protocol. * Usage: PATH=/opt/node22/bin:$PATH node tools/seed-e2e.mjs && node tools/e2e.mjs [projectId] [baseUrl] * Prints PASS/FAIL per check, writes screenshots to /tmp/gm-e2e-*.png. No dependencies. * * Runs against the fixture project written by tools/seed-e2e.mjs and mutates it heavily; the * steps in tools/e2e/ run in order and build on each other's scene state. * * `GM_STEPS=1-12` or `GM_STEPS=6,20` runs a subset while working on one step. Since later steps * rely on what earlier ones leave behind, a subset that skips a prerequisite fails on purpose. */ import { STEPS } from './e2e/index.mjs'; import { runChecks } from './lib/harness.mjs'; import { PROJECTS, cliArgs } from './lib/projects.mjs'; const { projectId, baseUrl } = cliArgs('e2e.mjs', PROJECTS.fixture); function selectSteps(spec) { if (!spec) return STEPS; const wanted = new Set(); for (const part of spec.split(',')) { const [from, to] = part.trim().split('-').map(Number); if (!Number.isInteger(from) || from < 1) { console.error(`GM_STEPS: cannot read "${part}" – use 20, 1-12 or 6,20`); process.exit(2); } for (let i = from; i <= (Number.isInteger(to) ? to : from); i++) wanted.add(i); } return STEPS.filter((_, i) => wanted.has(i + 1)); } const steps = selectSteps(process.env.GM_STEPS); if (steps.length !== STEPS.length) { console.log(`running ${steps.length}/${STEPS.length} steps: ${steps.map((s) => s.name).join(', ')}`); } await runChecks({ projectId, baseUrl, port: 9333, shotPrefix: 'gm-e2e' }, async (t) => { await t.navigate(); await t.shot('01-loaded'); for (const step of steps) await step(t); });