Serves one static HTML page, accepts WS connections, broadcasts
room_state/log/metric messages to all clients. Replays current
state to late-joining clients so refreshing the dashboard during
a run shows the right grid. Exposes a reporter() method that
returns a DashboardReporter scenarios can call without knowing
about sockets.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
First full end-to-end milestone: parses CLI, builds SessionPool +
RoomCoordinator, loads a scenario by name, runs it, reports results,
cleans up. Watch modes other than "none" log a warning and fall back
until Tasks 19-24 implement them.
Smoke test passed against local dev:
bun run soak -- --scenario=populate --accounts=2 --rooms=1
--cpus-per-room=0 --games-per-room=1 --holes=1 --watch=none
→ Games completed: 1, Errors: 0, Duration: 78.2s, exit 0
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
parseArgs pulls --scenario/--rooms/--watch/etc from argv,
mergeConfig layers scenarioDefaults → env → CLI so CLI flags
always win. 12 Vitest unit tests cover both parse happy/edge
paths and the 4-way merge precedence matrix.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Partitions sessions into N rooms, runs gamesPerRoom games per room
in parallel via Promise.allSettled so a failure in one room never
unwinds the others. Errors roll up into ScenarioResult.errors.
Verified via tsx: listScenarios() returns [populate], getScenario()
resolves by name and returns undefined for unknown names.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Encapsulates the host-creates/joiners-join/loop-until-done flow so
populate and stress scenarios don't duplicate it. Honors abort
signal and a max-duration timeout, heartbeats on every turn.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Thin standalone entry for pre-seeding N accounts before the first
harness run. Wraps SessionPool.seed and writes .env.stresstest.
End-to-end verified: ran against local dev with --count=4, all 4
accounts landed in the DB with is_test_account=TRUE, cred file
written with correct format.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Owns BrowserContexts, seeds via POST /api/auth/register with the
invite code on cold start, warm-starts via localStorage injection of
the cached JWT, falls back to POST /api/auth/login if the token is
rejected. Exposes acquire(n) for scenarios.
Infrastructure changes needed to import the real GolfBot class from
tests/e2e/bot/ without the Task-10 structural-interface workaround:
- Add @playwright/test as devDep so value-imports in e2e/bot/*.ts
resolve at runtime (Page/Locator/expect are pulled even as types)
- Remove rootDir from tsconfig so TS follows cross-package imports;
add a paths entry so TS can resolve @playwright/test from the soak
package's node_modules when compiling files under tests/e2e/bot
- Drop the local GolfBot structural interface + its placeholder
GamePhase/StartGameOptions/TurnResult types; re-export the real
class from types.ts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Single file, no transport, writes one JSON line per call to stdout.
Child loggers inherit parent meta so scenarios can bind room/game
context once and forget about it.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Lazy Deferred per roomId with a timeout on await. Lets concurrent
joiner sessions block until their host announces the room code
without polling or page scraping.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Establishes the Scenario/Session/Logger/DashboardReporter contracts
the rest of the harness builds on. Deferred is the building block
for RoomCoordinator's host→joiners handoff.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Placeholder runner, tsconfig with @bot alias to tests/e2e/bot,
gitignored .env.stresstest + artifacts. Real behavior follows
in Task 10 onward.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>