Skip to content

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.

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
Terminal window
blop test --reporter all # default; everything
blop test --reporter junit # results.json + events.jsonl + report.xml
blop test --reporter json # results.json + events.jsonl
blop 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

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:

  • error if any test errored
  • failed if any test failed (but none errored)
  • passed otherwise

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.

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.

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.

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.

Blop never removes the report directory between runs. In CI, prefer either:

  • A unique --report-dir per run, e.g. --report-dir .blop/$GITHUB_RUN_ID.
  • An explicit rm -rf .blop step before blop test.

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/