Add an e2e suite that drives the real React web app (not the standalone harness): tests/playwright-web.config.ts + tests/web/tools-open.spec.ts navigate /p/demo/<tool>/<file> for all five tools and assert each boots, opens its demo file (title drops "untitled"), shows no first-run wizard, and emits no WASM abort or URL-regex modal. global-setup-web.ts re-seeds the demo project through the API if missing, so the suite is self-sufficient against a running dev stack. Wired as `npm run test:web`. Also at the harness level: - pl_editor-load.spec.ts: prove the pl_editor kicadOpenFile embind hook opens a .kicad_wks (mirrors eeschema-load.spec.ts). - seed KiCad config in pl_editor.html / symbol_editor.html (matching eeschema.html and the web app's boot.ts) so the harness boots wizard-free; repurpose pl_editor.spec.ts's stale "wizard completes" test into a wizard-skip regression. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
74 lines
2.7 KiB
TypeScript
74 lines
2.7 KiB
TypeScript
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
import type { FullConfig } from '@playwright/test';
|
|
|
|
/**
|
|
* Global setup for the web-app e2e suite.
|
|
*
|
|
* The tests drive the real React app at :3048 and open files from the committed
|
|
* "demo" project. That project is normally created by `pnpm db:migrate`
|
|
* (seedDemoProject). This setup makes the suite self-sufficient: it waits for
|
|
* the API to be reachable and, if the demo project is missing, recreates it by
|
|
* uploading the committed seed-data files through the public API — the same
|
|
* bytes db:seed uses. Idempotent.
|
|
*/
|
|
|
|
const API_BASE = process.env.VITE_API_BASE_URL ?? 'http://localhost:3050';
|
|
const SEED_DIR = path.resolve(__dirname, '../../web/apps/server/seed-data');
|
|
const DEMO_SLUG = 'demo';
|
|
const DEMO_FILES = ['demo.kicad_sch', 'demo.kicad_pcb', 'demo.kicad_wks'];
|
|
|
|
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(
|
|
`API not reachable at ${API_BASE} (${lastErr}). Start the web stack first: ` +
|
|
`from web/ run \`pnpm db:up && pnpm db:migrate && pnpm dev\`.`
|
|
);
|
|
}
|
|
|
|
async function ensureDemoProject(): Promise<void> {
|
|
const existing = await fetch(`${API_BASE}/api/projects/${DEMO_SLUG}`);
|
|
if (existing.ok) {
|
|
const body = (await existing.json()) as { files: { path: string }[] };
|
|
const have = new Set(body.files.map((f) => f.path));
|
|
if (DEMO_FILES.every((f) => have.has(f))) return;
|
|
} else {
|
|
// Create the project (ignore 409 if a race created it).
|
|
const created = await fetch(`${API_BASE}/api/projects`, {
|
|
method: 'POST',
|
|
headers: { 'content-type': 'application/json' },
|
|
body: JSON.stringify({ name: 'Demo Project', slug: DEMO_SLUG }),
|
|
});
|
|
if (!created.ok && created.status !== 409) {
|
|
throw new Error(`failed to create demo project: HTTP ${created.status}`);
|
|
}
|
|
}
|
|
|
|
// Upload the committed seed bytes (multipart field-name = project-relative path).
|
|
const form = new FormData();
|
|
for (const name of DEMO_FILES) {
|
|
const buf = fs.readFileSync(path.join(SEED_DIR, name));
|
|
form.append(name, new Blob([buf]), name);
|
|
}
|
|
const up = await fetch(`${API_BASE}/api/projects/${DEMO_SLUG}/files`, {
|
|
method: 'POST',
|
|
body: form,
|
|
});
|
|
if (!up.ok) throw new Error(`failed to seed demo files: HTTP ${up.status}`);
|
|
}
|
|
|
|
export default async function globalSetup(_config: FullConfig): Promise<void> {
|
|
await waitForApi();
|
|
await ensureDemoProject();
|
|
}
|