Reporters & output
After every run Blop writes a structured artifact bundle to reportDir
(default: .blop/). The bundle is the same regardless of how you trigger the
run — local CLI, watch mode, or programmatic runBlopTests.
Files written
Section titled “Files written”| File | Format | When |
|---|---|---|
.blop/results.json |
JSON | Every reporter mode |
.blop/events.jsonl |
JSONL | Every reporter mode |
.blop/report.xml |
JUnit XML | --reporter junit or --reporter all |
.blop/screenshots/* |
PNG | When the check captures screenshots or per-action capture is enabled |
Choosing a reporter
Section titled “Choosing a reporter”blop test --reporter all # default; everythingblop test --reporter junit # results.json + events.jsonl + report.xmlblop test --reporter json # results.json + events.jsonlblop test --reporter basic # results.json + events.jsonl, no JUnit XML| Use case | Recommended |
|---|---|
| Local development | all (or omit, it’s the default) |
| CI with JUnit ingest | all or junit |
| Programmatic consumer reading JSON | json |
| Quick smoke check without JUnit XML | basic |
results.json
Section titled “results.json”Top-level shape (see API reference → Runner for the full type):
{ "runId": "01...", "status": "passed", "startedAt": "2026-05-09T12:00:00.000Z", "finishedAt": "2026-05-09T12:00:42.000Z", "durationMs": 42_000, "results": [ { "id": "01...", "name": "homepage > loads", "status": "passed", "reason": "Homepage rendered with primary CTA visible.", "startedAt": "...", "finishedAt": "...", "durationMs": 12_000, "attempts": 1, "baseUrl": "http://localhost:3000", "provider": "openai", "model": "gpt-5", "ci": { "provider": "github-actions", "runId": "...", ... }, "screenshots": [".blop/screenshots/01....png"], "actions": [ { "name": "browser_goto", "input": {...}, "output": "...", "timestamp": "..." } ], "events": [ { "event_type": "step_start", ... } ] } ]}The aggregate status rolls up:
errorif any test erroredfailedif any test failed (but none errored)passedotherwise
events.jsonl
Section titled “events.jsonl”One JSON object per line. Useful for live tailing during a run, post-mortem debugging, or shipping into log aggregators.
{"event_type":"step_start","metadata":{},"timestamp":"..."}{"event_type":"text_delta","content":"I'll navigate to /","metadata":{},"timestamp":"..."}{"event_type":"tool_result","content":"navigated","metadata":{"name":"browser_goto"},"timestamp":"..."}{"event_type":"step_finish","metadata":{},"timestamp":"..."}The full event schema lives on
BlopAgentEvent.
report.xml (JUnit)
Section titled “report.xml (JUnit)”A flat <testsuite> per run, one <testcase> per Blop test. Mapping:
| Blop status | JUnit element |
|---|---|
passed |
<testcase> (no children) |
failed |
<testcase> with <failure> |
error |
<testcase> with <failure> |
Most CI providers (GitHub Actions, GitLab, CircleCI, Buildkite) read this format natively.
Screenshots
Section titled “Screenshots”Screenshots are recorded when the check calls the screenshot tool or when
per-action capture is enabled. File paths are absolute when the runner records
them and relative under .blop/screenshots/ when you publish artifacts.
Programmatic access
Section titled “Programmatic access”If you want to act on the result instead of reading from disk:
import { runBlopTests } from "@blopai/cli";
const result = await runBlopTests({ specFiles: ["tests/homepage.blop.ts"], baseUrl: "http://localhost:3000", reporter: "all",});
if (result.status !== "passed") { for (const test of result.results) { if (test.status !== "passed") console.error(test.name, test.reason); } process.exit(1);}The runner still writes results.json and events.jsonl to reportDir in
every reporter mode.
Cleaning up
Section titled “Cleaning up”Blop never removes the report directory between runs. In CI, prefer either:
- A unique
--report-dirper run, e.g.--report-dir .blop/$GITHUB_RUN_ID. - An explicit
rm -rf .blopstep beforeblop test.
Add .blop/ to .gitignore
Section titled “Add .blop/ to .gitignore”The report directory is regenerated on every run and can grow into the megabytes once screenshots accumulate. Don’t commit it:
# Blop test runs — regenerated on every `blop test`; never commit.blop/