246 lines
10 KiB
Markdown
246 lines
10 KiB
Markdown
---
|
|
phase: 09-ux-tech-debt-closure
|
|
plan: 02
|
|
type: execute
|
|
wave: 2
|
|
depends_on: ["09-01"]
|
|
files_modified:
|
|
- requirements-dev.txt
|
|
- tests/e2e/__init__.py
|
|
- tests/e2e/conftest.py
|
|
- tests/e2e/test_port_autofill.py
|
|
autonomous: true
|
|
requirements: [UX-02]
|
|
must_haves:
|
|
truths:
|
|
- "A headless chromium browser loads the printer form, types an IP, and observes the port_name input auto-populate with IP_<dotted_underscore>"
|
|
- "The Playwright test file path is the cited evidence for UX-02 in 09-VALIDATION.md"
|
|
- "The e2e suite runs in isolation from unit tests via --ignore path and has its own live server fixture"
|
|
artifacts:
|
|
- path: "requirements-dev.txt"
|
|
provides: "pytest-playwright + playwright dev deps"
|
|
contains: "pytest-playwright"
|
|
- path: "tests/e2e/conftest.py"
|
|
provides: "Session-scoped live_server fixture (uvicorn in thread) with tmp data dir + /health readiness poll"
|
|
contains: "live_server"
|
|
- path: "tests/e2e/test_port_autofill.py"
|
|
provides: "UX-02 Playwright headless test"
|
|
contains: "test_port_autofill"
|
|
key_links:
|
|
- from: "tests/e2e/test_port_autofill.py"
|
|
to: "imptune.main:app (uvicorn thread)"
|
|
via: "live_server fixture yields http://127.0.0.1:<port>"
|
|
pattern: "live_server"
|
|
- from: "tests/e2e/test_port_autofill.py"
|
|
to: "printer_form.html Alpine @input handler"
|
|
via: "page.fill on ip_address, assert on port_name input_value"
|
|
pattern: "port_name"
|
|
---
|
|
|
|
<objective>
|
|
Add Playwright as a dev dependency and write a headless chromium test that loads the printer form, types an IP address, and asserts the port_name field auto-populates via the existing Alpine.js `@input` handler. The test file itself becomes the permanent evidence for UX-02.
|
|
|
|
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.
|
|
</objective>
|
|
|
|
<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>
|
|
|
|
<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.txt
|
|
|
|
<interfaces>
|
|
<!-- Key contracts for this plan -->
|
|
|
|
From imptune/main.py: exports `app: FastAPI`. Has a `GET /health` endpoint suitable for readiness polling.
|
|
|
|
Alpine handler already present in printer_form.html:
|
|
```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 `page` fixture.
|
|
- `page.goto(url)` — navigate
|
|
- `page.fill(selector, value)` — fill an input (triggers `input` event, which Alpine `@input` listens to)
|
|
- `page.input_value(selector)` — read current value of an input
|
|
</interfaces>
|
|
</context>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto">
|
|
<name>Task 1: Add Playwright dev deps + e2e package scaffolding</name>
|
|
<files>requirements-dev.txt, tests/e2e/__init__.py, tests/e2e/conftest.py</files>
|
|
<action>
|
|
Step 1 — Append to `requirements-dev.txt`:
|
|
```
|
|
pytest-playwright
|
|
playwright
|
|
```
|
|
Do NOT touch `requirements.txt` — production image must not install Playwright.
|
|
|
|
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.
|
|
</action>
|
|
<verify>
|
|
<automated>python -c "import pytest_playwright, playwright; print('playwright ok')" && pytest --collect-only tests/e2e/ 2>&1 | head -20</automated>
|
|
</verify>
|
|
<done>pytest-playwright + playwright on requirements-dev.txt. chromium binary installed. tests/e2e/ package exists with live_server fixture. `pytest --collect-only tests/e2e/` reports 0 tests with no import errors.</done>
|
|
</task>
|
|
|
|
<task type="auto" tdd="true">
|
|
<name>Task 2: Write UX-02 Playwright test for IP→port auto-fill</name>
|
|
<files>tests/e2e/test_port_autofill.py</files>
|
|
<behavior>
|
|
- Loads the printer form page in chromium.
|
|
- Fills `input[name='ip_address']` with `192.168.1.100`.
|
|
- Asserts `input[name='port_name']` input value equals `IP_192_168_1_100`.
|
|
- Test passes (Alpine handler already exists and is known-working).
|
|
</behavior>
|
|
<action>
|
|
Create `tests/e2e/test_port_autofill.py`:
|
|
```python
|
|
"""UX-02: live-browser verification of PRNT-03 Alpine IP→port auto-derivation."""
|
|
from __future__ import annotations
|
|
|
|
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`
|
|
</action>
|
|
<verify>
|
|
<automated>pytest tests/e2e/test_port_autofill.py -v</automated>
|
|
</verify>
|
|
<done>Playwright test green. tests/e2e/test_port_autofill.py file path cited as evidence for UX-02 in 09-VALIDATION.md.</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<verification>
|
|
- `pytest tests/e2e/ -v` passes
|
|
- `pytest tests/ -x -q --ignore=tests/e2e` still passes (no unit regressions)
|
|
- Optional live eyeball: `pytest tests/e2e/test_port_autofill.py -v --headed` to observe the auto-fill in a visible browser window
|
|
</verification>
|
|
|
|
<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>
|
|
|
|
<output>
|
|
After completion, create `.planning/phases/09-ux-tech-debt-closure/09-02-SUMMARY.md` with: exact printer form route used, pytest command run, green output snippet, and 09-VALIDATION.md citation update.
|
|
</output>
|