Skip to main content

Testosterone

One YAML file to run your whole test cycle (pytest, Behave, BehaveX or any JUnit-writing command), then see what changed since the last run.

CI Pages demo PyPI Python 3.11+ License: MIT

Live demo: https://taltal-beep.github.io/testosterone/

Dashboard: health, pass count and duration trends for the latest run, with the recent self-test and fake-api runs

Run detail (failed fake-api run) Compare (two fake-api runs)
Run detail of a failed fake-api run: summary, test pyramid and per-stage health Compare view: outcome mix, reliability and performance deltas, and per-stage health between two fake-api runs
Cycles Dashboard on a phone
Cycles page listing the self-test and fake-api cycles with their stages and frameworks Dashboard at phone width: stacked health, failed, pass count and duration cards

What you're looking at: Testosterone 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, about 1 minute)

Walkthrough of the web UI: starting the fake-api cycle from the browser with live stage status and event stream, then browsing the results

Why

Most projects run several test frameworks, each with its own command, report format and CI glue. Testosterone puts them in one testosterone.yaml as a cycle of stages, runs it, records every run, and answers the question you actually have after a red build: what changed? You get run-to-run deltas, a test pyramid, and an optional AI summary of the failures. The same engine runs the cycle whether you start it from the CLI, from CI, or from the web UI.

How it compares. Allure and ReportPortal display the results of a run someone else started. Testosterone starts the run: it orchestrates the stages, keeps the history and diffs runs against each other, then hands results to Allure, ReportPortal, Extent or TestBeats as reporters. It sits upstream of those tools rather than replacing them.

Architecture: the REST API and testo CLI both call CycleRunService, which runs config, the engine and reporting; the engine drives framework adapters and saves results through persistence and the repository; insight services read run history through the same repository

What ships: the testo CLI (--ci streams NDJSON events, exit codes 0–4), a FastAPI backend (testo_api, /api/v1, live runs over server-sent events), a React frontend (frontend/), and GitHub Action / GitLab CI wrappers around testo run --ci. See ARCHITECTURE.md for how the pieces fit.

Quickstart

Install the CLI from PyPI and point it at your own tests:

pip install testo-core            # the `testo` CLI; add [api] for the REST API, [db] for Postgres
testo config init                 # writes a starter testosterone.yaml; edit its stages
testo run --cycle smoke-test      # run a cycle from it

Or work from a checkout of this repository, which ships sample cycles:

git clone https://github.com/taltal-beep/testosterone.git
cd testosterone
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"

testo run --cycle sample-pytests      # run a cycle from testosterone.yaml
testo report --cycle sample-pytests   # build the Allure report for it
Watch it run: install, testo doctor, then a cycle against the deliberately broken fake-api app

Terminal recording: installing from a checkout, testo doctor passing, then showing and running the fake-api cycle

Adding Testosterone 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 testosterone.yaml, or DATABASE_URL). Postgres and MySQL work too; docker compose up -d postgres starts one.

UI

testo-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, Cycles (run any cycle and watch its stages live), Runs and Run Detail (reports, test pyramid, AI summary), Compare (delta between two runs), 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: web
        equipment: command               # any runner; results come from JUnit XML
        args: [npx, jest, --ci]
        junit_xml: ["reports/junit/*.xml"]

testo config validate checks the file; testo init writes one interactively. The full schema and every flag are in Command Reference.

CLI

Command What it does
testo run --cycle <name> Run a cycle (--ci NDJSON, --stream live output, --force ignore triggers, --no-persist)
testo report … Generate, list, open and export reports (testo diff compares two archived runs)
testo cycles list/show Inspect configured cycles
testo watch --cycle <name> Re-run a cycle when files change
testo 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.

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)
Runs GET /runs, GET /runs/{id}, GET /runs/{id}/reports, GET /runs/{id}/pyramid
Analytics GET /analytics/delta, GET /analytics/delta/cases, 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
Health GET /health/live, GET /health/ready

Interactive docs at http://127.0.0.1:8000/docs while testo-api runs.

CI

GitHub Actions:

- uses: taltal-beep/testosterone/integrations/github-action@v1
  with:
    cycle: smoke          # optional: config-path, ci-mode, persist, python-version

GitLab CI (a GitLab mirror of this repo is coming; until then, include the template straight from GitHub, or copy ci/gitlab/testo.gitlab-ci.yml):

include:
  - remote: "https://raw.githubusercontent.com/taltal-beep/testosterone/v1/ci/gitlab/testo.gitlab-ci.yml"
variables:
  TESTO_CYCLE: "smoke"

Both run testo run --ci, keep the NDJSON event stream, and expose the final plan_finished event (exit code, per-stage results). 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_ORG and INFLUXDB_BUCKET are set, and to a Prometheus Pushgateway when PROMETHEUS_PUSHGATEWAY_URL is set (PROMETHEUS_JOB_NAME defaults to testo). Pushes are best-effort and never change the run result.
  • Reporters: allure, extent, reportportal, testbeats under reporters:.
  • AI failure summaries: opt in on the AI settings page with an OpenAI or Anthropic 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 testo_core testo_api
pytest -q -m "tier_fast and not quarantined" --no-cov
npm --prefix frontend run typecheck
npm --prefix frontend test

ruff check, ruff format --check and mypy testo_core testo_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 internal design notes and ADRs (an Obsidian vault, so some links only resolve inside Obsidian).

History

The project started as UQO ("Unified Quality Orchestrator") and was renamed to Testosterone (the uqo command and UQO_* variables became testo 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 testo, TESTO_*
uqo run --config runs.yaml (runs: list of test_type/cli_args) a cycle in testosterone.yaml, run with testo 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.testo-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 in testo_core/frameworks/

Metadata

Release files for testo-core 1.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for testo-core 1.1.1
File Size Uploaded
testo_core-1.1.1.tar.gz 262.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for testo-core 1.1.1
File Interpreter ABI Platform
testo_core-1.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 477.6 kB

Release files / testo_core-1.1.1.tar.gz

Download URL testo_core-1.1.1.tar.gz
Size 262.2 kB
Tags Source
SHA-256 checksum
How to use checksums
a2396a34daff21a5a2e12f163629596e3c630608e6917f500b5a09c73bf3286c
BLAKE2b-256 checksum
How to use checksums
83401b56c8bbb770615ae60a59e85e7c9c5d6d909c9df3e004e6dabd66d755ba
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Oct 19, 2026.

Transparency log

Release files / testo_core-1.1.1-py3-none-any.whl

Download URL testo_core-1.1.1-py3-none-any.whl
Size 215.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4dda72dfa54d4462f026d5d3bff073723b5651595f9a0acdcb2e19f38ed67e40
BLAKE2b-256 checksum
How to use checksums
d8e577e6682dbc0d2ed9a68010bbd5fe87795364c26a2fc88815a91971be48bd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Oct 19, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.1.1 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page