""" Screenshot the Exercise List page at a configurable viewport. Intended for visual investigations (e.g., layout discussions, documentation figures). Not a test — no assertions; see `tests/` for the test suite. Prerequisites: - Playwright for Python installed (see `tests/requirements.txt`) - Playwright browsers installed: `python -m playwright install firefox chromium` Typical usage (from project root): # 1. Start the Vite dev server in one terminal npm run dev -- --port 5199 --strictPort # 2. In another terminal, run the script python scripts/screenshots/exercise_list.py Options (all optional, override via CLI flags): --url URL App URL (default: http://localhost:5199) --browser NAME firefox | chromium | webkit (default: firefox) --width N Viewport width in px (default: 1920) --height N Viewport height in px (default: 1080) --out PATH Output file path (default: tmp/exercise-list--x.png) --no-seed Do not create sample exercises (use the existing DB as-is) Exit code: 0 on success, 1 on failure. """ from __future__ import annotations import argparse import os import sys import time from pathlib import Path from playwright.sync_api import sync_playwright SAMPLE_EXERCISES = [ {"title": "Push-ups", "desc": "Classic bodyweight push-up", "reps": "15"}, {"title": "Squats", "desc": "Bodyweight squat", "reps": "20"}, {"title": "Plank", "desc": "Core stability hold", "reps": "60"}, ] def ensure_sample_exercises(page, url: str) -> None: """Create a few exercises via the UI so the list has visible cards. No-op if the list already contains at least one card. """ page.goto(f"{url}/#/exercises", wait_until="networkidle") cards = page.locator('[data-testid^="exercise-card-"]') if cards.count() > 0: return for ex in SAMPLE_EXERCISES: page.goto(f"{url}/#/exercises/new", wait_until="networkidle") page.wait_for_selector('[data-testid="exercise-title"]', timeout=10000) page.locator('[data-testid="exercise-title"]').fill(ex["title"]) page.locator('[data-testid="exercise-description"]').fill(ex["desc"]) page.locator('[data-testid="exercise-reps"]').fill(ex["reps"]) page.locator('[data-testid="exercise-save"]').click() # After save the app navigates to the detail page; just wait for URL # to move away from `/new`. page.wait_for_function( "() => !location.hash.endsWith('/exercises/new')", timeout=5000, ) def parse_args(argv: list[str]) -> argparse.Namespace: parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument("--url", default=os.environ.get("APP_URL", "http://localhost:5199")) parser.add_argument("--browser", default="firefox", choices=["firefox", "chromium", "webkit"]) parser.add_argument("--width", type=int, default=1920) parser.add_argument("--height", type=int, default=1080) parser.add_argument("--out", default=None, help="Output file path") parser.add_argument("--no-seed", action="store_true", help="Skip creating sample exercises") return parser.parse_args(argv) def default_out_path(browser: str, width: int, height: int) -> Path: """Default output goes to tmp/ at the project root (gitignored).""" project_root = Path(__file__).resolve().parents[2] tmp_dir = project_root / "tmp" tmp_dir.mkdir(parents=True, exist_ok=True) return tmp_dir / f"exercise-list-{browser}-{width}x{height}.png" def main(argv: list[str] | None = None) -> int: args = parse_args(argv or sys.argv[1:]) out_path = Path(args.out) if args.out else default_out_path(args.browser, args.width, args.height) out_path.parent.mkdir(parents=True, exist_ok=True) viewport = {"width": args.width, "height": args.height} with sync_playwright() as p: browser_type = getattr(p, args.browser) browser = browser_type.launch(headless=True) try: context = browser.new_context(viewport=viewport) page = context.new_page() page.goto(args.url, wait_until="networkidle") # Wait for the app to finish DB init overlay, if any. try: page.wait_for_selector(".db-overlay", state="detached", timeout=10000) except Exception: pass if not args.no_seed: ensure_sample_exercises(page, args.url) page.goto(f"{args.url}/#/exercises", wait_until="networkidle") page.wait_for_selector('[data-testid^="exercise-card-"]', timeout=5000) # Let layout settle before capturing. time.sleep(0.5) page.screenshot(path=str(out_path), full_page=True) print(f"Saved screenshot: {out_path}") return 0 finally: browser.close() if __name__ == "__main__": sys.exit(main())