Attach/detach CDP sessions per Playwright Page, start/stop JPEG
screencasts with configurable quality and frame rate, forward each
frame to a callback. Used by the dashboard for click-to-watch
live video (wired in Task 23).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Starts DashboardServer on port 7777 (or --dashboard-port), uses its
reporter as ctx.dashboard, auto-opens the URL via xdg-open/open/start.
Cleans up on exit. WS client connections logged as info events so
you can see when the browser attaches.
Verified: 2-account populate run with --watch=dashboard serves the
static page on :7777, accepts WS connections, cleanly shuts down
when the run completes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Static HTML page served by DashboardServer. Renders the 2×2 room
grid with progress bars and player tiles, subscribes to WS events,
updates tiles live. Click-to-watch modal is wired but receives
frames once the CDP screencaster ships in Task 22.
Adds escapeHtml() on all user-controlled strings (roomId, player
key) — not strictly needed for our trusted bot traffic but cheap
XSS hardening against future scenarios that accept user input.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
Two small fixes to tests/e2e/bot/golf-bot.ts needed to run the bot
from the soak harness with authenticated accounts:
1. createGame and joinGame now check whether #player-name is
visible before filling it. Authenticated sessions hide that
input (the server uses the logged-in username); guest sessions
still fill it as before. Existing e2e tests behave identically
since they register guests who always see the input.
2. startGame's 'decks' option was calling selectOption on
#num-decks, which is a hidden input inside a stepper widget,
not a <select>. Replaced with stepper-click logic that
increments/decrements until the hidden input matches the
target value.
End-to-end verified via the soak runner: 2 authenticated sessions
played a complete 1-hole game against local dev, 26 turns, 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>
Animation fixes:
- Fix held card positioning bug (was appearing at bottom of page)
- Fix discard pile blank/white flash on turn transitions
- Fix blank card at round end by skipping animations during round_over/game_over
- Set card content before triggering flip animation to prevent flash
- Center suit symbol on 10 cards
Timing improvements:
- Reduce post-discard delay from 700ms to 500ms
- Reduce post-swap delay from 1800ms to 1000ms
- Speed up swap flip animation from 1150ms to 550ms
- Reduce CPU initial thinking delay from 150-250ms to 80-150ms
- Pause now happens after swap completes (showing result) instead of before
E2E test suite:
- Add Playwright-based test bot that plays full games
- State parser extracts game state from DOM for validation
- AI brain ports decision logic for automated play
- Freeze detector monitors for UI hangs
- Visual validator checks CSS states
- Full game, stress, and visual test specs
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>