executable-stories-formatters 0.7.12 → 0.7.13

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/dist/index.d.cts CHANGED
@@ -1,12 +1,5 @@
1
- import { S as StoryMeta, C as CIInfo$1, a as StoryStep, D as DocEntry, N as NormalizedTicket, O as OtelSpan, b as CIProvider, R as RawStatus, c as RawAttachment, d as RawRun, e as RawCIInfo, f as adaptJestRun, g as adaptPlaywrightRun, h as adaptVitestRun } from './index-C0OOaaiK.cjs';
2
- export { i as DocPhase, J as JestAdapterOptions, j as JestAggregatedResult, k as JestFileResult, l as JestTestResult, m as OtelAttributeValue, P as PlaywrightAdapterOptions, n as PlaywrightAnnotation, o as PlaywrightAttachment, p as PlaywrightError, q as PlaywrightLocation, r as PlaywrightStatus, s as PlaywrightTestCase, t as PlaywrightTestResult, u as RawStepEvent, v as RawTestCase, w as STORY_META_KEY, x as StepKeyword, y as StepMode, z as StoryFileReport, V as VitestAdapterOptions, A as VitestSerializedError, B as VitestState, E as VitestTestCase, F as VitestTestModule, G as VitestTestResult, H as toCIInfo, I as toRawCIInfo } from './index-C0OOaaiK.cjs';
3
-
4
- /**
5
- * Canonical types for Layer 2: Anti-Corruption Layer output.
6
- *
7
- * These types are strict and have all required fields populated.
8
- * Formatters (Layer 3) accept only these canonical types.
9
- */
1
+ import { S as StoryMeta, R as RawStatus, C as CIInfo$1, a as StoryStep, D as DocEntry, N as NormalizedTicket, O as OtelSpan, b as CIProvider, c as RawAttachment, d as RawRun, e as RawCIInfo, f as adaptJestRun, g as adaptPlaywrightRun, h as adaptVitestRun } from './index-fqrm5-Xr.cjs';
2
+ export { i as DocPhase, J as JestAdapterOptions, j as JestAggregatedResult, k as JestFileResult, l as JestTestResult, m as OtelAttributeValue, P as PlaywrightAdapterOptions, n as PlaywrightAnnotation, o as PlaywrightAttachment, p as PlaywrightError, q as PlaywrightLocation, r as PlaywrightStatus, s as PlaywrightTestCase, t as PlaywrightTestResult, u as RawStepEvent, v as RawTestCase, w as STORY_META_KEY, x as StepKeyword, y as StepMode, z as StoryFileReport, V as VitestAdapterOptions, A as VitestSerializedError, B as VitestState, E as VitestTestCase, F as VitestTestModule, G as VitestTestResult, H as toCIInfo, I as toRawCIInfo } from './index-fqrm5-Xr.cjs';
10
3
 
11
4
  /** Canonical test status (Cucumber-compatible) */
12
5
  type TestStatus = "passed" | "failed" | "skipped" | "pending";
