Dogfood the app from CI with JSON tours
Merged
TE
19d ago+980+980+012345678901234567890123456789-5-5-0123456789
Summary
Agents can now drive the built app on a CI runner and look at what happened, with no per-tour compile. You write a JSON tour and run:
scripts/run-e2e.sh --scenario dogfood/scenarios/sidebar-and-chrome-tour.json --ref <sha> --frames
You get the tour's screenshots, contact sheets, accessibility trees, and socket replies under $TMPDIR/cmux-ui-frames/<run>/. This builds on the frames tool from #14916.
How it works
cmuxUITests/DogfoodScenarioUITestsreads the tour at run time and executes the steps:- socket requests, which can save results and reuse their fields in later steps
- key presses and typing
- clicks and hovers, targeted by accessibility id, label, or window point
- menu paths, waits, and existence checks
shotandtreesteps attach a screenshot or the accessibility tree with.keepAlways, so they survive a passing test.- A failed step is recorded along with a screenshot, and the tour continues. The test fails at the end with the list.
- Without a scenario the test skips, so ordinary UI runs are unaffected.
test-e2e.ymlgains adogfood_scenarioinput (base64). The e2e action forwards it to the runner asTEST_RUNNER_CMUX_DOGFOOD_SCENARIO_B64, which is required becausesudo -uresets the environment.- Scenario runs add their dispatch id to the concurrency group, so several tours of one commit run side by side instead of cancelling each other.
dispatch-focused-test.py --scenario:- validates and encodes the tour and defaults the selector to the dogfood test
- skips the already-failed/running guard, since every tour shares one selector
- refuses a tour past the dispatch-input size limit
e2e-frames.pynow also exports text attachments (trees, socket replies,steps.log) next to the frames.- Guide:
skills/cmux-testing/references/dogfood-scenarios.md, linked from the cmux-testing skill. The starter tour is indogfood/scenarios/.
Testing
- The three new launcher tests in
tests/test_run_e2e.pypass locally: scenario dispatch and encoding, a tour with no steps refused, and a selector or scenario required. - A live tour of this branch follows (with
--workflow-ref, since the input isn't onmainyet). Its frames will be posted here.
🤖 Generated with Claude Code
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.
Summary by cubic
Agents can now drive the built app on a CI runner with a JSON tour instead of compiling per tour: scripts/run-e2e.sh --scenario <tour>.json --ref <sha> --frames returns screenshots, contact sheets, accessibility trees, and socket replies.
DogfoodScenarioUITests
- Reads the tour at run time and executes socket requests, key presses, typing, clicks, hovers, menu paths, waits, and existence checks.
- The socket is enabled through launch environment overrides since the launch argument loses to the runner's settings file, and binds inside the runner's temporary directory because the sandboxed runner cannot
connect(2)to /tmp (EPERM); paths the app reports in diagnostics are also tried. shotandtreesteps keep their attachments, and a failed step is recorded with a screenshot while the tour continues, failing at the end with the full list.- Shots crop to the app's front window by default, the window zooms at launch, and a final whole-display shot shows what covered the app on the shared CI desktop.
- Shell text goes in
initial_input;initial_commandreplaces the shell, so quick commands show Ghostty's failed-launch screen. - A launch landing in the background on headless runners is absorbed as an expected failure and activated explicitly; a 0-frame expected failure means the runner couldn't foreground the app, so dispatch the tour again.
- A silent socket attaches socket-diagnostics.txt with the app's diagnostics, socket files, and launch environment.
- Starter tours cover the sidebar and chrome, browser notifications, and the right sidebar with menus, context menus, and the terminal find bar.
- Skips when no scenario is set, so ordinary UI runs are unaffected.
Tooling
test-e2e.ymltakes a base64dogfood_scenarioinput, forwarded to the runner asTEST_RUNNER_CMUX_DOGFOOD_SCENARIO_B64sincesudo -uresets the environment.- The runner's dialog cleanup now uses SIGKILL and closes Diagnostics Reporter (matched by executable path, since its name outgrows pkill's 16-byte
-xcompare), Problem Reporter, and CoreServicesUIAgent (a launch-failure dialog covered a tour). - Scenario runs add their dispatch id to the concurrency group so simultaneous tours of one commit don't cancel each other.
dispatch-focused-test.py --scenariovalidates and encodes the tour, defaults the selector to the dogfood test, and skips the already-failed/running guard.e2e-frames.pyexports text attachments (trees, socket replies,steps.log) next to the frames and skips XCUITest's per-event snapshots; contact sheets are one xstack per 12 frames since ffmpeg 9's tile filter wrote only the first frame.
Summary by CodeRabbit
- New Features
- Added configurable UI tours for testing browser notifications, workspaces, the sidebar, and app controls.
- UI tours can run in CI with screenshots and accessibility details, and report failed steps alongside supporting attachments.
- CI test runs can now accept a tour scenario, including runs without a separate test filter.
- Documentation
- Added guidance for creating and running UI tours in CI.