131 lines
4.9 KiB
Python
131 lines
4.9 KiB
Python
"""
|
|
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-<browser>-<W>x<H>.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())
|