Files
workout-suggester/scripts/e2e.py
T
bdeb1337 d7342d6096 feat: workout-suggester, weather-aware workouts from a local Gemma
Go + Templ + HTMX app that checks the weather (Open-Meteo, MET Norway
fallback), scores run/ride/walk with plain rules, and has a local
open-weight model (Gemma 4 E2B via any OpenAI-compatible server) write
the plan. Routes are loops from your door (BRouter) plus signposted
OpenStreetMap routes (Overpass), with GPX export.
2026-10-11 19:29:58 +02:00

69 lines
2.7 KiB
Python

# /// script
# requires-python = ">=3.11"
# dependencies = ["playwright"]
# ///
"""Browser end-to-end test: taps "📍 Here" (with a faked GPS fix), waits for
the coach's streamed advice and the route map, asks a follow-up, and saves
screenshots.
uv run scripts/e2e.py [--url http://127.0.0.1:3000] [--dark] [--mobile]
"""
import argparse, sys
from playwright.sync_api import sync_playwright, expect
p = argparse.ArgumentParser()
p.add_argument("--url", default="http://127.0.0.1:3000")
p.add_argument("--channel", default="msedge", help="installed browser: chrome, msedge, ...")
p.add_argument("--out", default="docs")
p.add_argument("--dark", action="store_true")
p.add_argument("--mobile", action="store_true")
p.add_argument("--lat", type=float, default=51.0543)
p.add_argument("--lon", type=float, default=3.7174)
args = p.parse_args()
name = "screenshot" + ("-mobile" if args.mobile else "") + ("-dark" if args.dark else "")
viewport = {"width": 400, "height": 860} if args.mobile else {"width": 1000, "height": 900}
with sync_playwright() as pw:
browser = pw.chromium.launch(channel=args.channel, headless=True)
ctx = browser.new_context(
viewport=viewport,
color_scheme="dark" if args.dark else "light",
geolocation={"latitude": args.lat, "longitude": args.lon},
permissions=["geolocation"],
device_scale_factor=2,
)
page = ctx.new_page()
errors = []
page.on("pageerror", lambda e: errors.append(str(e)))
page.goto(args.url)
page.evaluate("localStorage.clear()")
page.reload()
expect(page.locator("#health .dot.ok")).to_be_visible(timeout=10_000)
page.get_by_text("🔥 Hard").click()
page.get_by_text("📍 Here").click() # locates and submits
expect(page.locator(".weather")).to_be_visible(timeout=20_000)
expect(page.locator(".score.picked")).to_be_visible()
expect(page.locator("#thread .markdown")).to_be_visible(timeout=180_000)
if page.locator(".score.indoor.picked").count() == 0:
expect(page.locator(".routes .map.leaflet-container")).to_be_visible(timeout=60_000)
page.wait_for_timeout(1500) # tiles
page.screenshot(path=f"{args.out}/{name}.png", full_page=True)
page.get_by_text("⏱️ Only 30 min").click()
expect(page.locator("#thread .markdown")).to_have_count(2, timeout=180_000)
replies = page.locator("#thread .markdown").all_inner_texts()
print("--- plan ---\n" + replies[0] + "\n--- follow-up ---\n" + replies[1])
page.reload() # the plan survives a reload
expect(page.locator("#thread .markdown")).to_have_count(2)
browser.close()
if errors:
print("page errors:", errors, file=sys.stderr)
sys.exit(1)
print(f"ok: {args.out}/{name}.png")