executable-stories-formatters 0.7.14 → 0.8.0

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.cjs CHANGED
@@ -35,6 +35,8 @@ __export(src_exports, {
35
35
  CucumberHtmlFormatter: () => CucumberHtmlFormatter,
36
36
  CucumberJsonFormatter: () => CucumberJsonFormatter,
37
37
  CucumberMessagesFormatter: () => CucumberMessagesFormatter,
38
+ ES_THEME_TOKENS_CSS: () => ES_THEME_TOKENS_CSS,
39
+ ES_THEME_TOKEN_VALUES: () => ES_THEME_TOKEN_VALUES,
38
40
  HtmlFormatter: () => HtmlFormatter,
39
41
  JUnitFormatter: () => JUnitFormatter,
40
42
  MIN_FLAKINESS_SAMPLES: () => MIN_FLAKINESS_SAMPLES,
@@ -45,6 +47,9 @@ __export(src_exports, {
45
47
  RunDiffHtmlFormatter: () => RunDiffHtmlFormatter,
46
48
  RunDiffMarkdownFormatter: () => RunDiffMarkdownFormatter,
47
49
  STORY_META_KEY: () => STORY_META_KEY,
50
+ STORY_REPORT_SCHEMA_MAJOR: () => STORY_REPORT_SCHEMA_MAJOR,
51
+ STORY_REPORT_SCHEMA_VERSION: () => STORY_REPORT_SCHEMA_VERSION,
52
+ StoryReportJsonFormatter: () => StoryReportJsonFormatter,
48
53
  adaptJestRun: () => adaptJestRun,
49
54
  adaptPlaywrightRun: () => adaptPlaywrightRun,
50
55
  adaptVitestRun: () => adaptVitestRun,
@@ -101,13 +106,14 @@ __export(src_exports, {
101
106
  stripAnsi: () => stripAnsi,
102
107
  toCIInfo: () => toCIInfo,
103
108
  toRawCIInfo: () => toRawCIInfo,
109
+ toStoryReport: () => toStoryReport,
104
110
  tryGetActiveOtelContext: () => tryGetActiveOtelContext,
105
111
  updateHistory: () => updateHistory,
106
112
  validateCanonicalRun: () => validateCanonicalRun
107
113
  });
108
114
  module.exports = __toCommonJS(src_exports);
109
115
  var fs8 = require("fs");
110
- var path8 = __toESM(require("path"), 1);
116
+ var path9 = __toESM(require("path"), 1);
111
117
  var fsPromises = __toESM(require("fs/promises"), 1);
112
118
 
113
119
  // src/converters/acl/status.ts
@@ -785,9 +791,299 @@ ${doc.markdown}`,
785
791
  }
786
792
  };
787
793
 
794
+ // src/converters/story-report.ts
795
+ var import_node_path = require("path");
796
+
797
+ // src/types/story-report.ts
798
+ var STORY_REPORT_SCHEMA_VERSION = "1.0";
799
+ var STORY_REPORT_SCHEMA_MAJOR = 1;
800
+
801
+ // src/converters/story-report.ts
802
+ function reportSlug(text2) {
803
+ return text2.toLowerCase().replace(/[/\\.]+/g, "-").replace(/[^\w\s-]/g, "").replace(/[\s_]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
804
+ }
805
+ function toRelativeSourceFile(sourceFile, projectRoot) {
806
+ if (!sourceFile) return sourceFile;
807
+ const normalized = sourceFile.split(import_node_path.posix.sep).join("/");
808
+ const root = projectRoot.split(import_node_path.posix.sep).join("/").replace(/\/$/, "");
809
+ if (root && normalized.startsWith(root + "/")) return normalized.slice(root.length + 1);
810
+ return normalized;
811
+ }
812
+ function fileBasenameTitle(sourceFile) {
813
+ const base = sourceFile.split("/").pop() ?? sourceFile;
814
+ return base.replace(/\.(story\.)?(test|spec)\.[tj]sx?$/, "").replace(/\.[tj]sx?$/, "");
815
+ }
816
+ function emptySummary() {
817
+ return { total: 0, passed: 0, failed: 0, skipped: 0, pending: 0, durationMs: 0 };
818
+ }
819
+ function addToSummary(summary, status, durationMs) {
820
+ summary.total += 1;
821
+ summary[status] += 1;
822
+ summary.durationMs += durationMs;
823
+ }
824
+ function isKeyword(value) {
825
+ return value === "Given" || value === "When" || value === "Then" || value === "And" || value === "But";
826
+ }
827
+ function copyDocEntries(entries) {
828
+ if (!entries || entries.length === 0) return [];
829
+ return entries.map(copyDocEntry);
830
+ }
831
+ function copyDocEntry(entry) {
832
+ const children = entry.children ? { children: copyDocEntries(entry.children) } : {};
833
+ switch (entry.kind) {
834
+ case "note":
835
+ return { kind: "note", text: entry.text, phase: entry.phase, ...children };
836
+ case "tag":
837
+ return { kind: "tag", names: [...entry.names], phase: entry.phase, ...children };
838
+ case "kv":
839
+ return { kind: "kv", label: entry.label, value: entry.value, phase: entry.phase, ...children };
840
+ case "code":
841
+ return {
842
+ kind: "code",
843
+ label: entry.label,
844
+ content: entry.content,
845
+ ...entry.lang ? { lang: entry.lang } : {},
846
+ phase: entry.phase,
847
+ ...children
848
+ };
849
+ case "table":
850
+ return {
851
+ kind: "table",
852
+ label: entry.label,
853
+ columns: [...entry.columns],
854
+ rows: entry.rows.map((r) => [...r]),
855
+ phase: entry.phase,
856
+ ...children
857
+ };
858
+ case "link":
859
+ return { kind: "link", label: entry.label, url: entry.url, phase: entry.phase, ...children };
860
+ case "section":
861
+ return { kind: "section", title: entry.title, markdown: entry.markdown, phase: entry.phase, ...children };
862
+ case "mermaid":
863
+ return {
864
+ kind: "mermaid",
865
+ code: entry.code,
866
+ ...entry.title ? { title: entry.title } : {},
867
+ phase: entry.phase,
868
+ ...children
869
+ };
870
+ case "screenshot":
871
+ return {
872
+ kind: "screenshot",
873
+ path: entry.path,
874
+ ...entry.alt ? { alt: entry.alt } : {},
875
+ phase: entry.phase,
876
+ ...children
877
+ };
878
+ case "custom":
879
+ return {
880
+ kind: "custom",
881
+ type: entry.type,
882
+ data: entry.data,
883
+ phase: entry.phase,
884
+ ...children
885
+ };
886
+ }
887
+ }
888
+ function buildStep(args) {
889
+ const step = {
890
+ id: `${args.scenarioId}--step-${args.index}`,
891
+ index: args.index,
892
+ keyword: args.keyword,
893
+ text: args.text,
894
+ status: args.status,
895
+ durationMs: args.durationMs,
896
+ docEntries: args.docEntries
897
+ };
898
+ if (args.errorMessage !== void 0) step.errorMessage = args.errorMessage;
899
+ if (args.mode !== void 0) step.mode = args.mode;
900
+ return step;
901
+ }
902
+ function buildSteps(scenarioId, tc) {
903
+ const declared = tc.story.steps ?? [];
904
+ const results = tc.stepResults;
905
+ const max = Math.max(declared.length, results.length);
906
+ const steps = [];
907
+ for (let i = 0; i < max; i++) {
908
+ const decl = declared[i];
909
+ const res = results[i];
910
+ const keywordSource = decl?.keyword;
911
+ const keyword = keywordSource && isKeyword(keywordSource) ? keywordSource : "Given";
912
+ const text2 = decl?.text ?? "";
913
+ const status = res?.status ?? "pending";
914
+ const durationMs = res?.durationMs ?? decl?.durationMs ?? 0;
915
+ const docEntries = copyDocEntries(decl?.docs);
916
+ steps.push(buildStep({
917
+ scenarioId,
918
+ index: i,
919
+ keyword,
920
+ text: text2,
921
+ status,
922
+ durationMs,
923
+ ...res?.errorMessage !== void 0 ? { errorMessage: res.errorMessage } : {},
924
+ ...decl?.mode !== void 0 ? { mode: decl.mode } : {},
925
+ docEntries
926
+ }));
927
+ }
928
+ return steps;
929
+ }
930
+ function buildAttachments(tc) {
931
+ return tc.attachments.map((a) => ({
932
+ name: a.name,
933
+ mediaType: a.mediaType,
934
+ body: a.body,
935
+ contentEncoding: a.contentEncoding
936
+ }));
937
+ }
938
+ function buildScenario(tc, featureId) {
939
+ const titleRaw = tc.story.scenario?.trim() || "(untitled scenario)";
940
+ const id = `${featureId}--${reportSlug(titleRaw) || `case-${tc.id}`}`;
941
+ const steps = buildSteps(id, tc);
942
+ const scenario = {
943
+ id,
944
+ title: titleRaw,
945
+ status: tc.status,
946
+ durationMs: tc.durationMs,
947
+ tags: [...tc.tags],
948
+ retry: tc.retry,
949
+ retries: tc.retries,
950
+ docEntries: copyDocEntries(tc.story.docs),
951
+ steps,
952
+ attachments: buildAttachments(tc)
953
+ };
954
+ if (tc.sourceLine && tc.sourceLine > 0) scenario.sourceLine = tc.sourceLine;
955
+ if (tc.errorMessage !== void 0) scenario.errorMessage = tc.errorMessage;
956
+ if (tc.errorStack !== void 0) scenario.errorStack = tc.errorStack;
957
+ const tickets = tc.story.tickets;
958
+ if (tickets && tickets.length > 0) {
959
+ scenario.tickets = tickets.map((t) => t.url ? { id: t.id, url: t.url } : { id: t.id });
960
+ }
961
+ return scenario;
962
+ }
963
+ function deriveFeatureTitle(group, relSourceFile) {
964
+ for (const tc of group) {
965
+ const head = tc.titlePath?.[0];
966
+ if (head && head.trim()) return head.trim();
967
+ }
968
+ return fileBasenameTitle(relSourceFile);
969
+ }
970
+ function compareScenarios(a, b) {
971
+ const aLine = a.sourceLine ?? Number.POSITIVE_INFINITY;
972
+ const bLine = b.sourceLine ?? Number.POSITIVE_INFINITY;
973
+ if (aLine !== bLine) return aLine - bLine;
974
+ return a.title.localeCompare(b.title);
975
+ }
976
+ function buildFeature(relSourceFile, group) {
977
+ const id = `feature-${reportSlug(relSourceFile.replace(/\.[^.]+$/, "")) || "untitled"}`;
978
+ const title = deriveFeatureTitle(group, relSourceFile);
979
+ const summary = emptySummary();
980
+ const scenarios = [];
981
+ for (const tc of group) {
982
+ const scenario = buildScenario(tc, id);
983
+ scenarios.push(scenario);
984
+ addToSummary(summary, scenario.status, scenario.durationMs);
985
+ }
986
+ scenarios.sort(compareScenarios);
987
+ return { id, title, sourceFile: relSourceFile, summary, scenarios };
988
+ }
989
+ function ensureUniqueFeatureIds(features) {
990
+ const seen = /* @__PURE__ */ new Map();
991
+ for (const f of features) {
992
+ const count = seen.get(f.id) ?? 0;
993
+ if (count > 0) f.id = `${f.id}-${count + 1}`;
994
+ seen.set(f.id, count + 1);
995
+ }
996
+ }
997
+ function ensureUniqueScenarioIds(feature) {
998
+ const seen = /* @__PURE__ */ new Map();
999
+ for (const s of feature.scenarios) {
1000
+ const count = seen.get(s.id) ?? 0;
1001
+ if (count > 0) {
1002
+ const newId = `${s.id}-${count + 1}`;
1003
+ for (const step of s.steps) {
1004
+ step.id = step.id.replace(s.id, newId);
1005
+ }
1006
+ s.id = newId;
1007
+ }
1008
+ seen.set(s.id, count + 1);
1009
+ }
1010
+ }
1011
+ function toStoryReport(run) {
1012
+ const groups = /* @__PURE__ */ new Map();
1013
+ for (const tc of run.testCases) {
1014
+ const rel = toRelativeSourceFile(tc.sourceFile, run.projectRoot);
1015
+ const existing = groups.get(rel);
1016
+ if (existing) existing.push(tc);
1017
+ else groups.set(rel, [tc]);
1018
+ }
1019
+ const features = [];
1020
+ for (const [rel, group] of groups) {
1021
+ features.push(buildFeature(rel, group));
1022
+ }
1023
+ features.sort((a, b) => a.title.localeCompare(b.title));
1024
+ ensureUniqueFeatureIds(features);
1025
+ for (const f of features) ensureUniqueScenarioIds(f);
1026
+ const summary = emptySummary();
1027
+ for (const f of features) {
1028
+ summary.total += f.summary.total;
1029
+ summary.passed += f.summary.passed;
1030
+ summary.failed += f.summary.failed;
1031
+ summary.skipped += f.summary.skipped;
1032
+ summary.pending += f.summary.pending;
1033
+ summary.durationMs += f.summary.durationMs;
1034
+ }
1035
+ const report = {
1036
+ schemaVersion: STORY_REPORT_SCHEMA_VERSION,
1037
+ runId: run.runId,
1038
+ startedAtMs: run.startedAtMs,
1039
+ finishedAtMs: run.finishedAtMs,
1040
+ durationMs: run.durationMs,
1041
+ projectRoot: run.projectRoot,
1042
+ summary,
1043
+ features
1044
+ };
1045
+ if (run.packageVersion) report.packageVersion = run.packageVersion;
1046
+ if (run.gitSha) report.gitSha = run.gitSha;
1047
+ if (run.ci) {
1048
+ const ci = { name: run.ci.name };
1049
+ if (run.ci.url) ci.url = run.ci.url;
1050
+ if (run.ci.buildNumber) ci.buildNumber = run.ci.buildNumber;
1051
+ if (run.ci.branch) ci.branch = run.ci.branch;
1052
+ if (run.ci.commitSha) ci.commitSha = run.ci.commitSha;
1053
+ if (run.ci.prNumber) ci.prNumber = run.ci.prNumber;
1054
+ report.ci = ci;
1055
+ }
1056
+ if (run.coverage) {
1057
+ const cov = {};
1058
+ if (run.coverage.linesPct !== void 0) cov.linesPct = run.coverage.linesPct;
1059
+ if (run.coverage.branchesPct !== void 0) cov.branchesPct = run.coverage.branchesPct;
1060
+ if (run.coverage.functionsPct !== void 0) cov.functionsPct = run.coverage.functionsPct;
1061
+ if (run.coverage.statementsPct !== void 0) cov.statementsPct = run.coverage.statementsPct;
1062
+ report.coverage = cov;
1063
+ }
1064
+ return report;
1065
+ }
1066
+
1067
+ // src/formatters/story-report-json.ts
1068
+ var StoryReportJsonFormatter = class {
1069
+ options;
1070
+ constructor(options = {}) {
1071
+ this.options = {
1072
+ pretty: options.pretty ?? true
1073
+ };
1074
+ }
1075
+ toReport(run) {
1076
+ return toStoryReport(run);
1077
+ }
1078
+ format(run) {
1079
+ const report = toStoryReport(run);
1080
+ return this.options.pretty ? JSON.stringify(report, null, 2) : JSON.stringify(report);
1081
+ }
1082
+ };
1083
+
788
1084
  // src/formatters/html/renderers/index.ts
789
1085
  var fs2 = __toESM(require("fs"), 1);
790
- var path2 = __toESM(require("path"), 1);
1086
+ var path3 = __toESM(require("path"), 1);
791
1087
 
792
1088
  // src/formatters/html/template.ts
793
1089
  var JS_THEME = `
@@ -1138,11 +1434,53 @@ function initKeyboardShortcuts() {
1138
1434
  });
1139
1435
  }
1140
1436
 
1141
- // Collapse/expand functionality
1437
+ // Collapse/expand functionality (persisted in localStorage)
1438
+ var COLLAPSE_KEY = 'es-collapsed-ids';
1439
+
1440
+ function loadCollapseState() {
1441
+ try {
1442
+ var raw = localStorage.getItem(COLLAPSE_KEY);
1443
+ return raw ? new Set(JSON.parse(raw)) : new Set();
1444
+ } catch (e) {
1445
+ return new Set();
1446
+ }
1447
+ }
1448
+
1449
+ function saveCollapseState(set) {
1450
+ try {
1451
+ localStorage.setItem(COLLAPSE_KEY, JSON.stringify(Array.from(set)));
1452
+ } catch (e) { /* quota or disabled */ }
1453
+ }
1454
+
1455
+ function persistCollapse(container) {
1456
+ if (!container || !container.id) return;
1457
+ var state = loadCollapseState();
1458
+ if (container.classList.contains('collapsed')) {
1459
+ state.add(container.id);
1460
+ } else {
1461
+ state.delete(container.id);
1462
+ }
1463
+ saveCollapseState(state);
1464
+ }
1465
+
1142
1466
  function toggleCollapse(header, container) {
1143
1467
  container?.classList.toggle('collapsed');
1144
1468
  const isCollapsed = container?.classList.contains('collapsed');
1145
1469
  header.setAttribute('aria-expanded', !isCollapsed);
1470
+ persistCollapse(container);
1471
+ }
1472
+
1473
+ function restoreCollapseState() {
1474
+ var state = loadCollapseState();
1475
+ if (state.size === 0) return;
1476
+ state.forEach(function(id) {
1477
+ var el = document.getElementById(id);
1478
+ if (!el) return;
1479
+ if (!el.classList.contains('feature') && !el.classList.contains('scenario')) return;
1480
+ el.classList.add('collapsed');
1481
+ var header = el.querySelector('.feature-header, .scenario-header');
1482
+ if (header) header.setAttribute('aria-expanded', 'false');
1483
+ });
1146
1484
  }
1147
1485
 
1148
1486
  function initCollapse() {
@@ -1189,14 +1527,20 @@ function expandAll() {
1189
1527
  const header = el.querySelector('.feature-header, .scenario-header, .trace-view-header');
1190
1528
  header?.setAttribute('aria-expanded', 'true');
1191
1529
  });
1530
+ saveCollapseState(new Set());
1192
1531
  }
1193
1532
 
1194
1533
  function collapseAll() {
1534
+ var ids = new Set();
1195
1535
  document.querySelectorAll('.feature, .scenario, .trace-view').forEach(el => {
1196
1536
  el.classList.add('collapsed');
1197
1537
  const header = el.querySelector('.feature-header, .scenario-header, .trace-view-header');
1198
1538
  header?.setAttribute('aria-expanded', 'false');
1539
+ if (el.id && (el.classList.contains('feature') || el.classList.contains('scenario'))) {
1540
+ ids.add(el.id);
1541
+ }
1199
1542
  });
1543
+ saveCollapseState(ids);
1200
1544
  }
1201
1545
 
1202
1546
  // Detail level toggle
@@ -1323,6 +1667,68 @@ function copyScenarioAsMarkdown(scenarioId) {
1323
1667
  });
