Skip to content

Integration Cucumber Messages

aryehcitron@gmail.com edited this page Aug 22, 2026 · 1 revision

Overview

Kronikol can read the Cucumber Messages protocol directly. Any runner that emits it — playwright-bdd (cucumberReporter('message')), cucumber-js --format message, Cucumber-JVM --plugin message:target/messages.ndjson — becomes a first-class Kronikol producer: the feature description, rules, backgrounds, Given/When/Then keywords, data tables, doc strings, scenario outlines with their example values, step outcomes, exceptions, retries and attachments all reach TestRunReport.html and Specifications.html without a Kronikol adapter in the test process.

kronikol ingest ./captures \
  --tests ./captures/tests.ndjson \
  --cucumber-messages ./cucumber/messages.ndjson \
  -o ./Reports

The messages file is not an interaction capture. Keep it outside the directory you pass as an input — kronikol ingest scans input directories recursively for *.ndjson / *.jsonl and would otherwise try to replay it as traffic. (If it does end up inside one, naming it on --cucumber-messages removes it from the capture set; the recommendation stands because the intent is clearer.)


What it is for

Ingesting External Captures already gives an out-of-process runner a way to supply scenario names, outcomes, steps and assertions: the tests NDJSON. That format is deliberately small. A BDD runner knows much more than it can express there — which steps came from a Background:, which Rule: a scenario belongs to, which row of an Examples: table produced it, what the data table under a step held. Rather than grow the tests format until it becomes a second Gherkin, Kronikol reads the one the BDD world already standardised on.

The result is a living document that reads like the feature file, with the captured traffic of each scenario underneath it.


What maps to what

Cucumber Messages Kronikol
GherkinDocument.feature.name / .description Feature.DisplayName / Feature.Description
feature tags Feature.Labels (minus the conventions below)
Rule Scenario.Rule
Background steps Scenario.BackgroundStepsexplicit, never guessed
Pickle.name, scenario description Scenario.DisplayName, Scenario.Description
Pickle.astNodeIds → outline + example row Scenario.OutlineId, ExampleValues, ExampleRawValues
Pickle.tags Scenario.Labels / Categories / IsHappyPath / Feature.Endpoint
Gherkin step keyword (Given , And , But …) ScenarioStep.Keyword (as authored)
PickleStep.type (Context/Action/Outcome) the phase an And inherits — used for phase attribution
PickleStep.text (placeholders expanded) ScenarioStep.Text (+ TextSegments highlighting the substituted values)
step dataTable a tabular StepParameter named table
step docString ScenarioStep.DocString / DocStringMediaType
TestStepFinished.testStepResult.status ScenarioStep.Status — see the table below
…​.duration ScenarioStep.Duration
…​.exception.message / .stackTrace step Comments + Scenario.ErrorMessage / ErrorStackTrace
TestStepStarted.timestamp the <<stepDelimiter>> bar in the sequence diagram
hook test steps dropped, or sub-steps named by the hook with --include-hooks
Attachment Scenario.Attachments / ScenarioStep.Attachments
TestCaseStarted.attempt retries — last attempt wins, earlier ones leave a retry N label
TestRunStarted / TestRunFinished the run window in the report header

Statuses map as PASSED → Passed, SKIPPED/PENDING → Skipped, FAILED/UNDEFINED/AMBIGUOUS → Failed. A scenario's result is its worst step result.

Tag conventions

The same conventions the ReqNRoll integration uses, with the leading @ stripped:

Tag Effect
@happy-path (@happy_path, @happypath) Scenario.IsHappyPath — the scenario sorts first and is highlighted
@category:<name> Scenario.Categories
@endpoint:<path> Feature.Endpoint
anything else Scenario.Labels (scenario's own tags) / Feature.Labels (feature's own tags)

Categories and the endpoint are read from the pickle's tags, so a @category: on the feature applies to every scenario in it. Labels and happy-path are read from the node's own tags, so a feature-level tag does not repeat on every scenario row.


Joining the captured traffic — kronikol-test-id

Cucumber ids are not Kronikol test ids, so on their own the messages give you the Gherkin structure but no traffic under it. The join is one attachment: have a hook attach the 32-hex Kronikol test id (the same value the fixture stamps on the outgoing requests, and the W3C trace id) under the name kronikol-test-id.

// playwright-bdd — steps/hooks.ts
import { createBdd } from 'playwright-bdd';
const { Before } = createBdd(test);