@@ -14,6 +7,8 @@ type TestStatus = "passed" | "failed" | "skipped" | "pending";
14
7
  interface StepResult {
15
8
  /** Step index (0-based) */
16
9
  index: number;
10
+ /** Stable step ID when available */
11
+ stepId?: string;
17
12
  /** Step status */
18
13
  status: TestStatus;
19
14
  /** Duration in milliseconds (default 0) */
@@ -57,6 +52,8 @@ interface TestCaseResult {
57
52
  sourceLine: number;
58
53
  /** Test status (required) */
59
54
  status: TestStatus;
55
+ /** Original adapter/framework status (preserved for diagnostics). */
56
+ rawStatus?: RawStatus;
60
57
  /** Duration in milliseconds (required, default 0) */
61
58
  durationMs: number;
62
59
  /** Error message if failed */
@@ -1094,6 +1091,7 @@ declare function deriveStepResults(steps: StoryStep[], scenarioStatus: TestStatu
1094
1091
  */
1095
1092
  declare function mergeStepResults(derived: StepResult[], events?: Array<{
1096
1093
  index?: number;
1094
+ stepId?: string;
1097
1095
  status?: string;
1098
1096
  durationMs?: number;
1099
1097
  errorMessage?: string;
package/dist/index.d.ts CHANGED
@@ -1,12 +1,5 @@
1
- import { S as StoryMeta, C as CIInfo$1, a as StoryStep, D as DocEntry, N as NormalizedTicket, O as OtelSpan, b as CIProvider, R as RawStatus, c as RawAttachment, d as RawRun, e as RawCIInfo, f as adaptJestRun, g as adaptPlaywrightRun, h as adaptVitestRun } from './index-C0OOaaiK.js';
2
- export { i as DocPhase, J as JestAdapterOptions, j as JestAggregatedResult, k as JestFileResult, l as JestTestResult, m as OtelAttributeValue, P as PlaywrightAdapterOptions, n as PlaywrightAnnotation, o as PlaywrightAttachment, p as PlaywrightError, q as PlaywrightLocation, r as PlaywrightStatus, s as PlaywrightTestCase, t as PlaywrightTestResult, u as RawStepEvent, v as RawTestCase, w as STORY_META_KEY, x as StepKeyword, y as StepMode, z as StoryFileReport, V as VitestAdapterOptions, A as VitestSerializedError, B as VitestState, E as VitestTestCase, F as VitestTestModule, G as VitestTestResult, H as toCIInfo, I as toRawCIInfo } from './index-C0OOaaiK.js';
3
-
4
- /**
5
- * Canonical types for Layer 2: Anti-Corruption Layer output.
6
- *
7
- * These types are strict and have all required fields populated.
8
- * Formatters (Layer 3) accept only these canonical types.
9
- */
1
+ import { S as StoryMeta, R as RawStatus, C as CIInfo$1, a as StoryStep, D as DocEntry, N as NormalizedTicket, O as OtelSpan, b as CIProvider, c as RawAttachment, d as RawRun, e as RawCIInfo, f as adaptJestRun, g as adaptPlaywrightRun, h as adaptVitestRun } from './index-fqrm5-Xr.js';
2
+ export { i as DocPhase, J as JestAdapterOptions, j as JestAggregatedResult, k as JestFileResult, l as JestTestResult, m as OtelAttributeValue, P as PlaywrightAdapterOptions, n as PlaywrightAnnotation, o as PlaywrightAttachment, p as PlaywrightError, q as PlaywrightLocation, r as PlaywrightStatus, s as PlaywrightTestCase, t as PlaywrightTestResult, u as RawStepEvent, v as RawTestCase, w as STORY_META_KEY, x as StepKeyword, y as StepMode, z as StoryFileReport, V as VitestAdapterOptions, A as VitestSerializedError, B as VitestState, E as VitestTestCase, F as VitestTestModule, G as VitestTestResult, H as toCIInfo, I as toRawCIInfo } from './index-fqrm5-Xr.js';
10
3
 
11
4
  /** Canonical test status (Cucumber-compatible) */
12
5
  type TestStatus = "passed" | "failed" | "skipped" | "pending";
@@ -14,6 +7,8 @@ type TestStatus = "passed" | "failed" | "skipped" | "pending";
14
7
  interface StepResult {
15
8
  /** Step index (0-based) */
16
9
  index: number;
10
+ /** Stable step ID when available */
11
+ stepId?: string;
17
12
  /** Step status */
18
13
  status: TestStatus;
19
14
  /** Duration in milliseconds (default 0) */
@@ -57,6 +52,8 @@ interface TestCaseResult {
57
52
  sourceLine: number;
58
53
  /** Test status (required) */
59
54
  status: TestStatus;
55
+ /** Original adapter/framework status (preserved for diagnostics). */
56
+ rawStatus?: RawStatus;
60
57
  /** Duration in milliseconds (required, default 0) */
61
58
  durationMs: number;
62
59
  /** Error message if failed */
@@ -1094,6 +1091,7 @@ declare function deriveStepResults(steps: StoryStep[], scenarioStatus: TestStatu
1094
1091
  */
1095
1092
  declare function mergeStepResults(derived: StepResult[], events?: Array<{
1096
1093
  index?: number;
1094
+ stepId?: string;
1097
1095
  status?: string;
1098
1096
  durationMs?: number;
1099
1097
  errorMessage?: string;
package/dist/index.js CHANGED
@@ -51,6 +51,7 @@ function deriveStepResults(steps, scenarioStatus, error) {
51
51
  if (scenarioStatus === "passed") {
52
52
  return steps.map((_, index) => ({
53
53
  index,
54
+ stepId: steps[index].id,
54
55
  status: "passed",
55
56
  durationMs: 0
56
57
  }));
@@ -58,6 +59,7 @@ function deriveStepResults(steps, scenarioStatus, error) {
58
59
  if (scenarioStatus === "skipped" || scenarioStatus === "pending") {
59
60
  return steps.map((_, index) => ({
60
61
  index,
62
+ stepId: steps[index].id,
61
63
  status: scenarioStatus,
62
64
  durationMs: 0
63
65
  }));
@@ -65,16 +67,17 @@ function deriveStepResults(steps, scenarioStatus, error) {
65
67
  const failingIndex = findFailingStepIndex(steps, error);
66
68
  return steps.map((_, index) => {
67
69
  if (index < failingIndex) {
68
- return { index, status: "passed", durationMs: 0 };
70
+ return { index, stepId: steps[index].id, status: "passed", durationMs: 0 };
69
71
  } else if (index === failingIndex) {
70
72
  return {
71
73
  index,
74
+ stepId: steps[index].id,
72
75
  status: "failed",
73
76
  durationMs: 0,
74
77
  errorMessage: error?.message
75
78
  };
76
79
  } else {
77
- return { index, status: "skipped", durationMs: 0 };
80
+ return { index, stepId: steps[index].id, status: "skipped", durationMs: 0 };
78
81
  }
79
82
  });
80
83
  }
@@ -96,18 +99,23 @@ function mergeStepResults(derived, events) {
96
99
  return derived;
97
100
  }
98
101
  const actualByIndex = /* @__PURE__ */ new Map();
102
+ const actualByStepId = /* @__PURE__ */ new Map();
99
103
  for (const event of events) {
100
104
  if (event.index !== void 0) {
101
105
  actualByIndex.set(event.index, event);
102
106
  }
107
+ if (event.stepId) {
108
+ actualByStepId.set(event.stepId, event);
109
+ }
103
110
  }
104
111
  return derived.map((step) => {
105
- const actual = actualByIndex.get(step.index);
112
+ const actual = (step.stepId ? actualByStepId.get(step.stepId) : void 0) ?? actualByIndex.get(step.index);
106
113
  if (!actual) {
107
114
  return step;
108
115
  }
109
116
  return {
110
117
  index: step.index,
118
+ stepId: step.stepId ?? actual.stepId,
111
119
  status: normalizeStepStatus(actual.status) ?? step.status,
112
120
  durationMs: actual.durationMs ?? step.durationMs,
113
121
  errorMessage: actual.errorMessage ?? step.errorMessage
@@ -241,6 +249,7 @@ function canonicalizeTestCase(raw, options, projectRoot) {
241
249
  derivedSteps,
242
250
  raw.stepEvents?.map((e) => ({
243
251
  index: e.index,
252
+ stepId: e.stepId,
244
253
  status: e.status,
245
254
  durationMs: e.durationMs,
246
255
  errorMessage: e.errorMessage
@@ -262,6 +271,7 @@ function canonicalizeTestCase(raw, options, projectRoot) {
262
271
  sourceFile,
263
272
  sourceLine: raw.sourceLine ?? 1,
264
273
  status,
274
+ rawStatus: raw.status,
265
275
  durationMs: raw.durationMs ?? 0,
266
276
  errorMessage: raw.error?.message,
267
277
  errorStack: raw.error?.stack,
@@ -629,19 +639,34 @@ ${doc.markdown}`,
629
639
  }
630
640
  };
631
641
  }
632
- case "tag":
642
+ case "custom":
643
+ if (doc.type === "visual" && doc.data && typeof doc.data === "object") {
644
+ const data = doc.data;
645
+ const status = typeof data.status === "string" ? data.status : "unknown";
646
+ const lines = [`Visual Check (${status})`];
647
+ if (typeof data.baseline === "string") lines.push(`Baseline: ${data.baseline}`);
648
+ if (typeof data.actual === "string") lines.push(`Actual: ${data.actual}`);
649
+ if (typeof data.diff === "string") lines.push(`Diff: ${data.diff}`);
650
+ return {
651
+ doc_string: {
652
+ content: lines.join("\n"),
653
+ content_type: "text/plain",
654
+ line: 0
655
+ }
656
+ };
657
+ }
633
658
  return {
634
659
  doc_string: {
635
- content: doc.names.map((n) => `@${n}`).join(" "),
636
- content_type: "text/plain",
660
+ content: JSON.stringify(doc.data, null, 2),
661
+ content_type: "application/json",
637
662
  line: 0
638
663
  }
639
664
  };
640
- case "custom":
665
+ case "tag":
641
666
  return {
642
667
  doc_string: {
643
- content: JSON.stringify(doc.data, null, 2),
644
- content_type: "application/json",
668
+ content: doc.names.map((n) => `@${n}`).join(" "),
669
+ content_type: "text/plain",
645
670
  line: 0
646
671
  }
647
672
  };
@@ -2159,6 +2184,14 @@ body {
2159
2184
  font-family: var(--font-mono);
2160
2185
  }
2161
2186
 
2187
+ .outcome-tag {
2188
+ background: var(--status-fail-bg, color-mix(in srgb, var(--destructive, #ef4444) 12%, transparent));
2189
+ color: var(--destructive, #b91c1c);
2190
+ border-color: color-mix(in srgb, var(--destructive, #ef4444) 35%, transparent);
2191
+ text-transform: uppercase;
2192
+ letter-spacing: 0.04em;
2193
+ }
2194
+
2162
2195
  .scenario-duration {
2163
2196
  font-size: 0.75rem;
2164
2197
  color: var(--muted-foreground);
@@ -2922,6 +2955,63 @@ body {
2922
2955
  opacity: 0.8;
2923
2956
  }
2924
2957
 
2958
+ /* ============================================================================
2959
+ Documentation Entries - Visual Check
2960
+ ============================================================================ */
2961
+ .doc-visual {
2962
+ margin-bottom: 0.5rem;
2963
+ padding: 0.75rem;
2964
+ border: 1px solid var(--border);
2965
+ border-radius: calc(var(--radius) - 2px);
2966
+ background: var(--muted, transparent);
2967
+ }
2968
+
2969
+ .doc-visual:last-child {
2970
+ margin-bottom: 0;
2971
+ }
2972
+
2973
+ .doc-visual-header {
2974
+ font-size: 0.8125rem;
2975
+ font-weight: 600;
2976
+ margin-bottom: 0.5rem;
2977
+ display: flex;
2978
+ align-items: center;
2979
+ gap: 0.5rem;
2980
+ }
2981
+
2982
+ .doc-visual-status {
2983
+ font-size: 0.6875rem;
2984
+ font-family: var(--font-mono);
2985
+ font-weight: 500;
2986
+ padding: 0.125rem 0.5rem;
2987
+ border-radius: 9999px;
2988
+ background: var(--tag-bg);
2989
+ color: var(--tag-color);
2990
+ border: 1px solid var(--tag-border);
2991
+ text-transform: uppercase;
2992
+ letter-spacing: 0.04em;
2993
+ }
2994
+
2995
+ .doc-visual-grid {
2996
+ display: grid;
2997
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
2998
+ gap: 0.5rem;
2999
+ }
3000
+
3001
+ .doc-visual-item {
3002
+ display: flex;
3003
+ flex-direction: column;
3004
+ gap: 0.25rem;
3005
+ }
3006
+
3007
+ .doc-visual-label {
3008
+ font-size: 0.6875rem;
3009
+ font-weight: 600;
3010
+ color: var(--muted-foreground);
3011
+ text-transform: uppercase;
3012
+ letter-spacing: 0.04em;
3013
+ }
3014
+
2925
3015
  /* ============================================================================
2926
3016
  Documentation Entries - Custom
2927
3017
  ============================================================================ */
@@ -13044,6 +13134,22 @@ function renderDocScreenshot(entry, deps) {
13044
13134
  </div>`;
13045
13135
  }
13046
13136
  function renderDocCustom(entry, deps) {
13137
+ if (entry.type === "visual" && entry.data && typeof entry.data === "object") {
13138
+ const data = entry.data;
13139
+ const status = typeof data.status === "string" ? data.status : "unknown";
13140
+ const baseline = typeof data.baseline === "string" ? data.baseline : void 0;
13141
+ const actual = typeof data.actual === "string" ? data.actual : void 0;
13142
+ const diff = typeof data.diff === "string" ? data.diff : void 0;
13143
+ const maybeImg = (src, label) => src ? `<div class="doc-visual-item"><div class="doc-visual-label">${deps.escapeHtml(label ?? "")}</div><img src="${deps.escapeHtml(src)}" alt="${deps.escapeHtml(label ?? "visual image")}" class="doc-screenshot-img" /></div>` : "";
13144
+ return `<div class="doc-visual">
13145
+ <div class="doc-visual-header">Visual Check <span class="doc-visual-status">${deps.escapeHtml(status)}</span></div>
13146
+ <div class="doc-visual-grid">
13147
+ ${maybeImg(baseline, "Baseline")}
13148
+ ${maybeImg(actual, "Actual")}
13149
+ ${maybeImg(diff, "Diff")}
13150
+ </div>
13151
+ </div>`;
13152
+ }
13047
13153
  const dataStr = JSON.stringify(entry.data, null, 2);
13048
13154
  return `<div class="doc-custom">
13049
13155
  <div class="doc-custom-type">${deps.escapeHtml(entry.type)}</div>
@@ -13112,8 +13218,11 @@ function renderStep(step, stepResult, index, deps) {
13112
13218
  </div>${stepDocs}`;
13113
13219
  }
13114
13220
  function renderSteps(args, deps) {
13221
+ const byStepId = new Map(
13222
+ args.stepResults.filter((sr) => typeof sr.stepId === "string" && sr.stepId.length > 0).map((sr) => [sr.stepId, sr])
13223
+ );
13115
13224
  const stepsHtml = args.steps.map((step, index) => {
13116
- const stepResult = args.stepResults.find((sr) => sr.index === index);
13225
+ const stepResult = (step.id ? byStepId.get(step.id) : void 0) ?? args.stepResults.find((sr) => sr.index === index);
13117
13226
  return renderStep(step, stepResult, index, deps);
13118
13227
  }).join("");
13119
13228
  return `<div class="steps">${stepsHtml}</div>`;
@@ -13166,6 +13275,7 @@ function renderScenario(args, deps) {
13166
13275
  const duration = tc.durationMs > 0 ? `${(tc.durationMs / 1e3).toFixed(2)}s` : "";
13167
13276
  const tags = tc.tags.map((t) => `<span class="tag">${deps.escapeHtml(t)}</span>`).join("");
13168
13277
  const tickets = (tc.story.tickets ?? []).map((t) => renderTicket(t, deps.ticketUrlTemplate, deps.escapeHtml)).join("");
13278
+ const outcomeBadge = tc.rawStatus === "timeout" || tc.rawStatus === "interrupted" ? `<span class="tag outcome-tag">${deps.escapeHtml(tc.rawStatus)}</span>` : "";
13169
13279
  const otelMeta = tc.story.meta?.otel;
13170
13280
  let traceBadge = "";
13171
13281
  if (otelMeta?.traceId) {
@@ -13233,7 +13343,7 @@ function renderScenario(args, deps) {
13233
13343
  <span class="status-icon ${statusClass}">${statusIcon2}</span>
13234
13344
  <span class="scenario-name">${deps.escapeHtml(tc.story.scenario)}</span>
13235
13345
  </div>
13236
- <div class="scenario-meta">${tags}${tickets}${sourceLink}${traceBadge}${metricBadges}</div>
13346
+ <div class="scenario-meta">${tags}${tickets}${outcomeBadge}${sourceLink}${traceBadge}${metricBadges}</div>
13237
13347
  </div>
13238
13348
  <div class="scenario-actions">
13239
13349
  <button class="copy-scenario-btn" onclick="copyScenarioAsMarkdown('scenario-${tc.id}')" aria-label="Copy scenario as markdown" title="Copy as Markdown"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg></button>
@@ -14237,6 +14347,9 @@ var MarkdownFormatter = class {
14237
14347
  });
14238
14348
  meta.push(`Tickets: ${ticketLinks.join(", ")}`);
14239
14349
  }
14350
+ if (tc.rawStatus === "timeout" || tc.rawStatus === "interrupted") {
14351
+ meta.push(`Outcome: \`${tc.rawStatus}\``);
14352
+ }
14240
14353
  const otelMeta = tc.story.meta?.otel;
14241
14354
  if (otelMeta?.traceId) {
14242
14355
  const traceTemplate = this.options.traceUrlTemplate;
@@ -14399,6 +14512,16 @@ var MarkdownFormatter = class {
14399
14512
  lines.push(`${indent}![${entry.alt ?? "Screenshot"}](${entry.path})`);
14400
14513
  break;
14401
14514
  case "custom":
14515
+ if (entry.type === "visual" && entry.data && typeof entry.data === "object") {
14516
+ const data = entry.data;
14517
+ const status = typeof data.status === "string" ? data.status : "unknown";
14518
+ lines.push(`${indent}**Visual Check** (${status})`);
14519
+ if (typeof data.baseline === "string") lines.push(`${indent}- Baseline: ${data.baseline}`);
14520
+ if (typeof data.actual === "string") lines.push(`${indent}- Actual: ${data.actual}`);
14521
+ if (typeof data.diff === "string") lines.push(`${indent}- Diff: ${data.diff}`);
14522
+ lines.push(`${indent}`);
14523
+ break;
14524
+ }
14402
14525
  lines.push(`${indent}**[${entry.type}]**`);
14403
14526
  lines.push(`${indent}`);
14404
14527
  lines.push(`${indent}\`\`\`json`);
@@ -17050,6 +17173,14 @@ function adaptPlaywrightRun(testResults, options = {}) {
17050
17173
  message: result.errors[0].message,
17051
17174
  stack: result.errors[0].stack
17052
17175
  } : void 0;
17176
+ const stepEvents = result.stepEvents?.length ? result.stepEvents : story.steps.map(
17177
+ (step, index) => step.durationMs !== void 0 ? {
17178
+ index,
17179
+ stepId: step.id,
17180
+ title: step.text,
17181
+ durationMs: step.durationMs
17182
+ } : void 0
17183
+ ).filter((e) => e !== void 0);
17053
17184
  testCases.push({
17054
17185
  externalId: test.titlePath().join(" > "),
17055
17186
  title: story.scenario,
@@ -17060,8 +17191,7 @@ function adaptPlaywrightRun(testResults, options = {}) {
17060
17191
  status: mapPlaywrightStatus(result.status),
17061
17192
  durationMs: result.duration,
17062
17193
  error,
17063
- stepEvents: void 0,
17064
- // Playwright could provide step info, but we don't capture it yet
17194
+ stepEvents: stepEvents.length > 0 ? stepEvents : void 0,
17065
17195
  attachments,
17066
17196
  meta: {
17067
17197
  playwrightStatus: result.status,