From be2fbadc9d01493385e768f6b898ccf51255707c Mon Sep 17 00:00:00 2001 From: PJ Date: Wed, 12 Aug 2026 16:48:52 +0530 Subject: [PATCH] fix(chrome): enable the swiftshader webgl fallback Headless Chrome runs with --disable-gpu, and without this flag it refuses the software WebGL backend: getContext returns null, so a canvas-rendered app paints nothing and every screenshot is identical black. Claude-Session: https://claude.ai/code/session_01Fj4wJUikdABuMQEETwW55J --- internal/driver/chrome/driver.go | 5 +++++ internal/driver/chrome/driver_test.go | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/internal/driver/chrome/driver.go b/internal/driver/chrome/driver.go index 2a1a898..b58b2b2 100644 --- a/internal/driver/chrome/driver.go +++ b/internal/driver/chrome/driver.go @@ -39,6 +39,11 @@ func New() *Driver { append(chromedp.DefaultExecAllocatorOptions[:], chromedp.Flag("headless", true), chromedp.Flag("disable-gpu", true), + // Chrome refuses to fall back to the SwiftShader WebGL backend + // without this flag, so with --disable-gpu a canvas app (Compose + // for Web, Flutter web, anything on WebGL) gets a null context and + // paints nothing: black screenshots and an empty accessibility DOM. + chromedp.Flag("enable-unsafe-swiftshader", true), chromedp.NoSandbox, // CI runners give Chrome a tiny /dev/shm; without this the browser // process hangs on startup and never reports its DevTools socket. diff --git a/internal/driver/chrome/driver_test.go b/internal/driver/chrome/driver_test.go index 4eabdb9..15ea5e5 100644 --- a/internal/driver/chrome/driver_test.go +++ b/internal/driver/chrome/driver_test.go @@ -62,6 +62,28 @@ func TestLaunch_ClearStateWipesStorageForTheTargetOrigin(t *testing.T) { } } +// TestLaunch_WebGLContextIsAvailable pins the SwiftShader fallback. Headless +// Chrome runs with --disable-gpu, and without --enable-unsafe-swiftshader it +// refuses the software WebGL backend: getContext returns null, so a +// canvas-rendered app paints nothing and every screenshot is identical black. +func TestLaunch_WebGLContextIsAvailable(t *testing.T) { + d := New() + defer d.Terminate(context.Background()) + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + if err := d.Launch(ctx, "data:text/html,", false, nil); err != nil { + t.Fatalf("Launch: %v", err) + } + var hasContext bool + if err := chromedp.Run(d.tabCtx, chromedp.Evaluate( + `!!document.createElement("canvas").getContext("webgl2")`, &hasContext)); err != nil { + t.Fatalf("evaluate: %v", err) + } + if !hasContext { + t.Error("webgl2 context is null; a canvas-rendered app would render nothing") + } +} + // TestActionMethods_HonorCallerCancellation confirms the DeviceDriver action // methods route through runCtx so a cancelled caller context aborts the CDP // round-trip instead of blocking on d.tabCtx. Without this a hung browser would