pcbjam/tests/playwright.config.ts

107 lines
3.8 KiB
TypeScript
Raw Normal View History

import { defineConfig, devices } from '@playwright/test';
import { execSync } from 'child_process';
import * as fs from 'fs';
import * as path from 'path';
const PORT_FILE = path.join(__dirname, '.test-port');
// Resolve the static-server port for this run.
//
// This config file is re-imported by EVERY Playwright process: the main runner
// (which launches the webServer) and each worker process (which calls
// page.goto(baseURL)). They must all agree on one port. Playwright also
// *recreates* a worker mid-run after a test times out or crashes — and that new
// worker re-imports this config.
//
// The previous heuristic ("reuse .test-port if it's <60s old, else pick a new
// free port") broke exactly there: once a run passed the 60s mark, a recreated
// worker treated the file as stale, picked a DIFFERENT free port, and every
// subsequent page.goto hit a dead port (net::ERR_CONNECTION_REFUSED) because the
// webServer was still listening on the original port. A single failing test
// thus cascaded into ~all later tests failing.
//
// Fix (same as playwright-kicad.config.ts): drop the time window entirely. The
// main runner always picks a fresh port and writes it; workers always reuse
// whatever the main runner wrote. The main runner is the only process whose
// argv carries the `test` command (workers are forked with an empty argv), and
// it imports this config — and so writes the file — before any worker is
// spawned.
function resolvePort(): number {
const isMainRunner = process.argv.slice(2).includes('test');
if (!isMainRunner) {
try {
const existing = parseInt(fs.readFileSync(PORT_FILE, 'utf-8').trim(), 10);
if (existing > 0 && existing < 65536) {
return existing;
}
} catch {
// No readable port file — fall through. Shouldn't happen in a worker,
// since the main runner writes the file before spawning workers.
}
}
const port = findFreePort();
fs.writeFileSync(PORT_FILE, port.toString());
return port;
}
// Find a free port dynamically using a shell command
function findFreePort(): number {
// Use Python to find a free port (works on macOS and Linux)
try {
const result = execSync(
'python3 -c "import socket; s=socket.socket(); s.bind((\'\',0)); print(s.getsockname()[1]); s.close()"',
{ encoding: 'utf-8' }
);
return parseInt(result.trim());
} catch {
// Fallback to default port range
return 9000 + Math.floor(Math.random() * 1000);
}
}
const port = resolvePort();
refactor: collapse dual-mode plumbing — the DOM port is the only WASM build The canvas (wxUniversal) mode is gone (wxwidgets submodule); remove every piece of side-by-side plumbing so there is exactly one build and one test flow: - scripts/build-wxuniversal-wasm.sh -> scripts/build-wx-wasm.sh; no --dom/--enable-universal; builds into build-wasm/wxwidgets - build-wasm-test.sh: no DOM_BUILD / apps-dom rsync mirror / PORT=dom; apps build straight into tests/apps (Makefile.wasm PORT conditionals collapsed; wx.js + wx-dom.js always pre-js) - docker/build.sh, build-kicad-target.sh, env.sh: WX_PORT / -dom / -universal suffixes removed; kicad builds to kicad-<app>, outputs to output/; wx.js/wx-dom.js copied from the real source path (/workspace/wxwidgets/build/wasm — the old build-wasm path never existed and silently failed) - setup-kicad-wasm.sh: single target dir; the perl wx-dom.js injection is gone — the 7 checked-in kicad pages now reference wx-dom.js directly - playwright configs serve apps/; fixtures drop the test-results/dom and logs/wxwidgets/dom namespacing; boot.spec asserts wxDomPort unconditionally; pcbnew.spec uses one reference image; appearance.spec assertions unconditional - compare/update-baseline-screenshots.sh: --port removed - tests/gal-regression/wasm/Makefile: links build-wasm/wxwidgets and carries wx-dom.js as a second pre-js — the gal-webgl suite (30 specs) now actually builds and runs here (it needed host-side boost+glm via scripts/deps; the bundle had been missing, timing the whole spec out) - tests: clickCanvas() dispatches via page.mouse (DOM widgets legitimately cover the canvas; locator actionability refused the click); the comprehensive spec drives wxChoice through its native <select> (browser-owned popup cannot be coordinate-clicked) - docs: README/CLAUDE.md/build.md script names and dirs; features/wx-dom-port README reframed (DOM is THE port), visual-notes bugs 26-28; FindwxWidgets.cmake config label drops 'wasmuniv' - wxwidgets submodule -> 9dbacc9448 (DOM-only port, fork diff shrunk) Gate: full wx e2e suite 292 passed / 1 skipped / 0 failed — first run ever with the gal-webgl specs green (28 scenarios + load + sequential). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 10:18:16 +02:00
const appsDir = 'apps';
export default defineConfig({
globalSetup: './global-setup.ts',
testDir: './e2e',
fullyParallel: true,
forbidOnly: !!process.env.CI,
2026-06-04 13:20:55 +02:00
// 1 local retry absorbs transient `npx serve` connection refusals under heavy
// parallel load (many workers fetching large WASM bundles at once).
retries: process.env.CI ? 2 : 1,
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
timeout: 60000, // WASM can be slow to load
use: {
baseURL: `http://localhost:${port}`,
trace: 'on-first-retry',
// Grant clipboard and font permissions for tests
permissions: ['clipboard-read', 'clipboard-write', 'local-fonts'],
},
projects: [
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
// CI runners have no GPU and several wx specs use WebGL
// (gal-webgl.spec.ts etc.): newer headless Chromium refuses software
// WebGL without --enable-unsafe-swiftshader. CI-gated so local runs
// keep stock behavior (same pattern as playwright-kicad.config.ts).
...(process.env.CI ? {
launchOptions: { args: ['--enable-unsafe-swiftshader'] },
} : {}),
},
},
],
webServer: {
command: `npx serve ${appsDir} -p ${port} -c ../serve.json`,
port: port,
reuseExistingServer: !process.env.CI,
},
});