executable-stories-formatters 0.6.2 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "executable-stories-formatters",
3
- "version": "0.6.2",
3
+ "version": "0.7.1",
4
4
  "description": "Cucumber-compatible report formats (HTML, Markdown, JUnit XML, Cucumber JSON) for executable-stories test results.",
5
5
  "author": "Jag Reehal <jag@jagreehal.com>",
6
6
  "license": "MIT",
@@ -57,16 +57,16 @@
57
57
  ],
58
58
  "dependencies": {
59
59
  "@cucumber/html-formatter": "^23.0.0",
60
- "@cucumber/messages": "^32.0.1",
60
+ "@cucumber/messages": "^32.2.0",
61
61
  "ajv": "^8.18.0"
62
62
  },
63
63
  "devDependencies": {
64
64
  "@faker-js/faker": "^10.3.0",
65
- "@types/node": "^25.3.2",
65
+ "@types/node": "^25.5.0",
66
66
  "tsup": "^8.5.1",
67
67
  "tsx": "^4.21.0",
68
68
  "typescript": "~5.9.3",
69
- "vitest": "^4.0.18",
69
+ "vitest": "^4.1.0",
70
70
  "vitest-mock-extended": "^3.1.0"
71
71
  },
72
72
  "scripts": {
@@ -164,8 +164,21 @@
164
164
  },
165
165
  "tickets": {
166
166
  "type": "array",
167
- "items": { "type": "string" },
168
- "description": "Ticket/issue references for requirements traceability (e.g., ['JIRA-123'])."
167
+ "items": {
168
+ "oneOf": [
169
+ { "type": "string" },
170
+ {
171
+ "type": "object",
172
+ "properties": {
173
+ "id": { "type": "string" },
174
+ "url": { "type": "string" }
175
+ },
176
+ "required": ["id"],
177
+ "additionalProperties": false
178
+ }
179
+ ]
180
+ },
181
+ "description": "Ticket/issue references. Each item is either a string ID or an object with id and optional url."
169
182
  },
170
183
  "meta": {
171
184
  "type": "object",
@@ -208,6 +221,19 @@
208
221
  "type": "array",
209
222
  "items": { "$ref": "#/$defs/DocEntry" },
210
223
  "description": "Rich documentation entries attached to this step."
224
+ },
225
+ "id": {
226
+ "type": "string",
227
+ "description": "Unique step identifier within the scenario (e.g., 'step-0')."
228
+ },
229
+ "wrapped": {
230
+ "type": "boolean",
231
+ "description": "Whether this step wraps a function call (Fn/Expect pattern)."
232
+ },
233
+ "durationMs": {
234
+ "type": "number",
235
+ "minimum": 0,
236
+ "description": "Step-level duration in milliseconds (from startTimer/endTimer)."
211
237
  }
212
238
  },
213
239
  "required": ["keyword", "text"],
@@ -237,7 +263,8 @@
237
263
  "properties": {
238
264
  "kind": { "const": "note" },
239
265
  "text": { "type": "string" },
240
- "phase": { "$ref": "#/$defs/DocPhase" }
266
+ "phase": { "$ref": "#/$defs/DocPhase" },
267
+ "children": { "type": "array", "items": { "$ref": "#/$defs/DocEntry" }, "description": "Nested child doc entries for grouping." }
241
268
  },
242
269
  "required": ["kind", "text", "phase"],
243
270
  "additionalProperties": false
@@ -251,7 +278,8 @@
251
278
  "type": "array",
252
279
  "items": { "type": "string" }
253
280
  },
254
- "phase": { "$ref": "#/$defs/DocPhase" }
281
+ "phase": { "$ref": "#/$defs/DocPhase" },
282
+ "children": { "type": "array", "items": { "$ref": "#/$defs/DocEntry" }, "description": "Nested child doc entries for grouping." }
255
283
  },
256
284
  "required": ["kind", "names", "phase"],
257
285
  "additionalProperties": false
@@ -263,7 +291,8 @@
263
291
  "kind": { "const": "kv" },
264
292
  "label": { "type": "string" },
265
293
  "value": {},
266
- "phase": { "$ref": "#/$defs/DocPhase" }
294
+ "phase": { "$ref": "#/$defs/DocPhase" },
295
+ "children": { "type": "array", "items": { "$ref": "#/$defs/DocEntry" }, "description": "Nested child doc entries for grouping." }
267
296
  },
268
297
  "required": ["kind", "label", "value", "phase"],
269
298
  "additionalProperties": false
@@ -276,7 +305,8 @@
276
305
  "label": { "type": "string" },
277
306
  "content": { "type": "string" },
