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.
134 lines
4.5 KiB
Python
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())
|