2026-07-02 12:59:42 +02:00
|
|
|
import { defineConfig, devices } from "@playwright/test";
|
|
|
|
|
import { execSync } from "child_process";
|
|
|
|
|
import * as fs from "fs";
|
|
|
|
|
import * as path from "path";
|
2025-12-27 16:01:35 +01:00
|
|
|
|
2026-07-02 12:59:42 +02:00
|
|
|
const PORT_FILE = path.join(__dirname, ".test-port");
|
2025-12-27 16:01:35 +01:00
|
|
|
|
2025-12-29 10:51:03 +01:00
|
|
|
// NOTE: Chrome headless crashes on ARM Mac due to SwiftShader WebGL bug
|
|
|
|
|
// (Chromium issues #1416283, #338414704). Firefox headless works reliably.
|
|
|
|
|
// Use --project=firefox for headless, --project=chromium for headed debugging.
|
|
|
|
|
|
2026-06-04 13:20:55 +02:00
|
|
|
// 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 (NS_ERROR_CONNECTION_REFUSED) because the
|
|
|
|
|
// webServer was still listening on the original port. A single timing-out test
|
|
|
|
|
// (e.g. eeschema-load) thus cascaded into ~all later tests failing.
|
|
|
|
|
//
|
|
|
|
|
// Fix: 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 {
|
2026-07-02 12:59:42 +02:00
|
|
|
const isMainRunner = process.argv.slice(2).includes("test");
|
2026-06-04 13:20:55 +02:00
|
|
|
if (!isMainRunner) {
|
|
|
|
|
try {
|
2026-07-02 12:59:42 +02:00
|
|
|
const existing = parseInt(fs.readFileSync(PORT_FILE, "utf-8").trim(), 10);
|
2026-06-04 13:20:55 +02:00
|
|
|
if (existing > 0 && existing < 65536) {
|
|
|
|
|
return existing;
|
2025-12-27 16:01:35 +01:00
|
|
|
}
|
2026-06-04 13:20:55 +02:00
|
|
|
} catch {
|
|
|
|
|
// No readable port file — fall through. Shouldn't happen in a worker,
|
|
|
|
|
// since the main runner writes the file before spawning workers.
|
2025-12-27 16:01:35 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const port = findFreePort();
|
|
|
|
|
fs.writeFileSync(PORT_FILE, port.toString());
|
|
|
|
|
return port;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function findFreePort(): number {
|
|
|
|
|
try {
|
|
|
|
|
const result = execSync(
|
2026-07-02 12:59:42 +02:00
|
|
|
"python3 -c \"import socket; s=socket.socket(); s.bind(('',0)); print(s.getsockname()[1]); s.close()\"",
|
2026-07-03 11:37:36 +02:00
|
|
|
{ encoding: "utf-8", timeout: 5000 }
|
2025-12-27 16:01:35 +01:00
|
|
|
);
|
|
|
|
|
return parseInt(result.trim());
|
|
|
|
|
} catch {
|
|
|
|
|
return 9000 + Math.floor(Math.random() * 1000);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-04 13:20:55 +02:00
|
|
|
const port = resolvePort();
|
2025-12-27 16:01:35 +01:00
|
|
|
|
2026-07-02 14:48:11 +02:00
|
|
|
// The merged kicad_editor wasm (~190M+ debug build; pcbnew+eeschema engines in one
|
|
|
|
|
// image since editor-unification Part 2) exceeds SpiderMonkey's per-process code
|
2026-06-11 16:12:01 +02:00
|
|
|
// budget on x86-64 CI even with the baseline-only JIT (run 27343416511:
|
|
|
|
|
// "InternalError: out of memory" at instantiation; the same module compiles
|
2026-07-02 14:48:11 +02:00
|
|
|
// on arm64, whose denser code fits). V8 handles it, so on CI every spec that
|
|
|
|
|
// boots the merged module — i.e. ALL FOUR editors — runs on bundled Chromium
|
|
|
|
|
// (the 'chromium-ci' project) and firefox skips them. Firefox keeps the small
|
|
|
|
|
// separate bundles (pl_editor / calculator / gerbview).
|
|
|
|
|
const BIG_MODULE_SPECS = [
|
2026-07-02 12:59:42 +02:00
|
|
|
"**/pcbnew.spec.ts",
|
|
|
|
|
"**/pcbnew-collab.spec.ts",
|
|
|
|
|
"**/load-pcb.spec.ts",
|
|
|
|
|
"**/load-pcb-probe.spec.ts",
|
2026-06-15 11:52:17 +02:00
|
|
|
// Specs added 2026-06-11..06-13 that boot pcbnew (pcbnew.html /
|
|
|
|
|
// pcbnew-collab.html). Without routing here they ran on Firefox in CI and
|
|
|
|
|
// timed out at instantiation (the SpiderMonkey/x86 code-budget OOM above).
|
2026-07-02 14:48:11 +02:00
|
|
|
// appearance/roundtrip/save-hook are parametrized across pl_editor/eeschema/
|
|
|
|
|
// pcbnew; routing the whole file moves the small-bundle variants to chromium-ci
|
|
|
|
|
// too (they boot fine on V8) — only the browser changes, not whether they run.
|
2026-07-02 12:59:42 +02:00
|
|
|
"**/appearance.spec.ts",
|
|
|
|
|
"**/contextmenu-scrollbar-pcbnew.spec.ts",
|
|
|
|
|
"**/dark-mode.spec.ts",
|
|
|
|
|
"**/items-bridge.spec.ts",
|
|
|
|
|
"**/roundtrip.spec.ts",
|
|
|
|
|
"**/save-hook.spec.ts",
|
2026-06-17 08:56:03 +02:00
|
|
|
// boots pcbnew.html — must run on V8 (chromium-ci); on Firefox/x86 CI the
|
|
|
|
|
// ~190M module OOMs at instantiation and #canvas never appears (run 27626037849).
|
2026-07-02 12:59:42 +02:00
|
|
|
"**/pcbnew-move.spec.ts",
|
2026-06-17 17:02:25 +02:00
|
|
|
// 3D viewer specs boot pcbnew.html (3D-enabled build) — same V8 routing.
|
2026-07-02 12:59:42 +02:00
|
|
|
"**/3d-viewer.spec.ts",
|
|
|
|
|
// Isolated (own file → own worker) so its heavy single load isn't degraded by the
|
|
|
|
|
// Worker accumulation of the other 3D-viewer tests sharing a process (see the file header).
|
|
|
|
|
"**/3d-viewer-deadlock.spec.ts",
|
|
|
|
|
"**/3d-viewer-models.spec.ts",
|
|
|
|
|
"**/footprint-3d-preview.spec.ts",
|
2026-07-02 14:48:11 +02:00
|
|
|
// Parametrized over the library editors — both cases now boot the merged module.
|
2026-07-01 17:39:42 +02:00
|
|
|
"**/frame-runtime.spec.ts",
|
2026-07-02 14:48:11 +02:00
|
|
|
// eeschema family: since Part 2 these boot the SAME merged kicad_editor module
|
|
|
|
|
// (eeschema.html / symbol_editor.html load kicad_editor.js) — same V8 routing.
|
|
|
|
|
"**/eeschema.spec.ts",
|
|
|
|
|
"**/eeschema-collab.spec.ts",
|
|
|
|
|
"**/eeschema-crosshair.spec.ts",
|
|
|
|
|
"**/eeschema-load.spec.ts",
|
|
|
|
|
"**/eeschema-subschema.spec.ts",
|
|
|
|
|
"**/eeschema-ui.spec.ts",
|
|
|
|
|
"**/eeschema-url-regex.spec.ts",
|
|
|
|
|
"**/symbol_editor.spec.ts",
|
2026-07-02 15:35:45 +02:00
|
|
|
// Cross-face probe: schematic session lazily starts the PCB kiface (Preferences).
|
|
|
|
|
"**/xface-probe.spec.ts",
|
feat(wasm): occ-split — lazy occ_service worker; kicad_editor drops OCC (−31%)
Move OpenCASCADE out of the merged editor image into occ_service: a separate
emscripten module (-sASYNCIFY=0, MODULARIZE, in-container -Oz finalize, 2N+8
pre-warmed pthread pool) booted lazily in a dedicated Web Worker on the first
STEP export or STEP/IGES model parse. kicad_editor.wasm ~190 MB -> 130 MB;
sessions that never touch OCC never fetch its 57 MB. STEP export works in the
browser for the first time: the unchanged desktop dialog runs EXPORTER_STEP,
whose wasm shadow suspends into globalThis.occService and the export bytes go
straight to a browser download (never entering the editor heap). STEP/IGES 3D
models parse in the worker via the oce shadow (S3D WriteCache/ReadCache wire).
- wasm/occ-service/: service CMake target (hooked from the kicad fork's
top-level CMakeLists, wasm/editor pattern), embind entry
(occExport/occLoadModel), wxConfig pre-js.
- wasm/stubs/{exporter_step,oce_plugin}_stub.cpp: EM_ASYNC_JS worker bridges
(callee-shadowing; no caller #ifdefs).
- web/standalone: provider installed whenever the kicad_editor bundle boots
(cross-face safe); ONE shared worker-boot source occ-worker.js (vite ?raw;
the e2e stub reads the same file) — blob worker with locateFile absolutized
against the glue URL; export download-name guard.
- deps: OCC builds with RapidJSON so its glTF/GLB writer exists — pinned to
the vcpkg master snapshot 2025-02-26 (24b5e7a8b27f), the same code official
KiCad consumes via vcpkg.json's opencascade[rapidjson]; rapidjson's latest
tag (v1.1.0, 2016) is ill-formed under modern clang.
- tests: occ-export dialog e2e (lazy-fetch boundary + STEP download bytes),
occ-probe incl. a 9-format matrix (step/stpz/brep/xao/ply/stl/glb/u3d/pdf),
3d-viewer-models hard-asserts the worker parse; occ provider stub installed
ambiently by the kicad fixtures.
Validated against desktop kicad-cli 10.0.4: geometric exact equality (bbox
delta 0 um, volume delta 0.0000%) for STEP/GLB/STL/BREP/STPZ across three
boards and option sweeps — with desktop OCC 7.9 vs wasm OCC 7.8; PLY/XAO/PDF
structurally equal; U3D same-size (quantizer float LSBs differ). Full kicad
e2e green on Firefox and Chromium; standalone verified end to end (lazy fetch
only on the Export click; export.step 60,628 B ISO-10303-21; loadModel 700 KB
STEP -> 569 KB scenegraph cache).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 12:39:58 +02:00
|
|
|
// OCC split: occ-export boots pcbnew.html (the merged module) and drives the
|
|
|
|
|
// export dialog through the occ_service worker — same V8 routing. occ-probe
|
|
|
|
|
// only boots the (small) occ_service module but shares the harness page.
|
|
|
|
|
"**/occ-export.spec.ts",
|
|
|
|
|
"**/occ-probe.spec.ts",
|
test(ysync): repro tests for review bugs 01-07 + v2 items-wire e2e port (miss 11)
The 2026-07-02 sync review (docs/features/ysync-review, ysync-review branch)
found 7 bugs and that the two-tab e2e only exercised the DEAD legacy scalar
wire. This lands plan doc 15 in full; results + empirical findings in doc 16.
- tests/collab/browser-entry-v2.ts (+build.mjs): the PRODUCTION v2 stack
bundled for e2e (connectKicadDoc + attachKicadCollab, kdoc_* keys), with
in-page renderActiveDoc/singleSeedRender/driftReport helpers and yjs forced
to ONE copy (the two web pnpm workspaces otherwise bundle two
instanceof-incompatible instances).
- tests/kicad/ysync-two-tab.spec.ts: pl_editor green baseline (A↔B edits,
ITEM-level drift silence) + divergent-uuid adopt; bug-01 pcb/ee fresh-room
repros (Chromium-only: two kicad_editor tabs exceed Firefox's per-process
wasm budget); bug-06 concurrent-seed race; bug-03 Y-half.
- tests/kicad/ysync-repros-{pcbnew,eeschema}.spec.ts: bugs 02/03/05 + the
bug-04 matrix (anchor-centred fp rotation, pad resize, endpoint drag,
symbol rotation, Value-field edit), each with green landed-preconditions;
the "local move emits" controls double as headless-emit probes — GREEN on
both tools, so every emit-dependent repro is a live test.fail.
- wasm/bindings: 7 local-edit test hooks via real commits
(CallAfter+COROUTINE) — TestRemoveItem/TestRotateItem (both tools,
dispatched in the merged image), TestSetPadSize/TestMoveEndpoint (pcbnew),
TestSetFieldText (eeschema).
- web/standalone ysync-repros.test.ts: bug-01 units (C++-faithful fake gating
emit on ensureBridge) + bug-07a/b (stale DOWN hook, real sheet-manager gap).
- web/pcbjam-shared bump: bug-03/06 unit repros.
Convention: every repro asserts the CORRECT behavior and is expected-fail
(test.fail/it.fails) naming its bug doc; a fix flips it to "unexpected pass",
forcing marker removal — the repro becomes the regression test. Every
expected failure verified (JSON reporter) to fail at its documented assert.
Suite state: 39 passed / 0 failed / 0 flaky / 5 skipped (2 firefox guards,
2 pre-existing legacy two-tab skips, 1 pre-existing roundtrip fixme).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DPfrVhfYgPPgtawjSssZfn
2026-07-03 12:43:35 +02:00
|
|
|
// ysync v2-wire repros/coverage: the two-tab file boots pcbnew + eeschema
|
|
|
|
|
// (pl_editor cases ride along — only the browser changes), the repro files
|
|
|
|
|
// boot pcbnew-collab.html / eeschema.html — same V8 routing.
|
|
|
|
|
"**/ysync-two-tab.spec.ts",
|
|
|
|
|
"**/ysync-repros-pcbnew.spec.ts",
|
|
|
|
|
"**/ysync-repros-eeschema.spec.ts",
|
2026-06-11 16:12:01 +02:00
|
|
|
];
|
|
|
|
|
|
2026-07-01 14:43:54 +02:00
|
|
|
// Runtime-perf specs run ONLY on the Chromium 'perf' project below: they need
|
|
|
|
|
// CDP CPU throttling (Chromium-only) and pcbnew needs V8. Excluded from the
|
|
|
|
|
// firefox/chromium projects so they don't double-run there.
|
2026-07-02 12:59:42 +02:00
|
|
|
const PERF_SPECS = ["**/*-perf.spec.ts"];
|
2026-07-01 14:43:54 +02:00
|
|
|
|
2026-07-02 12:59:42 +02:00
|
|
|
const appsDir = "apps";
|
2026-06-11 00:19:53 +02:00
|
|
|
|
2025-12-27 16:01:35 +01:00
|
|
|
export default defineConfig({
|
2026-07-02 12:59:42 +02:00
|
|
|
globalSetup: "./global-setup.ts",
|
|
|
|
|
testDir: "./kicad",
|
2026-07-02 09:20:35 +02:00
|
|
|
// See playwright.config.ts: keep CI's outputDir cleanup off test-results/ so the kicad +
|
|
|
|
|
// perf runs don't wipe the accumulated screenshots.
|
2026-07-02 12:59:42 +02:00
|
|
|
outputDir: process.env.CI ? "pw-artifacts/kicad" : "test-results",
|
2025-12-27 16:01:35 +01:00
|
|
|
fullyParallel: true,
|
|
|
|
|
forbidOnly: !!process.env.CI,
|
2026-07-07 10:50:24 +02:00
|
|
|
// retries:0 — the suite is deterministic (no blind sleeps or "if element exists" branches;
|
|
|
|
|
// screenshots go through stableShot for the offline gate, not asserted inline), so a failure
|
|
|
|
|
// is a real failure rather than flake to mask with a retry.
|
|
|
|
|
retries: 0,
|
fix(e2e): rescope the 3D fixes for the webgl-era viewer (rebased onto main)
The 3d-webgl merge (kicad eb13ff3bdc: the viewer now defaults to the real
OpenGL renderer via wasm/gl1, and occ-split moves STEP parsing into the
occ_service worker) made the raytracer-era orchestration on this branch moot —
main's chromium-ci phase is green at 15-way parallelism (28666407570 /
28698861536). Drop what no longer earns its complexity, keep the diagnostics,
fix main's live flake, and make the deadlock spec test what it was written for.
- REVERT the chromium-ci-3d serial project, the two-phase test:kicad:ci, the
SwiftShader GPU-process flags, and the resize-drag/models skips: config and
package.json are byte-for-byte back to main's shape. The raytracer contention
they guarded is no longer on the CI path.
- FIX main's live flake: run 28698861536 is green only via retry
(3d-viewer.spec:26 flaky) and 28666407570's deadlock red sampled an ALL-ZERO
pixel signature — the viewer's first frame lags the canvas's creation on
software WebGL under parallel load, and sampling too early reads an all-black
backbuffer. New waitForThreeDRender() gates render assertions on actual
pixels (1s-interval full-frame CPU reads) instead of fixed sleeps, used by
3d-viewer.spec:26 and the models render tail.
- KEEP the storm-proofed samplers (one full-frame getImageData on a
willReadFrequently canvas replacing 256 per-pixel GPU round-trips per sample
— the "GPU stall due to ReadPixels" trigger) and the logThreeDDiag
instrumentation: engine-independent, and they de-risk every remaining
software-GL pixel read.
- models spec: bridge assertions stay front-loaded (the protocol regression
signal is independent of the render); the occ_service parse verdict is now
POLLED — it lands async relative to the bridge ensures, so asserting it
immediately raced the worker; the render tail runs again everywhere. (The
pre-webgl raytracer+models renderer-death documented in a17f3be does not
affect the OpenGL default path — the raytracer-toggle+models combination
remains untested product surface, tracked outside this branch.)
- deadlock spec: the deadlock it guards is raytracer-specific and the viewer
now defaults to OpenGL — on the GL engine it either passes vacuously (fast
renders make every liveness assertion trivial, 28698861536) or fails on the
black first frame (28666407570). It now flips the engine via the "Use
raytracing" toolbar toggle (loud assert if the toggle moved) and
cross-checks engagement by requiring the canvas pixels to CHANGE after the
flip with no input in between (the raytraced frame is lit differently; a GL
re-render reproduces identical pixels; heap growth is unusable — mimalloc
satisfies the raytracer from freed arena pages). That guard immediately
caught a REAL defect: on the webgl-era wasm build the toggle is INERT (the
click lands and "Reload time" updates, but the canvas never changes —
suspects: DoRePaint's silent catch(runtime_error) freezing the canvas after
a raytracer Redraw throw, or ToggleRaytracing writing m_boardAdapter.m_Cfg
while RenderEngineChanged() reads GetAppSettings<…>(), possibly different
instances in the merged bundle). The spec is therefore test.skip-annotated
as a KNOWN ISSUE with the full engine-force machinery in place — unskipping
it self-validates the product fix. The CI-skip also stays (raytracer
liveness needs real-GPU pacing; the Worker-boot deadlock mechanism is
covered on CI by the standalone wx harnesses).
- 180s viewer-open waits kept as pure CI headroom (never slow a passing run).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 11:37:27 +02:00
|
|
|
// Run parallel workers on CI too (Playwright default ≈ 50% of cores), same as
|
|
|
|
|
// local — the serial CI run was the dominant wall-clock cost. Cap (e.g. '50%'
|
2026-07-07 10:50:24 +02:00
|
|
|
// or a fixed count) if contention OOMs/flakes.
|
fix(e2e): rescope the 3D fixes for the webgl-era viewer (rebased onto main)
The 3d-webgl merge (kicad eb13ff3bdc: the viewer now defaults to the real
OpenGL renderer via wasm/gl1, and occ-split moves STEP parsing into the
occ_service worker) made the raytracer-era orchestration on this branch moot —
main's chromium-ci phase is green at 15-way parallelism (28666407570 /
28698861536). Drop what no longer earns its complexity, keep the diagnostics,
fix main's live flake, and make the deadlock spec test what it was written for.
- REVERT the chromium-ci-3d serial project, the two-phase test:kicad:ci, the
SwiftShader GPU-process flags, and the resize-drag/models skips: config and
package.json are byte-for-byte back to main's shape. The raytracer contention
they guarded is no longer on the CI path.
- FIX main's live flake: run 28698861536 is green only via retry
(3d-viewer.spec:26 flaky) and 28666407570's deadlock red sampled an ALL-ZERO
pixel signature — the viewer's first frame lags the canvas's creation on
software WebGL under parallel load, and sampling too early reads an all-black
backbuffer. New waitForThreeDRender() gates render assertions on actual
pixels (1s-interval full-frame CPU reads) instead of fixed sleeps, used by
3d-viewer.spec:26 and the models render tail.
- KEEP the storm-proofed samplers (one full-frame getImageData on a
willReadFrequently canvas replacing 256 per-pixel GPU round-trips per sample
— the "GPU stall due to ReadPixels" trigger) and the logThreeDDiag
instrumentation: engine-independent, and they de-risk every remaining
software-GL pixel read.
- models spec: bridge assertions stay front-loaded (the protocol regression
signal is independent of the render); the occ_service parse verdict is now
POLLED — it lands async relative to the bridge ensures, so asserting it
immediately raced the worker; the render tail runs again everywhere. (The
pre-webgl raytracer+models renderer-death documented in a17f3be does not
affect the OpenGL default path — the raytracer-toggle+models combination
remains untested product surface, tracked outside this branch.)
- deadlock spec: the deadlock it guards is raytracer-specific and the viewer
now defaults to OpenGL — on the GL engine it either passes vacuously (fast
renders make every liveness assertion trivial, 28698861536) or fails on the
black first frame (28666407570). It now flips the engine via the "Use
raytracing" toolbar toggle (loud assert if the toggle moved) and
cross-checks engagement by requiring the canvas pixels to CHANGE after the
flip with no input in between (the raytraced frame is lit differently; a GL
re-render reproduces identical pixels; heap growth is unusable — mimalloc
satisfies the raytracer from freed arena pages). That guard immediately
caught a REAL defect: on the webgl-era wasm build the toggle is INERT (the
click lands and "Reload time" updates, but the canvas never changes —
suspects: DoRePaint's silent catch(runtime_error) freezing the canvas after
a raytracer Redraw throw, or ToggleRaytracing writing m_boardAdapter.m_Cfg
while RenderEngineChanged() reads GetAppSettings<…>(), possibly different
instances in the merged bundle). The spec is therefore test.skip-annotated
as a KNOWN ISSUE with the full engine-force machinery in place — unskipping
it self-validates the product fix. The CI-skip also stays (raytracer
liveness needs real-GPU pacing; the Worker-boot deadlock mechanism is
covered on CI by the standalone wx harnesses).
- 180s viewer-open waits kept as pure CI headroom (never slow a passing run).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 11:37:27 +02:00
|
|
|
workers: undefined,
|
2026-07-02 12:59:42 +02:00
|
|
|
reporter: "html",
|
|
|
|
|
timeout: 180000, // KiCad WASM needs more time to load (3 minutes)
|
2025-12-27 16:01:35 +01:00
|
|
|
|
2026-07-07 10:50:24 +02:00
|
|
|
// No expect.toHaveScreenshot block: screenshots are captured via stableShot() (render-settle +
|
|
|
|
|
// raw PNG to test-results/) and compared OFFLINE by tools/screenshots against the committed
|
|
|
|
|
// baselines on CI's deterministic Linux render — Playwright does no inline pixel comparison.
|
|
|
|
|
|
2025-12-27 16:01:35 +01:00
|
|
|
use: {
|
|
|
|
|
baseURL: `http://localhost:${port}`,
|
2026-07-02 12:59:42 +02:00
|
|
|
trace: "retain-on-failure",
|
|
|
|
|
screenshot: "only-on-failure",
|
2025-12-27 16:01:35 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
projects: [
|
|
|
|
|
{
|
2025-12-29 10:51:03 +01:00
|
|
|
// Firefox is the default for headless testing (works on ARM Mac)
|
2026-07-02 12:59:42 +02:00
|
|
|
name: "firefox",
|
2026-07-01 14:43:54 +02:00
|
|
|
// Perf specs always run on the dedicated 'perf' project, never here. On CI
|
2026-07-02 14:48:11 +02:00
|
|
|
// every merged-module (kicad_editor) spec moves to chromium-ci (see
|
|
|
|
|
// BIG_MODULE_SPECS).
|
2026-07-02 12:59:42 +02:00
|
|
|
testIgnore: [
|
|
|
|
|
...PERF_SPECS,
|
2026-07-02 14:48:11 +02:00
|
|
|
...(process.env.CI ? BIG_MODULE_SPECS : []),
|
2026-07-02 12:59:42 +02:00
|
|
|
],
|
2025-12-29 10:51:03 +01:00
|
|
|
use: {
|
2026-07-02 12:59:42 +02:00
|
|
|
...devices["Desktop Firefox"],
|
2025-12-29 10:51:03 +01:00
|
|
|
viewport: { width: 1280, height: 720 },
|
2026-06-11 13:22:29 +02:00
|
|
|
// CI-only prefs: GPU-less CI VMs hit two Firefox blockers (identical on
|
|
|
|
|
// Hetzner ccx53 and ubicloud-standard-30, runs 27329612719/27330989479).
|
|
|
|
|
// Gated on CI so local runs keep stock Firefox behavior.
|
2026-07-02 12:59:42 +02:00
|
|
|
...(process.env.CI
|
|
|
|
|
? {
|
|
|
|
|
// Headless Firefox cannot create any GL context on the GPU-less CI
|
|
|
|
|
// VMs (blocklist bypass still ends in FEATURE_FAILURE_WEBGL_EXHAUSTED_
|
|
|
|
|
// DRIVERS) — run headed under Xvfb instead, where GLX + Mesa llvmpipe
|
|
|
|
|
// provides software WebGL. CI invokes the suite via `xvfb-run`.
|
|
|
|
|
headless: false,
|
|
|
|
|
launchOptions: {
|
|
|
|
|
firefoxUserPrefs: {
|
|
|
|
|
// Skip the no-GPU blocklist ("AllowWebgl2:false restricts
|
|
|
|
|
// context creation") so the GAL canvas gets a WebGL context.
|
|
|
|
|
"webgl.force-enabled": true,
|
2026-07-02 14:48:11 +02:00
|
|
|
// kicad_editor.wasm (~190M+) OOMs the optimizing wasm JIT at compile
|
2026-07-02 12:59:42 +02:00
|
|
|
// time ("InternalError: out of memory") and the app never boots.
|
|
|
|
|
// Baseline-only compilation trades runtime speed for a compile
|
|
|
|
|
// that fits in memory.
|
|
|
|
|
"javascript.options.wasm_optimizingjit": false,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
: {}),
|
2025-12-29 10:51:03 +01:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
wip: nested-asyncify fix, wxAuiToolBar registration, tests, research docs
Main-repo side of a multi-part WIP covering the KiCad WASM tool-selection
and nested-Asyncify work. Submodule commits are in kicad@f6e9239aaa
(libcontext hygiene) and wxwidgets@bb80f91e8b (auibar registration +
dialog diagnostics).
## scripts/common/inject-dyncall-shims.sh
Wrap Asyncify.handleSleep / allocateData to save-and-restore Asyncify.currData
around each EM_ASYNC_JS sleep. This fixes the nested Asyncify collision where
a fiber swap that fired during a modal's event loop clobbered currData, and
the modal's later doRewind used the fiber's buffer and hit "RuntimeError:
index out of bounds". Root cause documented as Emscripten Issue #9153
(wontfix upstream).
Diagnostic-rewind logging (forcedBottomOfCallStack, callStack traces) is
retained to help future debugging of Asyncify state corruption.
## tests/
- tests/playwright-kicad.config.ts: add `channel: 'chrome'` for the
chromium project so --project=chromium --headed uses system Chrome
(real GPU) instead of SwiftShader on ARM Mac. Also switch trace to
retain-on-failure + screenshot on-failure for easier E2E debugging.
- tests/kicad/pcbnew.spec.ts: replace `tool.checked` assertions with a
label-suffix check (`[checked]`) since our auibar registration encodes
checked state in the label (no schema change to the registry).
- tests/apps/Makefile.wasm: add `coroutine-nested` build target + include
it in the all: list.
- tests/apps/standalone/coroutine/: kicad_coroutine_harness.h + test app
reproducing KiCad COROUTINE semantics against real libcontext.
- tests/apps/standalone/coroutine-nested/: nested_test.cpp reproduces the
EM_ASYNC_JS-modal + fiber-swap nesting bug in isolation. 8 scenarios
from baseline_modal_alone through nested_fibers_inside_modal.
- tests/e2e/coroutine.spec.ts + coroutine-nested.spec.ts: Playwright specs
that load the standalone apps and assert all case cases pass via
[COROUTINE_TEST] SUMMARY log parsing.
## research/ and features/browser-tools/
Three background docs capturing the investigation trajectory:
- features/browser-tools/0001-kicad-wasm-tool-activation-investigation.md
Early investigation: why tools don't activate; initial dynCall-empty-
callback hypothesis.
- features/browser-tools/0002-wasm-coroutine-deep-dive.md
Deep dive on Asyncify internals, fiber API, QEMU's coroutine-wasm
reference implementation.
- features/browser-tools/0003-wxauitoolbar-registration-fix.md
The narrow fix: why wxAuiToolBar needs a registration block, where to
add it, what the fallback plan is.
- research/threading_1.md: corrected root-cause analysis after reading
runtime logs — nested-Asyncify currData collision, Emscripten #9153.
- research/threading_2.md: extended research on alternative approaches
(JSPI/WasmFX/state-machines) and why they don't help here.
## Submodule pointer updates
kicad: f6e9239aaa (wip: libcontext WASM hygiene cleanup)
wxwidgets: bb80f91e8b (wip: wxAuiToolBar element-registry registration +
dialog diagnostics)
## Open threads not yet in scope
- Firefox/Chrome divergent behavior: "indirect call signature mismatch"
traps in Firefox vs renderer crash in system Chrome (tracked in
plans/peaceful-hugging-pnueli.md and the research docs).
- E2E pixel-diff for Draw Lines fails because the test's diff region does
not cover where the line is actually drawn; tool activation works, the
line is visible in test-results/pcbnew-draw-lines-02-after-drawing.png.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 13:58:16 +02:00
|
|
|
// Uses the SYSTEM-installed Google Chrome (not the Playwright-bundled
|
|
|
|
|
// Chromium) so WebGL runs on the real GPU instead of SwiftShader.
|
|
|
|
|
// The bundled Chromium fails with canvas hidden on ARM Mac because of
|
|
|
|
|
// Chromium issues #1416283, #338414704 (SwiftShader WebGL bug).
|
|
|
|
|
// Run via: npm run test:kicad:headed
|
2026-07-02 12:59:42 +02:00
|
|
|
name: "chromium",
|
2026-07-01 14:43:54 +02:00
|
|
|
testIgnore: PERF_SPECS,
|
2025-12-29 10:51:03 +01:00
|
|
|
use: {
|
2026-07-02 12:59:42 +02:00
|
|
|
channel: "chrome",
|
2025-12-29 10:51:03 +01:00
|
|
|
viewport: { width: 1280, height: 720 },
|
|
|
|
|
},
|
2025-12-27 16:01:35 +01:00
|
|
|
},
|
2026-06-11 16:12:01 +02:00
|
|
|
{
|
2026-07-02 14:48:11 +02:00
|
|
|
// CI-only carrier for the merged-module specs (see BIG_MODULE_SPECS):
|
2026-06-11 16:12:01 +02:00
|
|
|
// Playwright-bundled Chromium, headless, software WebGL via SwiftShader
|
|
|
|
|
// (fine on x86 Linux; the SwiftShader bug above is ARM-Mac-specific).
|
|
|
|
|
// --enable-unsafe-swiftshader: newer Chromium refuses software WebGL in
|
|
|
|
|
// headless without it.
|
2026-07-02 12:59:42 +02:00
|
|
|
name: "chromium-ci",
|
fix(e2e): rescope the 3D fixes for the webgl-era viewer (rebased onto main)
The 3d-webgl merge (kicad eb13ff3bdc: the viewer now defaults to the real
OpenGL renderer via wasm/gl1, and occ-split moves STEP parsing into the
occ_service worker) made the raytracer-era orchestration on this branch moot —
main's chromium-ci phase is green at 15-way parallelism (28666407570 /
28698861536). Drop what no longer earns its complexity, keep the diagnostics,
fix main's live flake, and make the deadlock spec test what it was written for.
- REVERT the chromium-ci-3d serial project, the two-phase test:kicad:ci, the
SwiftShader GPU-process flags, and the resize-drag/models skips: config and
package.json are byte-for-byte back to main's shape. The raytracer contention
they guarded is no longer on the CI path.
- FIX main's live flake: run 28698861536 is green only via retry
(3d-viewer.spec:26 flaky) and 28666407570's deadlock red sampled an ALL-ZERO
pixel signature — the viewer's first frame lags the canvas's creation on
software WebGL under parallel load, and sampling too early reads an all-black
backbuffer. New waitForThreeDRender() gates render assertions on actual
pixels (1s-interval full-frame CPU reads) instead of fixed sleeps, used by
3d-viewer.spec:26 and the models render tail.
- KEEP the storm-proofed samplers (one full-frame getImageData on a
willReadFrequently canvas replacing 256 per-pixel GPU round-trips per sample
— the "GPU stall due to ReadPixels" trigger) and the logThreeDDiag
instrumentation: engine-independent, and they de-risk every remaining
software-GL pixel read.
- models spec: bridge assertions stay front-loaded (the protocol regression
signal is independent of the render); the occ_service parse verdict is now
POLLED — it lands async relative to the bridge ensures, so asserting it
immediately raced the worker; the render tail runs again everywhere. (The
pre-webgl raytracer+models renderer-death documented in a17f3be does not
affect the OpenGL default path — the raytracer-toggle+models combination
remains untested product surface, tracked outside this branch.)
- deadlock spec: the deadlock it guards is raytracer-specific and the viewer
now defaults to OpenGL — on the GL engine it either passes vacuously (fast
renders make every liveness assertion trivial, 28698861536) or fails on the
black first frame (28666407570). It now flips the engine via the "Use
raytracing" toolbar toggle (loud assert if the toggle moved) and
cross-checks engagement by requiring the canvas pixels to CHANGE after the
flip with no input in between (the raytraced frame is lit differently; a GL
re-render reproduces identical pixels; heap growth is unusable — mimalloc
satisfies the raytracer from freed arena pages). That guard immediately
caught a REAL defect: on the webgl-era wasm build the toggle is INERT (the
click lands and "Reload time" updates, but the canvas never changes —
suspects: DoRePaint's silent catch(runtime_error) freezing the canvas after
a raytracer Redraw throw, or ToggleRaytracing writing m_boardAdapter.m_Cfg
while RenderEngineChanged() reads GetAppSettings<…>(), possibly different
instances in the merged bundle). The spec is therefore test.skip-annotated
as a KNOWN ISSUE with the full engine-force machinery in place — unskipping
it self-validates the product fix. The CI-skip also stays (raytracer
liveness needs real-GPU pacing; the Worker-boot deadlock mechanism is
covered on CI by the standalone wx harnesses).
- 180s viewer-open waits kept as pure CI headroom (never slow a passing run).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 11:37:27 +02:00
|
|
|
testMatch: BIG_MODULE_SPECS,
|
fix(ci): serialize the heavy 3D-viewer e2e specs (dedicated chromium-ci-3d project)
The three heavy 3D-viewer specs (3d-viewer, 3d-viewer-deadlock, 3d-viewer-models)
crashed on CI (run 28604015154): "Target crashed", "browser has been closed", and a
black frozen canvas. Each boots the 3D-enabled pcbnew build, which pre-warms
~hardwareConcurrency*2+8 (~68 on the 30-core VM) Web Workers AND runs a multi-threaded
CPU raytracer over SwiftShader software-WebGL. Under fullyParallel the three spec FILES
run in separate browser processes concurrently, and the simultaneous raytrace tabs
exhaust the shared GPU process's ~16 live-WebGL-context limit / renderer memory. The
pool pre-warm (the deadlock fix, 7630c7e) is correct and present in the tested binary;
per-file isolation prevented in-process Worker accumulation but NOT this cross-process
concurrency — the gap these specs' first CI exposure hit.
Fix (test-orchestration only; no wasm/submodule changes):
- Route the three specs to a dedicated `chromium-ci-3d` Playwright project (own
outputDir=pw-artifacts/kicad-3d so its failure traces survive the later perf run's
wipe; the existing tests/pw-artifacts/** upload already captures it).
- test:kicad:ci runs it as a SECOND `playwright test` pass with --workers=1, so at most
one raytrace tab is alive at a time (the environment in which a single 3D load is
reliably green — docs/features/3d-raytracer/README.md §7). An exit-code-aggregating
sequencer runs both phases and fails if either fails (no silent skip of the 3D pass).
- Cap phase-1 CI workers to 12 as headroom under the WebGL-context cap.
- Log a resource-diagnostic snapshot (hardwareConcurrency, pthread pool, GL-canvas
count, wasm/JS heap) before each 3D interaction so any recurrence is attributable.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 10:49:23 +02:00
|
|
|
use: {
|
|
|
|
|
...devices["Desktop Chrome"],
|
|
|
|
|
viewport: { width: 1280, height: 720 },
|
|
|
|
|
launchOptions: {
|
|
|
|
|
args: ["--enable-unsafe-swiftshader"],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
2026-07-01 14:43:54 +02:00
|
|
|
{
|
|
|
|
|
// Runtime-perf specs (*-perf.spec.ts): bundled Chromium for CDP CPU throttling.
|
|
|
|
|
// Bundled (not system Chrome) because system Chrome paces rAF oddly under CDP
|
|
|
|
|
// throttle (FPS rose with throttle). --enable-unsafe-swiftshader lets it use
|
|
|
|
|
// software WebGL headless on CI; harmless with a real GPU locally. Add --headed
|
|
|
|
|
// locally for real-GPU FPS numbers.
|
2026-07-02 12:59:42 +02:00
|
|
|
name: "perf",
|
2026-07-01 14:43:54 +02:00
|
|
|
testMatch: PERF_SPECS,
|
|
|
|
|
use: {
|
2026-07-02 12:59:42 +02:00
|
|
|
...devices["Desktop Chrome"],
|
2026-07-01 14:43:54 +02:00
|
|
|
viewport: { width: 1280, height: 720 },
|
2026-07-02 12:59:42 +02:00
|
|
|
launchOptions: { args: ["--enable-unsafe-swiftshader"] },
|
2026-07-01 14:43:54 +02:00
|
|
|
},
|
|
|
|
|
},
|
2025-12-27 16:01:35 +01:00
|
|
|
],
|
|
|
|
|
|
|
|
|
|
webServer: {
|
2026-06-11 00:19:53 +02:00
|
|
|
command: `npx serve ${appsDir} -p ${port} -c ../serve.json`,
|
2025-12-27 16:01:35 +01:00
|
|
|
port: port,
|
|
|
|
|
reuseExistingServer: !process.env.CI,
|
|
|
|
|
},
|
|
|
|
|
});
|