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 | Always (except --reporter basic) |
.blop/events.jsonl |
JSONL | Always (except --reporter basic) |
.blop/report.xml |
JUnit XML | --reporter junit or --reporter all |
.blop/screenshots/* |
PNG | Always — captured during the run |
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 # console only, no files| 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, no artifacts needed | 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 <error> |
Most CI providers (GitHub Actions, GitLab, CircleCI, Buildkite) read this format natively.
Screenshots
Section titled “Screenshots”Captured by browser_screenshot and on every finish_test. 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 "blop";
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 files to reportDir. Set reporter: "basic" to skip
file output and keep only the in-memory BlopRunResult.
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/