🧪 E2E Verification Standard
How to write and run browser proofs on Funday, and what a “green suite” is actually worth. Implements Platform Laws 16-18 (funday-bible.md §10): Wire Truth Over Pixel Truth, Measured Runner Capacity, Proof Of Execution.
Why this exists
A suite can be green and still prove nothing:
- A feature can be deployed and inert — a match-rendezvous path once passed 12 mocked unit tests while every production request silently fell back, because a storage call threw and its own
catchswallowed the error. Only the Nakama log revealed it. - A suite can be red without a defect — on a loaded host, two concurrent WebGL specs starve the software renderer, and assertions fail on absent peers and stalled runners that work perfectly in isolation.
Assert on frames, not on pixels
| Assert this | Not this | Why |
|---|---|---|
| Authoritative opcode/state frames off the socket | HUD text at a point in time | Frames are durable and timestamped; UI is transient |
Existence and geometry in one in-page evaluate | toBeVisible() then boundingBox() | Short-lived overlays (chat bubbles live ~6s) expire between two round trips |
| A settled end state, polled until stable | One sample after a toggle | A starved compositor can hold a mid-animation frame long enough to look stable |
| Local counters already recorded off the wire | Repeated DOM queries in a loop | Each remote-control call costs real time under load |
Concurrency follows measured capacity
- Measure before choosing:
nproc,/proc/loadavg, and the cost of a single interaction. This production VM has 6 shared cores and routinely sits at load 12-20 from scheduled jobs. - Multi-client game specs stay serial here.
frontend/e2e/theme-dash-roam.spec.tsdeclarestest.describe.configure({ mode: "serial" })for this reason; three separate 2-worker experiments failed with an absent roster peer, a stalled runner, and viewport timeouts — all starvation, no product defect. - Raise concurrency only on a runner proven to sustain it, and prove it with two consecutive green runs plus wall times.
- Under load a single Playwright/CDP call can cost seconds: a 5.5s in-game sprint cadence measured 20s per cycle, starving the player into a loss. Drive long in-app cadences from inside the page (one
evaluatepaced by the renderer’s own timers) after a real key/pointer interaction has proven the input pipeline. - NEVER widen a timeout to make a logic failure pass. Asset work (texture decode, font load) is the one legitimate place for a generous budget — say so in a comment.
Harness gotchas that cost real time
browser.newContext()does not inherituse.baseURLfromplaywright.config.ts. Relativepage.requestcalls on a hand-made context throwInvalid URLuntil the page navigates. PassbaseURLexplicitly to every manual context.- The BFF enforces CSRF by
Origin/Refereron mutations: directPOST /api/matchesfrom a request context needs both headers. - Isolation matters. Roam-style modes converge every caller into one shared shard by design, so a spec needing a private shard MUST create it with
excludeMatchIdsand join by id — never assume an exact occupancy count. - Prefer one specialised test per contract over one long journey: a mobile-layout regression should reproduce in minutes, not after a full match.
A green run is bound to a build
Record the identity of what you tested, in the same breath as the result:
systemctl show -p ActiveEnterTimestamp funday-frontend # frontend build serving traffic
stat -c '%y' frontend/build/server/index.js # when that bundle was written
sudo kubectl -n funday-platform get po -l app=nakama \
-o jsonpath='{.items[0].status.startTime}' # server runtime generation
git log --oneline -1 # source snapshotIf anyone deploys after your run, the claim is stale — re-run before repeating it. To check whether a file is actually in the live bundle, compare mtimes: a source older than build/server/index.js was compiled into it.
Proof of execution for server changes
- Rebuild and roll out:
cd nakama-modules && npm run build, thensudo kubectl -n funday-platform rollout restart deploy/nakama. - Exercise the path.
- Read the log and find your own line:
sudo kubectl -n funday-platform logs deploy/nakama --tail=40. No line means the branch never ran, whatever the unit tests say. - Ensure every degrading
catchlogs atwarnwith the reason, so a silent fallback can never masquerade as a working feature.