Add backend E2E story tests: dashboard user stories over a fake agent - #1527
Merged
Conversation
Every major dashboard flow now has an end-to-end test on the backend alone: the stories call the same telefunctions the browser invokes, against the real daemon runtime (createProjectRuntime), real registry state, and real spawned run processes — with the deterministic --fake driver in the agent seat, so the whole suite runs offline and adds ~2s of wall clock. No browser involved. Covered stories: add/register a project and the sidebar reads; start a session and follow its live feed to the archived row; two concurrent sessions in their own worktrees (#736); publish a finished session's branch to a real bare origin (#799); answer a parked question from the questions hub (#304/#1455); live chat becoming the next agent turn and its committed conversation (#714/#908); rearming the handoff mid-run (#1102); stop -> retained worktree -> remove -> delete (#737/#1032); tickets list/detail and cross-project pages (#697/#1144); queueing a ticket and a drain run claiming it (#1164/#1117); dashboard-written preferences reaching a resumed run's argv (#858/#1467); and the usage panel + auto-PM sweep surface (#533/#1210). The harness (src/e2e/harness.ts) wires the Telefunc request context the way runDaemon does, isolates $XDG_CONFIG_HOME per test process, spawns runs through fake-agent-bin.ts (the real CLI with --fake appended), and records each spawn's argv — the only observable place the launcher's toggle-to-flag contract can be asserted, since the spawn is detached. Writing the stories surfaced two real races in the finished-session seam (documented in src/e2e/spec.md): acting on a session the moment its meta flips done collides with teardown's own commits, and a live event tail whose file is retired misses the final events when the fs.watch signal is lost. The stories model the same healing the dashboard uses (wait for retirement; swap to the archived replay). src/e2e is excluded from the publish build; it compiles only into dist-test with the rest of the suite. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XKihCTfcazhDgt4dhUmfM9
This was referenced Aug 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
A backend-only E2E suite (
packages/the-framework/src/e2e/) that walks every major dashboard user story through the real business logic — no browser, no real AI agent, and fast: the 14 stories add ~2s to the suite's wall clock (files parallelize; full suite stays ~45s).Each story is end-to-end in the sense that matters:
sendStart,sendChoice,onRuns,onQueue, …), with the Telefunc request context wired exactly asrunDaemonwires it (runtime closures, registry-backed preferences/discord stores; stub quota/auto-PM reporters where the daemon holds live pollers).createProjectRuntime, exercising the production lifecycle — worktree allocation, run store,events.jsonl, control watcher, gates, teardown, retention. Only the agent turn is scripted: the harnessbinPathpoints atfake-agent-bin.ts, which forwards to the real CLI with--fakeappended (FRAMEWORK_FAKE_AWAITparks a run on a scripted gate).sendAddProject, an isolated$XDG_CONFIG_HOMEper test process, and (for the publish story) a real bareoriginthe push actually lands on.Stories covered
story-projects-and-settingsstory-session-lifecyclestory-steering-and-gatesstory-tickets-and-queueThe spawned argv is recorded per run (opt-in via
$FRAMEWORK_E2E_ARGV_FILE), which is the only observable place the launcher's toggle→flag contract (startOptionFlags) can be asserted — the spawn is detached.Product findings (documented in
src/e2e/spec.md)Writing the stories surfaced two real races in the finished-session seam. The stories model the same healing the dashboard relies on, but both are candidates for tightening:
doneraces teardown.sendPushBranch/resume inside that window runscommitPendingWorkagainst the checkout teardown is committing in; the loser reports "could not commit the work this session left uncommitted", and teardown then retains a worktree it would have removed. A user clicking Push right as a run finishes can hit this; a second click succeeds.events.jsonlinto the archive; whentailEvents' fs.watch misses the final appends (watcher pressure), the 1s poll finds the file already gone and the tail never delivers the run'send. The dashboard heals because the view swaps to the archived replay once the row settles — the harness'stailRunmirrors that swap.Not in scope
The Telefunc transport hop (mount, CSRF/rebinding, Channel/SSE) — covered by
dashboard/server.test.tsanddashboard-rpc/stream-channel.test.ts; telefunc Channels only pump over a real wire, so stream assertions tail the sameevents.jsonlthe channel wraps.Verification
src/e2eis excluded from the publish build (tsconfig.build.json); nothing ships indist/. Note the baseline requires the dashboard bundle built (pnpm buildat root beforepnpm test), same as CI's order.Generated by Claude Code