278
307
  "lang": { "type": "string" },
279
- "phase": { "$ref": "#/$defs/DocPhase" }
308
+ "phase": { "$ref": "#/$defs/DocPhase" },
309
+ "children": { "type": "array", "items": { "$ref": "#/$defs/DocEntry" }, "description": "Nested child doc entries for grouping." }
280
310
  },
281
311
  "required": ["kind", "label", "content", "phase"],
282
312
  "additionalProperties": false
@@ -298,7 +328,8 @@
298
328
  "items": { "type": "string" }
299
329
  }
300
330
  },
301
- "phase": { "$ref": "#/$defs/DocPhase" }
331
+ "phase": { "$ref": "#/$defs/DocPhase" },
332
+ "children": { "type": "array", "items": { "$ref": "#/$defs/DocEntry" }, "description": "Nested child doc entries for grouping." }
302
333
  },
303
334
  "required": ["kind", "label", "columns", "rows", "phase"],
304
335
  "additionalProperties": false
@@ -310,7 +341,8 @@
310
341
  "kind": { "const": "link" },
311
342
  "label": { "type": "string" },
312
343
  "url": { "type": "string" },
313
- "phase": { "$ref": "#/$defs/DocPhase" }
344
+ "phase": { "$ref": "#/$defs/DocPhase" },
345
+ "children": { "type": "array", "items": { "$ref": "#/$defs/DocEntry" }, "description": "Nested child doc entries for grouping." }
314
346
  },
315
347
  "required": ["kind", "label", "url", "phase"],
316
348
  "additionalProperties": false
@@ -322,7 +354,8 @@
322
354
  "kind": { "const": "section" },
323
355
  "title": { "type": "string" },
324
356
  "markdown": { "type": "string" },
325
- "phase": { "$ref": "#/$defs/DocPhase" }
357
+ "phase": { "$ref": "#/$defs/DocPhase" },
358
+ "children": { "type": "array", "items": { "$ref": "#/$defs/DocEntry" }, "description": "Nested child doc entries for grouping." }
326
359
  },
327
360
  "required": ["kind", "title", "markdown", "phase"],
328
361
  "additionalProperties": false
@@ -334,7 +367,8 @@
334
367
  "kind": { "const": "mermaid" },
335
368
  "code": { "type": "string" },
336
369
  "title": { "type": "string" },
337
- "phase": { "$ref": "#/$defs/DocPhase" }
370
+ "phase": { "$ref": "#/$defs/DocPhase" },
371
+ "children": { "type": "array", "items": { "$ref": "#/$defs/DocEntry" }, "description": "Nested child doc entries for grouping." }
338
372
  },
339
373
  "required": ["kind", "code", "phase"],
340
374
  "additionalProperties": false
@@ -346,7 +380,8 @@
346
380
  "kind": { "const": "screenshot" },
347
381
  "path": { "type": "string" },
348
382
  "alt": { "type": "string" },
349
- "phase": { "$ref": "#/$defs/DocPhase" }
383
+ "phase": { "$ref": "#/$defs/DocPhase" },
384
+ "children": { "type": "array", "items": { "$ref": "#/$defs/DocEntry" }, "description": "Nested child doc entries for grouping." }
350
385
  },
351
386
  "required": ["kind", "path", "phase"],
352
387
  "additionalProperties": false
@@ -358,7 +393,8 @@
358
393
  "kind": { "const": "custom" },
359
394
  "type": { "type": "string" },
360
395
  "data": {},
361
- "phase": { "$ref": "#/$defs/DocPhase" }
396
+ "phase": { "$ref": "#/$defs/DocPhase" },
397
+ "children": { "type": "array", "items": { "$ref": "#/$defs/DocEntry" }, "description": "Nested child doc entries for grouping." }
362
398
  },
363
399
  "required": ["kind", "type", "data", "phase"],
364
400
  "additionalProperties": false
@@ -36,6 +36,12 @@ executable-stories format raw-run.json --format html,markdown,junit
36
36
  # Read from stdin
37
37
  cat raw-run.json | executable-stories format --stdin --format markdown
38
38
 
39
+ # Compare two canonical runs for review-friendly output
40
+ executable-stories compare baseline.json current.json \
41
+ --input-type canonical \
42
+ --format html,markdown \
43
+ --output-name review-diff
44
+
39
45
  # Validate JSON against schema
40
46
  executable-stories validate raw-run.json
