Skip to content

chore: add SwiftShader flags for Chromium WebGL rendering - #1469

Open
TortoiseWolfe wants to merge 15 commits into
mainfrom
fix/webgl-swiftshader-chromium
Open

TortoiseWolfe wants to merge 15 commits into
mainfrom
fix/webgl-swiftshader-chromium

Conversation

@TortoiseWolfe

@TortoiseWolfe TortoiseWolfe commented Nov 7, 2026 •

Copy link
Copy Markdown
Owner

In plain words: this PR, which means a proposed change to the code, makes the 3D tests run in every browser on CI, which means the automatic checks that run on every change. It lives on a branch, which means a separate line of work, and it changes the config, which means the settings file, for the browsers. It isn't ready to go in: WebKit still crashes now and then, and the cause is in the browser itself.

Enable software WebGL (3D drawing done on the processor) on every browser this project (the project's code folder) runs, and delete every conditional skip in tests/e2e/game-3d.spec.ts so a green shard now proves the 3D tests actually ran.

What this change (pull request, a proposed change to the code) does, in plain terms

  • Adds --use-gl=angle --use-angle=swiftshader --ignore-gpu-blocklist --enable-unsafe-swiftshader to all 10 Chromium projects in playwright.config.ts (the config, which means the browser settings file), so headless Chromium on a GPU-less runner still renders Three.js.
  • Runs Firefox headful (with a window, not hidden) inside Xvfb (a virtual screen) in the automatic checks (the automatic checks that run on every change). scripts/ci/playwright-in-container.sh already starts Xvfb :99 at 4096×2304; playwright.config.ts (that is, the browser settings file) now sets headless: FIREFOX_HEADLESS, where FIREFOX_HEADLESS = !(process.env.CI && process.env.DISPLAY). Firefox on the Linux test image then gets llvmpipe WebGL instead of being headless with no WebGL at all.
  • Deletes all test.skip / conditionals from tests/e2e/game-3d.spec.ts (6 → 0). Each formerly-conditioned test now asserts expect(webglOk).toBe('true') before doing its work, so a runner without WebGL fails loudly instead of skipping quietly.
  • Replaces the flaky colorblind rehydration assertion in tests/e2e/accessibility/colorblind-toggle.spec.ts with await expect(select).toHaveValue(/deuteranopia/), which auto-waits for the mount effect that restores the saved palette. The old inputValue() read raced that effect and failed chromium-gen 1/6.
  • Makes the WebKit PWA (installable web app) offline test run instead of skip (see "Remaining skips: none" below).
  • Rewords stale "Kept skip" comments (they pointed at the Firefox skip ticket (#792)) in the twins, twin-walk-visible and band-stage specs to "Guard only: Firefox runs under Xvfb".
  • Adds a .gitignore backstop line for stray .env.local-supabase copies. The tracked file is the safe demo settings pnpm run use:local reads; the rule only keeps a stray copy from being committed. Caveat: this lane's tool policy denies any command containing .env, so git check-ignore -v .env.local-supabase couldn't be run from here; the hub asked for the line and the line is in this change.

Evidence the cross-browser path is real

The full-e2e label makes the local lane run its 24-shard matrix (Chromium, Firefox, WebKit) against a per-runner Supabase, so no hosted quota is used. Run 42513738088 on head 690bf908: 24/24 shards green, 2358 passed, 0 skipped, 0 failed.

Browser Passed Skipped Failed
chromium 786 0 0
firefox 786 0 0
webkit 786 0 0

(Counted from the Playwright JSON (machine-readable results file) in each shard's result artifact: every test is expected.)

tests/e2e/game-3d.spec.ts runs all 13 tests on each browser, including the five that used to skip. They are real assertions with real passes on all three engines.

Remaining skips: none

The WebKit PWA test "app works offline after service worker activation" (tests/pwa-installation.spec.ts) used to be skipped on WebKit (the WebKit offline ticket (#1399)). It now runs on WebKit. Honest limit: Playwright's Linux WebKit can't do an offline reload or an offline in-page fetch with a service worker (it gives "internal error" / "TypeError: Load failed"). So on WebKit the test reads the worker's cache with caches.match(location.href) and checks the cached page is ok, isn't the "You're Offline" page, and contains the expected <title>. That proves the worker cached the real page; it doesn't prove the offline reload. Chromium and Firefox still run the full offline reload. This is weaker coverage on WebKit, and it's stated here on purpose. Playwright 1.64 fixed that WebKit bug; the follow-up ticket that upgrades to 1.65 and restores the full WebKit offline test (#1480) tracks it. A first try at the upgrade in this change passed the offline test but made unrelated WebKit page loads time out, so it was reverted here and moved to that ticket.

Static test.skip calls in game-3d.spec.ts: 6 → 0.

The four checks that show "skipping" on this change are path-gated workflow jobs, not tests: Validate Component Structure, Live auth config matches the repo (a check, which means a test, that the live sign-in settings match the code), RLS + provider conformance, and Real-form signup + email confirmation. Each doesn't apply to this change, and its always-reporting aggregate (Component Structure result, Auth Config Drift result, Conformance result, Signup Mailer result) is green.

Automatic-check counts (from artifacts)

Run Mode Ran Passed Skipped Failed
5c56d28f (42333572757) Chromium only, before 786 785 0 1 (colorblind race)
b65f016d (42355405887) Chromium only, after 786 786 0 0
690bf908 (42513738088) Full matrix, after 2358 2358 0 0
443a9be8 (42530313985) Full matrix, final head 2358 2358 (3 WebKit tests passed on retry) 0 0
09405d5b (42542406684) Full matrix, with kernel-log capture 2358 2358 (4 WebKit tests passed on retry) 0 0

An earlier head (1e6fb859, run 42508256736) failed only webkit-gen 6/6, because the offline step can't run in WebKit; 690bf908 is the fix described above.

Latest head (443a9be8): flake fixes and evidence capture

  • Fixed: the Firefox twins test "Top-down compare mode" failed on its first try 6 times in 30. It happens because the "1887 Victorian" button joins the HUD (on-screen controls) dock 3 to 9 seconds after load and shifts "More controls" by about 94 px, so a click pressed one button and released on another. The test now waits for that button. With no retries, Firefox passes 60 of 60. Visitors will see that the dock does shift when that button arrives.
  • Evidence capture: Playwright now keeps the trace of the first failed attempt (retain-on-first-failure), and the automatic checks upload e2e-local-diagnostics-* even when a shard passes on retry. Before this, a flaky test that passed on retry left nothing to read. A guard test pins the setting.
  • Final run (42530313985): Chromium 786 and Firefox 786, all passed first try, 0 skipped, 0 failed, 0 flaky. The earlier first-try failures in landmarks.spec.ts (skip link, Chromium) and encrypted-messaging.spec.ts (Send click, Firefox) didn't recur; they were never reproduced locally, so they aren't fixed, only watched. If either returns, its trace will be in the diagnostics artifact.
  • WebKit: 783 passed first try, 3 flaky (passed on retry), 0 skipped, 0 failed. Two failed with "WebKit encountered an internal error" on page.reload / page.goto (admin dashboard event log; mobile touch targets). That is the WebKit page-load problem described in the Playwright 1.65 ticket (#1480). The third (admin-depth.spec.ts:78) found the admin rail missing on its first load and passed on retry. The previous full run on 690bf908 had 0 flaky WebKit, so these three were not seen before; this change changes none of those pages.

WebKit flakes: the cause, found on head 09405d5b

Where it stands: this change isn't clean. Chromium and Firefox pass first try. WebKit still has a few tests that pass only on retry, and the evidence says the browser itself is crashing on the test runners, not this project's code (the project's code folder).

The newest full run (run 42542406684, head 09405d5b) finished green: Chromium 786 and Firefox 786, all first try, 0 flaky. WebKit had 782 first-try passes and 4 flaky (passed on retry), 0 skipped, 0 failed. The four were different tests from the earlier three, so any WebKit page load can be hit:

  • admin-depth.spec.ts:78 "/admin/payments: every table scroller sits in a padded well" (admin rail missing)
  • color-contrast.spec.ts:259 "scripthammer-dark — /admin"
  • color-contrast.spec.ts:259 "scripthammer-light — /admin/email" ("WebKit encountered an internal error")
  • color-contrast.spec.ts:259 "scripthammer-forge — /admin" ("WebKit encountered an internal error")

Why it happens. A new check step (in 09405d5b) saves the runner's kernel log. On shard webkit-gen 1/6 it shows WPENetworkProces[15364]: segfault ... error 4 in libgobject-2.0.so.0.8000.0 at 00:58:47 UTC (the WebKit network process crashed inside the glib library). The admin-depth:78 trace shows its first failed request at 00:58:47.536 UTC, with 22 of 86 requests failing. The graphics worker (SkiaGPUWorker) also segfaulted at 01:04:39 and 01:04:49 UTC, and the dark /admin colour test lost its requests at 01:05:00. Shard webkit-gen 2/6 has an empty kernel log although its two tests failed the same way, so the crash is proven on one shard and the same symptom is seen on the other.

Ruled out: memory (at least 11.5 GB free of about 16 GB, no out-of-memory line), Docker containers, the service worker (the background script that caches pages; it's blocked in the test settings), this change's code (it touches none of these pages), and late-session decay. Host load peaked at 8.8 on 4 virtual processor cores, so heavy load may be a trigger; that isn't proven. A local repeat of the "Display popover" test, 40 times in the Playwright 1.55 WebKit image, passed 40 of 40, so it doesn't reproduce outside the automatic checks (the automatic checks rate is about 4 in 786).

Options for the maintainer:

  1. Treat a WebKit network-process crash as infrastructure: require 0 failed and 0 skipped on WebKit, and allow a flaky test only when that shard's kernel log shows the WPENetworkProcess or SkiaGPUWorker crash at that second.
  2. Try a newer Playwright and WebKit build. The line of work (a separate line of work) fix/playwright-165-webkit-load (Playwright 1.65, the Playwright 1.65 ticket (#1480)) is pushed, but 1.65 showed its own WebKit page.goto timeouts, so it's unproven either way.
  3. Add a WebKit-only navigation retry helper. Project rules say retries aren't a fix, so only if the maintainer allows it.
Technical details
  • Kernel log artifacts: e2e-local-monitor-webkit-* (one per shard), from the monitor step added in 09405d5b.
  • Original three flaky tests (run 42530313985, head 443a9be8): tests/e2e/admin/admin-dashboard.spec.ts:307 (webkit-gen 1/6), tests/e2e/admin/admin-depth.spec.ts:78 /admin/messaging (webkit-gen 1/6), tests/e2e/tests/mobile-touch-targets.spec.ts:341 (webkit-gen 6/6).
  • Local repeat: static export from run 42530313985 served on port 3010, playwright.repro.config.ts (a throwaway settings file, that is, one I did not commit), WebKit, Playwright 1.55 v1.55.0-noble image, 40 runs, 16 minutes.

Part of the sh-skips lane to eliminate skipped/never-run tests.

TortoiseWolfe and others added 4 commits November 6, 2026 19:44
Enable software WebGL (SwiftShader via ANGLE) for all Chromium-based
Playwright projects so headless browsers can render Three.js scenes.
Previously, game-3d tests skipped on CI when WebGL probe returned null.

Fixes the ~12 test.skip(webglOk !== 'true') calls in game-3d.spec.ts
by providing a working software rasterizer. Firefox and WebKit projects
unchanged — investigate separately whether they need Mesa/llvmpipe or
WPE software rendering equivalents.
- Convert 5 runtime test.skip() calls to conditional if-blocks so they only
  skip when WebGL is genuinely unavailable (Firefox headless CI)
- SwiftShader flags on all 10 Chromium projects now provide WebGL
- Firefox touch-event skip (#1399) remains as separate browser limitation
@TortoiseWolfe TortoiseWolfe added the full-e2e Full firefox+webkit matrix on the local lane, AND opts this PR into the hosted lane (#1119) label Nov 8, 2026
TortoiseWolfe and others added 11 commits November 7, 2026 23:19
…ide the page

Playwright's Linux WebKit cannot reload a page while offline with a service
worker (#1399), so the test was skipped there. A fetch of the home page from
inside the page, with the network off, asks the same question without the
reload.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…th a worker

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…the PWA offline test is the full reload on all three browsers

Playwright 1.64 fixed WebKit offline emulation rejecting service-worker
navigation (upstream issue 42775). The repo pinned 1.55.0, so the test had a
WebKit-only cache check instead. Bump @playwright/test, the E2E image tag and
the contrast-audit path to 1.65.0, and delete the WebKit branch.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…now, so the PWA offline test is the full reload on all three browsers"

This reverts commit 2e44e2c.
…1480)

Puts the branch back to the content of the clean full run 42513738088. The
Playwright 1.65 upgrade and the full WebKit offline test move to #1480.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The 1887 Victorian fly-to button is added to the dock 3-9 s after load
(onTwinPlaced), inserted before 'More controls', which shifts that button
about 94 px. A click in that window pressed on one button and released on
another, so no click event fired and the compare-mode test timed out in
Firefox (6 of 30 runs with no retries). The test now waits for the button
before touching the dock: 60 of 60 pass with retries off.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…rd passes

A flaky test fails its first attempt and passes its retry. The config traced
the retry (on-first-retry) and the upload ran only when the shard failed, so
the evidence of what went wrong was either never recorded or thrown away.
Three flakes in the 1469 run could only be reasoned about from log text for
that reason. Trace the first attempt, keep it only on failure, and upload the
output directory always; passing tests leave it empty. The guard test now
checks the new gate and that no artefact kind is kept for every test.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… WebKit shards (#1480)

Run 42530313985 had three WebKit tests lose every request at once, app and
Supabase origins together, about 2.5 minutes into the browser's life on two
runners. A local rerun (40 of 40 on the CI build) did not reproduce it, so the
next occurrence needs evidence: host.log, docker-events.log and dmesg.log are
uploaded for every WebKit shard.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@TortoiseWolfe

Copy link
Copy Markdown
Owner Author

This note records the first split test on the WebKit flakes in this pull request (a pull request is a proposed change waiting for review). The tracing setting in the test settings file looks like the cause. One run each isn't proof, and nothing is merged, which means no change has joined main.

What we did. We ran only the WebKit tests, on 8 shards (a shard is one slice of the test suite run in parallel), twice from the same code. The first run, the control run (42546123403), kept this pull request's setting, trace: 'retain-on-first-failure'. That setting records a trace on every first attempt. The second run, the trace-off run (42546125631), put it back to 'on-first-retry', which is what main (the shared line of finished work) uses.

What we saw.

What it means. Tracing every first attempt adds load, and that load seems to raise how often the WebKit crash fires. Main has the same crash, but about once in 2,115 runs. Two runs can't separate a real effect from luck, so treat this as the lead to confirm.

A separate failure in both runs. band.spec.ts line 225, the test that opens the band page and checks it sets no vote cookie, failed on all three attempts in both runs. This branch is behind main, which means it lacks main's newer changes, and main already replaced that test's networkidle wait with a wait for the vote list. Merging main into this branch, which means pulling those newer changes in, should clear it.

Next split to try. Keep tracing only where it's affordable, for example trace: process.env.PW_TRACE_ALL ? 'retain-on-first-failure' : 'on-first-retry', or tracing on the non-WebKit projects only. Then run all 24 shards and require 0 flaky in all three browsers. The 0-flaky gate stays as it is.

Technical details
  • Control run 42546123403: webkit-gen 1/6 failed (band.spec.ts:225, 3 attempts), flaky admin/admin-dashboard.spec.ts:266 and messaging/message-editing.spec.ts:653 (both page.reload: WebKit encountered an internal error). Totals 783 expected, 2 flaky, 1 unexpected.
  • Trace-off run 42546125631: webkit-gen 1/6 failed (band.spec.ts:225 only). Totals 785 expected, 0 flaky, 1 unexpected.
  • The two scratch branches (throwaway copies of this work, which means they hold no new change to ship) are scratch/webkit-control at 2972e59 and scratch/webkit-trace-off at cc8f44c. Each is this pull request's latest saved change (a commit, which means one recorded step of work) plus a workflow edit that limits the browsers to WebKit.
  • The 1 in 2,115 figure is main's WebKit flaky rate across three nightly runs.
  • Playwright 1.55's WebKit (webkit-2203) bundles libsoup 3.6.5, which has the use-after-free; libsoup 3.6.6 fixes it.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

full-e2e Full firefox+webkit matrix on the local lane, AND opts this PR into the hosted lane (#1119)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant