Build out a Playwright-based regression-detection harness covering the compat-matrix surfaces (KDE-W, KDE-X, GNOME, Sway, i3, Niri, packaging formats). Adds: - Planning + decision docs under docs/testing/ — README, matrix, runbook, automation, cases/ (11 case files), quick-entry-closeout - Playwright scaffolding (config, tsconfig) - 78 spec runners under tools/test-harness/src/runners/ — T## case- doc runners and S## distribution/smoke runners - Substrate primitives in tools/test-harness/src/lib/: AX-tree loader (snapshotAx + waitForAxNode + axTreeToSnapshot), focus- shifter, eipc-registry, niri-native bridge, drag-drop bridge, electron-mocks, claudeai page-objects, inspector client S03 (DEB Depends declared) and S04 (RPM Requires declared) ship marked test.fail() — they're regression detectors for the case-doc gap (deb.sh emits no Depends:, rpm.sh sets AutoReqProv: no), and the expected-failure shape lets them report green on every host until upstream packaging starts declaring runtime deps. 127 files, no runtime changes; harness is opt-in via 'cd tools/test-harness && npx playwright test'. Co-authored-by: Claude <claude@anthropic.com>
14 KiB
Platform Integration
Tests covering autostart, Cowork integration, WebGL graceful degradation, .desktop-launch env inheritance, encrypted env-var storage, the macOS/Windows-only Computer Use feature, and Dispatch session pairing. See ../matrix.md for status.
T09 — AutoStart via XDG
Severity: Critical Surface: XDG Autostart Applies to: All rows Issues: PR #450
Steps:
- In Settings, toggle "Open at Login" / "Start at boot" ON.
- Inspect
~/.config/autostart/for a.desktopentry. - Logout/login. Verify app launches automatically.
- Toggle OFF. Verify the autostart entry is removed.
Expected: Toggling ON creates a ~/.config/autostart/*.desktop entry that is XDG-spec compliant (not a custom systemd unit or shell hook). After login, app launches automatically. Toggling OFF removes the entry.
Diagnostics on failure: ls -la ~/.config/autostart/, content of the .desktop file, desktop-file-validate on it, launcher log.
References: PR #450
Code anchors:
scripts/frame-fix-wrapper.js:376— XDG Autostart shim interceptingapp.{get,set}LoginItemSettings(writes/removes$XDG_CONFIG_HOME/autostart/claude-desktop.desktop).scripts/frame-fix-wrapper.js:429—buildAutostartContent()emits the spec-compliant[Desktop Entry]block.build-reference/app-extracted/.vite/build/index.js:524205— upstreamisStartupOnLoginEnabled/setStartupOnLoginEnabledIPC surface that the wrapper interposes on.
T10 — Cowork integration
Severity: Should
Surface: Cowork tab + VM daemon
Applies to: All rows
Issues: docs/learnings/cowork-vm-daemon.md
Steps:
- Sign into the app. Open the Cowork tab.
- Confirm Cowork-specific UI renders (ghost icon in topbar, Cowork menus).
- Trigger a Cowork action that needs the VM daemon.
- Kill the VM daemon process; verify it respawns within the documented timeout.
Expected: Cowork features render. VM daemon spawns when needed, files are visible, daemon respawns within the documented timeout if it crashes.
Diagnostics on failure: pgrep -af cowork, daemon logs, launcher log, the respawn-logic code path (see learnings doc).
References: docs/learnings/cowork-vm-daemon.md
Code anchors:
build-reference/app-extracted/.vite/build/index.js:143371— upstream's Windows named-pipe path (\\.\pipe\cowork-vm-service) thatscripts/patches/cowork.shPatch 1 rewrites to$XDG_RUNTIME_DIR/cowork-vm-service.sock.build-reference/app-extracted/.vite/build/index.js:143453—kUe()retry loop (5 attempts, 1 s gap) that the auto-launch injection from Patch 6 piggybacks on after the rewrite.scripts/patches/cowork.sh:244— Patch 6 (auto-launch + stdio pipe + 10 s rate-limited respawn — issue #408).scripts/patches/cowork.sh:365— Patch 6b (extends the reinstall-delete list withsessiondata.img/rootfs.img.zstso a wedged daemon can self-recover).
T12 — WebGL warn-only
Severity: Could Surface: Chromium GPU diagnostics Applies to: All rows (especially VM rows and hybrid-GPU laptops) Issues: —
Steps:
- Launch the app. Open DevTools → navigate to
chrome://gpu. - Inspect WebGL1/WebGL2 status.
- Use the app for ~5 minutes — exercise UI, sidebar, settings.
Expected: WebGL1/2 may report as blocklisted (typical on virtio-gpu in VMs and on hybrid GPU laptops). This is informational. UI continues to render without graphical glitches; no feature is broken by the blocklist.
Diagnostics on failure: chrome://gpu full content, screenshot of any visual glitch, glxinfo | head -20 (X11) or eglinfo (Wayland), lspci -k | grep -A2 VGA.
References: —
Code anchors:
build-reference/app-extracted/.vite/build/index.js:524809—app.disableHardwareAcceleration()is gated on the user-toggleableisHardwareAccelerationDisabledsetting; upstream does not pass--ignore-gpu-blocklistor--use-gl=*, so chrome://gpu reflects Chromium's stock blocklist behaviour.build-reference/app-extracted/.vite/build/index.js:500571— the onlywebgl:!1override is scoped to the feedback popup (in-memory-feedbackpartition); main UI does not disable WebGL.
S17 — App launched from .desktop inherits shell PATH
Severity: Critical
Surface: .desktop-launch env handling
Applies to: All rows
Issues: —
Steps:
- Configure
~/.bashrc(or~/.zshrc) withexport PATH="$HOME/.custom-bin:$PATH"and a custom binary in that dir. - Launch the app via dmenu/krunner/GNOME Activities/Plasma launcher (i.e. not from a terminal).
- Open a Code-tab terminal pane. Run
which <custom-binary>. - Repeat for
npm,node,git,gh.
Expected: Code session can find tools defined in the user's shell profile, even when the app was launched non-interactively. Either the launcher script sources the user's shell profile, or the app reads ~/.bashrc / ~/.zshrc to extract PATH the way macOS does.
Diagnostics on failure: echo $PATH from inside the integrated terminal, the env passed to the app process (cat /proc/$(pgrep -f electron)/environ | tr '\0' '\n' | grep PATH), launcher log.
References: Local sessions, Session not finding installed tools
Code anchors:
build-reference/app-extracted/.vite/build/index.js:259300—SLr()resolves the bundledshell-path-worker/shellPathWorker.js.build-reference/app-extracted/.vite/build/index.js:259349—NLr()forks it viautilityProcess.fork; on successFX()(line 259311) merges the extracted env intoprocess.env.build-reference/app-extracted/.vite/build/shell-path-worker/shellPathWorker.js:205—extractPathFromShell()runs the user's login shell (-l -i) and parses the printed$PATHbetween sentinels (mac-style env inheritance now applied on Linux too).
S18 — Local environment editor persists across reboot
Severity: Should Surface: Local env editor / encrypted store Applies to: All rows Issues: —
Steps:
- Open the local environment editor. Add
TEST_VAR=hello. - Restart the app — verify variable is still there.
- Reboot the host. Sign back in. Verify variable is still there.
Expected: Variables saved via the local environment editor (per-app, encrypted) survive a logout/login cycle and a full reboot. On Linux this implies the encrypted store is wired to libsecret / kwallet / gnome-keyring and unlocks at session start.
Diagnostics on failure: secret-tool search (libsecret), kwallet5-query (KDE), seahorse UI inspection (GNOME), launcher log, the env-editor IPC call.
References: Local sessions
Code anchors:
build-reference/app-extracted/.vite/build/index.js:259251—I2t = new K_({ name: "ccd-environment-config", ... })electron-store backing file (~/.config/Claude/ccd-environment-config.json).build-reference/app-extracted/.vite/build/index.js:259253—hLr()writes viasafeStorage.encryptString(libsecret on Linux).build-reference/app-extracted/.vite/build/index.js:259268—J1()decrypts on read; bails to{}ifsafeStoragereports encryption unavailable (no keyring backend running).build-reference/app-extracted/.vite/build/index.js:70782—LocalSessionEnvironment.saveIPC entry that calls intohLr.
S22 — Computer-use toggle is absent or visibly disabled on Linux
Severity: Should Surface: Settings → Desktop app → General Applies to: All rows Issues: —
Steps:
- Open Settings → Desktop app → General.
- Look for the "Computer use" toggle.
Expected: Toggle either does not render on Linux, or renders as a disabled control with a clear "not supported on Linux" hint. Must not appear functional and silently fail (e.g. flip on but never produce screen-control behavior).
Diagnostics on failure: Screenshot of the Settings page, DevTools inspection of the toggle DOM (is it conditionally hidden? disabled? always-rendered?), launcher log.
References: Let Claude use your computer, Dispatch and computer use
Code anchors:
build-reference/app-extracted/.vite/build/index.js:240557—qDA = new Set(["darwin", "win32"])excludes Linux from the computer-use platform set.build-reference/app-extracted/.vite/build/index.js:241190—TF()(the master enable check) short-circuits tofalsewhenqDA.has(process.platform)is false, so togglingchicagoEnabledon Linux can't activate the feature.build-reference/app-extracted/.vite/build/index.js:242387—tvr()returns{ status: "unsupported", reason: "Computer use is not available on this platform", unsupportedCode: "unsupported_platform" }for the Settings UI — confirms the toggle should render with a platform-unavailable hint, not silent failure.
S23 — Dispatch-spawned sessions don't soft-lock on a never-approvable computer-use prompt
Severity: Critical (for Dispatch users) Surface: Dispatch session lifecycle on Linux Applies to: All rows with Dispatch enabled Issues: —
Steps:
- From a paired phone, dispatch a task that would invoke computer use.
- Observe the Code-tab session that spawns on the desktop.
- Try to interact with other parts of the app.
Expected: Permission prompt times out or denies cleanly rather than hanging the session indefinitely. User can continue interacting with the rest of the app.
Diagnostics on failure: Screenshot of session state, launcher log, sidebar state (is the Dispatch session blocking the whole sidebar?), pgrep -af claude.
References: Sessions from Dispatch
Code anchors:
build-reference/app-extracted/.vite/build/index.js:512789—tool_permission_requestnotification handler explicitly skipstoolName.startsWith("computer:"), so the desktop never queues a user-facing prompt for computer-use tool calls (which couldn't run on Linux anyway — see S22).build-reference/app-extracted/.vite/build/index.js:241190—TF()gates computer-use execution off entirely on Linux, so a Dispatch-spawned session that requests it should hit the upstream "Set up computer use" remote-client setup card (index.js:330114) rather than block on a desktop prompt.
S24 — Dispatch-spawned Code session appears with badge and notification
Severity: Critical Surface: Dispatch handoff Applies to: All rows with Dispatch enabled Issues: —
Steps:
- From a paired phone, dispatch a task that routes to Code (e.g. "fix this bug").
- Observe the desktop sidebar.
- Confirm a desktop notification fires.
- Open the session and confirm 30-min approval expiry per upstream docs.
Expected: Dispatch task creates a sidebar entry tagged Dispatch, posts a desktop notification, and lands ready for review. App-permission approvals on this session expire after 30 minutes per upstream docs.
Diagnostics on failure: Screenshot of sidebar (badge present?), notification daemon state, launcher log, the Dispatch pairing config under ~/.config/Claude/.
References: Sessions from Dispatch, Dispatch and computer use
Code anchors:
build-reference/app-extracted/.vite/build/index.js:144561—Sd = "dispatch_child"session-type constant.build-reference/app-extracted/.vite/build/index.js:512200—onRemoteSessionStartIPC routes a Dispatch-initiated child session into the local sidebar viadispatchOnRemoteSessionStart.build-reference/app-extracted/.vite/build/index.js:285621—notifyDispatchParentIfNeeded()posts theTask "<title>" <state>meta-notification when the dispatch child finishes (lands the result in the parent thread's notification queue).build-reference/app-extracted/.vite/build/index.js:285954—kind:"dispatch_child"is the sidebar badge tag.
S25 — Mobile pairing survives Linux session restart
Severity: Should Surface: Dispatch pairing persistence Applies to: All rows with Dispatch enabled Issues: —
Steps:
- Pair the desktop with a phone.
- Quit the app fully. Re-launch.
- Try a Dispatch task. Verify pairing still works without re-pairing.
- Logout/login the desktop. Re-test.
Expected: Pairing remains active across app restart and logout/login. Pairing token is stored under ~/.config/Claude/ (or wherever the secure store lives) and survives.
Diagnostics on failure: ls -la ~/.config/Claude/, secret-store inspection, launcher log, pairing-flow IPC.
References: Sessions from Dispatch
Code anchors:
build-reference/app-extracted/.vite/build/index.js:511984—ZEe = "coworkTrustedDeviceToken"electron-store key for the trusted-device token.build-reference/app-extracted/.vite/build/index.js:511989—oYn()writes the token viasafeStorage.encryptString(libsecret on Linux);aYn()(:512003) decrypts on read.build-reference/app-extracted/.vite/build/index.js:512022—gYn()re-enrolls viaPOST /api/auth/trusted_devicesonly when there's no cached token, so a successful pair survives restart.build-reference/app-extracted/.vite/build/index.js:330229—_5r = "bridge-state.json"(per-org/account bridge state under~/.config/Claude/bridge-state.json);JF()/X0A()at:330230read/locate it.