Repository navigation
Releases
v1.6.0
Install: pip install qasight==1.6.0 (team server and web UI: pip install "qasight[api]==1.6.0")
Testosterone is now QASight. The package is qasight, the command is qasight and the config file is qasight.yaml. Nothing old stops working: pip install -U testo-core upgrades you (it now installs qasight), and the testo command, testosterone.yaml, pytest --testo and every TESTO_* setting still work. The repository moved to arieleven/qasight; old links forward. Details: Rename to QASight.
Highlights: qasight init sets a project up in one command; a Report page and qasight export pdf|pptx|xlsx for managers; qasight demo and qasight try to see it on sample or local results; qasight mcp for coding agents; qasight export-site publishes a read-only history site to GitHub Pages; quarantine from the UI; releases and environments on runs; stop or delete runs from the UI; storage limits, backups and token rotation for the team server.
Upgrade note: the team server compose file now keeps backups in their own testo_backups volume at /backups. Copy any backups you want to keep out of /data/backups after updating the compose file.
Renamed
- Testosterone is now QASight. The package is
qasight(pip install qasight), the command isqasight, the Python modules areqasightandqasight_api, the config file isqasight.yaml, the pytest flags are--qasight..., plugin entry points areqasight.frameworks/qasight.reporters, and the Docker images areqasight-runner,qasight-serverandqasight-runner-service. Everything from before keeps working: thetestocommand,testosterone.yaml/testosterone.ymland[tool.testosterone],pytest --testo..., thetesto.*entry point groups, and everyTESTO_*setting (whichQASIGHT_*can now also set).testo-coreis still released at the same version: it installsqasightand keepsimport testo_core/import testo_apiworking with a deprecation warning, sopip install -U testo-coreupgrades too. Files the tool writes keep their names (.testo/,testo_history.db,testo-summary.md), and the team server's Postgres user and database staytesto. See Rename to QASight
Added
- Storage limits: report archives (
qasight report list) now followhistory.retentionlike runs, andqasight report delete --idremoves them by hand.TESTO_RETENTION_DAYSandTESTO_RETENTION_RUNS_PER_CYCLEset (or override) the limits without aqasight.yaml, so the Docker team server can be limited by its environment.history.max_file_mb/TESTO_HISTORY_MAX_FILE_MBleaves big files (videos, traces) out of a run's history copy and report archive.qasight server initsets 180 days and 100 MB for new servers; existing servers keep everything until a limit is set - Storage visibility: the Runs page shows how many runs there are, the database and file sizes, report archives and free disk, and warns when the disk is getting full (
GET /api/v1/storage,TESTO_LOW_DISK_PCT)./api/v1/health/readyhas adiskcheck, andqasight doctorprintsDiskandHistory sizerows - A run that dies mid-way (
kill -9, out of memory, a crashed server) is recorded as a failed run ("stopped without finishing") when its cycle next runs or the API server starts, instead of leaving no trace - Token rotation:
TESTO_API_TOKEN_PREVIOUSis accepted next to a newTESTO_API_TOKENwhile CI secrets are updated, andTESTO_SESSION_KEYsigns sign-in cookies instead of the token so a new token keeps people signed in.qasight server initgenerates a session key qasight export-sitewrites the web UI and recent runs as a read-only static site for GitHub Pages or any file host, and the GitHub Action'sstatic-site: "true"uploads it for anactions/deploy-pagesjob. The UI bundled in the wheel now works under any path, so one build servesqasight uiand the site. Theapiextra addshttpx- Delete chosen runs: the Runs page has Select runs, then a checkbox per finished run and Delete selected (with a confirm). The CLI does it with
qasight clean --history --run ID(repeatable), and the API withPOST /api/v1/runs:delete({"run_ids": [...]}; an admin's call in accounts mode). Like retention, a run's row, its per-test rows and its report copy go together, and a running run is skipped - Stop a run: the cycle page and Quick run show a Stop button while a run is going (press again to kill stages that ignore the first stop). Only that run's stages are stopped, the rest of its stages are skipped, and it is still recorded, as stopped. A queued run is dropped before it starts. API:
POST /api/v1/cycle-executions/{id}/cancel; the execution's status becomescancelled - The Runs page says how long runs are kept (
history.retention) and the default stage time limit (timeout_s), fromGET /api/v1/run-settings - Report: where failures concentrate (the few tests behind most failures, the most repeated errors), retry rate and line coverage by day, tests with unstable timing, when failures happen (by hour, weekday and environment, with a sentence when one fails far more often), and tests that caught real bugs against tests that only raised false alarms. The test page shows each test's slowest-runs time and flags unstable timing
- Report: a By team table (each CODEOWNERS owner or
ownerlabel: tests, failing, open failures and the oldest, time to fix, flaky tests and their cost) and the suite's size against the period before with the tests added and removed, tests skipped in every run, and slow tests that never failed - Report: why runs were red and what flakiness costs. The
/reportpage counts the period's red runs by cause (real bug, bug in the test, environment, flaky tests, crash or setup, not marked yet) from the causes marked on failures and the flaky classification, with the share that was noise, and shows the CI minutes flaky tests cost (their retries plus the red runs only they caused), the branches they held up and the most costly tests. The cycle page's flaky tests show their cost and are sorted most costly first - Report and dashboard: how long failing tests stay red and what the suite costs. The
/reportpage has a Speed and stability section (median time to fix failing tests, open failures by age with the oldest listed, the share of the period the default branch was red and its longest red spell, CI minutes, first-try green rate of pull request commits), a Suite time by day chart, and a per-cycle table with suite time, slowest 10% and the stage that grew most. The dashboard has a Last 7 days card with the same numbers (GET /api/v1/dashboard/delivery). Flaky tests and reruns of the same commit are left out of time to fix - Issues with red tests: the cycle page's new "Issues" card groups tests by the issue they point at (issue links and
issuelabels from N1's labels and links) and lists the issues with a failing test first, with links to the tracker and to the failing tests. API:GET /api/v1/analytics/issues?cycle= - Releases: record the release a run tests with
qasight run --release 2.3(alsoqasight ingest --release,TESTO_RELEASE, and the GitHub Action'sreleaseinput). A new Releases page lists every release with its pass rate, and each release's page shows its cycles, failing tests (with the ones new since the release before), flaky tests and what it fixed. API:GET /api/v1/releasesandGET /api/v1/releases/{release};releaseon run items and the run detail - Run a chosen set of tests:
qasight run --test NAME(repeatable) andqasight run --rerun-failed RUN_ID, with pytest node ids or-k, Playwright--grepand Behave--name, plus Allure's test plan file (ALLURE_TESTPLAN_PATH) for other runners. The run page has "Run these tests again", and the cycle page's label filter has "Run these". API:testsandrerun_failedonPOST /api/v1/cycles/{cycle}/executions - Environments:
qasight run --env browser=firefox --env os=linux(alsoqasight ingest, theTESTO_ENVvariable, Allure'senvironment.properties, and the GitHub Action's newenvironmentinput) records what a run tested on. Flaky detection and failing streaks keep environments apart, so a test that always fails on one browser is a failure there, not flaky. The run page and runs list show the environment, and the test page has a "By environment" card ("Fails only on browser=firefox") - Test labels and issue links: each test keeps what it is about (feature, story, epic, severity, owner, tags and custom labels) and its issue links, read from Allure results, allure-pytest markers, Playwright tags and annotations, Behave tags and a JUnit test case's own
<properties>(record_property("feature", "Checkout")). The run page and the test page show them as chips; the cycle page has a "By label" card ("Checkout: 12 tests, 3 failing") and filters its tests with?label=. API:GET /api/v1/analytics/labels?cycle=, andlabel=onGET /api/v1/analytics/tests. Migration0006adds thelabelsandlinkscolumns totest_result, run automatically qasight export pdf|pptx|xlsx|png|html|csv|jsonwrites the manager report as a file, and the Report page's Download menu gets the same files from the server (GET /api/v1/report/export). PowerPoint has six slides with native, editable charts and tables; Excel has Summary, Trend (with a chart), Cycles, Tests and Findings sheets; HTML is one file that opens offline; PDF and PNG are drawn by headless Chromium. PDF, PNG, PowerPoint and Excel need the newexportextra (pip install 'qasight[export]', thenplaywright install chromiumfor PDF and PNG). The team server image includes PowerPoint and Excel, and PDF with `--build-arg TESTO_REPORT_PDF=1...
v1.5.0
Install: pip install testo-core==1.5.0 (OpenTelemetry export: pip install "testo-core[otel]==1.5.0")
Highlights: send runs to OpenTelemetry as traces; run only the tests a change can affect (testo affected, pytest --testo-affected); and manage VM pools with testo pool status, add, remove and sweep, dry run by default and every command logged.
Added
- OpenTelemetry export: a run, its stages and its tests as one trace over OTLP, so test runs show up in Datadog, Grafana Tempo, Honeycomb, Jaeger or any other OTLP backend. Tests keep their real start and stop times; failures are error spans with the message. The
otelreporter sends it aftertesto run, andtesto otel --cycle/--run-idsends any recorded run (--dry-runprints the spans). Configured with the standardOTEL_EXPORTER_OTLP_*variables; off unless configured. Needs the new[otel]extra (pip install 'testo-core[otel]') - Affected-test selection, off unless asked:
testo affectedlists the tests the changes since the baseline branch can affect, each with why it was picked (--json, or--listfor any runner's command line), andpytest --testo-affectedruns only those. Changed files map to tests throughaffected.rulesglobs intestosterone.yaml, changed test files, and a coverage.py report with test contexts; a change nothing maps, a lockfile orconftest.py, or a git problem runs everything and says why testo pool add,removeandsweeprun a pool's ownprovider.create/provider.destroycommands, when a person types them: dry run unless--apply(which asks unless--yes), only verbs listed underprovider.allow, at most 5 keys per command,max_itemsandname_prefixlimits, never a key initemsor one someone holds, andTESTO_PROVIDER_ACTIONS=0turns them off. Every command run is logged with who, where and how it went:testo pool log, a "Provider commands" card on the Resources page,GET/POST /api/v1/resources/actions. New tableresource_actiontesto pool status: compare each resource pool with the keys that really exist. A pool's newprovider.listis your own read-only command that prints them; the report shows keys that are missing, VMs outside the pool (leftovers) and, with--check, free keys that failon_check. Exits1when something needs a look. Testosterone never creates or deletes a key on its own: onlytesto pool, typed by a person, runs provider commands, and a test checks that runs, leases, stages and the API never reach them
Changed
- The PyPI page's Documentation link goes to the docs site instead of the GitHub wiki, and README links to guides open the rendered docs site pages
Full changelog: v1.4.0...v1.5.0
v1.4.0
Install: pip install testo-core==1.4.0 (the web UI: pip install "testo-core[api]==1.4.0" && testo ui)
Highlights: testo ingest reads Jest, Vitest, .NET, Go, Robot, Allure, RSpec, k6 and Xcode results as they are; pytest --testo; a dashboard that leads with what needs attention and a Project health score; triage memory; screenshots, traces and flakiness over time on the test page; resource pools and time-based shards for parallel runs; testo badge; a try-it page; and the GitHub Action at uses: taltal-beep/testosterone@v1.
Added
- The GitHub Action at the repository root, ready for the GitHub Marketplace:
uses: taltal-beep/testosterone@v1(name "Testosterone Test History", with an icon).taltal-beep/testosterone/integrations/github-action@v1keeps working; a test keeps the two copies the same testo badge: a README badge for a cycle's latest run ("tests | 412 passed · 2 flaky", green, yellow or red), as an SVG or a shields.io endpoint JSON. A team server serves it live atGET /api/v1/badge.svgandGET /api/v1/badge.json(?cycle=)testo lease demofills the Resources page with made-up pools, holders, a disabled key and a queue, to try it out (--clearundoes it); the live demo shows them too.testo ingest,junit_xmlglobs andPOST /api/v1/ingestalso read RSpec JSON (rspec --format json), k6 summaries (--summary-exportorhandleSummaryJSON: each check and threshold is one result) and Xcode test results exported byxcrun xcresulttool get test-results tests(XCTest and Swift Testing, retries flagged flaky). Android instrumentation JUnit XML (Espresso,connectedAndroidTest) is labelledandroidwith its device. "Rerun these tests" addsrspec <files> -eand./gradlew connectedAndroidTestwith the failing classes and methods- Resource pools: an
on_checkhook (run afteron_take; a key that fails it is taken out of the pool and another is tried),TESTO_RESOURCE_CLEAN=1when the last holder gave the key back after a successfulon_release(ortesto lease release --clean), soon_takecan skip a reset it doesn't need, andreuse_delay(defaultlease_ttl / 3): an expired lease keeps its key out of the pool that much longer, so a frozen holder notices before anyone else gets it.testo lease listand the Resources page show such keys as expired. New columnsresource_item.cleanandresource_lease.reuse_delay_s, added to existing databases automatically - "Rerun these tests" covers more runners: Jest and Vitest (
-ton thedescribepath),go test -run,dotnet test --filter,robot --test, and Maven (-Dtest=Class#method) or Gradle (--tests) for JUnit XML from a JVM build whosepom.xmlor Gradle build is in the run's target repo. Dotted JUnit names are only offered as pytest tests when they point at atest_*.pyor*_test.pyfile - Equipment
jest,vitestandcypress:argsgo tonpx jest,npx vitest runornpx cypress run, and the stage adds the report Testosterone reads (Jest's and Vitest's own JSON, Cypress's built-in JUnit reporter) with no extra package. Jest and Vitest take--maxWorkersfromworkers testo ingest,junit_xmlglobs andPOST /api/v1/ingestread runners' own result files, recognised by content, so these suites need no JUnit reporter package: Jest and Vitest JSON (jest --json,vitest --reporter=json;describeblocks, retried tests flagged flaky, files that failed to load), .NET TRX (dotnet test --logger trx),go test -jsonoutput (subtests, build failures), Robot Frameworkoutput.xml(tags, messages) and Allure results (testo ingest allure-results/, with attachments). A folder argument stands for the files in it- Shards by time:
pytest --testo-shard I/N(orTESTO_SHARD=I/N testo run ...) runs one of N shards that take about the same time, from the recorded test times;testo splitprints a shard's tests for other runners.testo runsetsTESTO_CYCLEandTESTO_STAGEfor each stage. - Stages can ask for keys:
resources: POOLon a stage gives each pytest worker (per: worker), each test (per: test) or the stage (per: stage) its own key, andworkers: autostarts one worker per free key. In plain pytest:--testo-resource POOLand thetesto_resourcefixture. - Resource pools for parallel runs: list keys (VMs, devices, test accounts, seats) under
resources:intestosterone.yaml, andtesto lease run POOL -- CMDgives each worker its own key inTESTO_RESOURCE_ID. Leases are rows with a heartbeat and a TTL, so a crashed worker frees its key; requests wait in a fair queue; failingon_take/on_releasehooks take a key out of the pool. WithTESTO_SERVER_URLthe leases live on the team server, so developers on different laptops share one pool. Alsotesto lease take/heartbeat/release/list/disable/enable/free,/api/v1/resourcesand a Resources page in the UI. New tablesresource_item,resource_lease,resource_waiter - A Project health page (
/health): a 0-100 test health score for the project you test, from pass rate, green builds, flaky tests, tests that got slower, skipped and quarantined tests and the test pyramid, with what to fix first and a score per cycle. An optional AI review scores the tests with checks you switch on and off (why tests are flaky, related failures, test strategy gaps, test debt, slow tests, test churn, test names), each expandable to what it checks and reads. It uses your AI provider, never reads source code and is labelled an AI estimate. New endpointsGET /api/v1/project-health,GET /api/v1/project-health/skillsandGET/POST /api/v1/project-health/ai-review. On the read-only demo the checks are listed but the review can't be run - A cycle's run history: the cycle page has a "Recent runs" card (pass rate, one square per run, the latest three), and Runs filters by cycle (
/runs?cycle=web, backed byGET /api/v1/runs?cycle=) with the same pass-rate line - Docs: "Compared with other tools", where Testosterone fits next to Allure, ReportPortal, Currents, Trunk and Datadog, and when to pick one of them instead
- Triage memory: on a failed run's page, "Mark the cause" of a failing test (bug in the product, bug in the test, environment, flaky, other, plus a note). Later failures in the same cycle with the same message (numbers, ids and quoted values aside) show that cause too, with where it was marked. New table
failure_triage, endpointsGET/PUT/DELETE /api/v1/triage, and atriagefield on each test ofGET /api/v1/runs/{run_id}/failures - "Flakiness over time" on the test page: the share of outcome changes over the five runs ending at each run, and how it compares with five runs before. Test-history points add
flip_rate - The test page shows what the last failing run kept: screenshots and videos in place, the Playwright trace as a download with the command that opens it.
GET /api/v1/analytics/test-historyaddslast_failure_attachments pytest --testo(or--testo-cycle NAME): a pytest plugin, installed with testo-core and inert without the flag, that records each session as a run the waytesto ingestdoes, with the checkout's branch and commit.--testo-serveruploads to a team server instead- The dashboard leads with "Needs attention": red cycles (failing and new-failure counts, red for how many runs), tests that just started flaking and tests that got slower, each linking to its page. New endpoint
GET /api/v1/dashboard/attention. The rollup and quick-links cards and the KPI icons are gone, and the no-runs page explainstesto ingest,testo config init/testo runand the try-it page - The run page has a "Rerun these tests" command per runner (pytest node ids,
npx playwright test --grep,behave --name) with a Copy button, from the newrerun_commandsfield ofGET /api/v1/runs/{run_id}/failures - Compare opens on the latest run against the one before it in its cycle, and each run in the pickers shows its branch (
GET /api/v1/runsaddsbranch) - The run page shows the branch, pull request target and commit the run tested (linked on GitHub), and
GET /api/v1/runs/{run_id}returns them - Cycles recorded with
testo ingest(or by another machine) have a cycle page:GET /api/v1/cycleslists them after the config's cycles withsource: "history", and their page shows the test search, slowest tests and flaky card without a Run panel.testo uiin a folder with notestosterone.yamllists recorded cycles instead of an error - Docs: second pass of the product review (docs/product-review.md): 6.5 to 7.5, what a 10 needs in each area, and new findings, chiefly that cycles created by
testo ingesthave no cycle page - A try-it page (
/try, "Try a file" under Tools, also on the static demo): drop JUnit XML files, or use the sample, and see the run the way Testosterone shows one (verdict, failing tests with shared causes grouped, slowest tests), read in the browser with nothing uploaded
Changed
- Claude Code on the web sessions install
.[dev]into.venvat startup (.claude/hooks/session-start.sh), since the container's system pip can't build csscompressor - One health score: the dashboard's Health card now shows the Project health score (same number as
/health) and opens that page. A run's own pass percentage is labelled "Pass rate" on the run page and in Compare, and "passed" in the dashboard's recent runs - The test page's duration chart shows its scale (longest run and zero); slowdown percentages read the same on the dashboard and the cycle page; the dashboard says "All 3 cycles are red." when none passed; Runs says "Every recorded run" instead of "archived cycle run"
- Charts have text alternatives: the test page's duration chart reads its values, and the flaky card's outcome strip is announced
- UI accessibility and copy: the skip link is i...
v1.3.0
Install: pip install testo-core==1.3.0 (the web UI: pip install "testo-core[api]==1.3.0" && testo ui)
Highlights: failing tests first on the run page, testo ui, a page per test with search and slowest tests, PR comments judged against the target branch, owners and issue links for flaky tests, and opt-in team-server features (accounts, persistent queue, parallel stages, throwaway runner containers).
Added
- Flaky tests and the per-test page show the test's owners from CODEOWNERS and an "Open an issue" link (GitHub's new-issue form, prefilled with the test, cycle, last failure and owners). Runs record their
repository(owner/name) from CI variables or a github.comoriginremote - A page for each test (
/cycles/<cycle>/test?key=): pass rate, flaky or not, last failure, duration per run and every run with its branch. The cycle page can search its tests and lists the slowest tests and the ones that got slower. New endpointsGET /api/v1/analytics/testsandGET /api/v1/analytics/test-history - Pull request comments and
testo report markdowncompare a PR's run with the latest run on its target branch (new--baseline-branchoverrides), not with whichever run came before, and say which baseline they used. Runs record the PR's target branch (ci_base_ref, also accepted bytesto ingest), the PR's own branch on GitHub, and the local git branch and commit outside CI testo ui: the web UI ships inside the wheel, sopip install 'testo-core[api]' && testo uiopens the dashboard on this machine's run history (http://127.0.0.1:8000) with no checkout or Node.testo-apialso serves the bundled UI whenTESTO_UI_DIRisn't set- UI accessibility and copy pass: stronger primary-button contrast, a visible keyboard focus ring, a skip link, h1 → h2 headings, a 44px mobile menu button, AI settings fits a phone, load errors say what to check, sentence-case titles, "Advanced" is now "Tools" with a plain Docs link, and the dashboard hides unknown trends and missing reports.
testo --helphas the README's tagline and no RST double backticks - The run page leads with its failing tests: each with its failure message, stage, "New" or "Failing for N runs", and a flaky badge, and failures that share a message are grouped ("2 tests: ConnectionRefusedError ..."). A one-line verdict sits under the title; the run id, exit code (now explained, e.g. "1 (tests failed)") and duration moved to a Details card. New endpoint
GET /api/v1/runs/{run_id}/failures - Compare and the dashboard ignore timing noise: a duration change under 5% of the baseline, or under 100 ms (5 ms for the per-test average), is "Within noise" instead of a red regression or a green improvement
- Docs: a product review (docs/product-review.md) scoring positioning, onboarding, the red-build workflow, design, accessibility and copy against Allure, ReportPortal, Currents and Trunk, with a Now / Next / Later roadmap
- Opt-in throwaway containers for runs started on a team server: with
TESTO_RUNNER=containereach stage runs in a fresh container started by a separate runner service (testo-runner-service,Dockerfile.testo-runner-service,deploy/server/docker-compose.runner.yml), with no Linux capabilities, a process limit, fixed mounts and only the stage's own variables. The API never gets the Docker socket. Defaulthost(unchanged) - Sign-in throttling with
TESTO_AUTH=accounts: afterTESTO_LOGIN_MAX_FAILURES(default 5) wrong passwords for a name in 15 minutes, or four times that from one address,/loginanswers429until the window slides TESTO_REPORTS_ORIGINserves report pages from a second hostname, so script in a report can't act with the viewer's session on the API's origin. Off by default- Per-user accounts for the team server, off by default: with
TESTO_AUTH=accountspeople sign in with their own username and password, rolesviewer/runner/admindecide who can start runs or change settings, every write and sign-in is in an audit log (GET /api/v1/audit), andtesto usersmanages accounts. The UI shows who is signed in. The API token keeps working as an admin for CI TESTO_RUN_QUEUE=dbkeeps API runs that are waiting for a worker in the database, and the server queues them again on startup, so a restart or upgrade no longer drops them. Defaultmemory(unchanged)- Opt-in parallel stages: a cycle with
max_parallel_stages: Nruns up to N stages at once (default1, unchanged). Events and the renderer stay on one thread, results keep config order,fail_faststarts no new stages after a failure, and stage names must be unique in a parallel cycle - The API runs at most
TESTO_MAX_CONCURRENT_RUNSexecutions at once (default2) on a fixed worker pool; further runs wait asqueuedinstead of each starting its own thread - Team server sign-in:
TESTO_PROTECT_READS=1makes every read (API,/historyreport files, the UI, the live run stream) need the API token, and a/loginpage trades the token for an HttpOnly session cookie that also authorizes writes. On indeploy/server/docker-compose.yml; the UI it serves redirects to/loginwhen the session is missing. Off by default elsewhere, so local use is unchanged - Frontend lint: ESLint with
typescript-eslintand the React hook rules (frontend/eslint.config.js,npm --prefix frontend run lint), blocking in CI'sfrontendjob - Unit tests for the TestBeats and ReportPortal reporter payloads, the Anthropic provider, and the
testo configandtesto reportcommands (coverage 80% to 83%) - Frontend tests for the loading, error and empty states of the run detail and Compare pages
- Unit tests for the Allure report wrapper, the local report server, the Behave adapter's argument building, the BehaveX-to-Allure export and
testo watch's debounced re-runs (each module now at 92% or more; total coverage 84% to 86%)
Changed
- Internal split of the three largest modules: run event streaming moved to
testo_api/execution_events.py, config section parsing totesto_core/config/sections.py, andtesto report list/opentotesto_core/cli/commands/report_archive.py. No behaviour change - The release workflow's "Verify install from PyPI" step retries for up to five minutes instead of one 30-second wait, so a slow PyPI index no longer marks a published release as failed
- The
testo-serverimage setsTESTO_PROTECT_READS=1, so a plaindocker runprotects reads too, not onlydeploy/server/docker-compose.yml. Set it to0to open reads on a trusted network - Frontend on React 19 (
react,react-domand their types together),@testing-library/jest-dom7 and the latest TanStack Query and PostCSS patches; replaces Dependabot #132, #133 and #135, which bumpedreactwithoutreact-dom - The pixel mascot's frame source moved from
testo_core/mascot/toscripts/mascot/, so it no longer ships in the wheel; the UI animations are unchanged. Play it withpython -m scripts.mascot flex, regenerate the frontend frames withpython -m scripts.mascot.export_ts - Frontend toolchain: Vite 8 (Rolldown) and Vitest 5, replacing Vite 5 and Vitest 2; clears the six dev-only
npm auditadvisories (esbuild dev server, Vitest mocker, tinypool).vite.config.tstakesdefineConfigfromvitest/config, and theEventSourcetest mocks are constructor functions, which Vitest 4+ requires
Fixed
- An API run that raised
SystemExit(say, a reporter plugin callingsys.exit()) took its worker thread down for good, shrinking the pool until queued runs never started; the worker now survives it and dead workers are replaced - The API sends
X-Content-Type-Options: nosniffandframe-ancestors 'self'on every response, and serves report attachments under/historywithContent-Security-Policy: sandbox, so an uploaded HTML attachment can no longer run script on the team server's origin test_python_build_module_availableskips whenbuildis not installed, and the pytest rootdir test runspython -P -m pytestwith the suite's own interpreter, so a contributor's first local run is greentesto config initgenerates asmoke-testcycle that runs ordinary pytest tests intests/without a marker filter. Marker-based pytest and BehaveX configurations remain as commented-out examples, so a new pytest project does not require smoke markers or BDD features (#100, thanks @evidentart)
Full changelog: v1.2.0...v1.3.0
v1.2.0
Install: pip install testo-core==1.2.0
If a cycle has behave or behavex stages, install testo-core[bdd] (Behave is now an extra).
Added
-
testo ingest: record JUnit XML that any runner already wrote (Jest, Vitest,go test, JUnit5, ...) as a run of a cycle, with notestosterone.yamlneeded. Ingested runs get the same history, Compare,testo flaky, reporters and PR comment astesto run. Backed by a newequipment: junitthat runs no process and fails the stage when a test failed or no report matched -
Team server:
POST /api/v1/ingestrecords uploaded JUnit XML, andtesto ingest --server URL --tokenuploads with the CI job's provenance (provider, pipeline, job, commit, branch, repository). The API serves the built web UI whenTESTO_UI_DIRis set. Newtesto-serverDocker image (Dockerfile.testo-server, published withtesto-runner) anddeploy/server/docker-compose.ymlwith Postgres. Guide:docs/Processes & Guides/Team Server.md -
Playwright JSON reports:
testo ingest results.json(and anyjunit_xmlglob that matches a.jsonfile) reads Playwright'sjsonreporter output, keeping what JUnit loses: every retry as a step with its own error, flaky tests (passed on retry) flagged, and traces, screenshots, videos andtestInfo.attach()files copied as Allure attachments.testo ingest --serveruploads.jsonreports too. Seedocs/Specs & ADRs/Playwright JSON Import - 2026-10-22.md -
Unit tests for the Extent, ReportPortal and TestBeats reporters, the reporter factory, reporter config parsing and Docker results collection (recovered from an old branch)
-
AI failure summaries without a paid key: an
openai_compatibleprovider with a base URL, so Gemini, Groq, OpenRouter (free keys) or a local Ollama (no key) work. The settings page offers presets for them -
Flaky test detection:
testo flaky --cycle <name>(--window,--json,--fail-on-flaky),GET /api/v1/analytics/flakyand a Flaky tests card on the cycle page (also in the Pages demo). A test is flaky when it both passed and failed and flipped at least twice in the cycle's last runs (testo_core/services/flaky_service.py) -
GitHub Action run summary: after a persisted run the action writes the results, failing tests and per-test changes since the previous run to the job summary, and with
comment-on-pr: "true"keeps one comment per cycle on the pull request (edited in place). New inputsjob-summary,comment-on-pr,github-tokenandpackage. The text comes from the newtesto report markdowncommand. This repository's CI runs the action on its own checkout and comments on its own pull requests -
Plugin API: a package can add a test runner or a reporter through the
testo.frameworksandtesto.reportersentry point groups (testo_core/plugins.py), with no change to Testosterone.testo pluginslists built-ins and installed plugins (--checkimports and verifies them,--json), andexamples/plugin/is a complete plugin with aunittestadapter and ajson-summaryreporter -
equipment: playwright: runsnpx playwright testwith its JUnit and HTML reporters wired in, so Playwright tests count in summaries, diffs and the UI with nojunit_xmlsetup, the HTML report is kept per run as the native report, andworkersbecomes--workers. Also offered on the Quick Run page -
Docs site: the
docs/vault built with MkDocs Material (mkdocs.yml,scripts/mkdocs_hooks.py,[docs]extra) and published at https://taltal-beep.github.io/testosterone/docs/ by the Pages demo workflow. A newdocs_siteCI job builds it with--strict -
JSON Schema for
testosterone.yaml(schema/testosterone.schema.json, generated byscripts/export_config_schema.pyand checked in CI) so VS Code and other editors autocomplete and validate the config.testo config initandtesto initadd the# yaml-language-server: $schema=...line -
Flaky test quarantine: a
quarantine:list intestosterone.yaml(top-level and per cycle; test name globs, optionalreasonandowner). A stage that exits1passes when every failed test is quarantined; the failures are still reported everywhere and recorded per stage asquarantined(CLI summary,--ciNDJSON, run record,testo report markdown).testo flakymarks quarantined tests, prints a snippet for the rest, and--fail-on-flakyignores quarantined ones (testo_core/engine/quarantine.py) -
docs/project-review.md: a whole-repo review scorecard (architecture, code quality, tests, security, docs, CI) with findings and a remediation plan, linked from the docs index and site nav -
docs/Getting Started.md: frompip install testo-coreto a regression intesto summaryand the GitHub Action, with the same project inexamples/getting-started/. A newgetting_startedCI job runs that example through the GitHub Action -
Alerts where developers look:
slackandteamsreporters and atesto notify --to slack|teamscommand post a run's failing tests, new regressions, newly flaky tests and fixes to an incoming webhook (URL fromTESTO_SLACK_WEBHOOK_URL/TESTO_TEAMS_WEBHOOK_URLor a${env:…}option;--when failure|change|always).testo report annotationsprints failing tests as GitHub::errorannotations on their file and line, andtesto flaky --newlists tests the latest run made flaky. The GitHub Action gainsannotations(on by default),slack-webhook,teams-webhook,notify-whenandflaky-issues(opens one issue per newly flaky test)
Changed
testo-apirefuses to start on a non-loopback host withoutTESTO_API_TOKEN(exit 2) instead of only warning; setTESTO_ALLOW_UNAUTHENTICATED=1to start anyway on a trusted network- The
?config_path=on read routes (GET /api/v1/cycles,/cycles/{cycle},/runs/{run_id}/pyramid) only loads files namedtestosterone.yaml,testosterone.ymlorpyproject.tomland returns400for anything else. Reads need no token, so an arbitrary path let anyone probe the server's files and read fragments of them in parse errors - The API reports the installed package version instead of a hard-coded one
- CI: a
test (python 3.12)/test (python 3.13)job runs the fast suite on the other Python versionspyproject.tomllists; BehaveX tests skip without thebddextra - CI:
trivy-actionandclaude-code-base-actionpinned to commit SHAs instead of@master/@beta;commitlint,pr-heavyandrelease-gateworkflows get a read-onlypermissions:block - AI settings (never the key) are saved to
artifacts/ai_settings.jsonand survive an API restart (TESTO_AI_SETTINGS_PATHmoves it; empty keeps them in memory). The settings page resets the model when the provider changes, names the missing environment variable and refuses a key pasted as a variable name. The summary prompt asks for a short plain-textWhat failed/Likely cause/Next stepsreply, and the Run Details card shows provider errors in words equipment: playwrightuses Playwright'sjsonreporter instead ofjunit(--reporter=list,json,html, written totest-results/testo-results.json), sotesto rungets the same retries, flaky flags and traces. A stage that sets its own--reporter=junitis still imported fromtest-results/testo-junit.xml- Behave and BehaveX are now an extra.
pip install testo-coreno longer installsbehave,behavexandallure-behave(35 packages instead of 47). Migration: if a cycle hasbehaveorbehavexstages, installtesto-core[bdd]; in the GitHub Action passpackage: "testo-core[bdd]", and in the GitLab template setTESTO_PACKAGE: "testo-core[bdd]"(new variable). Thetesto-runnerimage and thedevextra include it. Without it a Behave stage fails with exit 3 and the stage error names the extra, which the CLI stage panel now shows (the--cistream keeps it in the artifact event mirror, as before) - README and PyPI description lead with what the project answers on every run (what broke, what's flaky, what got slower) and with
testo ingestfor any runner, next to running cycles; new PyPI keywords - Web UI restyled after the Vision UI dashboard look: navy palette, glass cards over a blue glow, Plus Jakarta Sans, a desktop sidebar with icon tiles and a docs card, and icon tiles on the Dashboard KPI cards. Faint line art of the testosterone molecule and of vials with a syringe sits behind the pages. Phones keep the top bar and menu. See
docs/Specs & ADRs/UI Restyle - 2026-10-19.md publish.ymlpublishes straight to PyPI (the Test PyPI step is gone; the build, metadata and clean-install checks still run before the upload) and can be run by hand for an existing release tag- Runner image:
Dockerfile.testo-runneruses the floatingpython:3.11-slim-bookwormbase (the3.11.9pin carried 9 fixable CRITICAL Debian CVEs that failed the v1.1.1 image scan), anddocker-publish.ymlscans and smoke-runs the image before pushing it and can be run by hand for an existing release tag - Setup prompts install
testo-corefrom PyPI instead of the GitHub zip move-major-tag.ymlkeeps the movingv1tag on the latest stable release (and can be run by hand for an existing tag)- README: a "What's new" section for the changes since 1.1.1, new screenshots and UI walkthrough of the restyled UI on real self-test and fake-api runs (including the flaky tests card), a terminal recording of
testo flaky,testo report markdownandtesto plugins, and the Playwright equipment, plugins,testo report markdown,testo summaryand PR comments in the reference sections
Fixed
- BehaveX stages get BehaveX's own HTML report again. The adapter passed BehaveX's Allure
--formatter, and BehaveX skipsreport.htmlwhenever a formatter is set, so the report Testo showed was re-rendered afterwards without scenario logs. BehaveX now runs plain and the executor converts itsreport.jsonto Allure results after the stage...
v1.1.1
Install: pip install testo-core==1.1.1
First release meant for PyPI (pip install testo-core).
Added
- Open-source community files:
SECURITY.md(private vulnerability reporting),CODE_OF_CONDUCT.md(Contributor Covenant 2.1),CITATION.cff, issue forms (.github/ISSUE_TEMPLATE/*.yml, blank issues off, links to Discussions and the security form) and.github/dependabot.yml(weekly grouped updates for pip, npm and GitHub Actions,chore(deps)commits with theno-changeloglabel;commitlint.config.jsskips Dependabot's long commit bodies).CONTRIBUTING.mdgains "Finding something to work on" and a note on the AI-agent files, and its Quickstart link is fixed - README: animated walkthroughs of the web UI and of the CLI (
docs/assets/demo/) docs/Prompts & Snippets/Setup Prompts.md: copy-paste prompts for an AI coding assistant to install Testosterone in another project, write itstestosterone.yaml, run it in GitHub Actions or GitLab CI, open the web UI and troubleshoot. Command Reference no longer callsdoctor,clean,watchandinitunimplemented
Fixed
pip install testo-corefollowed bytesto runcrashed withModuleNotFoundError: No module named 'sqlalchemy': run history is always saved (SQLite by default) butsqlmodelwas only in the[db]extra. It is now a core dependency;[db]keeps the Postgres driver andtomli-w. A newpackageCI job (scripts/package_smoke.sh) installs the built wheel with no extras and runs a cycle, and the publish workflow runs the same check before uploading- Compare showed "n/a" / "Unknown" for "Test time (sum)" and "Avg per test" on every run because nothing stored them. Both persistence backends now write
metrics_duration_msandavg_case_ms, plustest_time_msper stage. Runs saved earlier still show "n/a"
Changed
- Package metadata for PyPI: description, MIT license expression, author, keywords, classifiers and project links. The README shown on PyPI has its relative images and links rewritten to GitHub URLs at build time, and the sdist now carries only the Python package, its tests and the top-level docs (about 260 KB instead of 3.5 MB)
publish.ymlfails before uploading when the release tag does not matchpyproject.toml's version, and runstwine check --strict- README: PyPI badge and a
pip install testo-corequickstart - Pages demo: the read-only notice is now a dismissible alert, and a "Read-only demo" badge in the header reopens it
- Detailed architecture diagram showing
CycleRunService, thehistory/read side and the repository, in the README anddocs/Architecture/System Diagram.md
Full changelog: CHANGELOG.md