Skip to content

Understanding the report

The reporter turns story metadata and test results into Markdown and HTML. Each scenario gets a status icon and optional headings, metadata, and source links.

Report Outputwhat this code generatesOpen live report ↗
The full Executable Stories HTML report: run summary, search and status filters, and Given/When/Then scenarios with inline code and tables
The HTML report your tests generate. Search it, filter by status or tag, and toggle dark mode. Click to open the live one.
Report Outputwhat this code generates
Report header showing summary cards, search input, and tag filter bar
Summary cards, search, and tag filters at the top of every report

Scenarios show a status icon based on step results, with this precedence:

  1. — Any step failed
  2. — All steps passed
  3. 📝 — All steps are todo (or fixme on Playwright)
  4. — All steps are skipped
  5. ⚠️ — Mixed (e.g. some passed, some skipped)

So a scenario is marked failed if any step failed, even if others passed or were skipped.

  • When scenarios are grouped by file (default), each scenario title uses heading level 3 (###). The file group uses level 2 (##).
  • When grouped by none (groupBy: "none"), scenario titles use level 2 (##).

You can override with scenarioHeadingLevel / storyHeadingLevel in reporter options (see each framework’s reference).

  • Scenario title — The scenario heading normally comes from the test title used with story.init(...). If you attach explicit story metadata in a framework-native pattern, that story title is what the report renders.
  • Steps — Each story.given / story.when / story.then (and story.and, story.but) as a bullet. Framework modifiers (skip/todo/fails etc.) are reflected in the step label (e.g. (skipped), (todo)).
  • Planned scenarios — A bodyless it.todo("title") in a file that contains story tests appears as a Planned scenario (Jest and Vitest): specified behavior with no implementation yet. Markdown renders the heading with (planned); the HTML report shows a Planned badge instead of Pending.
  • Tags and options — If you pass { tags: [...], meta: {...} } to story.init(..., options), they can be included (see reporter options).
  • Step documentation — Notes, key-value pairs, code blocks, tables, and links added via story.note, story.json, story.table, etc. appear under the corresponding step.
  • Source link — If permalinkBaseUrl is set (or in GitHub Actions with the built-in fallback), each scenario can get a “Source: file” line.
  • Storyboard — When two or more steps carry a screenshot (e.g. Playwright await story.screenshot({ page, alt }) after each step) or a state snapshot, the scenario opens with a horizontal filmstrip: one frame per step, captioned with the step keyword, each linking to the full detail under its step. Nothing to configure and nothing separately authored — the storyboard is derived from the step docs, so it appears in the HTML report and on Astro story pages alike. This is the view to show product owners: Given → When → Then as pictures, data, or both.
Report Outputwhat this code generates
A scenario with Given/When/Then steps and syntax-highlighted code blocks
Scenarios render with status icons, step keywords, and inline documentation

Inline documentation renders as semantic HTML. You author tables and diagrams in your test; the report draws them, so there are no image files to keep in sync:

Report Outputwhat this code generates
A data table rendered in the report with header row and aligned columns
story.table renders as a real, accessible HTML table.
Report Outputwhat this code generates
A Mermaid flowchart diagram rendered inside the report
story.mermaid draws the diagram live from its source.
Report Outputwhat this code generates
An OpenTelemetry trace waterfall showing spans and durations
An OpenTelemetry trace renders as a span waterfall, so you can see where the time went.

When a scenario fails, the report leads with the error and the step that failed:

Report Outputwhat this code generates
A failed scenario showing the error message, the failing Then step, and a trace
A failure shows the assertion error and points at the exact step that broke.

Storyboards are not screenshot-only. story.state({ label?, value }) captures what the world looks like at a step as a JSON-serializable snapshot, and any step carrying a state doc becomes a filmstrip frame — so API tests, domain logic, and batch jobs get the same visual walkthrough UI code gets:

it('adding an item updates the basket total', ({ task }) => {
story.init(task);
story.given('an empty basket');
const basket = createBasket();
story.state({ label: 'Basket', value: { items: [], total: 0 } });
story.when('the shopper adds a hoodie');
basket.add({ sku: 'hoodie', price: 45 });
story.state({ label: 'Basket', value: { items: [{ sku: 'hoodie', qty: 1 }], total: 45 } });
story.then('the total reflects the item price');
expect(basket.total).toBe(45);
});

The report renders this diff-first:

  • First appearance shows the snapshot. The first Basket frame renders the full value (collapsed behind a summary in Markdown output).
  • Repeats show the change. Consecutive snapshots with the same label are diffed at render time — the second frame reads items[0].qty: added, total: 0 → 45 — so the reader sees what the step did, not two blobs to compare by eye. Diffs are derived, never stored, and never cross scenario boundaries.
  • Labels are lanes. Snapshot two entities (Basket and Order) and each label gets its own side-by-side lane in consistent order. A step can carry a screenshot and a state — the screen next to the backend record it proves.

The same frames feed the stakeholder surfaces:

  • Journey pages (journey:<id> tags) treat scenarios as chapters and show each chapter’s final state card, so a walkthrough ends every chapter with “here is what the world looked like”.
  • The /states catalog (state:<name> tags) gives non-UI scenarios data-card thumbnails, so the grid is no longer screenshots-only. One concept at two granularities: tags name states, story.state() shows them. See Tagging for your audience.

There is no size cap, but the JS adapters warn above ~100KB per snapshot. Capture the business-relevant projection, not the ORM entity: { status, total, items } reads as documentation; forty persistence columns read as noise. Done well, this makes the generated site the document of record for how a feature behaves — the thing teams otherwise hand-maintain in Confluence or TestRail — for backend behaviour as much as UI flows.

When the CLI runs with --history-file (or the reporter is configured with history.filePath), the interactive HTML report layers run-over-run context on top of the current results:

  • Per-scenario timeline — a dot per recent run on each scenario card (oldest → newest), with a tooltip summary like “8/10 runs passed · Passing for the last 5 runs”.
  • Flaky badge — scenarios whose recent runs flip between pass and fail get a Flaky badge next to the timeline, so an unreliable scenario can’t hide behind a green run.
  • “Since last run” strip — one line in the report header summarizing what changed against the previous run: newly failing scenarios (deep-linked), fixed scenarios, and first-seen scenarios. A quiet run says “no behavior changes” rather than nothing.

All of this is presentation-layer data derived from the history store; the StoryReport JSON contract is unchanged. See Run history in the CLI reference.

Reporter options (under markdown in framework reporter config) control what’s included:

  • Status icons: includeStatusIcons: true (default) — show ✅❌⏩ etc. for scenario status.
  • Errors in Markdown: includeErrors: true (default) — include failure messages for failed scenarios.
  • Summary table: includeSummaryTable: true to add a table with start time, duration, and counts.
  • Metadata block: includeMetadata, metadata.date, metadata.packageVersion, metadata.gitSha.
  • Source links: includeSourceLinks: true and permalinkBaseUrl (or rely on GitHub Actions fallback).

See Vitest reporter options, Jest reporter options, and Playwright reporter options for the full list.