1324
1668
  }
1325
1669
 
1670
+ // Copy scenario as Claude-ready prompt (failure investigation context)
1671
+ function copyScenarioAsPrompt(scenarioId) {
1672
+ var scenario = document.getElementById(scenarioId);
1673
+ if (!scenario) return;
1674
+
1675
+ var feature = scenario.closest('.feature');
1676
+ var featureTitle = feature ? (feature.querySelector('.feature-title') || {}).textContent || '' : '';
1677
+ var title = (scenario.querySelector('.scenario-name') || {}).textContent || '';
1678
+ var statusEl = scenario.querySelector('.status-icon');
1679
+ var status = statusEl && statusEl.classList.contains('status-passed') ? 'passed' :
1680
+ statusEl && statusEl.classList.contains('status-failed') ? 'failed' :
1681
+ statusEl && statusEl.classList.contains('status-skipped') ? 'skipped' : 'pending';
1682
+ var sourceLink = scenario.querySelector('.source-link');
1683
+ var source = sourceLink ? sourceLink.textContent || '' : '';
1684
+ var tags = Array.from(scenario.querySelectorAll('.scenario-meta .tag')).map(function(t) { return t.textContent.trim(); });
1685
+ var steps = scenario.querySelectorAll('.step, .step.continuation');
1686
+
1687
+ var lines = [];
1688
+ lines.push('I need help investigating a failing executable-story scenario.');
1689
+ lines.push('');
1690
+ if (featureTitle.trim()) lines.push('Feature: ' + featureTitle.trim());
1691
+ lines.push('Scenario: ' + title.trim());
1692
+ lines.push('Status: ' + status);
1693
+ if (source.trim()) lines.push('Source: ' + source.trim());
1694
+ if (tags.length > 0) lines.push('Tags: ' + tags.join(', '));
1695
+ lines.push('');
1696
+ lines.push('Steps:');
1697
+ steps.forEach(function(step) {
1698
+ var keyword = step.getAttribute('data-keyword') || '';
1699
+ var text = step.getAttribute('data-text') || '';
1700
+ var stepStatusEl = step.querySelector('.step-status');
1701
+ var marker = ' ';
1702
+ if (stepStatusEl) {
1703
+ if (stepStatusEl.classList.contains('status-failed')) marker = 'x ';
1704
+ else if (stepStatusEl.classList.contains('status-passed')) marker = '+ ';
1705
+ else if (stepStatusEl.classList.contains('status-skipped')) marker = '- ';
1706
+ }
1707
+ lines.push(marker + keyword + ' ' + text);
1708
+ });
1709
+
1710
+ var errorBox = scenario.querySelector('.error-message');
1711
+ if (errorBox) {
1712
+ lines.push('');
1713
+ lines.push('Error:');
1714
+ lines.push((errorBox.textContent || '').trim());
1715
+ }
1716
+ var stackBox = scenario.querySelector('.error-stack');
1717
+ if (stackBox) {
1718
+ lines.push('');
1719
+ lines.push('Stack:');
1720
+ lines.push((stackBox.textContent || '').trim());
1721
+ }
1722
+
1723
+ lines.push('');
1724
+ lines.push('Please read the source file, identify the root cause, and propose a fix.');
1725
+
1726
+ var text = lines.join('\\n');
1727
+ navigator.clipboard.writeText(text).then(function() {
1728
+ showCopyToast(scenario);
1729
+ });
1730
+ }
1731
+
1326
1732
  // Hash scroll on load
