Rework HomePage into KiCad-launcher-style zones: local file-load, a Tools grid, backend projects, and a backend libraries listing (useLibs over the shared listLibs). The Tools grid shows all seven tools in KiCad's standalone order (Schematic/Symbol/PCB/Footprint editors, Gerber Viewer, Calculator, Drawing Sheet); clicking a tool or a library launches the editor with no project (launchedTool → WasmTool slug=local) — file-less editors browse backend libs, document editors open blank. Harden the web e2e global-setup to assert the self-provisioned origin libs (Device, Resistor_SMD) are present, failing fast. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
85 lines
3.1 KiB
TypeScript
85 lines
3.1 KiB
TypeScript
import type { FullConfig } from '@playwright/test';
|
|
|
|
/**
|
|
* Global setup for the web-app e2e suite.
|
|
*
|
|
* The stack is the standalone editor (:3048) plus the reference backend
|
|
* (:3060, @pcbjam/backend-example), which serves a single project off the
|
|
* local filesystem — PROJECT_DIR=tests/fixtures/demo, so the slug is "demo"
|
|
* (basename of PROJECT_DIR). Nothing to seed: just wait for the backend and
|
|
* verify it serves the committed demo files the specs open.
|
|
*/
|
|
|
|
const API_BASE = process.env.BACKEND_URL ?? 'http://localhost:3060';
|
|
const DEMO_SLUG = 'demo';
|
|
const DEMO_FILES = ['demo.kicad_sch', 'demo.kicad_pcb', 'demo.kicad_wks'];
|
|
// Origin libs the remote-lib specs browse/place (symbol-write/footprint-browse).
|
|
// The backend self-provisions these on dev/start (src/extract/ensure-example-libs).
|
|
const REQUIRED_LIBS: Record<'symbol' | 'footprint', string[]> = {
|
|
symbol: ['Device'],
|
|
footprint: ['Resistor_SMD'],
|
|
};
|
|
|
|
async function waitForApi(timeoutMs = 60000): Promise<void> {
|
|
const deadline = Date.now() + timeoutMs;
|
|
let lastErr = '';
|
|
while (Date.now() < deadline) {
|
|
try {
|
|
const r = await fetch(`${API_BASE}/health`);
|
|
if (r.ok) return;
|
|
lastErr = `HTTP ${r.status}`;
|
|
} catch (e) {
|
|
lastErr = (e as Error).message;
|
|
}
|
|
await new Promise((res) => setTimeout(res, 1000));
|
|
}
|
|
throw new Error(
|
|
`backend not reachable at ${API_BASE} (${lastErr}). Start the web stack first: ` +
|
|
`from web/ run \`pnpm dev\` (PROJECT_DIR defaults to tests/fixtures/demo via ` +
|
|
`backend/.env or the playwright webServer env).`
|
|
);
|
|
}
|
|
|
|
async function verifyDemoProject(): Promise<void> {
|
|
const r = await fetch(`${API_BASE}/api/projects/${DEMO_SLUG}`);
|
|
if (!r.ok) {
|
|
throw new Error(
|
|
`GET /api/projects/${DEMO_SLUG} -> HTTP ${r.status}; ` +
|
|
`is the backend's PROJECT_DIR pointing at tests/fixtures/demo?`
|
|
);
|
|
}
|
|
const body = (await r.json()) as { files: { path: string }[] };
|
|
const have = new Set(body.files.map((f) => f.path));
|
|
const missing = DEMO_FILES.filter((f) => !have.has(f));
|
|
if (missing.length) {
|
|
throw new Error(`demo project missing files: ${missing.join(', ')}`);
|
|
}
|
|
}
|
|
|
|
async function verifyLibs(): Promise<void> {
|
|
for (const kind of ['symbol', 'footprint'] as const) {
|
|
const r = await fetch(`${API_BASE}/api/libs?kind=${kind}`);
|
|
if (!r.ok) {
|
|
throw new Error(`GET /api/libs?kind=${kind} -> HTTP ${r.status}`);
|
|
}
|
|
const libs = (await r.json()) as { id: string }[];
|
|
const have = new Set(libs.map((l) => l.id));
|
|
const missing = REQUIRED_LIBS[kind].filter((id) => !have.has(id));
|
|
if (missing.length) {
|
|
throw new Error(
|
|
`backend missing ${kind} origin lib(s): ${missing.join(', ')}. The example ` +
|
|
`backend self-provisions libs on dev/start (clones + extracts a KiCad slice ` +
|
|
`into web/backend/.libs) — check that step ran (network on first run).`
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
export default async function globalSetup(_config: FullConfig): Promise<void> {
|
|
await waitForApi();
|
|
await verifyDemoProject();
|
|
await verifyLibs();
|
|
console.log(
|
|
`web e2e setup: backend at ${API_BASE} serving "${DEMO_SLUG}" + origin libs — OK`
|
|
);
|
|
}
|