Ingest and Team Server - 2026-10-22¶
Context¶
A review of what keeps QASight niche (2026-10-22) found that the tools teams actually adopt (ReportPortal, Currents, Trunk, Testmo) ask for one line in an existing CI job and give back one place the whole team looks at. QASight asked users to move their test commands into qasight.yaml, and kept history in a SQLite file on one machine. In CI that file disappears with the runner. Two features close the gap: record results that some other step produced, and a server many CI jobs can record into.
Decision¶
equipment: junit(qasight/frameworks/junit_adapter.py). An adapter withruns_process = False. The executor (_record_junit_stage) skips the subprocess, imports the stage'sjunit_xmlreports with the existing converter (reporting/junit_import.py), without the "newer than the stage" filter, and derives the returncode: 1 when a case failed or broke, or when nothing matched; 0 otherwise. Stage duration is the summed test time, and the plan's duration is the larger of the wall clock and the summed stage durations, so ingested runs have meaningful duration trends.qasight ingestbuilds a one-stagejunitcycle (cycle_run.junit_ingest_plan, strict name and pattern checks) and runs it throughCycleRunService, likeqasight run. Exit codes follow the engine contract. Noqasight.yamlis needed (loader.load_config_or_defaults).POST /api/v1/ingest(qasight_api/routes/ingest.pyโservices/junit_ingest.py) writes the uploaded files to a scratch directory and records them through the same service, one upload at a time (a process lock). The client's CI provenance is sent as form fields, because the server's own environment says nothing about the client's CI; it reaches the run record through a newCycleRunOptions.run_metadataโrun_plan(run_metadata=)โPlanResult.extra["run_metadata"], merged last by the DB backend. Uploads are size-limited and never executed.qasight ingest --server URL --token(services/ingest_client.py) uploads instead of recording locally, and exits with the server's result, or 3 when the upload fails.- The API serves the UI when
TESTO_UI_DIRpoints at a build (qasight_api/ui.py, SPA fallback after all API routes). Same-origin writes are allowed only whenTESTO_API_TOKENis set, because a DNS-rebinding page is also "same origin". -
Dockerfile.qasight-server(UI build + API with[api,db]) anddeploy/server/docker-compose.yml(plus Postgres, token required).docker-publish.ymlpublishesqasight-servernext toqasight-runner; theteam_serverCI job runs the compose file end to end. -
Lighter install (the third item of the same review). Behave, BehaveX and Allure-Behave move from the core dependencies to a
bddextra, sopip install qasightforqasight ingestor pytest/Playwright cycles pulls 35 packages instead of 47. Thedevextra and theqasight-runnerimage includebdd. Abehave/behavexstage without it fails with rc 127 (exit 3), and the stage error, now shown in the CLI stage panel, names the extra.scripts/package_smoke.shchecks that a plain install has neither. The GitHub Action'spackageinput and a newTESTO_PACKAGEvariable in the GitLab template select the extra. The README and PyPI description now lead with what a run answers (what broke, what's flaky, what got slower) and withqasight ingest.
Options considered¶
- A separate ingest pipeline that writes run records directly. Rejected: a second path into history is what the Architecture Consolidation removed. Going through
CycleRunServicegives ingested runs reporters, snapshots, the report archive and metrics push for free. - Upload Allure results instead of JUnit. Deferred. Almost every runner writes JUnit XML; native formats (Playwright JSON) can be added as more
runs_process = Falseparsers. - Accounts and projects on the server. Deferred. One token is enough to try it in a team, and it matches the existing
TESTO_API_TOKENmodel. - Ship the UI inside the PyPI wheel (
qasight ui). Still open. The server image covers the team case without growing the wheel.
Consequences¶
SUPPORTED_FRAMEWORKSgainsjunit. The ad-hoc run API doesn't offer it (it needs files, not a command).- New CLI command, API route, env var (
TESTO_UI_DIR), Docker image and CI job; documented in Command Reference ยง qasight ingest, Team Server, Publishing Docker Images, Architecture Overview and Deep Dive - Execution Logic. - Tests:
tests/unit/qasight/test_junit_ingest.py(config, plan validation, executor, run metadata, CLI) andtests/contract/api/test_ingest_contract.py(endpoint, token, limits, UI serving, same-origin rule, upload client). - The first
qasight-serverpush creates a private GHCR package that the owner makes public once. - Uploaded-only cycles don't appear on the Cycles page (it lists
qasight.yaml). Follow-up.