lawn-estimator-dev / scripts /browser_smoke.py
TempuraML's picture
chore(dev): browser-test harness for the dashboard (seed + Playwright smoke)
ca81019
Raw
History Blame Contribute Delete
5.27 kB
"""Browser click-test of the dashboard address popup (view + drag-edit + history/revert), driven
by Playwright for Python against a locally-seeded app (see scripts/dev_seed.py). No Node / no MCP.
# terminal 1
./.venv/Scripts/python.exe scripts/dev_seed.py
# terminal 2
./.venv/Scripts/python.exe scripts/browser_smoke.py
Prints PASS/FAIL per step and drops screenshots in scripts/_browser_shots/.
"""
from __future__ import annotations
import sys
from pathlib import Path
from playwright.sync_api import expect, sync_playwright
BASE = sys.argv[1] if len(sys.argv) > 1 else "http://127.0.0.1:8000"
TOKEN = sys.argv[2] if len(sys.argv) > 2 else "devtoken"
SHOTS = Path(__file__).parent / "_browser_shots"
SHOTS.mkdir(exist_ok=True)
results: list[tuple[bool, str]] = []
def check(cond: bool, msg: str):
results.append((bool(cond), msg))
print(f" {'PASS' if cond else 'FAIL'} {msg}")
def run():
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page(viewport={"width": 900, "height": 1200})
page.goto(f"{BASE}/dashboard?token={TOKEN}", wait_until="networkidle")
# 1) auto-login + nav renders
page.wait_for_selector(".navlink", timeout=15000)
check(page.is_hidden("#gate"), "signed in (gate hidden)")
# 2) go to Leads, a batch row is clickable
page.click('.navlink[data-target="leads"]')
page.wait_for_selector("#leads a.viewl", timeout=10000)
rows = page.locator("#leads a.viewl")
check(rows.count() >= 1, f"Leads shows {rows.count()} clickable batch row(s)")
# a batch contact reads "Batch: …"
check("Batch:" in page.inner_text("#leads"), 'contact shows "Batch: <email>"')
# 3) open the popup on the row that has prior history (the 8,600 edit)
target = None
for i in range(rows.count()):
if "144th" in rows.nth(i).inner_text():
target = rows.nth(i)
break
(target or rows.first).click()
page.wait_for_selector("#resultModal.show", timeout=10000)
page.wait_for_selector("#modalImgWrap img", timeout=10000)
check(page.locator("#modalImgWrap img").count() == 1, "overlay image rendered in popup")
check(page.locator("#modalPrice tr").count() >= 1, "price breakdown rows present")
area_before = page.inner_text("#modalArea")
check("sqft" in area_before, f"lawn area shown ({area_before})")
page.screenshot(path=str(SHOTS / "01_popup_open.png"))
# 4) drag editor present + a drag changes the area and enables Save
check(page.locator("#modalImgWrap svg.lawnsvg .vh").count() >= 4, "drag handles present")
handle = page.locator("#modalImgWrap svg.lawnsvg .vh").first
box = handle.bounding_box()
page.mouse.move(box["x"] + box["width"] / 2, box["y"] + box["height"] / 2)
page.mouse.down()
page.mouse.move(box["x"] - 60, box["y"] - 60, steps=8) # drag the corner outward
page.mouse.up()
page.wait_for_timeout(200)
area_after = page.inner_text("#modalArea")
check(area_after != area_before, f"area updates live on drag ({area_before} -> {area_after})")
check(not page.locator("#modalSave").is_disabled(), "Save enabled after an edit")
page.screenshot(path=str(SHOTS / "02_after_drag.png"))
# 5) save persists (server round-trip) → "Saved."
page.click("#modalSave")
page.wait_for_function("document.getElementById('modalSaveMsg').textContent.includes('Saved')", timeout=10000)
check(True, "Save round-trip succeeded (Saved.)")
# 6) history + revert
try: # two async loadHistory() calls can flicker the box — expect() auto-retries
expect(page.locator("#modalHistory")).to_contain_text("Measurement history", timeout=10000)
check(True, "history section shown")
except Exception:
check(False, "history section shown")
revs = page.locator("#modalHistory .hrev")
check(revs.count() >= 1, f"{revs.count()} revert target(s) available")
page.screenshot(path=str(SHOTS / "03_history.png"))
area_pre_revert = page.inner_text("#modalArea")
revs.first.click()
page.wait_for_function(
f"document.getElementById('modalArea').textContent !== {area_pre_revert!r}", timeout=10000)
check(page.inner_text("#modalArea") != area_pre_revert,
f"revert changed the area ({area_pre_revert} -> {page.inner_text('#modalArea')})")
# 7) close
page.click("#modalClose")
page.wait_for_selector("#resultModal", state="hidden", timeout=5000)
check(not page.is_visible("#resultModal"), "popup closes with X")
page.screenshot(path=str(SHOTS / "04_closed.png"))
browser.close()
if __name__ == "__main__":
try:
run()
except Exception as exc: # noqa: BLE001
print(f" FAIL harness error: {type(exc).__name__}: {exc}")
results.append((False, "harness error"))
passed = sum(1 for ok, _ in results if ok)
print(f"\n{passed}/{len(results)} checks passed")
sys.exit(0 if passed == len(results) and results else 1)