Note
ARCHIVED (2026-06-11) — superseded by PLATFORM_PLAN.md, the single source of truth. Kept for historical reference. The Part E critical bugs catalogued below were fixed on 2026-06-11 (see PLATFORM_PLAN.md §14 for current status).
The product is a local test-automation platform: an admin dashboard where a user sees every test, its workflow, run history, and results. The deterministic engine (replay + checks) is the backend worker — the "sub part" that the dashboard drives. AI stays optional/bounded.
Architecture (three layers):
┌──────────────────────────────────────────────────────────────┐
│ ADMIN DASHBOARD (React/Vite frontend — studio/frontend) │
│ Dashboard · Tests · Workflow view · Run history · Reports │
└───────────────────────────┬──────────────────────────────────┘
│ REST + WebSocket (live runs)
┌───────────────────────────▼──────────────────────────────────┐
│ CONTROL API (FastAPI — studio/backend/main.py, extend) │
│ CRUD tests · trigger runs · stream progress · settings │
│ Storage: SQLite (tests, runs, results, screenshots) │
└───────────────────────────┬──────────────────────────────────┘
│ imports as a library
┌───────────────────────────▼──────────────────────────────────┐
│ ENGINE = "the sub part" (deterministic, mostly exists) │
│ Selector Engine · NavigationExecutor (replay) · Checks │
│ (visual diff, a11y, console, network) · optional bounded AI │
└──────────────────────────────────────────────────────────────┘
What already exists vs. needs building:
| Layer | Exists | To build |
|---|---|---|
| Engine | replay, recorder, scanner, screenshots, report | Selector Engine, checks (visual/a11y), AI budget |
| API | FastAPI scaffold, browser manager, CDP proxy | test CRUD, run trigger, SQLite, live WS |
| Dashboard | empty Vite scaffold + index.css |
every screen (from scratch) |
New module studio/backend/models.py (Pydantic + SQLModel/SQLAlchemy → SQLite at ~/.scrapewizard/studio.db).
TestCase id, name, url, tags[], created_at, updated_at, suite_id?
TestStep id, test_id, order, action(click/fill/wait/scroll/press/navigate),
selector_strategies[] (CSS+XPath+fallback), value?, assertions[]
Assertion id, step_id, kind(visible/text/url/a11y/visual/no_console_err), expected
Suite id, name, description
TestRun id, test_id|suite_id, status(queued/running/passed/failed),
started_at, finished_at, duration_ms, ai_calls, ai_cost
StepResult id, run_id, step_id, status, screenshot_path, visual_diff_score,
console_errors[], network_errors[], a11y_violations[], healed_by_ai(bool)
Setting key, value (ai_mode, api_key_ref, base_url, visual_threshold…)
- ✔️ DB auto-creates on first run;
GET /healthreports DB ready.
Add to studio/backend/ (split routers: routes_tests.py, routes_runs.py, routes_settings.py).
| Method | Endpoint | Purpose |
|---|---|---|
GET |
/tests |
List all tests (+ last-run status badge) |
POST |
/tests |
Create a test (from a recording .jsonl or manual) |
GET |
/tests/{id} |
Test detail + ordered steps (the workflow view) |
PUT |
/tests/{id} |
Edit name/steps/assertions |
DELETE |
/tests/{id} |
Remove |
POST |
/tests/{id}/record |
Open headed browser, capture flow → steps (reuse start_interactive_recording) |
POST |
/tests/{id}/run |
Queue a run (background task) → returns run_id |
POST |
/suites/{id}/run |
Run a whole suite |
GET |
/runs?test_id=&status= |
Run history (filterable) |
GET |
/runs/{id} |
Run detail: per-step results, screenshots, diffs, errors |
WS |
/runs/{id}/live |
Live progress stream while running |
GET/PUT |
/settings |
AI budget, thresholds, keys |
- The run executor is a thin wrapper that: loads the test → calls the engine's
NavigationExecutor+ checks per step → writesStepResultrows → emits WS progress. - ✔️
POST /tests/{id}/runproduces aTestRunrow that moves queued→running→passed/failed, with oneStepResultper step and a screenshot path each.
This is Milestones 1–3 from IMPLEMENTATION_PLAN.md, exposed as importable functions the API calls.
- Selector Engine (
scrapewizard/recon/selector_engine.py) — CSS + anchored XPath + fallback chain, container-relative, stability-filtered. (Shared with scraper.) - Replay — existing
NavigationExecutor, upgraded to resolve each step via the strategy ladder (try CSS → XPath → fallback). - Checks per step (100% deterministic, no API):
- console errors · network 4xx/5xx (scanner already intercepts)
- selector-resolved? (broken-element detection)
- visual regression — screenshot vs. baseline (pixelmatch),
visual_diff_score - accessibility — inject
axe-core, collect violations
- Bounded AI layer (optional):
ai_mode = off(default) → zero callsai_mode = heal→ max 1 call when the entire fallback chain fails, to re-locate the element by intent; markhealed_by_ai=trueai_mode = triage→ max 1 call per failed run, to summarize why it failed- Every call logged with token cost (reuse
LLMClientcost tracking) into the run.
- ✔️ A green run records
ai_calls = 0. A broken-selector run inhealmode records ≤1 call and recovers.
Stack: keep Vite + React (already scaffolded). Add a router + a data layer (React Query) + a component lib (e.g. shadcn/ui or Mantine). Build these screens:
- Cards: total tests, pass rate (last 7d), runs today, avg duration, total AI cost.
- Recent runs list (status pills) + a small pass/fail trend chart.
- ✔️ Loads stats from
/tests+/runs; empty-state guides to "Record your first test."
- Table: name · URL · #steps · last run (✅/❌/—) · last run time · Run button.
- Actions: New (record), Edit, Delete, add to Suite, filter by tag/suite.
- ✔️ Clicking Run triggers
/tests/{id}/runand navigates to the live run view.
- Vertical, ordered list of steps (click → fill → wait → assert…), each card showing: action, the resolved selector + its fallback chain, value, and attached assertions.
- Inline edit: reorder, edit selector/assertion, delete a step, add an assertion.
- ✔️ Editing a step persists via
PUT /tests/{id}and re-runs cleanly.
- Subscribes to
WS /runs/{id}/live; shows each step turning green/red in real time, with the current screenshot. - ✔️ Watching a run updates step-by-step without refresh; ends on passed/failed.
- History table (filter by test/status/date).
- Run detail = per-step timeline + screenshot, visual diff (before/after/diff), a11y violations list, console/network errors, AI heal/triage notes + cost.
- Reuse/extend
scrapewizard/report/html_generator.pyfor an exportable HTML report. - ✔️ A failed run clearly shows the failing step, its screenshot, and the reason.
- AI mode toggle (
off/heal/triage), visual-diff threshold, API key entry (stored via existing keyringsecurity.py), base URL. - ✔️ Switching to
offguarantees future runs make zero AI calls (visible in run metadata).
- Group tests into suites; run a suite; optional schedule (cron). Defer to a later milestone.
| # | File:line | Bug | Fix |
|---|---|---|---|
| 1 | core/orchestrator.py:71 |
LLMClient used in __init__, only imported locally at L854 → NameError |
Add top-level from scrapewizard.llm.client import LLMClient; drop local import |
| 2 | studio/backend/main.py:190 |
except WebSocketDisconnect but it's never imported → NameError on disconnect |
from fastapi import WebSocketDisconnect |
| 3 | studio/backend/main.py:85-99 |
browser_to_client() is a dead placeholder (only sleeps) |
Remove, or implement real CDP→client forwarding |
| # | File:line | Issue | Fix |
|---|---|---|---|
| 4 | recon/dom_analyzer.py:41-48 |
Unreachable code after return in _is_rich_container |
Delete or restore intended recursion before the return |
| 5 | core/orchestrator.py:901-906 & 918-922 |
_bundle_output copies logs/ twice |
Remove duplicate block |
| 6 | core/orchestrator.py (×2+) |
pagination_config built identically in multiple spots |
Extract build_pagination_config() |
| # | File:line | Issue | Fix |
|---|---|---|---|
| 7 | recon/dom_analyzer.py:116 |
find_all(text=True) deprecated |
find_all(string=True) |
| 8 | studio/backend/main.py:167 |
run_coroutine_threadsafe(..., get_event_loop()) fragile |
Capture loop once at startup; use a queue for WS fan-out |
-
Replace scrape-only
StudioProject/StudioState(studio/backend/state.py) usage in the test flow with the new test models (Part A) — keep scrape models separate. -
Split
main.py(200+ lines, mixed concerns) into routers + arun_executor.py. -
Add type hints + docstrings on new modules; remove
except:/bare excepts (e.g.main.py:197). -
Tests: API contract tests (
tests/studio/) for tests-CRUD + a fake run; engine unit tests from Milestone 1; one end-to-end "record → run → report" smoke test. -
✔️
pytestgreen;ruff/flake8clean on touched files; app boots with noNameError.
- E0 critical bugs — unbreak CLI + backend (hours).
- Engine M1 (Selector Engine) + per-step deterministic checks — the worker.
- Part A (SQLite models) + Part B (tests CRUD + run trigger + run history) — backend.
- Part D1–D5 dashboard screens (Dashboard, Tests, Workflow, Live run, Report) — the product.
- Part C bounded AI (
off/heal/triage) + D6 Settings. - E1–E3 cleanup folded in as modules are touched; finish with tests.
- D7 suites/scheduling + Electron packaging (
studio/desktop) — later.
First demo-able milestone: after step 4 — record a test, see its workflow, run it from the dashboard, watch it go green/red live, open the report. Zero AI calls. That's the sellable core.
- Storage: SQLite (recommended — real history/queries, still local & zero-setup) or flat JSON?
- Frontend kit: shadcn/ui, Mantine, or plain CSS? (Affects build speed.)
- Packaging: ship as a local web app (
uvicorn+ browser tab) first, or go straight to the Electron desktop shell instudio/desktop? (Web-app-first recommended — simpler.) - Auth: single local user (no login) for v1, or multi-user from the start? (Single recommended.)