Skip to main content

How It Works

pytest-web is a thin layer around standard pytest — it doesn't replace or patch pytest, it just orchestrates it.


Architecture overview

Browser (UI)
│ WebSocket (live events)
│ HTTP (fetch/run/cancel)

FastAPI server ←──── HTTP webhook ──── pytest subprocess
(server.py) (plugin.py loaded)

└── spawns ──► pytest --collect-only (Discover)
└── spawns ──► pytest <nodeids> -n N (Run)

Step by step

1. Discover

When you click Fetch Tests, the server runs:

pytest --collect-only -q -p pytest_web.plugin

The plugin writes all collected node IDs to a temp JSON file. The server reads that file and returns the list to the browser. The browser renders one row per test, grouped by file.

The -k / -m args you type are passed straight to pytest's collector — so pytest's own filtering logic applies, including parametrize expansion.

2. Run

When you click ▶ Run Selected, the server launches:

pytest <selected-nodeids> -n <workers> -p pytest_web.plugin

The plugin is injected via -p pytest_web.plugin. It reads two environment variables set by the server:

VariablePurpose
PYTEST_WEB_WEBHOOKURL to POST events to
PYTEST_WEB_RUN_IDUUID to correlate events with the current run

3. Stream events

As tests run, the plugin fires HTTP POSTs to POST /internal/event on the FastAPI server:

EventWhen
session_startAfter collection, before the first test
test_startWhen a test begins executing
test_endWhen a test finishes (with outcome + duration)
session_endWhen the entire run finishes

test_start uses a synchronous HTTP call to guarantee it arrives before the first test_end. All other events are fire-and-forget (daemon thread) so they never block test execution.

4. Broadcast to browser

The server receives each event and broadcasts it over WebSocket to all connected browser tabs. The browser updates the test row's status dot and counters in real time.

On WebSocket connect (or reconnect), the server sends a snapshot of current run state — so a refreshed browser tab can rebuild the live view without missing anything.

5. xdist deduplication

When running with multiple workers, pytest-xdist re-fires each worker's report in the master process. Without deduplication, every test would be counted twice. pytest-web tracks started and finished node ID sets per run and silently drops duplicate events.


Key design decisions

No monkey-patching — the plugin only uses standard pytest hooks (pytest_sessionstart, pytest_collection_finish, pytest_runtest_logstart, pytest_runtest_logreport, pytest_sessionfinish). Your test suite runs identically with or without the plugin.

Safe to load anywhere — the plugin is a no-op when PYTEST_WEB_WEBHOOK and PYTEST_WEB_COLLECT_FILE are absent. You can add -p pytest_web.plugin to your addopts permanently and it won't affect normal pytest runs.

No build step — the frontend is vanilla JS with no bundler. static/app.js is served directly. Frontend changes are visible on browser refresh with zero tooling.