Skip to content

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.

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:

Terminal window
npx @blopai/ingest upload --junit report.xml

Or install globally:

Terminal window
pnpm add -g @blopai/ingest
blop-ingest upload --junit "build/test-results/**/*.xml"
.github/workflows/test.yml
name: Tests
on: [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).

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.

  1. Parses one or more JUnit XML files (handles both <testsuite> and <testsuites> roots, nested suites, failure/error/skipped states).
  2. Computes counts (passed/failed/skipped/flaky) and top_failures (max 25, message capped at 2000 chars).
  3. Emits qa.run.started.v1 (creates the run row on the platform).
  4. Zips all JUnit files + extra artifacts into a report-bundle.zip and uploads via presigned URL (if R2 is configured).
  5. Emits qa.run.finished.v1 with counts, failures, artifact pointers, and CI metadata (branch, commit, run URL — auto-detected on GitHub Actions).

Pass --artifact to upload additional files (traces, coverage, logs):

Terminal window
blop-ingest upload --junit report.xml --artifact trace.zip --artifact coverage/

Each extra file becomes its own artifact pointer on the finished event.