Mirrors the cucucaracha (lacucarachanews) toolkit pattern adapted for
the voxel game:
bridges.rs adds TestCommand + Telemetry plumbing:
- thread_local TEST_COMMANDS queue + TELEMETRY snapshot.
- drain_test_commands() called by App::tick at frame start.
- publish_telemetry(t) called at frame end.
- wasm_api exports: set_scene_time, teleport, look_at, plus
getters get_scene_time / get_position / get_camera_angles.
app.rs:
- drain_test_commands() applies SetSceneTime / Teleport / LookAt
before physics integrates. Teleport zeroes velocity and syncs the
camera to feet+EYE_HEIGHT.
- publish_telemetry() at end of tick exposes scene state to JS.
test/:
launch.py Open Chromium with persistent profile + CDP:9222.
Navigates to https://voxel.mxvs.art by default;
--url for local dev.
peek.py Attach via CDP, screenshot canvas, dump telemetry
(scene_time, position, camera angles, hp). Read-only.
run.py Execute a YAML scenario:
wait_for, wait, eval, key, mouse_move, mouse,
screenshot, assert
Key allowlist prevents stray scenarios from sending
arbitrary input.
requirements.txt playwright + PyYAML.
README.md Setup, grammar, available bindings, why this
exists.
scenarios/
lighting-times-of-day.yaml Screenshots at noon / afternoon /
sunset / civil twilight / midnight
/ sunrise. Verifies the Round A
sunset fixes by visual diff.
god-rays-look-at-sun.yaml Pointed at the sun at four altitudes
to inspect the Round B shafts.
voxel-construction-darkness.yaml Visual baseline for the sky_vis
bake from Round D.
.gitignore Excludes the browser profile +
screenshots directory.
Visual regression workflow:
1. python3 launch.py
2. (separate terminal) python3 run.py scenarios/lighting-times-of-day.yaml
3. Compare screenshots/lighting-times-of-day_*.png against baseline.
Tests still 63 passing. Native + wasm release clean.
|
||
|---|---|---|
| .. | ||
| scenarios | ||
| .gitignore | ||
| launch.py | ||
| peek.py | ||
| README.md | ||
| requirements.txt | ||
| run.py | ||
Test harness — declarative visual + behavioral scenarios
Mirrors the cucucaracha (lacucarachanews) toolkit pattern: launch.py
opens a Chromium with persistent profile + CDP on port 9222; small
attach-only tools drive the same session via Playwright.
What's different: the game exposes a small set of wasm bindings so
scenarios can declaratively set scene state and read back telemetry,
not just click DOM elements. See src/bridges.rs wasm_api for the
exports.
Setup (once)
python3 -m venv .venv
. .venv/bin/activate
pip install -r requirements.txt
playwright install chromium
Open the game in a controllable browser
python3 launch.py # opens https://voxel.mxvs.art
python3 launch.py --url http://localhost:8080 # local dev server
The browser stays open; profile lives at ./.browser_profile/. CDP
listens on localhost:9222 so the other tools can attach.
One-shot inspection / screenshot
python3 peek.py # screenshot + dump game telemetry
python3 peek.py --json # machine-readable
Writes a PNG to screenshots/<ts>_peek.png.
Run a scenario
python3 run.py scenarios/lighting-times-of-day.yaml
Scenarios are YAML lists of steps. Each step is one of:
| step | meaning |
|---|---|
wait_for: <js_expr> |
block until js_expr evaluates truthy |
wait: <ms> |
sleep that many ms |
eval: <js> |
run JS in the page (state setters, etc.) |
key: <key> [hold: ms] |
press a key (optionally hold) |
mouse_move: [dx, dy] |
relative mouse motion |
mouse: <down|up|click> |
mouse button events |
screenshot: <name>.png |
save canvas screenshot to screenshots/ |
assert: <js_expr> |
fail scenario if js_expr is falsy |
Available game-state JS bindings
All exported by the wasm module (see src/bridges.rs::wasm_api).
After wait_for: "window.voxel_game !== undefined", call as
window.voxel_game.<fn>(...).
| Setter | Effect |
|---|---|
set_scene_time(t: f32) |
jump shader time to t seconds |
set_time_scale(s: f32) |
freeze (0) / fast-forward time |
teleport(x, y, z) |
move player feet to (x,y,z) |
look_at(yaw, pitch) |
set camera angles (radians) |
set_paused(b) |
pause input + physics |
set_fov(deg) / set_mouse_sens(s) / set_render_distance(blocks) |
settings |
respawn() |
one-shot respawn request |
| Getter | Returns |
|---|---|
get_scene_time() |
f32 |
get_position() |
[x, y, z] |
get_camera_angles() |
[yaw, pitch] |
get_hp() / is_alive() |
from death/respawn state |
Why this exists
When something looks wrong (e.g. "tops of blocks don't react to sunset"), the dev loop without this is: deploy, open browser, fiddle the time slider, compare to baseline by memory. With this, the loop is: write a scenario that screenshots the same view at noon / sunset / midnight, run it, diff the screenshots against a baseline. Bugs become visible in one command.
For non-visual behaviors (e.g. "is the joystick releasing correctly?") the same harness sends keyboard events and reads back position telemetry to assert "after release, player stops moving within N ms".
Sanity guarantees
- Persistent profile means cookies, settings, and game state survive
across
launch.pyruns. The first launch is "fresh"; subsequent ones resume where you left off. launch.pynever touches the page beyond the initial navigation. Scenarios drive the session; the launcher just hosts the browser.- All tools attach via CDP — closing them doesn't close the browser.