Any framework via JUnit XML
JUnit XML is the lingua franca of test frameworks. Blop ships a universal
uploader (blop-ingest) that parses JUnit XML, computes counts and failures,
and emits CloudEvents to Blop Cloud — so you get dashboard insights for
any test runner without a native adapter.
Supported frameworks
Section titled “Supported frameworks”Any framework that emits JUnit XML works out of the box:
| Framework | Command |
|---|---|
| pytest | pytest --junitxml=report.xml |
| Go | gotestsum --junitfile report.xml -- ./... |
| Vitest | vitest run --reporter=junit --outputFile=junit.xml |
| Jest | jest --reporters jest-junit |
| RSpec | rspec --format RspecJunitFormatter --out report.xml |
| PHPUnit | phpunit --log-junit report.xml |
| Mocha | mocha --reporter mocha-junit-reporter |
| Gradle | gradle test (writes JUnit XML to build/test-results/) |
| .NET | dotnet test --logger "junit" |
| Rust | cargo nextest run --no-fail-fast -- junit report.xml |
No install required — use npx:
npx @blopai/ingest upload --junit report.xmlOr install globally:
pnpm add -g @blopai/ingestblop-ingest upload --junit "build/test-results/**/*.xml"CI setup (GitHub Actions)
Section titled “CI setup (GitHub Actions)”name: Testson: [pull_request, push]
jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- name: Run tests run: pytest --junitxml=report.xml
- name: Upload to Blop if: always() run: npx @blopai/ingest upload --junit report.xml env: BLOP_INGEST_URL: ${{ vars.BLOP_INGEST_URL }} BLOP_INGEST_SECRET: ${{ secrets.BLOP_INGEST_SECRET }} BLOP_PROJECT_ID: ${{ vars.BLOP_PROJECT_ID }}if: always() is important — you want results uploaded even when tests fail
(that’s when insights matter most).
Environment variables
Section titled “Environment variables”| Variable | Required | Role |
|---|---|---|
BLOP_INGEST_URL |
yes | Platform ingest endpoint |
BLOP_INGEST_SECRET |
yes | Per-project ingest secret |
BLOP_PROJECT_ID |
yes | Project id the run belongs to |
BLOP_RUN_ID |
no | Run id (auto-generated if unset) |
BLOP_TRIGGER |
no | Trigger source (defaults to ci in CI, manual elsewhere) |
Flags override env vars: --ingest-url, --secret, --project-id,
--run-id, --trigger. Run blop-ingest upload --help for the full list.
How it works
Section titled “How it works”- Parses one or more JUnit XML files (handles both
<testsuite>and<testsuites>roots, nested suites,failure/error/skippedstates). - Computes
counts(passed/failed/skipped/flaky) andtop_failures(max 25, message capped at 2000 chars). - Emits
qa.run.started.v1(creates the run row on the platform). - Zips all JUnit files + extra artifacts into a
report-bundle.zipand uploads via presigned URL (if R2 is configured). - Emits
qa.run.finished.v1with counts, failures, artifact pointers, and CI metadata (branch, commit, run URL — auto-detected on GitHub Actions).
Extra artifacts
Section titled “Extra artifacts”Pass --artifact to upload additional files (traces, coverage, logs):
blop-ingest upload --junit report.xml --artifact trace.zip --artifact coverage/Each extra file becomes its own artifact pointer on the finished event.