Before(async ({ $testInfo }) => {
  await $testInfo.attach('kronikol-test-id', { body: kronikolTestId, contentType: 'text/plain' });
});

The importer uses that value as Scenario.Id, so the captured interactions (testId on the wire), the reporter's ui/assertion events and the Gherkin structure all converge on one scenario.

Without it the importer mints <pickleId>#<attempt> and records a warning that interactions cannot be joined — the scenario still renders, it just has no traffic.


kronikol ingest options

Option Default
--cucumber-messages <file> A Cucumber Messages NDJSON. Repeatable — pass one per shard/worker.
--include-hooks off Keep hook steps (BeforeEach hook, …) as steps in the report.

Programmatically:

IngestPipeline.Run(new IngestRequest
{
    InteractionFiles = Directory.GetFiles(".logs/taps", "*.ndjson"),
    TestsFile        = ".logs/taps/tests.jsonl",
    CucumberMessagesFiles = [".logs/cucumber/messages.ndjson"],
    IncludeHooks     = false,
    Options          = options,
});

Reading and synthesising can also be driven directly:

var messages = CucumberMessagesReader.ReadFile("messages.ndjson");
var result   = CucumberFeatureSynthesizer.Build(messages);   // Feature[], markers, warnings

Two sources, one report

When both --tests and --cucumber-messages are given, the messages win for structure:

  • for every scenario the messages own, the Gherkin model replaces whatever the tests file produced — feature, description, rule, background, keywords, tables, doc strings, example values and outcomes. The reporter's own step events for those scenarios are dropped, so the diagram never grows a second set of delimiter bars;
  • the tests file still contributes what only it knows: assertion events (nested under the Gherkin step whose time window contains them — the ✓/✗ rows and the notes in the diagram), UI actions, attachments the reporter recorded, the identity, and a failure message when no Gherkin step failed (a runner-level timeout);
  • scenarios the messages do not own — plain unit tests, the --fold-unknown bucket — are kept untouched and rendered after the Gherkin features.

Step delimiter bars, assertion notes and interactions all go through the ordinary ingest machinery: the importer synthesises the same start/step/end records the tests format uses, so nothing about the diagram is special-cased for Cucumber.


Attachments

An Attachment envelope carries either a url (the producer kept the file) or an inline body. Inline base64 bodies are written to a staging folder and then copied into <reports>/attachments/ by the report generator, exactly like any other attachment; a url is resolved to a local path and copied the same way. The display name gains an extension derived from the media type when it has none (end-screenshotend-screenshot.png), because that extension is what makes the report render a screenshot inline instead of as a link. Plain-text bodies are not written out by default (CucumberSynthesisOptions.WriteTextAttachments); the kronikol-test-id attachment never appears in the report.


Tolerance and diagnostics

The reader never fails an ingest because of the messages file:

  • an envelope type this version does not consume (source, stepDefinition, parameterType, anything a future schema adds) is counted and ignored;
  • unknown properties inside an envelope it does consume are ignored;
  • a line that is not valid JSON is counted and skipped;
  • an empty file yields an empty model.

The counts and the human-readable reasons come back as CucumberSynthesisResult.Warnings — malformed lines, unknown envelopes, scenarios with no kronikol-test-id, duplicate ids, attachments that could not be written.

Producers are not all equally careful with timestamps. playwright-bdd 9.2 stamps a step it never reached (SKIPPED after a failure) with the test case's start time, and stamps testCaseFinished before the last step reports back. The importer therefore keeps step starts monotonic within a scenario and takes the scenario end as the later of testCaseFinished and the last testStepFinished — otherwise a Then bar would sort before the When that failed.


Producer notes

Producer Command
playwright-bdd 9.x cucumberReporter('message', { outputFile: '../.logs/cucumber/messages.ndjson' }) — see Integration Playwright
cucumber-js npx cucumber-js --format message:messages.ndjson
Cucumber-JVM --plugin message:target/messages.ndjson

Kronikol targets the protocol, not a runner: the golden fixture in the test suite comes from playwright-bdd 9.2.0 (Cucumber Messages 32.2.0), and any other producer of the same schema works unchanged.


See also

Home


Demo


Getting Started

Common Tasks

Integration Guides

Uninstrumentable / polyglot backends

Extensions

Configuration

Features

Reference

Clone this wiki locally