terainia/test/launch.py
Maximus Gorog e7a232ba97 test harness: default to localhost, not the prod deploy
Testing is a dev process — point the harness at a local build you can
edit, rebuild, and screenshot in seconds. Pointing it at the prod
deploy by default was wrong: the deploy lags local code by a deploy
cycle, so visual changes you make wouldn't appear there until rebuilt
on the Linode.

test/launch.py:
  - DEFAULT_URL is now http://localhost:8080/.
  - Friendly pre-flight check: if --url is localhost and nothing is
    listening on the port, print a clear "start ./run.sh --no-tunnel"
    message and exit 1. Avoids the silent ERR_CONNECTION_REFUSED
    failure mode.
  - --url https://voxel.mxvs.art/ still works for one-off remote
    sanity checks.

test/README.md:
  - Lead with the dev-loop instruction: terminal 1 runs the local
    server, terminal 2 runs launch.py, terminal 3 drives scenarios.
  - Note the "pointing at deploy" path as a rarely-used escape hatch.
2026-05-24 11:29:55 -06:00

134 lines
4.5 KiB
Python

"""
Launch Chromium pointed at the voxel game with CDP attached.
Mirrors the cucucaracha launch pattern:
- Persistent profile dir (./.browser_profile/) so settings + cookies
survive between runs.
- Remote debugging on port 9222 so peek.py / run.py attach to the
same session.
- Disables saved-password autofill (irrelevant here but harmless).
- Navigates ONCE to the target URL and sits idle.
The browser stays open until you Ctrl-C this terminal or close the
window. Scenarios are driven from a second terminal via run.py.
Usage:
python3 launch.py
python3 launch.py --url http://localhost:8080
python3 launch.py --url http://localhost:8080 --headed/--headless
"""
from __future__ import annotations
import argparse
import sys
import time
from pathlib import Path
from playwright.sync_api import sync_playwright
HERE = Path(__file__).resolve().parent
USER_DATA_DIR = HERE / ".browser_profile"
# Default: local dev server (./run.sh --no-tunnel or docker-compose up).
# Tests run against a build *you control* on this machine, never against
# the deployed production site. Use --url https://voxel.mxvs.art/
# explicitly if you want to point at prod for a one-off sanity check.
DEFAULT_URL = "http://localhost:8080/"
CDP_PORT = 9222
def main() -> int:
ap = argparse.ArgumentParser(
description=(
"Open Chromium against the local voxel-game dev server. "
"Tests are a dev process — defaults to http://localhost:8080/. "
"Start the local server first (./run.sh --no-tunnel or docker "
"compose up)."
),
)
ap.add_argument("--url", default=DEFAULT_URL, help="game URL to open")
ap.add_argument(
"--headless",
action="store_true",
help="run headless (no visible window). Default is headed.",
)
args = ap.parse_args()
# Friendly check: if pointed at localhost, verify something is
# actually listening. Avoids the "browser opens with ERR_CONNECTION
# _REFUSED and you spend 5 minutes debugging Playwright" failure mode.
if "localhost" in args.url or "127.0.0.1" in args.url:
import socket
from urllib.parse import urlparse
u = urlparse(args.url)
host = u.hostname or "127.0.0.1"
port = u.port or (443 if u.scheme == "https" else 80)
s = socket.socket()
s.settimeout(0.5)
try:
s.connect((host, port))
s.close()
except OSError:
print(
f"[!] nothing listening on {host}:{port}.\n"
f" Start the local dev server first, e.g.:\n"
f" ./run.sh --no-tunnel\n"
f" (from the repo root)\n"
f" Or pass --url <other> if you really want a remote target.",
file=sys.stderr,
)
return 1
USER_DATA_DIR.mkdir(exist_ok=True)
chrome_args = [
f"--remote-debugging-port={CDP_PORT}",
"--disable-blink-features=AutomationControlled",
# WebGPU benefits from these on Linux — voxel game prefers
# WebGPU but will fall back to WebGL2 if disabled.
"--enable-unsafe-webgpu",
# Smaller window default
"--window-size=1280,800",
]
with sync_playwright() as p:
ctx = p.chromium.launch_persistent_context(
user_data_dir=str(USER_DATA_DIR),
headless=args.headless,
viewport={"width": 1280, "height": 800},
args=chrome_args,
ignore_default_args=["--enable-automation"],
)
page = ctx.pages[0] if ctx.pages else ctx.new_page()
try:
page.goto(args.url, wait_until="domcontentloaded", timeout=45000)
except Exception as e: # noqa: BLE001
print(
f"[!] navigation error (browser stays open): {e}",
file=sys.stderr,
flush=True,
)
print(
f"[i] Browser open at {args.url}\n"
f" CDP listening on http://localhost:{CDP_PORT}\n"
f" Profile: {USER_DATA_DIR}\n"
" Use peek.py / run.py from another terminal.\n"
" Stop with Ctrl-C or close the browser window.",
file=sys.stderr,
flush=True,
)
try:
while True:
time.sleep(3600)
except KeyboardInterrupt:
print("[i] Ctrl-C received; exiting.", file=sys.stderr, flush=True)
return 0
if __name__ == "__main__":
sys.exit(main())