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>
This commit is contained in:
Istvan Matejcsok 2026-07-03 10:49:23 +02:00
commit add7c60975
6 changed files with 87 additions and 8 deletions

View file

@ -1,6 +1,6 @@
import { test, expect } from './fixtures';
import { waitForPcbnew } from './utils/pcbnew-ready';
import { countGlCanvases, loadBoard, openThreeDViewer } from './utils/threed-viewer';
import { countGlCanvases, loadBoard, logThreeDDiag, openThreeDViewer } from './utils/threed-viewer';
/**
* Regression for the camera-move-on-canvas raytrace DEADLOCK.
@ -58,6 +58,7 @@ test.describe('3D viewer camera-move deadlock', () => {
// Let the INITIAL raytrace settle through the safe per-frame pump (Workers boot here).
await page.waitForTimeout(5000);
await logThreeDDiag(page, 'deadlock: after open+settle');
// Read the newest glcanvas-* (the 3D viewer) client rect in viewport coords.
const canvasRect = () => page.evaluate(() => {
@ -181,6 +182,7 @@ test.describe('3D viewer camera-move deadlock', () => {
await page.screenshot({ path: 'test-results/3d-viewer-deadlock.png', scale: 'device' });
// Sanity: the board still renders (not blank / crashed) after both moves.
await logThreeDDiag(page, 'deadlock: after moves');
const after = await sampleCanvas();
console.log(`[TEST] 3D render after interaction: ${after.distinctColors} distinct colours`);
expect(after.distinctColors,