41
47
  ```
@@ -55,6 +61,8 @@ const generator = new ReportGenerator({
55
61
  formats: ["markdown", "html"],
56
62
  outputDir: "docs",
57
63
  outputName: "user-stories",
64
+ outputNameTimestamp: true, // optional: unique filenames per run (e.g. user-stories-1739123456.md)
65
+ sortTestCases: "id", // optional: stable order for diff-friendly reports
58
66
  });
59
67
 
60
68
  const outputs = await generator.generate(canonical);
@@ -99,6 +107,8 @@ const cucumberJson = new CucumberJsonFormatter().formatToString(canonical);
99
107
  --format html,markdown,junit,cucumber-json,cucumber-html,cucumber-messages
100
108
  --output-dir reports # Base directory (default: reports)
101
109
  --output-name test-results # Base filename (default: test-results)
110
+ --output-name-timestamp # Append run timestamp (UTC seconds) to filename for before/after diffs
111
+ --sort-test-cases id|source|none # Deterministic scenario order (default: none). Use id for diff-friendly output
102
112
  --input-type raw # raw | canonical | ndjson
103
113
 
104
114
  # Filtering
@@ -118,8 +128,49 @@ const cucumberJson = new CucumberJsonFormatter().formatToString(canonical);
118
128
  # Machine output
119
129
  --json-summary # Print JSON summary to stdout
120
130
  --emit-canonical path.json # Write canonical JSON
131
+
132
+ # Asset Bundling
133
+ --asset-mode none|copy # Asset bundling strategy (default: none)
134
+ --allow-missing-assets # Warn on missing assets instead of failing
135
+ ```
136
+
137
+ ## Asset Bundling
138
+
139
+ Use `--asset-mode copy` to produce a portable report directory. All locally-referenced assets
140
+ (Playwright videos, screenshots, attachment files) are copied into an `assets/` subdirectory
141
+ and HTML paths are rewritten.
142
+
143
+ ```bash
144
+ executable-stories format raw-run.json --format html --output-dir report --asset-mode copy
121
145
  ```
122
146
 
147
+ Output:
148
+ ```
149
+ report/
150
+ test-results.html # paths rewritten to assets/
151
+ assets/
152
+ video-3f2c1a7b.webm
153
+ step-1-91ab22de.png
154
+ ```
155
+
156
+ ### GitHub Actions usage
157
+
158
+ ```yaml
159
+ - run: npx executable-stories format .executable-stories/raw-run.json --format html --output-dir report --asset-mode copy
160
+ - uses: actions/upload-artifact@v4
161
+ with:
162
+ name: test-report
163
+ path: report/
164
+ ```
165
+
166
+ ### Options
167
+
168
+ | Flag | Description |
169
+ |------|-------------|
170
+ | `--asset-mode none` | Default. No asset bundling. |
171
+ | `--asset-mode copy` | Copy local assets to `assets/`, rewrite paths. |
172
+ | `--allow-missing-assets` | Warn on missing assets instead of failing. |
173
+
123
174
  ### Validation
124
175
 
125
176
  ```typescript
@@ -138,6 +189,35 @@ const result = validateCanonicalRun(canonical);
138
189
  assertValidRun(canonical);
139
190
  ```
140
191
 
192
+ ### Before/after diffs (evolution of tests)
193
+
194
+ To compare reports across runs (e.g. in CI or locally), use timestamped filenames and deterministic ordering so diffs show real changes instead of random reordering from parallel test execution:
195
+
196
+ ```bash
197
+ executable-stories format raw-run.json --format markdown,html \
198
+ --output-name-timestamp \
199
+ --sort-test-cases id
200
+ ```
201
+
202
+ - `--output-name-timestamp`: appends run start time in UTC seconds (e.g. `test-results-1739123456.md`), so each run produces a unique, chronologically sortable file.
203
+ - `--sort-test-cases id`: sorts scenarios by deterministic id (hash of source file + scenario name) so report content order is stable across runs.
204
+
205
+ Programmatic: set `outputNameTimestamp: true` and `sortTestCases: "id"` (or `"source"` for file/line order) on `FormatterOptions`.
206
+
207
+ For first-class run comparisons, use the dedicated compare subcommand:
208
+
209
+ ```bash
210
+ executable-stories compare baseline.json current.json \
211
+ --input-type canonical \
212
+ --format html,markdown \
213
+ --output-dir reports \
214
+ --output-name test-results-diff
215
+ ```
216
+
217
+ - Generates a standalone HTML review report with filter chips for `Regressed`, `Fixed`, `Added`, `Removed`, and `Changed`.
218
+ - Generates Markdown with per-scenario before/after summaries for PR discussion or artifact storage.
219
+ - Use canonical input when you already persist prior runs; raw and ndjson inputs are also supported as long as both files use the same `--input-type`.
220
+
141
221
  ### Notifications
142
222
 
143
223
  ```bash