Overview
The HedgeEm v6 test harness validates the full game pipeline end-to-end — from the REST API’s card evaluation logic through to what the Phaser 3 client renders on the canvas. It uses Playwright as the test runner and operates against two live local servers simultaneously. The core challenge: Phaser renders everything to<canvas>. There is no DOM to inspect. Instead, the harness uses a window.__hedgeem hook injected into GameScene.create() that exposes the game engine’s internal state as a plain JavaScript object. Tests call this hook to read the game state, drive the game forward, and inject forced deals.
Architecture
callForcedDeal()— POST card data to/api/deal/forced; server evaluates odds fresh via Monte CarlomapToGameRecord()— map the BulkGameState response to the client’sGameRecordshapeloadForcedRecord(record)— inject the record into the running Phaser game viawindow.__hedgeem- Wait for
dealStatus === 0(HOLE stage rendered) - Assert HOLE-stage values via
getSnapshot() advance()→ FLOP → assertadvance()→ TURN → assertadvance()→ RIVER → assert +gameOver
Prerequisites
Both servers must be running before tests start. The global setup pre-flight check verifies both and fails with a clear error if either is missing. Start the API server (with forced-deal flag):results/test-report.md and results/test-log.json.
Repository Structure
Key Files
/api/deal/forced — Server Endpoint
File: hedgeem-v6-server/api/deal-forced.ts
Accepts a POST body with known card data and returns a full BulkGameState — the same shape as /api/games/bulk. It reuses the existing buildStageEntries / computeOddsChain pipeline, substituting supplied cards for dealGame() output.
Gated by the ALLOW_FORCED_DEAL=true environment variable. Returns 403 if the flag is not set, so the endpoint can never accidentally reach production.
handStageInfoList — one entry per hand per stage, covering all four betting stages (HOLE, FLOP, TURN, RIVER).
window.__hedgeem — Client Hook
File: hedgeem-v5/hedgeem-v6-client/src/scenes/GameScene.js
Injected during GameScene.create(). Exposes:
When
loadForcedRecord is called with a different numberOfHands than the current scene, _startDemoHand triggers scene.restart(). The test harness polls waitForFunction(() => window.__hedgeem?.getDealStatus?.() === 0) to wait for the restarted scene to be ready.
src/demo-records.ts — Oracle Data
Contains all 17 demo game records, each with:
hands[]— hole card pairs (4 cards per hand as a 4-character string, e.g."KdKc")community— the five board cardsstages[0–3]— per-hand oracle values for each betting stage
src/server-client.ts — Type Bridge
callForcedDeal(request, oracle, serverBaseUrl) sends the POST and returns BulkGameState.
mapToGameRecord(serverResp) maps the server response to the GameRecord shape the client expects:
src/code-map.ts — Failure Hints
Maps each assertion type to the source file locations where bugs would most likely be introduced. The markdown reporter uses this to annotate failure output with direct file:line references, saving time when diagnosing failures.
Oracle Strategy
The harness uses two oracle sources depending on the assertion:
Why not use coredata values for odds? The coredata odds were computed by the original v4 C# Monte Carlo implementation. The v6 server uses a fresh Node.js Monte Carlo run with different random seeds and possibly different simulation counts. Results are close but not identical (±0.1–0.3 variance at early stages). Testing client vs server (not client vs coredata) validates the complete server→client pipeline without fighting implementation differences between v4 and v6.
Assertion Types and Tolerances
Dead hands (percentWin=0 AND percentDraw=0) have their odds assertions skipped — the client renders them as eliminated, not with a meaningful odds figure.
cantLose hands (percentWinOrDraw >= 99.9%) skip odds assertions because the server returns
oddsActual=0 as a special sentinel value indicating certainty.
Test Structure
Tests are serial with a single browser context — the Phaser game is stateful and cannot be parallelised.beforeAll opens the page once, clicks the splash overlay, and waits for the __hedgeem hook to be registered. Each of the 17 demo test.describe blocks then injects its record and runs HOLE/FLOP/TURN/RIVER sub-describes in order.
Total: 856 tests across 17 demos × 4 stages × 3–8 assertions per hand.
Reporters
Markdown Reporter
Writesresults/test-report.md with:
- Summary line: total, passed, failed, skipped counts
- Full table: one row per test, pass/fail emoji
- Failures section: expected vs actual, code location hints from
CODE_MAP
JSON Log Reporter
Writesresults/test-log.json with one structured entry per test:
Global Setup Pre-flight Checks
Before any test runs,src/global-setup.ts verifies:
- Client reachable —
GET http://localhost:4000/returns 2xx - Server reachable and flag set —
POST http://localhost:3010/api/deal/forcedreturns 200 or 400 (not 403 or 404)
Known Limitations
No visual assertions — the harness reads game state viagetSnapshot(), not by inspecting the canvas. Rendering bugs (wrong colour, wrong position) are not caught.
Single browser — tests run in Chromium only. Firefox/WebKit are not covered.
No bet placement testing — validating win labels (F049) requires placing bets, which needs an additional window.__hedgeem.placeBet() hook. This is planned as Phase 2.
handDescShort naming differences — the coredata oracle uses v4 C# hand names (e.g. “AceFlush”, “QueenFlush”) which differ from pokersolver names (e.g. “Flush”). The harness asserts client against server for this field, bypassing the naming difference entirely.