Spaces:
Running
Running
| """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) | |