Skip to content

Tests ​

This folder holds the suites vitest does not run: the Playwright end-to-end tests. Unit and integration tests live next to the code they test. The full picture is in the Testing guide.

Taxonomy ​

KindWhereRunnerNeedsCommand
Unitpackages/**/*.test.ts, apps/**vitest project unitNothingnpm run test:unit
Integration**/*.integration.test.tsvitest project integrationNothing (in-process server + bots)npm run test:integration
End-to-endtests/e2e/specs/**/*.spec.tsPlaywright, two tierssmoke: a built client; core: nothingnpm run test:e2e[:smoke]

npm test = unit + integration with the coverage gate (part of npm run validate).

npm run test:load (tests/load/run.ts) is no gate: it joins a game server with bots over real WebSockets and reports what each client receives and what the server's loop takes (see Performance). npm run test:netcode (tests/netcode/, its README) is no gate either: a bot records what a server sends it, the client stack replays the trace exactly as the renderer runs it, and the metrics say what the other cars and people did on screen against what the server said (bursts, freezes, jumps, people sliding); its deterministic reproductions are vitest tests in the same folder.

Folder layout ​

tests/
  tsconfig.json          # type-checks this folder and playwright.config.ts (lib: ES2022 + DOM)
  e2e/
    TEST-PLAN.md         # one row per feature → @E2E-nn tag, tier, spec, status (gated by `npm run e2e-plan`)
    support/levels.ts    # the level of a spec (its folder), one game server per level folder, the ports, the level files (also read by build/e2e-plan.mjs)
    support/lanes.ts     # the tiers' budgets and how a run is packed into lanes by the tests' recorded seconds (E2E_ONLY, the levels each lane starts servers for)
    timings.json         # every test's seconds on the self-hosted runner, by spec file and plan id (npm run e2e:timings)
    support/poc.ts       # openGame(), pollPoc(), hold(), fire(), walkTo(), aimAt(), goToRange(), showLegendTab(): helpers over window.__poc()
    support/server.ts    # startGameServer(port, level, env?): a server a spec starts for itself (another level, a .json file, an empty city)
    support/weather.ts   # setWeather(), setNightSight(), skipThreeHours(): the room's weather and hour from the View tab
    support/gui.ts       # pollGuiText(), pollGuiShown(), pollGuiTags(), clickGui(): the in-game UI drawn in the scene, read and clicked for real
    support/dogs.ts      # openDogStreet(), waitForDog(), leashOf(), watchFor(): the dogs of window.__poc().dogs, a dog relative to its walker
    support/walk.ts      # walkStraight(): on foot in a straight line at any bearing (a jetty, a ramp off the axes)
    specs/
      smoke/             # the Electron app boots (built bundle)
      levels.spec.ts     # every exported level boots and its packed scene loads
      levels/<level>/    # one folder per level (every test-* level has one; main-* may); its specs run on that level's server
        app/             # the overlay, the level, the collider wireframe
        drive/           # driving in the browser renderer
        net/             # prediction, remote players
    report/              # Playwright HTML report (generated, git-ignored)
    results/             # traces and screenshots (generated, git-ignored)

How the e2e tests see the game ​

The canvas is opaque to Playwright, so the renderer exposes window.__poc() (see apps/client/src/renderer/game.ts): connection state, client id, whether the player is on foot and in which car and seat otherwise, the followed entity's pose and speed, the car's class, rear grip and wheelspin, prediction statistics, the simulated latency, the number of remote cars and people, health, cover (and whether the character is out past its end), whether the HUD is shown, the target on the line of fire, the hit log, where the character is drawn on the canvas (so the specs can aim with the mouse), the mouse as the game sees it (pointer: the trigger, the pop-up toggle, the wheel steps, where it aims from), the in-game UI drawn in the scene with Babylon's GUI (gui: every named block on screen, the overlay's element ids before, with its text, its state and where it is on the page, and where the prompt's point landed on which frame; support/gui.ts reads it and clicks a block with the real mouse at its place on the canvas), the level the server runs, the collider wireframe (colliders), the level's roads and the server's chaser readout (ai: how many, the nearest one's distance to its target, the modes, every chaser's position). The specs drive the car with real key presses and assert on that state. playwright.config.ts starts one game server per level folder (ports 7900 and up, in the folders' name order) and a Vite dev server on 5174 with FOI_WEB=1 whose same-origin /game proxy reaches the first, so npm run dev can keep running on 7777/5173. A spec that needs another level starts its own server on a port from 7800.