terainia/test/attach.py
Maximus Gorog 3ed16c2aaf Performant UI test harness — attach to real Chrome
Per your direction: tests must be able to debug UI/UX behaviors and
must be performant. Playwright's bundled Chromium falls to SwiftShader
on Linux which is fine for visual scenarios but tanks anything where
fps matters. New attach-mode lets us drive YOUR Chrome (hardware GPU)
without needing Playwright to spawn its own.

test/attach.py:
  - One-shot health check that connects to localhost:9222 (Chrome
    already running with --remote-debugging-port). Doesn't spawn,
    doesn't close. Just confirms attach + reports the FPS HUD value.
  - peek.py and run.py already attach via CDP, so they work as-is
    once Chrome is started with the debug port.

test/README.md:
  - New "Two modes" section up front: attach (your real Chrome,
    hardware) vs launch (Playwright Chromium, software). Each has a
    legitimate use; perf-sensitive work goes through attach.
  - Workflow:
      google-chrome --remote-debugging-port=9222 \\
        --user-data-dir=/tmp/voxel-dev-chrome http://localhost:8080/
      python3 attach.py        # health check
      python3 run.py scenarios/ui-menu-open-close.yaml

New UI scenarios that drive interactions via DOM events / wasm calls,
not pixel screenshots. Render-independent, fast on any backend:

  ui-menu-open-close.yaml    Click ≡ → assert menu-open class →
                              click resume → assert closed.
  ui-hotbar.yaml             pointerdown on slot 4 → assert .active
                              moved. Digit1 keypress → assert .active
                              back to slot 0.
  ui-respawn.yaml            teleport into void → wait → assert
                              is_alive()===false + body.dead class +
                              death screen visible. Click respawn-btn
                              → assert hp===20, alive===true.
  ui-settings-sliders.yaml   Slider .value = N + dispatch 'input' →
                              assert displayed value updates → unwind
                              so the page isn't left frozen.

README updates list all scenarios. No code in the game changed —
this is pure test-harness additions.
2026-05-24 17:41:05 -06:00

74 lines
2.6 KiB
Python

"""
Attach to an ALREADY-RUNNING Chrome instead of spawning a fresh
Chromium under Playwright. The bundled Playwright Chromium falls to
SwiftShader (software CPU rasterizer) on Linux, which makes any
perf measurement useless — and made the game itself unusable to
inspect.
This script does not launch a browser. It expects Chrome to already
be running with remote debugging enabled. Start Chrome once:
google-chrome \
--remote-debugging-port=9222 \
--user-data-dir=/tmp/voxel-dev-chrome \
http://localhost:8080/
(Or your distro's chromium binary — same flags.) Keep that window
open; from this script (and peek.py / run.py) we attach to it.
Why a separate user-data-dir: so the debug-port Chrome doesn't
interfere with your normal browsing session. The two share no
cookies, history, or extensions.
Why not auto-launch it: detecting the system Chrome binary varies
by distro, and Playwright's launch path always falls to its own
bundled build. The cleanest split is "you bring the browser,
Playwright drives it."
This script confirms attachment + dumps a one-line health check.
"""
from __future__ import annotations
import sys
from playwright.sync_api import sync_playwright
CDP_URL = "http://localhost:9222"
def main() -> int:
with sync_playwright() as p:
try:
browser = p.chromium.connect_over_cdp(CDP_URL)
except Exception as e: # noqa: BLE001
print(
f"[!] could not attach to Chrome at {CDP_URL}: {e}\n"
f" Start Chrome with:\n"
f" google-chrome --remote-debugging-port=9222 "
f"--user-data-dir=/tmp/voxel-dev-chrome http://localhost:8080/",
file=sys.stderr,
)
return 1
pages = [pg for ctx in browser.contexts for pg in ctx.pages]
if not pages:
print("[!] no open pages — open http://localhost:8080/ in the debug Chrome", file=sys.stderr)
return 2
page = pages[-1]
print(f"attached: {len(pages)} page(s); active: {page.url}")
try:
info = page.evaluate(
"() => ({voxel: typeof window.voxel_game, fps_dt: window.voxel_game?.get_frame_dt_ms?.() ?? null})"
)
print(f" voxel_game: {info['voxel']}")
if info.get("fps_dt") is not None:
fps = 1000.0 / info["fps_dt"] if info["fps_dt"] > 0 else 0
print(f" frame_dt: {info['fps_dt']:.1f} ms ({fps:.1f} fps)")
except Exception as e: # noqa: BLE001
print(f" (eval failed: {e})")
browser.close()
return 0
if __name__ == "__main__":
sys.exit(main())