living-documentation-publisher
Converts passing Cucumber JSON output into stakeholder-facing living documentation: generates HTML reports via multiple-cucumber-html-reporter (Node) or Serenity BDD aggregate (JVM), applies Gherkin tags to drive report sections, and publishes to GitHub/GitLab Pages in CI. Use when BDD scenarios are in use and the team needs an always-current, non-test-engineer-readable document showing which acceptance criteria pass.
living-documentation-publisher
Overview
Per the Serenity BDD book (serenity-bdd/the-serenity-book, living-documentation.adoc):
"Living documentation is generated by the automated test suite, and is therefore by definition it is always up-to-date."
The same source distinguishes living documentation from plain test reports: living documentation is authored before development starts, targets the whole team (BAs, product owners, stakeholders), and reads as a business-functionality narrative, not a pass/fail table.
This skill covers the full workflow: produce Cucumber JSON from a passing suite, feed it into a report renderer, apply tag-based section routing, and publish the HTML artefact to a Pages endpoint so stakeholders get a URL, not a zip file.
Two primary renderers are covered:
When to use
If only engineers read the results, the JUnit XML feed to junit-xml-analysis is sufficient.
Step 1 - Produce Cucumber JSON output
The report renderers both consume Cucumber JSON. Configure the JSON formatter before wiring the renderer.
Cucumber-JS (in package.json):
{
"scripts": {
"test": "cucumber-js features/ --format json:reports/cucumber.json"
}
}Use a timestamped filename when running parallel shards to avoid overwrite (multiple-cucumber-html-reporter usage):
cucumber-js features/ \
--format json:reports/cucumber-$(date +%s).jsonCucumber-JVM with Serenity (in serenity.properties):
The CucumberWithSerenity runner captures results automatically; no explicit JSON formatter is needed. Serenity writes its own JSON artefacts to target/site/serenity/ as part of mvn verify (serenity-bdd/the-serenity-book, maven.adoc).
Step 2 - Generate the HTML report (Node path)
Install the reporter as a dev dependency (multiple-cucumber-html-reporter installation):
npm install multiple-cucumber-html-reporter --save-devCreate scripts/generate-report.js:
const report = require("multiple-cucumber-html-reporter");
report.generate({
// required
jsonDir: "./reports/",
reportPath: "./docs/living-documentation/",
// identification metadata shown in the report header
metadata: {
browser: { name: "chrome", version: "latest" },
device: "CI runner",
platform: { name: "linux", version: "22.04" }
},
// custom info block (release, project, branch)
customData: {
title: "Run info",
data: [
{ label: "Project", value: "Checkout Service" },
{ label: "Release", value: process.env.RELEASE_TAG || "dev" }
]
},
// display options
reportName: "Checkout Service - Living Documentation",
pageTitle: "Acceptance Criteria Status",
displayDuration: true,
durationInMS: true
});Run it after the test step (multiple-cucumber-html-reporter usage):
npm test && node scripts/generate-report.jsKey options from the official docs (multiple-cucumber-html-reporter options):
| Option | Type | Default | Purpose |
|---|---|---|---|
jsonDir | String | required | Directory of Cucumber JSON files |
reportPath | String | required | Output directory for the HTML report |
reportName | String | Title displayed in the UI | |
pageTitle | String | "Multiple Cucumber HTML Reporter" | HTML <head> title |
displayDuration | Boolean | false | Show step/scenario timing |
durationInMS | Boolean | false | Interpret step durations as ms not ns |
saveCollectedJSON | Boolean | false | Keep merged JSON for debugging |
customStyle | Path | Append a CSS file for brand colours | |
overrideStyle | Path | Replace all default CSS |
Step 3 - Generate the HTML report (JVM / Serenity path)
Add the Serenity Maven plugin and bind it to post-integration-test (serenity-bdd/the-serenity-book, maven.adoc):
<plugin>
<groupId>net.serenity-bdd.maven.plugins</groupId>
<artifactId>serenity-maven-plugin</artifactId>
<version>${serenity.maven.version}</version>
<executions>
<execution>
<id>serenity-reports</id>
<phase>post-integration-test</phase>
<goals><goal>aggregate</goal></goals>
</execution>
</executions>
</plugin>Run the full pipeline:
mvn verifyOr regenerate the report from existing test data without re-running tests:
mvn serenity:aggregateThe Requirements tab of the generated report renders living documentation: Serenity reads the directory hierarchy under src/test/resources/features/[theme]/[capability]/ and maps it to the requirements hierarchy (serenity-bdd/the-serenity-book, living-documentation.adoc).
Set hierarchy labels in serenity.properties:
serenity.requirements.types=theme,capability,storyAdd a readme.md at each directory level; Serenity renders it as contextual prose above the scenario list, turning the Requirements tab into a readable illustrated user manual (serenity-bdd/the-serenity-book, living-documentation.adoc).
Step 4 - Tag scenarios for report sections
Use Gherkin tags to categorise scenarios in both renderers.
In the feature file:
@billing @sprint-12
Scenario: Apply promo at checkout
Given ...Serenity tag filtering - run only tagged tests and limit the aggregate report to those requirements (serenity-bdd/the-serenity-book, filtering-reports.adoc):
# Run and report on one sprint
mvn clean verify -Dcucumber.options="--tags=@sprint-12" \
-Dtags=sprint-12
# Post-run report filtered to a tag
mvn serenity:aggregate -Dtags=sprint-12Note: "requirements filtering only happens if you specify the tags option" (serenity-bdd/the-serenity-book, filtering-reports.adoc).
Excluding pending/WIP from published docs:
# Cucumber-JS: exclude anything tagged @wip from the JSON
npx cucumber-js features/ \
--tags "not @wip" \
--format json:reports/cucumber.jsonThis keeps the living-documentation page free of scenarios that are not yet passing.
Step 5 - Only publish passing runs
Gate the report publication step on a green test exit code. In a shell script:
set -e
npm test # exits non-zero on any failure
node scripts/generate-report.js # only reached if all tests passFor Serenity, use serenity:check after verify (serenity-bdd/the-serenity-book, maven.adoc):
mvn verify serenity:check # fails the build if any scenario is redThis ensures the published artefact reflects only a fully-green run.
Step 6 - Publish to GitHub Pages (CI)
GitHub Actions
name: Living Documentation
on:
push:
branches: [main]
jobs:
publish-docs:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Run tests and generate report
run: |
npm ci
npm test
node scripts/generate-report.js
- name: Publish to GitHub Pages
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./docs/living-documentationFor Serenity (JVM), replace the generate step and point publish_dir at target/site/serenity.
GitLab Pages
pages:
stage: deploy
script:
- npm ci
- npm test
- node scripts/generate-report.js
- mkdir -p public
- cp -r docs/living-documentation/* public/
artifacts:
paths:
- public
only:
- mainSee github-actions-test-jobs for general CI test-job conventions.
Anti-patterns
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Publishing on any push, including red runs | Stakeholders see failing-scenario counts; erodes trust in the document | Gate publish on exit code 0 (Step 5) |
Including @wip / @pending scenarios in the report | Document shows work-in-progress as if it is accepted behaviour | Filter with not @wip before generating JSON |
| One flat tag for all scenarios | Stakeholders cannot navigate to a capability area | Apply two-level tags: @capability-name and @sprint-N |
| Publishing stale JSON from a previous run | Report shows old results after source changes | Delete reports/*.json at the start of each CI run before running tests |
| Embedding full screenshots in every step | HTML artefact becomes hundreds of MB | Use Serenity's evidence API (Serenity.recordReportData()) selectively, or configure take.screenshots=FOR_FAILURES in serenity.properties |