Tagging for your audience
One run JSON serves every audience. Engineers read steps and traces, product owners read capabilities, designers read storyboards, support reads a behavior catalog. Tags your tests already support are what route a scenario to the right reader:
story.init(testInfo, { tags: ['audience:stakeholder', 'capability:checkout', 'storyboard'],});Tags cost one line, live next to the behavior they describe, and can never go stale independently of the test. This guide names a small vocabulary and shows how to surface it as persona views on your Astro site.
The vocabulary
Section titled “The vocabulary”The framework special-cases none of these. They are conventions the views filter and group on, so adopt the ones you need and skip the rest.
| Tag | Meaning | Who filters on it |
|---|---|---|
audience:stakeholder |
Readable by non-engineers: outcome-first title, no internals | Product, leadership |
capability:<name> |
The business capability this verifies (capability:checkout) |
Product: group by capability, not by file |
journey:<id>:<n> |
Position n in an ordered walkthrough, which becomes a page at /journeys/<id> |
Product, design, support |
storyboard |
Carries per-step screenshots, rendered as a visual filmstrip | Design |
state:<name> |
A UI state the product can be in, shown on the /states thumbnail grid |
Design: state catalog |
viewport:<name> |
Layout variant (viewport:mobile), shown side by side within its state |
Design: responsive review |
support |
Answers “when a customer does X, what should happen?” | Customer success |
known-issue |
A deliberate limitation, documented honestly (often with story.but) |
Support, QA |
criticality:<level> |
Business criticality (criticality:revenue) |
QA, leadership: risk weighting |
Two existing options complement the tags: ticket links a scenario to the
requirement it verifies (PM coverage questions), and story.link({ label: 'Figma: Checkout v3', url }) attaches design references. A link pointing at a
design tool (Figma, Zeplin, Sketch), or any link whose label starts with
“Design”, also surfaces as a Design strip at the top of the scenario’s
story page and of every journey it belongs to. Designers land on the mockup
next to the proof.
Persona views
Section titled “Persona views”The views config in executable-stories-astro mounts one filtered,
re-grouped index per audience. Same collection, different lens:
export default defineExecutableStories({ source: 'reports/by-file', views: [ { base: '/for/product', label: 'Product', description: 'What the product verifiably does, by capability.', include: { tags: ['audience:stakeholder'] }, groupBy: 'tag', }, { base: '/for/design', description: 'Visual walkthroughs and UI states, straight from the tests.', include: { tags: ['storyboard'] }, }, { base: '/for/support', description: 'When a customer does X, what should happen?', include: { tags: ['support'] }, }, { base: '/for/qa', description: 'Everything, including planned and known-issue scenarios.', groupBy: 'status', }, ],});Each view gets a page at its base, a sidebar group (“Audiences”) via
storiesSidebar(config), and the same interactive index the main /stories
page uses: search, filters, storyboard filmstrips, failure detail. A view
whose filters match nothing renders a getting-started note naming the tags it
expects, so an empty lens explains itself.
Views never hide failures: a red scenario in a stakeholder view is a feature, not a leak. Honest living docs are the point.
Journeys
Section titled “Journeys”Real user flows span several tests (isolation, parallelism, flake
containment), but stakeholders want one walkthrough. The journey:<id>:<n>
tag composes them:
test('Browse the catalog', async ({ page }, testInfo) => { story.init(testInfo, { tags: ['journey:guest-checkout:1'] }); // ...});
test('Guest checkout walkthrough', async ({ page }, testInfo) => { story.init(testInfo, { tags: ['journey:guest-checkout:2'] }); // ...});Each journey id becomes a page at /journeys/<id>. The member scenarios run
in order, rendered as full cards with their storyboards, under one aggregate
status (failed if any member failed, passed only when all passed). The
order suffix is optional; untagged order falls back to source order. Embed a
journey in prose with <StoryJourney id="guest-checkout" />.
Because journeys are a tag convention, they work in every adapter today.
Playwright journeys get filmstrips, and scenarios that capture
state snapshots
show each chapter’s final state card, so a data-only journey still ends every
chapter with what the world looked like. State diffs never cross scenario
boundaries: each chapter tells its own before-and-after. Support teams can
paste /journeys/<id> links straight into tickets.
If your CI runs the CLI with --history-file, point the site at the same
store and journey pages add a run-history badge (“7/10 recent runs passed ·
flaky”) aggregated from the member scenarios. A journey fails a run when any
member failed it:
export default defineExecutableStories({ source: 'reports/by-file', historyFile: 'reports/history.json',});The state catalog
Section titled “The state catalog”state:<name> tags feed /states: a thumbnail grid of every state the
product verifiably has. Each card shows a scenario’s first screenshot, or for
non-UI scenarios a data-card thumbnail from its first
state snapshot,
linking to its story page. Tag viewport variants (viewport:mobile,
viewport:desktop) and they sit side by side within their state, so one
state’s two layouts are compared at a glance. Designers browse what shipped, not
what a hand-maintained inventory claims.
The tag and the doc verb are one concept at two granularities: state:<name>
names a state the product can be in; story.state() shows the data that
proves it.
For auditors: the traceability CSV
Section titled “For auditors: the traceability CSV”QA and compliance get the same truth as a spreadsheet:
executable-stories format reports/raw-run.json --format traceability-csvOne row per requirement-scenario pair (ticket, requirement status, scenario,
evidence grade, source, covered code), plus a row per untraced scenario. The
coverage gaps are listed explicitly rather than hidden. The evidence_grade column is
the same weak → strong grading the Evidence Review applies (screenshot, OTEL
trace, mutation score, failing-first verification), so the spreadsheet says
not just “passed” but how credible the proof is. It is a flat projection of
the traceability-matrix format, so the two can never disagree.
Environment drift
Section titled “Environment drift”When the site combines two or more sources (a staging run and a production
run, or one run per repo in a docs hub), a /drift page appears: every
scenario’s status per source side by side, mismatches floated to the top.
Behavior verified in one environment but failing, or missing, in another is
exactly the gap a per-environment report hides. Force it on or off with
injectDrift, move it with driftBase.
Leadership
Section titled “Leadership”No separate dashboard is needed: a /for/leadership view grouped by
capability:* tags is the capability map, and the shipped
<HealthDashboard /> and <Trajectory /> components drop into any authored
page for run health and trend.
Linking in from a ticket or a wiki
Section titled “Linking in from a ticket or a wiki”Point external tools at a tag, not at a scenario. The Explorer reads its
filters from the query string, so /explorer/?tag=capability:checkout is a
stable address for “every scenario that verifies checkout”, and
?tag=BILL-402&status=failed narrows it further. q, status, and tag all
work, and the URL updates as you filter, so any view you reach is a link you
can paste back into the ticket.
A per-scenario URL is the thing to avoid. Gojko Adzic makes the case in Specification by Example (ch. 12):
Avoid referring to a particular specification in the living documentation system directly, because that prevents you from reorganizing the documentation later.
Specifications get renamed, moved, split, and merged as the domain model
changes; that is the documentation working, not failing. A tag survives all
four, so a Jira ticket that links ?tag=BILL-402 still resolves after the
scenario it pointed at was split in two. A link to
/stories/refund-a-part-used-subscription/ does not.
A tag with nothing behind it renders an empty result rather than the full list, so a link that has gone stale says so instead of quietly reading as green.
Rules of thumb
Section titled “Rules of thumb”- Tag behaviors, not tests.
capability:refundsdescribes what the scenario proves. Tags likesloworflakybelong to CI, not personas. - Group by the audience’s mental model. Product thinks in capabilities
(
groupBy: 'tag'), QA in statuses, engineers in features/files. - Start with two views.
/for/productand one more. Add lenses when someone asks for them, not before. - Link by tag, never by scenario URL. Tags survive the renames and merges that a healthy living documentation system goes through.
- Outcome-first titles do half the work. “Refund lands within 5 days” reads in every view; “test_refund_worker_retry” reads in none.