Reports
Every klera run can write a versioned JSON report. JSON is canonical;
HTML, JUnit, and OTLP replay are derivative views over the same
artefact. Read the JSON when you want machine-parseable output, render
the HTML when you want a self-contained page to share, emit JUnit
when you want PR-pane test results, replay OTLP when you want to
backfill an existing report into your observability stack.
klera run flows/login.flow.md --report .klera/reports/login.json
klera report .klera/reports/login.json --html out/login.html
klera report .klera/reports/login.json --junit out/login.xml
klera report .klera/reports/login.json --otlpThe JSON artefact
klera run --report <path> writes the JSON when the run finishes
(success or failure). The schema is versioned via a top-level
schemaVersion field; readers should branch on it.
{
"schemaVersion": 4,
"name": "Sign in and see today's notifications",
"status": "passed",
"durationMs": 4123,
"driftCount": 0,
"intentDriftCount": 0,
"replanCount": 0,
"triage": null,
"steps": [
{
"index": 0,
"kind": "tap",
"status": "passed",
"durationMs": 412,
"drift": null,
"intentDrift": null,
"error": null,
"details": null,
"telemetry": null,
"idleWait": { "animations": "idle", "network": "idle" },
"visualDiff": null,
"boundarySnapshots": null,
"matcherTrace": null,
"frames": null,
"sourceLinks": null
}
]
}Top-level fields:
| Field | Purpose |
|---|---|
schemaVersion | Integer; the only field whose meaning never changes. Branch on it. |
name | The flow’s name. |
status | "passed" | "failed" | "skipped". |
durationMs | Wall-clock duration of the whole flow. |
driftCount | Steps that resolved via the matcher’s self-healing strategies. |
intentDriftCount | Steps the planner flagged as semantically drifted from the cache. |
replanCount | Mid-run replans (off in --strict). |
triage | Auto-triage block on failure (verdict + narratives + suspect commits); null on pass. |
steps | Per-step detail array. |
Per-step fields populate as the executor accumulates evidence. On a
clean pass most are null; on a failure the report ships
matcherTrace (every strategy attempt), frames (PNG triplets),
boundarySnapshots (element graphs at the failure boundary), and
optional sourceLinks (denormalised paths back to the React tree —
dev-only).
The schema version bumps every time the shape grows a new field
with semantic meaning. A v4 reader handles v1 / v2 / v3 by reading
the version up front and treating absent fields as the documented
defaults. Don’t Zod.parse against the latest schema unless you
control both producer and consumer.
Rendering views
klera report <report.json> reads a JSON report and renders one or
more derivative views. Without flags it prints a text summary to
stdout; flags compose, each writes an independent artefact.
Text summary
klera report .klera/reports/login.jsonSign in and see today's notifications
status: PASSED (4123ms)
✓ [0] tap — passed
✓ [1] type — passed
✓ [2] assert — passedHTML
klera report .klera/reports/login.json --html out/login.htmlSelf-contained — no external assets, no relative file paths to
preserve. PNG diffs (visual snapshots, failure frames) are embedded as
data URIs. Open out/login.html directly in a browser; share the
file by email or Slack and it works for the recipient with no extra
setup.
The HTML view annotates idle-wait bars per step, draws the visual-diff triplet (actual / baseline / diff) when present, and renders the triage card with verdict + narratives + suspect commit list when the run failed. XSS is escaped at the render boundary.
JUnit XML
klera report .klera/reports/login.json --junit out/login.xmlMapping:
| JUnit element | klera report field |
|---|---|
<testsuite> | one per flow |
<testcase> | one per step |
<failure> | step error + matcher trace |
<skipped/> | skipped step |
<system-out> | drift + triage annotations |
Every CI test-results pane that reads JUnit picks this up: GitHub Actions test reporters, GitLab MR test panes, CircleCI test insights. See CI scaffolds for the wiring.
OTLP replay
OTEL_EXPORTER_OTLP_ENDPOINT=https://api.honeycomb.io \
OTEL_EXPORTER_OTLP_HEADERS=x-honeycomb-team=YOUR_KEY \
klera report .klera/reports/login.json --otlpReplays the report’s spans + metrics through the same bootstrap
klera run uses live. Adopters who wired OTel after they already had
an archive backfill with one shell loop:
for r in .klera/reports/*.json; do
klera report "$r" --otlp
doneOutput:
replayed to OTLP — 4 span(s), 7 counter increment(s), 2 histogram sample(s)--otlp requires a valid OTLP activation source (the standard env
vars or .klera/config.yaml telemetry.otlp.endpoint). Without one,
the command exits 1 with a clear message rather than silently
no-opping. See observability for
the full attribute schema.
Schema versioning
Each schemaVersion bump is additive — fields gain meaning, never
lose it. Older readers see new fields as opaque keys they ignore.
| Version | Added |
|---|---|
1 | base shape: name, status, durationMs, steps[] |
2 | per-step idleWait and visualDiff |
3 | per-step boundarySnapshots, matcherTrace, frames, sourceLinks |
4 | top-level triage block (verdict + narratives + suspect commits) |
Adopters writing report-consumers should:
const report = JSON.parse(await readFile(path, 'utf8'))
switch (report.schemaVersion) {
case 1: return readV1(report)
case 2: return readV2(report)
case 3: return readV3(report)
case 4: return readV4(report)
default: throw new Error(`unknown schemaVersion: ${report.schemaVersion}`)
}The klera/cli ships fixture-tested readers for v1 and v2 (legacy
backwards-compat). Use those if you need a parser that ages well.
Reports + watch
Watch mode honours --report <path>: each iteration overwrites the
same file. Pair with klera report --html and an auto-refreshing
browser tab to keep an HTML view live as you edit. See
watch mode for the full session shape.
Reports + CI
The canonical CI pipeline is JSON → JUnit → test-results pane.
The klera ci scaffolds wire this up automatically; the manual
shape:
GitHub Actions
- run: klera run flows/login.flow.md --report report.json
- run: klera report report.json --junit junit.xml
if: always()
- uses: dorny/test-reporter@v1
if: always()
with:
name: klera
path: junit.xml
reporter: java-junitSee CI scaffolds and observability for end-to-end walkthroughs.