1327
1733
  function initHashScroll() {
1328
1734
  if (!location.hash) return;
@@ -1492,6 +1898,7 @@ function generateScript(options) {
1492
1898
  initCalls.push("initStatusFilter();");
1493
1899
  initCalls.push("initKeyboardShortcuts();");
1494
1900
  initCalls.push("initCollapse();");
1901
+ initCalls.push("restoreCollapseState();");
1495
1902
  initCalls.push("initDetailLevel();");
1496
1903
  initCalls.push("applyAllFilters();");
1497
1904
  initCalls.push("initHashScroll();");
@@ -1596,6 +2003,106 @@ function escapeHtml(str) {
1596
2003
  return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
1597
2004
  }
1598
2005
 
2006
+ // src/theme/tokens.ts
2007
+ var ES_THEME_TOKENS_CSS = `
2008
+ :root,
2009
+ [data-theme="light"] {
2010
+ --es-color-bg: #ffffff;
2011
+ --es-color-fg: #111827;
2012
+ --es-color-muted: #6b7280;
2013
+ --es-color-border: #e5e7eb;
2014
+ --es-color-surface: #f9fafb;
2015
+ --es-color-link: #2563eb;
2016
+ --es-color-passed: #16a34a;
2017
+ --es-color-failed: #dc2626;
2018
+ --es-color-skipped: #9ca3af;
2019
+ --es-color-pending: #d97706;
2020
+ --es-color-passed-bg: #f0fdf4;
2021
+ --es-color-failed-bg: #fef2f2;
2022
+ --es-color-skipped-bg: #f3f4f6;
2023
+ --es-color-pending-bg: #fffbeb;
2024
+ --es-font-body: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
2025
+ --es-font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
2026
+ --es-size-base: 1rem;
2027
+ --es-size-sm: 0.875rem;
2028
+ --es-size-xs: 0.75rem;
2029
+ --es-size-h1: 1.875rem;
2030
+ --es-size-h2: 1.5rem;
2031
+ --es-size-h3: 1.25rem;
2032
+ --es-space-1: 0.25rem;
2033
+ --es-space-2: 0.5rem;
2034
+ --es-space-3: 0.75rem;
2035
+ --es-space-4: 1rem;
2036
+ --es-space-6: 1.5rem;
2037
+ --es-space-8: 2rem;
2038
+ --es-radius: 0.5rem;
2039
+ --es-line: 1.6;
2040
+ --es-measure: 72ch;
2041
+ }
2042
+
2043
+ @media (prefers-color-scheme: dark) {
2044
+ :root {
2045
+ --es-color-bg: #0b0f17;
2046
+ --es-color-fg: #e5e7eb;
2047
+ --es-color-muted: #9ca3af;
2048
+ --es-color-border: #1f2937;
2049
+ --es-color-surface: #111827;
2050
+ --es-color-link: #60a5fa;
2051
+ --es-color-passed: #4ade80;
2052
+ --es-color-failed: #f87171;
2053
+ --es-color-skipped: #6b7280;
2054
+ --es-color-pending: #fbbf24;
2055
+ --es-color-passed-bg: rgba(74, 222, 128, 0.08);
2056
+ --es-color-failed-bg: rgba(248, 113, 113, 0.08);
2057
+ --es-color-skipped-bg: rgba(107, 114, 128, 0.08);
2058
+ --es-color-pending-bg: rgba(251, 191, 36, 0.08);
2059
+ }
2060
+ }
2061
+
2062
+ [data-theme="dark"] {
2063
+ --es-color-bg: #0b0f17;
2064
+ --es-color-fg: #e5e7eb;
2065
+ --es-color-muted: #9ca3af;
2066
+ --es-color-border: #1f2937;
2067
+ --es-color-surface: #111827;
2068
+ --es-color-link: #60a5fa;
2069
+ --es-color-passed: #4ade80;
2070
+ --es-color-failed: #f87171;
2071
+ --es-color-skipped: #6b7280;
2072
+ --es-color-pending: #fbbf24;
2073
+ --es-color-passed-bg: rgba(74, 222, 128, 0.08);
2074
+ --es-color-failed-bg: rgba(248, 113, 113, 0.08);
2075
+ --es-color-skipped-bg: rgba(107, 114, 128, 0.08);
2076
+ --es-color-pending-bg: rgba(251, 191, 36, 0.08);
2077
+ }
2078
+ `.trim();
2079
+ var ES_THEME_TOKEN_VALUES = {
2080
+ light: {
2081
+ "--es-color-bg": "#ffffff",
2082
+ "--es-color-fg": "#111827",
2083
+ "--es-color-muted": "#6b7280",
2084
+ "--es-color-border": "#e5e7eb",
2085
+ "--es-color-surface": "#f9fafb",
2086
+ "--es-color-link": "#2563eb",
2087
+ "--es-color-passed": "#16a34a",
2088
+ "--es-color-failed": "#dc2626",
2089
+ "--es-color-skipped": "#9ca3af",
2090
+ "--es-color-pending": "#d97706"
2091
+ },
2092
+ dark: {
2093
+ "--es-color-bg": "#0b0f17",
2094
+ "--es-color-fg": "#e5e7eb",
2095
+ "--es-color-muted": "#9ca3af",
2096
+ "--es-color-border": "#1f2937",
2097
+ "--es-color-surface": "#111827",
2098
+ "--es-color-link": "#60a5fa",
2099
+ "--es-color-passed": "#4ade80",
2100
+ "--es-color-failed": "#f87171",
2101
+ "--es-color-skipped": "#6b7280",
2102
+ "--es-color-pending": "#fbbf24"
2103
+ }
2104
+ };
2105
+
1599
2106
  // src/formatters/html/styles.ts
1600
2107
  var CSS_STYLES = `
1601
2108
  /* ============================================================================
@@ -1603,6 +2110,14 @@ var CSS_STYLES = `
1603
2110
  ============================================================================ */
1604
2111
  @import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500&display=swap');
1605
2112
 
2113
+ /* ============================================================================
2114
+ executable-stories canonical tokens (--es-*).
2115
+ Shared with executable-stories-react. Override on :root or any ancestor of
2116
+ the report to re-color both the standalone HTML and the React component.
2117
+ ============================================================================ */
2118
+ ${ES_THEME_TOKENS_CSS}
2119
+
2120
+
1606
2121
  /* ============================================================================
1607
2122
  CSS Custom Properties - Light Mode (Default)
1608
2123
  Cucumber-branded shadcn/ui base theme
@@ -3491,6 +4006,33 @@ body {
3491
4006
  color: var(--primary);
3492
4007
  }
3493
4008
 
4009
+ .copy-prompt-btn {
4010
+ display: inline-flex;
4011
+ align-items: center;
4012
+ justify-content: center;
4013
+ width: 1.5rem;
4014
+ height: 1.5rem;
4015
+ border: none;
4016
+ background: none;
4017
+ color: var(--muted-foreground);
4018
+ cursor: pointer;
4019
+ opacity: 0.6;
4020
+ transition: opacity 0.15s ease, transform 0.15s ease;
4021
+ font-size: 0.95rem;
4022
+ padding: 0;
4023
+ flex-shrink: 0;
4024
+ }
4025
+
4026
+ .scenario-header:hover .copy-prompt-btn,
4027
+ .copy-prompt-btn:focus-visible {
4028
+ opacity: 1;
4029
+ }
4030
+
4031
+ .copy-prompt-btn:hover {
4032
+ color: var(--primary);
4033
+ transform: scale(1.15);
4034
+ }
4035
+
3494
4036
  /* ============================================================================
3495
4037
  Keyboard Navigation
3496
4038
  ============================================================================ */
@@ -3728,6 +4270,82 @@ a.toc-title:hover {
3728
4270
  outline-offset: 2px;
3729
4271
  }
3730
4272
 
4273
+ /* ============================================================================
4274
+ Mobile responsive refinements
4275
+ ============================================================================ */
4276
+ @media (max-width: 640px) {
4277
+ .container {
4278
+ padding: 0.875rem;
4279
+ }
4280
+
4281
+ .header {
4282
+ flex-direction: column;
4283
+ align-items: stretch;
4284
+ gap: 0.75rem;
4285
+ }
4286
+
4287
+ .header-actions {
4288
+ flex-wrap: wrap;
4289
+ gap: 0.5rem;
4290
+ }
4291
+
4292
+ .search-input {
4293
+ width: 100%;
4294
+ flex: 1 1 100%;
4295
+ min-width: 0;
4296
+ }
4297
+
4298
+ .header h1 {
4299
+ font-size: 1.25rem;
4300
+ }
4301
+
4302
+ .scenario-header,
4303
+ .feature-header {
4304
+ flex-wrap: wrap;
4305
+ gap: 0.5rem;
4306
+ }
4307
+
4308
+ .scenario-meta {
4309
+ flex-wrap: wrap;
4310
+ }
4311
+
4312
+ .scenario-actions {
4313
+ flex-wrap: wrap;
4314
+ }
4315
+
4316
+ /* Always-visible action buttons on touch (no hover) */
4317
+ .copy-scenario-btn,
4318
+ .copy-prompt-btn,
4319
+ .permalink-anchor {
4320
+ opacity: 1 !important;
4321
+ }
4322
+
4323
+ .summary-card {
4324
+ padding: 0.75rem 0.875rem;
4325
+ }
4326
+
4327
+ .summary-card .value {
4328
+ font-size: 1.5rem;
4329
+ }
4330
+
4331
+ .tag-bar {
4332
+ overflow-x: auto;
4333
+ -webkit-overflow-scrolling: touch;
4334
+ }
4335
+
4336
+ .shortcuts-overlay {
4337
+ padding: 1rem;
4338
+ }
4339
+ }
4340
+
4341
+ @media (hover: none) and (pointer: coarse) {
4342
+ .copy-scenario-btn,
4343
+ .copy-prompt-btn,
4344
+ .permalink-anchor {
4345
+ opacity: 1;
4346
+ }
4347
+ }
4348
+
3731
4349
  `;
3732
4350
 
3733
4351
  // src/formatters/html/themes/default.ts
@@ -13454,6 +14072,7 @@ function renderScenario(args, deps) {
13454
14072
  </div>
13455
14073
  <div class="scenario-actions">
13456
14074
  <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>
14075
+ ${tc.status === "failed" ? `<button class="copy-prompt-btn" onclick="copyScenarioAsPrompt('scenario-${tc.id}')" aria-label="Copy as Claude prompt" title="Copy as AI prompt">\u2728</button>` : ""}
13457
14076
  <button class="permalink-anchor" onclick="copyPermalink('scenario-${tc.id}')" aria-label="Copy link to scenario" title="Copy link">#</button>
13458
14077
  <span class="scenario-duration">${duration}</span>
13459
14078
  </div>
@@ -13853,7 +14472,7 @@ var SCREENSHOT_MIME_BY_EXT = {
13853
14472
  };
13854
14473
  function readScreenshotAsDataUri(filePath) {
13855
14474
  try {
13856
- const ext = path2.extname(filePath).slice(1).toLowerCase();
14475
+ const ext = path3.extname(filePath).slice(1).toLowerCase();
13857
14476
  const mime = SCREENSHOT_MIME_BY_EXT[ext];
13858
14477
  if (!mime) return void 0;
13859
14478
  if (!fs2.existsSync(filePath)) return void 0;
@@ -15330,8 +15949,8 @@ function extractDocAttachments(step) {
15330
15949
  }
15331
15950
  return attachments;
15332
15951
  }
15333
- function guessMediaType(path9) {
15334
- const lower = path9.toLowerCase();
15952
+ function guessMediaType(path10) {
15953
+ const lower = path10.toLowerCase();
15335
15954
  if (lower.endsWith(".png")) return "image/png";
15336
15955
  if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg";
15337
15956
  if (lower.endsWith(".gif")) return "image/gif";
@@ -16376,7 +16995,7 @@ function selectTestCases(args, deps) {
16376
16995
 
16377
16996
  // src/bundler/bundle-assets.ts
16378
16997
  var fs4 = __toESM(require("fs"), 1);
16379
- var path4 = __toESM(require("path"), 1);
16998
+ var path5 = __toESM(require("path"), 1);
16380
16999
 
16381
17000
  // src/bundler/scan-html-assets.ts
16382
17001
  function scanHtmlAssets(html) {
@@ -16407,7 +17026,7 @@ function isLocalAssetRef(ref) {
16407
17026
 
16408
17027
  // src/bundler/copy-asset.ts
16409
17028
  var fs3 = __toESM(require("fs"), 1);
16410
- var path3 = __toESM(require("path"), 1);
17029
+ var path4 = __toESM(require("path"), 1);
16411
17030
  var crypto = __toESM(require("crypto"), 1);
16412
17031
  function copyAsset(sourcePath, assetsDir) {
16413
17032
  if (!fs3.existsSync(assetsDir)) {
@@ -16415,10 +17034,10 @@ function copyAsset(sourcePath, assetsDir) {
16415
17034
  }
16416
17035
  const content = fs3.readFileSync(sourcePath);
16417
17036
  const hash = crypto.createHash("sha256").update(content).digest("hex").slice(0, 8);
16418
- const ext = path3.extname(sourcePath);
16419
- const baseName = sanitize(path3.basename(sourcePath, ext));
17037
+ const ext = path4.extname(sourcePath);
17038
+ const baseName = sanitize(path4.basename(sourcePath, ext));
16420
17039
  const destName = `${baseName}-${hash}${ext}`;
16421
- const destPath = path3.join(assetsDir, destName);
17040
+ const destPath = path4.join(assetsDir, destName);
16422
17041
  if (!fs3.existsSync(destPath)) {
16423
17042
  fs3.copyFileSync(sourcePath, destPath);
16424
17043
  }
@@ -16430,14 +17049,14 @@ function sanitize(name) {
16430
17049
 
16431
17050
  // src/bundler/bundle-assets.ts
16432
17051
  function bundleAssets(htmlPath, options = {}) {
16433
- const htmlDir = path4.dirname(htmlPath);
16434
- const assetsDir = path4.join(htmlDir, "assets");
17052
+ const htmlDir = path5.dirname(htmlPath);
17053
+ const assetsDir = path5.join(htmlDir, "assets");
16435
17054
  let html = fs4.readFileSync(htmlPath, "utf8");
16436
17055
  const refs = scanHtmlAssets(html);
16437
17056
  let copiedCount = 0;
16438
17057
  const missing = [];
16439
17058
  for (const ref of refs) {
16440
- const absolutePath = path4.resolve(htmlDir, ref);
17059
+ const absolutePath = path5.resolve(htmlDir, ref);
16441
17060
  if (!fs4.existsSync(absolutePath)) {
16442
17061
  missing.push(ref);
16443
17062
  continue;
@@ -16935,14 +17554,14 @@ function groupBy7(items, keyFn) {
16935
17554
 
16936
17555
  // src/formatters/astro-assets.ts
16937
17556
  var fs5 = __toESM(require("fs"), 1);
16938
- var path5 = __toESM(require("path"), 1);
17557
+ var path6 = __toESM(require("path"), 1);
16939
17558
  var SKIP_PREFIXES = ["http://", "https://", "data:", "#"];
16940
17559
  function isLocalPath(src) {
16941
17560
  const trimmed = src.trim();
16942
17561
  if (SKIP_PREFIXES.some((prefix) => trimmed.startsWith(prefix))) {
16943
17562
  return false;
16944
17563
  }
16945
- return !path5.posix.isAbsolute(trimmed) && !path5.win32.isAbsolute(trimmed);
17564
+ return !path6.posix.isAbsolute(trimmed) && !path6.win32.isAbsolute(trimmed);
16946
17565
  }
16947
17566
  function stripCodeContent(markdown) {
16948
17567
  let result = markdown.replace(/^[ \t]*(`{3,}|~{3,})[^\n]*\n[\s\S]*?^[ \t]*\1\s*$/gm, "");
