Production-grade pytest HTML dashboard with unified network capture (mitmproxy / Playwright native / Selenium 4 BiDi / HAR ingest), header/body redaction, doctor health checks, and multi-format export (PDF/CSV/JSON/XLSX/DOCX/ZIP).
Project description
qa-dashboard
A pip-installable pytest plugin that generates a suite-level HTML dashboard from a pytest run — including test results, network captures, screenshots, console logs, and multi-format export (PDF, CSV, JSON, XLSX, DOCX, ZIP).
Unified network capture (v0.7.0+). One flows[] schema, four
sources:
- mitmproxy for pytest (Appium / native apps / anywhere)
- Native request/response listeners for Playwright JS/TS
- Selenium 4 BiDi (CDP fallback) for Python Selenium tests
- HAR ingest for everything else (BrowserMob, k6, JMeter,
browser DevTools exports, Playwright
recordHar)
Also ingests JUnit XML output, so Java (Maven Surefire / TestNG / JUnit 5) and JavaScript (Jest / Playwright JS / Mocha) projects can feed into the same dashboard.
Quickstart — Python / pytest
pip install qa-dashboard
python -m pytest --qa-dashboard-mitm-skip
open captures/run_*/index.html
No conftest.py changes needed. The plugin auto-registers.
Quickstart — Java / JavaScript via JUnit XML
pip install qa-dashboard
# After your normal `mvn test` (Surefire) or `gradle test` run:
qa-dashboard ingest target/surefire-reports/ \
--screenshots-dir target/screenshots/
open captures/run_*/index.html
Quickstart — HAR file (any source)
pip install qa-dashboard
# Drop in a HAR exported from browser DevTools, Playwright recordHar,
# BrowserMob, mitmproxy hardump, k6, JMeter, Postman, etc.
qa-dashboard ingest path/to/trace.har
open captures/run_*/index.html
Format auto-detects from .har / .xml extensions, or override with
--format har / --format junit-xml.
Pytest options
| Option | Default | Effect |
|---|---|---|
--qa-dashboard-dir |
captures |
Where to write run directories |
--qa-dashboard-mitm-port |
8080 |
Port for mitmdump |
--qa-dashboard-mitm-skip |
off | Run without mitmproxy |
--qa-dashboard-host-filter |
(empty) | Only capture flows matching host |
--qa-dashboard-screenshot-on-fail |
true |
Auto-screenshot on failure |
--qa-dashboard-no-report |
off | Skip HTML generation |
--qa-dashboard-pretty |
true |
Pretty-print JSON |
--qa-dashboard-brand-color |
(none) | Brand accent color (e.g. #1d4ed8) |
--qa-dashboard-screenshot-max-width |
(none) | Downscale screenshots (requires Pillow) |
Environment variables shadow CLI options
(QA_DASHBOARD_DIR, QA_DASHBOARD_MITM_PORT,
QA_DASHBOARD_MITM_SKIP, QA_DASHBOARD_HOST_FILTER,
QA_DASHBOARD_SCREENSHOT_ON_FAIL, QA_DASHBOARD_NO_REPORT,
QA_DASHBOARD_PRETTY, QA_DASHBOARD_SCREENSHOT_MAX_WIDTH,
QA_DASHBOARD_BRAND_COLOR).
Setting options once (no per-run flags)
You don't need to pass these flags on every pytest command. Pick
whichever fits your project:
1. pyproject.toml (recommended for pytest projects)
[tool.pytest.ini_options]
addopts = "--qa-dashboard-dir=captures --qa-dashboard-brand-color=#1d4ed8"
2. pytest.ini / setup.cfg — same idea, addopts = ... under
[pytest] / [tool:pytest].
3. Environment variables — commit a .env or set them in CI:
export QA_DASHBOARD_DIR=captures
export QA_DASHBOARD_SCREENSHOT_ON_FAIL=true
export QA_DASHBOARD_BRAND_COLOR=#1d4ed8
Precedence: CLI flag > environment variable > built-in default.
Using the step fixture (optional but recommended)
def test_login(driver, step):
with step("Launch app"):
driver.launch_app()
step.screenshot(driver.get_screenshot_as_png(), label="splash")
with step("Tap sign in") as s:
s.substep("Locate button", lambda: driver.find(...))
s.substep("Tap", lambda: driver.tap())
with step("Verify home renders"):
assert driver.find_element(By.ID, "home").is_displayed()
step is auto-injected — no imports needed.
Auto-screenshot on failure (zero code required)
In v0.3.0+ the plugin detects driver / page fixtures in your tests
and captures a PNG automatically when a test fails. No callback to
register, no conftest.py changes.
Multi-format export
From the browser
The HTML report has a ⇩ Download ▾ menu in the header:
- Save as PDF (via browser print)
- Test summary (CSV)
- Full report (JSON)
- Failed tests only (CSV)
From the command line
qa-dashboard <run-dir> --export csv
qa-dashboard <run-dir> --export json
qa-dashboard <run-dir> --export zip # whole run, shareable
qa-dashboard <run-dir> --export xlsx # 4-sheet workbook (requires openpyxl)
qa-dashboard <run-dir> --export docx # Word report with embedded screenshots (requires python-docx)
Install with extras:
pip install qa-dashboard[xlsx] # adds openpyxl
pip install qa-dashboard[docx] # adds python-docx
pip install qa-dashboard[all] # everything (Pillow + openpyxl + python-docx)
Network capture support matrix
| Source | Mechanism | HTTPS / cert install? | Notes |
|---|---|---|---|
| pytest + Appium / native apps | mitmproxy auto-spawn | yes — manual | Default for pytest; works against any client that honours HTTP(S)_PROXY |
| pytest + Selenium 4 (Chrome/Edge) | BiDi/CDP via bidi_network_capture |
no | See Selenium 4 BiDi capture |
| Playwright JS/TS | Native context.on('request'/'response') |
no | Opt-in via qa-dashboard-fixtures.js |
| WebdriverIO | (planned) — use HAR ingest in the meantime | — | |
| Anything else | HAR ingest (qa-dashboard ingest *.har) |
no | DevTools, BrowserMob, k6, JMeter, Postman, Playwright recordHar |
Selenium 4 BiDi network capture (Python)
Use a Selenium 4 WebDriver in a pytest test, get full request/response data in the dashboard without setting up mitmproxy or installing a CA cert. Works automatically against Chromium-based drivers; Firefox support depends on your Selenium version's BiDi network module.
# conftest.py
import pytest
from selenium import webdriver
from qa_dashboard.integrations.selenium import bidi_network_capture
@pytest.fixture
def driver():
d = webdriver.Chrome()
with bidi_network_capture(d):
yield d
d.quit()
The context manager finds the active qa-dashboard run, subscribes to
network events for the lifetime of the driver, and writes captured
flows into the same _flows/<test_id>.jsonl layout the rest of the
plugin uses.
For browsers/drivers where automatic capture isn't available, push flows directly into the unified sink:
from qa_dashboard.integrations.selenium import FlowSink
sink = FlowSink() # auto-discovers run dir + active test_id
sink.record(
method="GET",
url="https://api.example.com/users",
status_code=200,
response_headers={"content-type": "application/json"},
response_body=b'{"ok": true}',
duration_ms=42,
)
Playwright TypeScript / JavaScript — native reporter (v0.7.0)
For richer step-level data than JUnit XML can carry, drop the bundled Playwright reporter into your project. One command:
cd /your/playwright-ts-project
qa-dashboard scaffold playwright
That writes qa-dashboard-reporter.js to the project root and prints
the playwright.config.ts snippet you need:
import { defineConfig } from '@playwright/test';
export default defineConfig({
reporter: [
['list'],
['./qa-dashboard-reporter.js', { outputDir: 'captures' }],
],
use: {
screenshot: 'only-on-failure',
trace: 'retain-on-failure',
},
});
Then your normal workflow:
npx playwright test
qa-dashboard captures/run_* # generates the HTML
open captures/run_*/index.html
What you get vs. the JUnit XML path:
| Feature | qa-dashboard ingest (JUnit) |
Native reporter (v0.7.0) |
|---|---|---|
| Test results, durations, errors | ✅ | ✅ |
| Auto-screenshot on failure | ✅ (with --screenshots-dir) |
✅ (from Playwright attachments) |
| Step / sub-step breakdown | ❌ | ✅ via test.step() |
| Sub-step timings | ❌ | ✅ |
| Console logs with timestamps + levels | ⚠️ heuristic | ✅ from stdout/stderr |
| Per-step screenshot grouping | ❌ | ✅ |
| Nested step support | ❌ | ✅ |
| Network capture (request + response bodies) | ❌ | ✅ via opt-in fixtures (below) |
Native network capture (opt-in)
qa-dashboard scaffold playwright now drops two files into your
project: qa-dashboard-reporter.js (the reporter) and
qa-dashboard-fixtures.js (an extended test fixture that subscribes
to BrowserContext request/response events).
Switch your test imports to pick up automatic capture:
// Before:
// import { test, expect } from '@playwright/test';
// After:
const { test, expect } = require('./qa-dashboard-fixtures');
That's it — every test now records its full network trace into the
unified flows[] schema and you'll see a populated Network tab
in the dashboard. Bodies are capped at 100 KB (configurable via
test.use({ qaDashboard: { maxBodyBytes: 524288 } })).
Tests using Playwright's native step syntax produce full breakdowns:
test('login flow', async ({ page }) => {
await test.step('Open login page', async () => {
await page.goto('/login');
});
await test.step('Enter credentials', async () => {
await page.fill('#email', 'x@y.com');
await page.fill('#password', 'pw');
});
await test.step('Submit and verify', async () => {
await page.click('#submit');
await expect(page).toHaveURL('/home');
});
});
…shows up as three explicit steps in the dashboard with individual timings, nested actions as sub-steps, and any screenshots attached to the step where they were taken.
WebdriverIO — native reporter (v0.7.0)
For WDIO + Mocha / Jasmine / Cucumber projects, drop the bundled reporter into your project. One command:
cd /your/wdio-project
qa-dashboard scaffold webdriverio
That writes qa-dashboard-wdio-reporter.js to the project root and
prints the wdio.conf.js snippet you need:
const QaDashboardReporter = require('./qa-dashboard-wdio-reporter');
exports.config = {
reporters: [
'spec',
[QaDashboardReporter, { outputDir: 'captures' }],
],
// ...
};
Then your normal workflow:
npx wdio run wdio.conf.js
qa-dashboard captures/run_*
open captures/run_*/index.html
What you get automatically:
- Per-test pass/fail/skip with durations
- Suite hierarchy in test nodeids (
Suite > Sub-suite > test name) - Auto-capture of
browser.takeScreenshot()results - Auto-capture of
browser.saveScreenshot(path)files - Failure error message + stack trace
- Capability info (browser, platform, Appium device) attached as
device - Multi-worker safe — workers converge on a single run directory via a
lockfile in
outputDir
Java / JavaScript integration via qa-dashboard ingest (JUnit XML)
# Maven (Surefire / Failsafe)
mvn test
qa-dashboard ingest target/surefire-reports/
# Gradle
./gradlew test
qa-dashboard ingest build/test-results/test/
# Jest (with junit reporter)
npx jest --reporters=jest-junit
qa-dashboard ingest junit.xml
# Attach screenshots saved by your tests:
qa-dashboard ingest target/surefire-reports/ --screenshots-dir target/screenshots/
The ingestor matches screenshots to tests by filename — if a PNG's filename contains the test method or class name, it's auto-attached.
What's automatic vs. what needs your code
Automatic with zero project code:
- Test results, durations, tags, parametrize parameters
- Console logs (stdlib
logging) - Environment info (Python, OS, pytest, git branch/commit)
- Network capture (when mitmproxy isn't skipped)
- Failure screenshots (driver/page auto-detected)
- HTML report + download buttons
Needs you to add code:
- Named per-step breakdowns (
with step("…"):) — user intent - Custom screenshot labels (
step.screenshot(png, label="…")) - Device/app metadata (
qa_dashboard_test.set_device(…))
Security & redaction
qa-dashboard captures HTTP traffic and writes it to disk. The
following headers are redacted to *** by default before being
persisted:
authorization, cookie, set-cookie, x-api-key, x-auth-token,
proxy-authorization
Extend or replace the list:
pytest --qa-dashboard-redact "authorization,x-internal-token,password"
# or
export QA_DASHBOARD_REDACT="authorization,x-internal-token,password"
Matches are case-insensitive substrings against header names and top-level JSON body keys. See SECURITY.md for the full threat model and how to report vulnerabilities.
Diagnose a run
qa-dashboard doctor captures/run_*
Validates the manifest and per-test JSON records, cross-references flows/screenshots/steps, and exits non-zero on any structural issue. Useful in CI right after a test run, before publishing the report.
Stability policy
qa-dashboard follows SemVer. Within 0.x:
- The on-disk data contract (
schema_version: 1) is stable. We will not breakmanifest.json/tests/*.jsonconsumers within a major schema version. - CLI flags and pytest options are stable across minor releases on a best-effort basis. Renames get one minor of deprecation warning.
- The Python public API is the names listed in each module's
__all__. Private names start with_. - JS reporter options are stable across minor releases.
See CHANGELOG.md for the full release history.
Contributing
See CONTRIBUTING.md. TL;DR: pip install -e ".[dev,all,mitm]", then ruff check ., mypy src/qa_dashboard,
pytest --cov=qa_dashboard --cov-fail-under=80.
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file qa_dashboard-0.8.0.tar.gz.
File metadata
- Download URL: qa_dashboard-0.8.0.tar.gz
- Upload date:
- Size: 100.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
97c4d7bab4d1f44a39279e8889342e41264271fd8c7c88c276a556984a368815
|
|
| MD5 |
4f3af47eb412a085bda38903e0df005e
|
|
| BLAKE2b-256 |
c968942c295e1e3086574a31d660519308c0efd57033c718d25a8282f48cae54
|
Provenance
The following attestation bundles were made for qa_dashboard-0.8.0.tar.gz:
Publisher:
publish.yml on GuruGd29/REPORTER-PROJECT
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
qa_dashboard-0.8.0.tar.gz -
Subject digest:
97c4d7bab4d1f44a39279e8889342e41264271fd8c7c88c276a556984a368815 - Sigstore transparency entry: 1791794231
- Sigstore integration time:
-
Permalink:
GuruGd29/REPORTER-PROJECT@8c55e132b6a5b726c551394298fd4e8b75506074 -
Branch / Tag:
refs/tags/v0.8.0 - Owner: https://github.com/GuruGd29
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8c55e132b6a5b726c551394298fd4e8b75506074 -
Trigger Event:
push
-
Statement type:
File details
Details for the file qa_dashboard-0.8.0-py3-none-any.whl.
File metadata
- Download URL: qa_dashboard-0.8.0-py3-none-any.whl
- Upload date:
- Size: 90.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
51c10253de2547daba538c58f7d05d1c44c420b79dde025a247ec406795e5d01
|
|
| MD5 |
a88e16998b0c128044439cd3f6cd1c14
|
|
| BLAKE2b-256 |
532a86d809cb591013bb3e74aaaab39815d24e9253ace0d83025e06fcae5246e
|
Provenance
The following attestation bundles were made for qa_dashboard-0.8.0-py3-none-any.whl:
Publisher:
publish.yml on GuruGd29/REPORTER-PROJECT
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
qa_dashboard-0.8.0-py3-none-any.whl -
Subject digest:
51c10253de2547daba538c58f7d05d1c44c420b79dde025a247ec406795e5d01 - Sigstore transparency entry: 1791795473
- Sigstore integration time:
-
Permalink:
GuruGd29/REPORTER-PROJECT@8c55e132b6a5b726c551394298fd4e8b75506074 -
Branch / Tag:
refs/tags/v0.8.0 - Owner: https://github.com/GuruGd29
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8c55e132b6a5b726c551394298fd4e8b75506074 -
Trigger Event:
push
-
Statement type: