Three plans covering UX-01 (driver upload 500 fix + inline HTMX OOB refresh on printer form), UX-02 (Playwright headless test for PRNT-03 IP->port auto-fill), and UX-03 (.ps1 script download routes + detail page links). VALIDATION.md finalized with real task IDs and nyquist_compliant=true. ROADMAP Phase 9 plan list filled in.
10 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 09-ux-tech-debt-closure | 02 | execute | 2 |
|
|
true |
|
|
Purpose: Closes UX-02 — produces a live-browser-verified, regression-guarded record of PRNT-03 port auto-derivation.
Output: Installable dev env (pip install -r requirements-dev.txt && playwright install chromium), green e2e test.
Depends on 09-01 because 09-01 modifies printer_form.html (adds stable id + inline upload form) and the Playwright test must run against that final template — running it on the pre-09-01 template would bake in stale assertions.
<execution_context> @C:/Users/SebastienQUEROL/.claude/get-shit-done/workflows/execute-plan.md @C:/Users/SebastienQUEROL/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/09-ux-tech-debt-closure/09-CONTEXT.md @.planning/phases/09-ux-tech-debt-closure/09-RESEARCH.md @.planning/phases/09-ux-tech-debt-closure/09-VALIDATION.md @imptune/templates/partials/printer_form.html @imptune/main.py @tests/conftest.py @requirements-dev.txtFrom imptune/main.py: exports app: FastAPI. Has a GET /health endpoint suitable for readiness polling.
Alpine handler already present in printer_form.html:
<input type="text" name="ip_address"
x-model="ip"
@input="if (!portEdited) port = 'IP_' + ip.replaceAll('.', '_')" ...>
<input type="text" name="port_name" x-model="port" ...>
Typing 192.168.1.100 into ip_address produces IP_192_168_1_100 in port_name.
Printer form route: served as an HTMX partial or full page. The e2e test must hit a route that renders printer_form.html top-level so Alpine loads. Check imptune/main.py / routers for the GET route — most likely /printers/new or /printers with a "new" partial. Confirm at implementation time.
Playwright/pytest-playwright basics:
- Plugin auto-provides
pagefixture. page.goto(url)— navigatepage.fill(selector, value)— fill an input (triggersinputevent, which Alpine@inputlistens to)page.input_value(selector)— read current value of an input
Step 2 — Create empty `tests/e2e/__init__.py`.
Step 3 — Create `tests/e2e/conftest.py` with a session-scoped `live_server` fixture:
```python
"""E2E test fixtures: live uvicorn server for Playwright."""
from __future__ import annotations
import socket
import threading
import time
from pathlib import Path
import httpx
import pytest
import uvicorn
def _free_port() -> int:
with socket.socket() as s:
s.bind(("127.0.0.1", 0))
return s.getsockname()[1]
@pytest.fixture(scope="session")
def live_server(tmp_path_factory):
"""Start the FastAPI app on a random port in a background thread."""
# Isolated data dir for E2E session
data_dir = tmp_path_factory.mktemp("imptune_e2e_data")
import imptune.config as _cfg
_cfg.DATA_DIR = data_dir
_cfg.DRIVERS_DIR = data_dir / "drivers"
_cfg.DRIVERS_DIR.mkdir(parents=True, exist_ok=True)
# Re-init DB against the tmp dir — follow the same pattern tests/conftest.py uses
from imptune.db.models import init_db # adapt import if name differs
init_db(data_dir / "imptune.db")
from imptune.main import app
port = _free_port()
config = uvicorn.Config(app, host="127.0.0.1", port=port, log_level="error")
server = uvicorn.Server(config)
thread = threading.Thread(target=server.run, daemon=True)
thread.start()
base_url = f"http://127.0.0.1:{port}"
# Readiness poll via /health (up to 5 s)
deadline = time.time() + 5.0
while time.time() < deadline:
try:
r = httpx.get(f"{base_url}/health", timeout=0.5)
if r.status_code == 200:
break
except Exception:
pass
time.sleep(0.1)
else:
raise RuntimeError("live_server did not become ready within 5 s")
yield base_url
server.should_exit = True
thread.join(timeout=2.0)
```
If `imptune/main.py` does NOT expose `GET /health`, either (a) add a trivial `@app.get("/health") def health(): return {"ok": True}` in main.py, or (b) poll the printer list route. Prefer adding /health because 09-RESEARCH.md references it.
Adapt imports if `init_db` / config names differ — match the exact pattern already used in `tests/conftest.py`. This is a straight port of the existing unit-test fixture into a session-scoped uvicorn variant.
Step 4 — Run `pip install -r requirements-dev.txt` then `playwright install chromium` in the dev environment.
import pytest
def test_port_autofill(page, live_server: str) -> None:
# Navigate to the route that renders printer_form.html as a full page.
# CONFIRM the exact path at implementation time — candidates:
# /printers/new | /printers (with HTMX modal) | /printers/form
# Pick the one that renders the Alpine x-data wrapper top-level.
page.goto(f"{live_server}/printers/new", wait_until="domcontentloaded")
# Wait for Alpine to initialise (x-data hydration)
page.wait_for_selector("input[name='ip_address']")
page.fill("input[name='ip_address']", "192.168.1.100")
# Alpine @input reacts synchronously; a short wait keeps test stable
page.wait_for_function(
"document.querySelector(\"input[name='port_name']\").value === 'IP_192_168_1_100'",
timeout=2000,
)
assert page.input_value("input[name='port_name']") == "IP_192_168_1_100"
```
Verify the printer form route name by reading `imptune/main.py` / `imptune/api/printers.py` first. If no full-page route exists and printer_form.html is only rendered as an HTMX partial, ADD a minimal GET route (e.g., `/printers/new`) that returns a full page rendering of the form (extend base.html, include printer_form.html). This is the smallest possible change and matches the user-facing flow described in 09-CONTEXT.md (technician opens the printer form).
Run: `pytest tests/e2e/test_port_autofill.py -v` — must be GREEN.
Commit: `test(09-02): add Playwright UX-02 port autofill test`
<success_criteria>
- UX-02 observable truth achieved: typing an IP into the printer form auto-populates port_name, verified in a real (headless) chromium session
- tests/e2e/test_port_autofill.py file path is the cited VALIDATION.md evidence
- No Playwright dependency in production image (only requirements-dev.txt) </success_criteria>