@@ -17036,7 +17655,7 @@ function copyMarkdownAssets(options) {
17036
17655
  const pathMap = /* @__PURE__ */ new Map();
17037
17656
  const missing = [];
17038
17657
  for (const ref of refs) {
17039
- const absPath = path5.resolve(markdownDir, ref);
17658
+ const absPath = path6.resolve(markdownDir, ref);
17040
17659
  if (!fs5.existsSync(absPath)) {
17041
17660
  if (!allowMissing) {
17042
17661
  throw new Error(`Asset not found: ${absPath}`);
@@ -18061,24 +18680,24 @@ function pickleStepArgumentToDocs(ps) {
18061
18680
 
18062
18681
  // src/utils/git-info.ts
18063
18682
  var fs6 = __toESM(require("fs"), 1);
18064
- var path6 = __toESM(require("path"), 1);
18683
+ var path7 = __toESM(require("path"), 1);
18065
18684
  function readGitSha(cwd = process.cwd()) {
18066
18685
  const envSha = process.env.GITHUB_SHA || process.env.GIT_COMMIT || process.env.CI_COMMIT_SHA;
18067
18686
  if (envSha) return envSha;
18068
18687
  const gitDir = findGitDir(cwd);
18069
18688
  if (!gitDir) return void 0;
18070
18689
  try {
18071
- const headPath = path6.join(gitDir, "HEAD");
18690
+ const headPath = path7.join(gitDir, "HEAD");
18072
18691
  const head = fs6.readFileSync(headPath, "utf8").trim();
18073
18692
  if (!head.startsWith("ref:")) {
18074
18693
  return head;
18075
18694
  }
18076
18695
  const refPath = head.replace("ref:", "").trim();
18077
- const refFile = path6.join(gitDir, refPath);
18696
+ const refFile = path7.join(gitDir, refPath);
18078
18697
  if (fs6.existsSync(refFile)) {
18079
18698
  return fs6.readFileSync(refFile, "utf8").trim();
18080
18699
  }
18081
- const packedRefs = path6.join(gitDir, "packed-refs");
18700
+ const packedRefs = path7.join(gitDir, "packed-refs");
18082
18701
  if (fs6.existsSync(packedRefs)) {
18083
18702
  const content = fs6.readFileSync(packedRefs, "utf8");
18084
18703
  for (const line of content.split("\n")) {
@@ -18095,19 +18714,19 @@ function readGitSha(cwd = process.cwd()) {
18095
18714
  function findGitDir(start) {
18096
18715
  let current = start;
18097
18716
  while (true) {
18098
- const candidate = path6.join(current, ".git");
18717
+ const candidate = path7.join(current, ".git");
18099
18718
  if (fs6.existsSync(candidate)) {
18100
18719
  const stat = fs6.statSync(candidate);
18101
18720
  if (stat.isFile()) {
18102
18721
  const content = fs6.readFileSync(candidate, "utf8").trim();
18103
18722
  const match = content.match(/^gitdir: (.+)$/);
18104
18723
  if (match) {
18105
- return path6.resolve(current, match[1]);
18724
+ return path7.resolve(current, match[1]);
18106
18725
  }
18107
18726
  }
18108
18727
  return candidate;
18109
18728
  }
18110
- const parent = path6.dirname(current);
18729
+ const parent = path7.dirname(current);
18111
18730
  if (parent === current) return void 0;
18112
18731
  current = parent;
18113
18732
  }
@@ -18118,7 +18737,7 @@ function readBranchName(cwd = process.cwd()) {
18118
18737
  const gitDir = findGitDir(cwd);
18119
18738
  if (!gitDir) return void 0;
18120
18739
  try {
18121
- const headPath = path6.join(gitDir, "HEAD");
18740
+ const headPath = path7.join(gitDir, "HEAD");
18122
18741
  const head = fs6.readFileSync(headPath, "utf8").trim();
18123
18742
  if (head.startsWith("ref:")) {
18124
18743
  const refPath = head.replace("ref:", "").trim();
@@ -18157,7 +18776,7 @@ function nanosecondsToMs(ns) {
18157
18776
 
18158
18777
  // src/utils/metadata.ts
18159
18778
  var fs7 = __toESM(require("fs"), 1);
18160
- var path7 = __toESM(require("path"), 1);
18779
+ var path8 = __toESM(require("path"), 1);
18161
18780
  var versionCache = /* @__PURE__ */ new Map();
18162
18781
  function readPackageVersion(root) {
18163
18782
  if (versionCache.has(root)) {
@@ -18168,9 +18787,9 @@ function readPackageVersion(root) {
18168
18787
  return version;
18169
18788
  }
18170
18789
  function findPackageVersion(startDir) {
18171
- let current = path7.resolve(startDir);
18790
+ let current = path8.resolve(startDir);
18172
18791
  while (true) {
18173
- const pkgPath = path7.join(current, "package.json");
18792
+ const pkgPath = path8.join(current, "package.json");
18174
18793
  try {
18175
18794
  if (fs7.existsSync(pkgPath)) {
18176
18795
  const raw = fs7.readFileSync(pkgPath, "utf8");
@@ -18179,7 +18798,7 @@ function findPackageVersion(startDir) {
18179
18798
  }
18180
18799
  } catch {
18181
18800
  }
18182
- const parent = path7.dirname(current);
18801
+ const parent = path8.dirname(current);
18183
18802
  if (parent === current) {
18184
18803
  return void 0;
18185
18804
  }
@@ -19138,7 +19757,8 @@ var FORMAT_EXTENSIONS = {
19138
19757
  junit: ".junit.xml",
19139
19758
  "cucumber-json": ".cucumber.json",
19140
19759
  "cucumber-messages": ".ndjson",
19141
- confluence: ".adf.json"
19760
+ confluence: ".adf.json",
19761
+ "story-report-json": ".story-report.json"
19142
19762
  };
19143
19763
  var TEST_EXTENSIONS = [
19144
19764
  ".test.ts",
@@ -19165,11 +19785,11 @@ function computeOutputPath(sourceFile, format, mode, colocatedStyle, baseOutputD
19165
19785
  const ext = FORMAT_EXTENSIONS[format];
19166
19786
  const effectiveName = outputName + (outputNameSuffix ?? "");
19167
19787
  if (mode === "aggregated") {
19168
- return toPosix(path8.join(baseOutputDir, `${effectiveName}${ext}`));
19788
+ return toPosix(path9.join(baseOutputDir, `${effectiveName}${ext}`));
19169
19789
  }
19170
19790
  const normalizedSource = toPosix(sourceFile);
19171
- const dirOfSource = path8.posix.dirname(normalizedSource);
19172
- let baseName = path8.posix.basename(normalizedSource);
19791
+ const dirOfSource = path9.posix.dirname(normalizedSource);
19792
+ let baseName = path9.posix.basename(normalizedSource);
19173
19793
  for (const testExt of TEST_EXTENSIONS) {
19174
19794
  if (baseName.endsWith(testExt)) {
19175
19795
  baseName = baseName.slice(0, -testExt.length);
@@ -19178,9 +19798,9 @@ function computeOutputPath(sourceFile, format, mode, colocatedStyle, baseOutputD
19178
19798
  }
19179
19799
  const fileName = `${baseName}.${effectiveName}${ext}`;
19180
19800
  if (colocatedStyle === "adjacent") {
19181
- return toPosix(path8.posix.join(dirOfSource, fileName));
19801
+ return toPosix(path9.posix.join(dirOfSource, fileName));
19182
19802
  }
19183
- return toPosix(path8.posix.join(baseOutputDir, dirOfSource, fileName));
19803
+ return toPosix(path9.posix.join(baseOutputDir, dirOfSource, fileName));
19184
19804
  }
19185
19805
  function groupTestCasesByOutput(testCases, format, options, logger, outputNameSuffix) {
19186
19806
  const groups = /* @__PURE__ */ new Map();
@@ -19249,7 +19869,7 @@ var ReportGenerator = class {
19249
19869
  exclude: options.exclude ?? [],
19250
19870
  includeTags: options.includeTags ?? [],
19251
19871
  excludeTags: options.excludeTags ?? [],
19252
- formats: options.formats ?? ["cucumber-json"],
19872
+ formats: options.formats ?? ["html"],
19253
19873
  outputDir: options.outputDir ?? "reports",
19254
19874
  outputName: options.outputName ?? "index",
19255
19875
  outputNameTimestamp: options.outputNameTimestamp ?? false,
@@ -19263,6 +19883,9 @@ var ReportGenerator = class {
19263
19883
  cucumberJson: {
19264
19884
  pretty: options.cucumberJson?.pretty ?? false
19265
19885
  },
19886
+ storyReportJson: {
19887
+ pretty: options.storyReportJson?.pretty ?? true
19888
+ },
19266
19889
  cucumberMessages: {
19267
19890
  uriStrategy: options.cucumberMessages?.uriStrategy ?? "sourceFile",
19268
19891
  includeSynthetics: options.cucumberMessages?.includeSynthetics ?? true,
@@ -19378,8 +20001,8 @@ var ReportGenerator = class {
19378
20001
  if (astroPaths) {
19379
20002
  for (const mdPath of astroPaths) {
19380
20003
  const content = await fsPromises.readFile(mdPath, "utf8");
19381
- const mdDir = path8.dirname(mdPath);
19382
- const assetsDir = path8.resolve(this.options.astro.assetsDir);
20004
+ const mdDir = path9.dirname(mdPath);
20005
+ const assetsDir = path9.resolve(this.options.astro.assetsDir);
19383
20006
  const result = copyMarkdownAssets({
19384
20007
  markdown: content,
19385
20008
  markdownDir: mdDir,
@@ -19410,9 +20033,9 @@ var ReportGenerator = class {
19410
20033
  if (groups.size === 0 && this.options.output.mode === "aggregated") {
19411
20034
  const ext = FORMAT_EXTENSIONS[format];
19412
20035
  const effectiveName = this.options.outputName + (outputNameSuffix ?? "");
19413
- const outputPath = toPosix(path8.join(this.options.outputDir, `${effectiveName}${ext}`));
20036
+ const outputPath = toPosix(path9.join(this.options.outputDir, `${effectiveName}${ext}`));
19414
20037
  const content = await this.formatContent(run, format);
19415
- const dir = path8.dirname(outputPath);
20038
+ const dir = path9.dirname(outputPath);
19416
20039
  await fsPromises.mkdir(dir, { recursive: true });
19417
20040
  await this.deps.writeFile(outputPath, content);
19418
20041
  return [outputPath];
@@ -19424,7 +20047,7 @@ var ReportGenerator = class {
19424
20047
  testCases
19425
20048
  };
19426
20049
  const content = await this.formatContent(groupRun, format);
19427
- const dir = path8.dirname(outputPath);
20050
+ const dir = path9.dirname(outputPath);
19428
20051
  await fsPromises.mkdir(dir, { recursive: true });
19429
20052
  await this.deps.writeFile(outputPath, content);
19430
20053
  writtenPaths.push(outputPath);
@@ -19531,6 +20154,12 @@ var ReportGenerator = class {
19531
20154
  });
19532
20155
  return formatter.format(run);
19533
20156
  }
20157
+ case "story-report-json": {
20158
+ const formatter = new StoryReportJsonFormatter({
20159
+ pretty: this.options.storyReportJson.pretty
20160
+ });
20161
+ return formatter.format(run);
20162
+ }
19534
20163
  default:
19535
20164
  throw new Error(`Unknown format: ${format}`);
19536
20165
  }
@@ -19547,7 +20176,7 @@ async function generateRunComparison(args) {
19547
20176
  await fsPromises.mkdir(outputDir, { recursive: true });
19548
20177
  for (const format of args.formats) {
19549
20178
  const ext = format === "html" ? ".html" : ".md";
19550
- const outputPath = toPosix(path8.join(outputDir, `${outputName}${ext}`));
20179
+ const outputPath = toPosix(path9.join(outputDir, `${outputName}${ext}`));
19551
20180
  const content = format === "html" ? new RunDiffHtmlFormatter({ title: args.title }).format(diff) : new RunDiffMarkdownFormatter({ title: args.title }).format(diff);
19552
20181
  await fsPromises.writeFile(outputPath, content, "utf8");
19553
20182
  files.push(outputPath);
@@ -19573,6 +20202,8 @@ function normalizePlaywrightResults(testResults, adapterOptions, canonicalizeOpt
19573
20202
  CucumberHtmlFormatter,
19574
20203
  CucumberJsonFormatter,
19575
20204
  CucumberMessagesFormatter,
20205
+ ES_THEME_TOKENS_CSS,
20206
+ ES_THEME_TOKEN_VALUES,
19576
20207
  HtmlFormatter,
19577
20208
  JUnitFormatter,
19578
20209
  MIN_FLAKINESS_SAMPLES,
@@ -19583,6 +20214,9 @@ function normalizePlaywrightResults(testResults, adapterOptions, canonicalizeOpt
19583
20214
  RunDiffHtmlFormatter,
19584
20215
  RunDiffMarkdownFormatter,
19585
20216
  STORY_META_KEY,
20217
+ STORY_REPORT_SCHEMA_MAJOR,
20218
+ STORY_REPORT_SCHEMA_VERSION,
20219
+ StoryReportJsonFormatter,
19586
20220
  adaptJestRun,
19587
20221
  adaptPlaywrightRun,
19588
20222
  adaptVitestRun,
@@ -19639,6 +20273,7 @@ function normalizePlaywrightResults(testResults, adapterOptions, canonicalizeOpt
19639
20273
  stripAnsi,
19640
20274
  toCIInfo,
19641
20275
  toRawCIInfo,
20276
+ toStoryReport,
19642
20277
  tryGetActiveOtelContext,
19643
20278
  updateHistory,
19644
20279
  validateCanonicalRun