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 and emits CloudEvents to Blop Cloud, so any test runner becomes a first-class Blop project without a native adapter and without writing a single scenario.

This is a supported way to start, not a compatibility fallback. Point one CI step at Blop and you get a reliability dashboard for the suite you already have.

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. A uuid. 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 with adapter: "junit" (creates the run row on the platform, and tells it which sections of the run page apply).
  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, per-test rows, failures, artifact pointers, and CI metadata (branch, commit, run URL — auto-detected on GitHub Actions). The branch is what the run is filed under, so a feature branch never lands in your default branch’s reliability window.

What you get without writing a single scenario

Section titled “What you get without writing a single scenario”

An uploaded suite is a first-class run. It is not a lesser row that fills in once you write Blop scenarios.

  • Per-test rows. Every test case gets a stable identity across runs, from its suite, class and name, so it can be tracked over time.
  • Retries told apart from passes. A test that failed and went green on a rerun is recorded as flaky, not as a pass.
  • A rolling pass rate. Each test carries a window of its recent outcomes on your default branch, and crosses into a flaky state when it drops below the threshold.
  • Failure clustering and classification. Failures with the same signature are grouped, and each cluster is classified where the evidence supports it: infrastructure, network race, animation timing, test data pollution, selector drift or a genuine regression.
  • Run detail. Per-test timings and failure messages, with the browser agent sections absent rather than empty.

Scenarios come later, if at all.

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.