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.
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. 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.
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.v1withadapter: "junit"(creates the run row on the platform, and tells it which sections of the run page apply). - 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, 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.
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.