Formerly Testosterone (testo-core). Old commands, imports and config files keep working; see the rename notes.
Run and track your tests, compare and export results, and catch the flaky ones. Get deeper insight with AI, using your own model. On your laptop, in CI, or on one shared server for the whole team.
▶ Try it now, no install: drop a JUnit XML file on https://arieleven.github.io/qasight/try and see what failed, what's flaky and what's slow. It's read in your browser; nothing is uploaded.
Live demo: https://arieleven.github.io/qasight/ · Docs: https://arieleven.github.io/qasight/docs/
| Run detail (failed fake-api run) | Compare (two fake-api runs) |
|---|---|
| Cycle page with its flaky tests | Dashboard on a phone |
What you're looking at: QASight testing itself (its own test suite) and fake-api, a
small app whose routes fail on purpose. CI runs both cycles and publishes the real results as a
read-only static site.
Watch a walkthrough of the UI (animated, under a minute)
Set it up in a minute on a project you already have:
pip install 'qasight[api]'
qasight init # finds your test runners, writes qasight.yaml and a GitHub Actions workflow, records a first run
qasight ui # what broke, what's flaky, what got slower
qasight init --dry-run shows the files first; qasight init --pr also opens the pull request that
adds them. Only want to record? pytest --qasight, or qasight ingest after any runner.
No tests at hand? qasight demo records eight sample runs (a flaky test, a new failure, a slower
test) and opens the UI on them. More ways in, from Jest to go test, are in the Quickstart.
What's new
1.6.0 renames Testosterone to QASight (pip install qasight; the old testo command, config
file and testo-core package keep working) and adds qasight init to set a project up in one
command, a Report page and qasight export for managers, qasight demo, qasight try,
qasight mcp, qasight export-site for a read-only history site on GitHub Pages, quarantine from the UI, releases and environments on runs, and storage limits for
the team server. 1.5.0 added qasight affected, OpenTelemetry export and qasight pool. 1.4.0 brought qasight ingest for Jest, Vitest, .NET, Go, Robot,
Allure, RSpec, k6 and Xcode results, pytest --qasight and the GitHub Action at
uses: arieleven/qasight@v1. Every change is in CHANGELOG.md.
Who it's for
- Teams whose CI goes red and someone has to ask "is this new, flaky or just slow?" QASight
answers that from the JUnit XML (or Jest, Playwright,
go test, .NET...) results you already have. - People who want it self-hosted and free: a
pip installon a laptop, or onedocker compose upfor a shared team server. No account, no cloud, MIT licensed.
Not the right pick if you want a hosted SaaS with nothing to run, or full manual test case management. The comparison page says which tool fits better then.
Why
After a red build the question is never just "what failed?". It's what changed since the last green run, is this test flaky, and did anything get slower? CI logs and one-off HTML reports can't answer that, because they forget every run as soon as it ends. QASight records every run and answers it: per-test regressions and fixes between runs, flaky tests, durations over time, a test pyramid and an optional AI summary of the failures.
There are two ways in. qasight ingest records JUnit XML that your existing CI already writes,
so adoption is one extra step. qasight run goes further: it runs several frameworks as one
cycle of stages from a qasight.yaml (pytest, Playwright, Behave, any command), the
same way from the CLI, CI or the web UI. Point either at a shared
team server and every CI job lands in one history.
How it compares. Allure renders one run; ReportPortal is a large server to operate.
QASight is a pip install that keeps the history and diffs runs against each other, works
with whatever runner you have, and can still hand results to Allure, ReportPortal, Extent or
TestBeats as reporters. The comparison page has the details, including when another tool is the better pick.
What ships: the qasight CLI (--ci streams NDJSON events, exit codes 0–4), a FastAPI
backend (qasight_api, /api/v1, live runs over server-sent events), a React frontend
(frontend/), and GitHub Action / GitLab CI wrappers around qasight run --ci. See
ARCHITECTURE.md for how the pieces fit.
Quickstart
The shortest path, for pytest, Jest, Vitest, Playwright, Go, .NET, Maven, Gradle, RSpec and PHPUnit:
pip install qasight
qasight init --dry-run # what it would write: qasight.yaml, .github/workflows/qasight.yml, .gitignore lines
qasight init --pr # writes them, records a first run and opens a pull request (needs gh; without it, prints the git commands)
No Python project? uvx --from qasight qasight init or pipx run --spec qasight qasight init runs it without
a virtualenv. Other CI systems: qasight init --ci gitlab|jenkins|circleci.
Already have tests that write JUnit XML, or a runner's own report? Record them, no config needed:
pip install qasight
npx jest --ci --json --outputFile=jest.json # or pytest --junitxml, go test -json, ...
qasight ingest jest.json --cycle web # recorded; run again to see the diff
qasight summary --cycle web
Using pytest? pytest --qasight records every run the same way, with no extra step.
Or let QASight run the tests as a cycle:
pip install qasight # the `qasight` CLI; add [api] for the REST API, [db] for Postgres,
# [bdd] for Behave and BehaveX
qasight config init # writes a starter qasight.yaml for pytest
qasight run --cycle smoke-test # runs pytest -q tests; edit args for a different test directory
An installed qasight keeps per-run report copies in ./.testo/ (set TESTO_HOME to move them).
Getting Started goes further in about five minutes: a regression between two runs, then the same cycle in GitHub Actions.
Or work from a checkout of this repository, which ships sample cycles:
git clone https://github.com/arieleven/qasight.git
cd qasight
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
qasight run --cycle sample-pytests # run a cycle from qasight.yaml
qasight report --cycle sample-pytests # build the Allure report for it
Watch it run: install, qasight doctor, then a cycle against the deliberately broken fake-api app
Adding QASight to your own project with an AI coding assistant? Paste one of the Setup Prompts.
Runs are stored in SQLite by default (database.url in qasight.yaml, or
DATABASE_URL). Postgres and MySQL work too; docker compose up -d postgres starts one.
UI
pip install 'qasight[api]'
qasight ui # API + web UI on http://127.0.0.1:8000, opens the browser
The wheel carries a built UI (1.3.0 and later), so this needs no checkout and no Node. Working on the frontend itself:
qasight-api # FastAPI on http://127.0.0.1:8000
npm --prefix frontend install
npm --prefix frontend run dev # React on http://localhost:5173
Pages: Dashboard (what needs attention first), Cycles (run any cycle and watch its stages live, see its flaky tests), Runs and Run Detail (failing tests first, reports, test pyramid, AI summary), a page per test (history, attachments, Playwright trace), Compare (delta between two runs), Project health, Resources, Quick Run (run one framework against a repo without defining a cycle) and AI settings.
Defining cycles
version: 1
defaults:
target_repo: .
artifacts_root: artifacts
timeout_s: 600
reporters:
- type: allure
cycles:
smoke:
description: Unit tests, then BDD flows.
trigger: # optional: skip unless these paths changed
paths: ["src/**", "tests/**"]
stages:
- name: unit
equipment: pytest
args: [-q, tests/unit]
- name: flows
equipment: behave
args: [features/smoke.feature] # always target Behave features explicitly
- name: browser
equipment: playwright # npx playwright test, JUnit + HTML report wired in
args: [tests/e2e]
- name: web
equipment: command # any runner; results come from JUnit XML
args: [npx, jest, --ci]
junit_xml: ["reports/junit/*.xml"]
Built-in equipment: pytest, behave, behavex, playwright, command and junit (records
JUnit XML another step wrote; what qasight ingest uses). behave and behavex need
pip install 'qasight[bdd]'. A package can add more (see Plugins).
qasight init writes one for you (it detects pytest, Jest, Vitest, Go, .NET, Maven, Gradle, RSpec
and PHPUnit, and also writes the GitHub Actions workflow); qasight config validate checks it. The full
schema and every flag are in Command Reference.
CLI
| Command | What it does |
|---|---|
qasight run --cycle <name> |
Run a cycle (--ci NDJSON, --stream live output, --force ignore triggers, --no-persist) |
qasight ingest <files> --cycle <name> |
Record JUnit XML from any runner as a run (--server/--token upload to a team server) |
qasight report … |
Generate, list, open and export reports (qasight diff compares two archived runs) |
qasight report markdown --cycle <name> |
The latest run as Markdown: results, failing tests, changes since the previous run (job summary or PR comment) |
qasight summary |
Rich diff of the two most recent runs (--cycle to pick one) |
qasight cycles list/show |
Inspect configured cycles |
qasight watch --cycle <name> |
Re-run a cycle when files change |
qasight flaky --cycle <name> |
Tests that keep flipping between pass and fail across recent runs (--fail-on-flaky for CI) |
qasight plugins |
Built-in and installed frameworks and reporters (--check imports them, --json) |
qasight ui |
API and web UI on http://127.0.0.1:8000 (needs the api extra) |
qasight mcp |
The run history as an MCP server for coding agents over stdio (needs the mcp extra; --allow-writes for triage) |
qasight demo |
Record eight sample runs with a flaky test, a new failure and a slower test, then open the UI on them |
qasight badge --cycle <name> |
A README badge for a cycle's latest run (SVG or shields.io endpoint JSON) |
qasight export pdf|pptx|xlsx|png|html|csv|json |
The manager report (the UI's Report page) as a file; --period day|week|month|quarter (PDF, PNG, slides and Excel need the export extra) |
qasight lease … |
Resource pools: give each parallel worker its own VM, device or test account |
qasight doctor |
Check config, database and tools on PATH |
Exit codes: 0 success (or trigger-skipped), 1 tests failed, 2 invalid input,
3 infrastructure failure, 4 internal error.
Details: Troubleshooting and Error Codes.
See it in a terminal: qasight flaky, qasight report markdown and qasight plugins on the fake-api cycle
API
| Area | Endpoints (/api/v1) |
|---|---|
| Cycles | GET /cycles, GET /cycles/{cycle} |
| Executions | POST /cycles/{cycle}/executions, POST /adhoc-executions, GET /cycle-executions/{id}, GET /cycle-executions/{id}/events (SSE) |
| Ingest | POST /ingest (JUnit XML upload from qasight ingest --server) |
| Runs | GET /runs, GET /runs/{id}, GET /runs/{id}/reports, GET /runs/{id}/pyramid, GET /runs/{id}/markdown |
| Report | GET /report (?period=&cycle=), GET /report/export?format=pdf|pptx|xlsx|png|html|csv|json |
| Analytics | GET /analytics/delta, GET /analytics/delta/cases, GET /analytics/flaky, GET /dashboard/overview, GET /dashboard/runs/recent |
| AI (bring your own key) | GET /ai/config/status, PUT /ai/config, GET /runs/{id}/ai-summary, POST /runs/{id}/ai-summary:generate, POST /ask |
| Badge | GET /badge.svg, GET /badge.json (?cycle=) |
| Health | GET /health/live, GET /health/ready |
Interactive docs at http://127.0.0.1:8000/docs while qasight-api runs.
CI
GitHub Actions:
- uses: arieleven/qasight@v1
with:
cycle: smoke # optional: config-path, ci-mode, persist, python-version
comment-on-pr: "true" # results as a pull request comment (needs pull-requests: write)
GitLab CI (include the template straight from GitHub, or copy ci/gitlab/testo.gitlab-ci.yml into your project):
include:
- remote: "https://raw.githubusercontent.com/arieleven/qasight/v1/ci/gitlab/testo.gitlab-ci.yml"
variables:
TESTO_CYCLE: "smoke"
Both run qasight run --ci, keep the NDJSON event stream, and expose the final plan_finished
event (exit code, per-stage results). The GitHub Action also writes the run to the job summary
(job-summary, on by default) and, with comment-on-pr, keeps one up-to-date comment per cycle
on the pull request: results per stage, failing tests and per-test changes since the last run. Run records written in CI carry the provider, pipeline,
job, commit and branch. Inputs and outputs: integrations/github-action/README.md.
Optional integrations
- Metrics push: after each run, test KPIs go to InfluxDB when
INFLUXDB_URL,INFLUXDB_TOKEN,INFLUXDB_ORGandINFLUXDB_BUCKETare set, and to a Prometheus Pushgateway whenPROMETHEUS_PUSHGATEWAY_URLis set (PROMETHEUS_JOB_NAMEdefaults toqasight). Pushes are best-effort and never change the run result. - Reporters:
allure,extent,reportportal,testbeatsunderreporters:. - Plugins: a package adds a test runner or a reporter through the
qasight.frameworks/qasight.reportersentry point groups, with no change to QASight;qasight pluginslists and checks them. examples/plugin/ adds aunittestrunner and ajson-summaryreporter. - AI failure summaries: opt in on the AI settings page with an OpenAI or Anthropic key, or point it at any OpenAI-compatible endpoint (a free tier such as Groq or Gemini, or a local Ollama). Running locally with Claude Code installed? Pick "Claude Code on this machine" and it uses your existing
claudelogin, no key. Failed runs store their failing cases, first traceback and log tail (redacted), which is what the summary is built from.
Development
pip install -e ".[dev]"
pre-commit install
ruff check .
ruff format --check .
mypy qasight qasight_api
pytest -q -m "tier_fast and not quarantined" --no-cov
npm --prefix frontend run typecheck
npm --prefix frontend run lint
npm --prefix frontend test
ruff check, ruff format --check and mypy qasight qasight_api are all blocking in CI
(.github/workflows/ci.yml's format job), as are the frontend typecheck and tests.
See CONTRIBUTING.md for the full checklist. Start with
ARCHITECTURE.md for the design; docs/ holds the design
notes and ADRs, also published as the docs site
(pip install -e ".[docs]" and mkdocs serve to preview it).
Contributors
Thank you to everyone who has helped make QASight better:
| Contributor | Contributions |
|---|---|
| @arieleven | Creator and maintainer |
| @evidentart | Run history records the repository each run tested (#115, fixes #102) |
Want to join them? The good first issues are a friendly place to start, and CONTRIBUTING.md walks you through the setup.
History
The project started as UQO ("Unified Quality Orchestrator") and was renamed to QASight
(the uqo command and UQO_* variables became qasight and TESTO_*). v1.1 replaced the Streamlit UI
with the React frontend and dropped the Docker-based headless runner (stages now run as host
subprocesses). Full details in CHANGELOG.md.
Migrating from v1.0
| v1.0 | v1.1 |
|---|---|
Streamlit UI (testo-ui, streamlit run app.py) |
React frontend (frontend/) |
uqo command, UQO_* env vars |
qasight, TESTO_* |
uqo run --config runs.yaml (runs: list of test_type/cli_args) |
a cycle in qasight.yaml, run with qasight run --cycle <name> |
--ghost, --json, --stream-json |
--ci (NDJSON events, plan_finished last) |
POST /api/v1/executions + /executions/{id}/events |
POST /api/v1/adhoc-executions (one framework) or POST /api/v1/cycles/{cycle}/executions; status and SSE under /cycle-executions/{id} |
| "Legacy Execution" page | Quick Run (/quick-run) |
Tests run in one-off Docker containers (UQO_RUNNER_IMAGE, UQO_RUNNER_PREBUILT) |
stages run as host subprocesses; use Dockerfile.qasight-runner as the CI job image if you want isolation |
| Allure results uploaded to MinIO per run | per-run reports under static/history/<run_id>/ (served at /history) and the report archive DB |
locust test type |
equipment: command with args: [locust, --headless, …] |
Pluggy plugins/*.py runner hooks |
a framework adapter or reporter in your own package, registered as a qasight.frameworks / qasight.reporters entry point (see examples/plugin/) |
Metadata
Release files for qasight 1.6.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| qasight-1.6.0.tar.gz | 1.1 MB | With provenance | Details for qasight-1.6.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| qasight-1.6.0-py3-none-any.whl | Python 3 | none | any | Details for qasight-1.6.0-py3-none-any.whl |
Total release size: 2.0 MB
Release files / qasight-1.6.0.tar.gz
| Download URL | qasight-1.6.0.tar.gz |
|---|---|
| Size | 1.1 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
99b0d387be638590e64b7bddbb5d3ff2dfaf167d45b3e36ee6c99c8021a0be73
|
|
BLAKE2b-256 checksum How to use checksums |
e79c6d7b4d47bc821ffbc108984d5e1c5fd1117c1ce340f5dcaafc1b660ba7ca
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 5, 2026.
Transparency logRelease files / qasight-1.6.0-py3-none-any.whl
| Download URL | qasight-1.6.0-py3-none-any.whl |
|---|---|
| Size | 885.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
535f7e583074be2d90d35b0ba582340e63c692bac7bce0d4ff23823560053156
|
|
BLAKE2b-256 checksum How to use checksums |
8aae12c08abf8f184c79334c8298e9ca05b0943af238ea1d6f11b178c7d32b2c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 5, 2026.
Transparency logSecurity for qasight 1.6.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for qasight-1.6.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 5, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.