🧪 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 catch swallowed 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 thisNot thisWhy
Authoritative opcode/state frames off the socketHUD text at a point in timeFrames are durable and timestamped; UI is transient
Existence and geometry in one in-page evaluatetoBeVisible() then boundingBox()Short-lived overlays (chat bubbles live ~6s) expire between two round trips
A settled end state, polled until stableOne sample after a toggleA starved compositor can hold a mid-animation frame long enough to look stable
Local counters already recorded off the wireRepeated DOM queries in a loopEach 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.ts declares test.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 evaluate paced 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 inherit use.baseURL from playwright.config.ts. Relative page.request calls on a hand-made context throw Invalid URL until the page navigates. Pass baseURL explicitly to every manual context.
  • The BFF enforces CSRF by Origin/Referer on mutations: direct POST /api/matches from 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 excludeMatchIds and 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 snapshot

If 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

  1. Rebuild and roll out: cd nakama-modules && npm run build, then sudo kubectl -n funday-platform rollout restart deploy/nakama.
  2. Exercise the path.
  3. 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.
  4. Ensure every degrading catch logs at warn with the reason, so a silent fallback can never masquerade as a working feature.

Ask Docs

AI assistant to help answer questions about the documentation. Answers are read-only and cite docs/source.

Hi! How can I help you with the documentation today? Answers are read-only and cite docs/source.

Ctrl+Enter to send