executable-stories-formatters 0.7.14 → 0.7.15

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 = `
@@ -1596,6 +1892,106 @@ function escapeHtml(str) {
1596
1892
  return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
1597
1893
  }
1598
1894
 
1895
+ // src/theme/tokens.ts
1896
+ var ES_THEME_TOKENS_CSS = `
1897
+ :root,
1898
+ [data-theme="light"] {
1899
+ --es-color-bg: #ffffff;
1900
+ --es-color-fg: #111827;
1901
+ --es-color-muted: #6b7280;
1902
+ --es-color-border: #e5e7eb;
1903
+ --es-color-surface: #f9fafb;
1904
+ --es-color-link: #2563eb;
1905
+ --es-color-passed: #16a34a;
1906
+ --es-color-failed: #dc2626;
1907
+ --es-color-skipped: #9ca3af;
1908
+ --es-color-pending: #d97706;
1909
+ --es-color-passed-bg: #f0fdf4;
1910
+ --es-color-failed-bg: #fef2f2;
1911
+ --es-color-skipped-bg: #f3f4f6;
1912
+ --es-color-pending-bg: #fffbeb;
1913
+ --es-font-body: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
1914
+ --es-font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
1915
+ --es-size-base: 1rem;
1916
+ --es-size-sm: 0.875rem;
1917
+ --es-size-xs: 0.75rem;
1918
+ --es-size-h1: 1.875rem;
1919
+ --es-size-h2: 1.5rem;
1920
+ --es-size-h3: 1.25rem;
1921
+ --es-space-1: 0.25rem;
1922
+ --es-space-2: 0.5rem;
1923
+ --es-space-3: 0.75rem;
1924
+ --es-space-4: 1rem;
1925
+ --es-space-6: 1.5rem;
1926
+ --es-space-8: 2rem;
1927
+ --es-radius: 0.5rem;
1928
+ --es-line: 1.6;
1929
+ --es-measure: 72ch;
1930
+ }
1931
+
1932
+ @media (prefers-color-scheme: dark) {
1933
+ :root {
1934
+ --es-color-bg: #0b0f17;
1935
+ --es-color-fg: #e5e7eb;
1936
+ --es-color-muted: #9ca3af;
1937
+ --es-color-border: #1f2937;
1938
+ --es-color-surface: #111827;
1939
+ --es-color-link: #60a5fa;
1940
+ --es-color-passed: #4ade80;
1941
+ --es-color-failed: #f87171;
1942
+ --es-color-skipped: #6b7280;
1943
+ --es-color-pending: #fbbf24;
1944
+ --es-color-passed-bg: rgba(74, 222, 128, 0.08);
1945
+ --es-color-failed-bg: rgba(248, 113, 113, 0.08);
1946
+ --es-color-skipped-bg: rgba(107, 114, 128, 0.08);
1947
+ --es-color-pending-bg: rgba(251, 191, 36, 0.08);
1948
+ }
1949
+ }
1950
+
1951
+ [data-theme="dark"] {
1952
+ --es-color-bg: #0b0f17;
1953
+ --es-color-fg: #e5e7eb;
1954
+ --es-color-muted: #9ca3af;
1955
+ --es-color-border: #1f2937;
1956
+ --es-color-surface: #111827;
1957
+ --es-color-link: #60a5fa;
1958
+ --es-color-passed: #4ade80;
1959
+ --es-color-failed: #f87171;
1960
+ --es-color-skipped: #6b7280;
1961
+ --es-color-pending: #fbbf24;
1962
+ --es-color-passed-bg: rgba(74, 222, 128, 0.08);
1963
+ --es-color-failed-bg: rgba(248, 113, 113, 0.08);
1964
+ --es-color-skipped-bg: rgba(107, 114, 128, 0.08);
1965
+ --es-color-pending-bg: rgba(251, 191, 36, 0.08);
1966
+ }
1967
+ `.trim();
1968
+ var ES_THEME_TOKEN_VALUES = {
1969
+ light: {
1970
+ "--es-color-bg": "#ffffff",
1971
+ "--es-color-fg": "#111827",
1972
+ "--es-color-muted": "#6b7280",
1973
+ "--es-color-border": "#e5e7eb",
1974
+ "--es-color-surface": "#f9fafb",
1975
+ "--es-color-link": "#2563eb",
1976
+ "--es-color-passed": "#16a34a",
1977
+ "--es-color-failed": "#dc2626",
1978
+ "--es-color-skipped": "#9ca3af",
1979
+ "--es-color-pending": "#d97706"
1980
+ },
1981
+ dark: {
1982
+ "--es-color-bg": "#0b0f17",
1983
+ "--es-color-fg": "#e5e7eb",
1984
+ "--es-color-muted": "#9ca3af",
1985
+ "--es-color-border": "#1f2937",
1986
+ "--es-color-surface": "#111827",
1987
+ "--es-color-link": "#60a5fa",
1988
+ "--es-color-passed": "#4ade80",
1989
+ "--es-color-failed": "#f87171",
1990
+ "--es-color-skipped": "#6b7280",
1991
+ "--es-color-pending": "#fbbf24"
1992
+ }
1993
+ };
1994
+
1599
1995
  // src/formatters/html/styles.ts
1600
1996
  var CSS_STYLES = `
1601
1997
  /* ============================================================================
@@ -1603,6 +1999,14 @@ var CSS_STYLES = `
1603
1999
  ============================================================================ */
1604
2000
  @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
2001
 
2002
+ /* ============================================================================
2003
+ executable-stories canonical tokens (--es-*).
2004
+ Shared with executable-stories-react. Override on :root or any ancestor of
2005
+ the report to re-color both the standalone HTML and the React component.
2006
+ ============================================================================ */
2007
+ ${ES_THEME_TOKENS_CSS}
2008
+
2009
+
1606
2010
  /* ============================================================================
1607
2011
  CSS Custom Properties - Light Mode (Default)
1608
2012
  Cucumber-branded shadcn/ui base theme
@@ -13853,7 +14257,7 @@ var SCREENSHOT_MIME_BY_EXT = {
13853
14257
  };
13854
14258
  function readScreenshotAsDataUri(filePath) {
13855
14259
  try {
13856
- const ext = path2.extname(filePath).slice(1).toLowerCase();
14260
+ const ext = path3.extname(filePath).slice(1).toLowerCase();
13857
14261
  const mime = SCREENSHOT_MIME_BY_EXT[ext];
13858
14262
  if (!mime) return void 0;
13859
14263
  if (!fs2.existsSync(filePath)) return void 0;
@@ -15330,8 +15734,8 @@ function extractDocAttachments(step) {
15330
15734
  }
15331
15735
  return attachments;
15332
15736
  }
15333
- function guessMediaType(path9) {
15334
- const lower = path9.toLowerCase();
15737
+ function guessMediaType(path10) {
15738
+ const lower = path10.toLowerCase();
15335
15739
  if (lower.endsWith(".png")) return "image/png";
15336
15740
  if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg";
15337
15741
  if (lower.endsWith(".gif")) return "image/gif";
@@ -16376,7 +16780,7 @@ function selectTestCases(args, deps) {
16376
16780
 
16377
16781
  // src/bundler/bundle-assets.ts
16378
16782
  var fs4 = __toESM(require("fs"), 1);
16379
- var path4 = __toESM(require("path"), 1);
16783
+ var path5 = __toESM(require("path"), 1);
16380
16784
 
16381
16785
  // src/bundler/scan-html-assets.ts
16382
16786
  function scanHtmlAssets(html) {
@@ -16407,7 +16811,7 @@ function isLocalAssetRef(ref) {
16407
16811
 
16408
16812
  // src/bundler/copy-asset.ts
16409
16813
  var fs3 = __toESM(require("fs"), 1);
16410
- var path3 = __toESM(require("path"), 1);
16814
+ var path4 = __toESM(require("path"), 1);
16411
16815
  var crypto = __toESM(require("crypto"), 1);
16412
16816
  function copyAsset(sourcePath, assetsDir) {
16413
16817
  if (!fs3.existsSync(assetsDir)) {
@@ -16415,10 +16819,10 @@ function copyAsset(sourcePath, assetsDir) {
16415
16819
  }
16416
16820
  const content = fs3.readFileSync(sourcePath);
16417
16821
  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));
16822
+ const ext = path4.extname(sourcePath);
16823
+ const baseName = sanitize(path4.basename(sourcePath, ext));
16420
16824
  const destName = `${baseName}-${hash}${ext}`;
16421
- const destPath = path3.join(assetsDir, destName);
16825
+ const destPath = path4.join(assetsDir, destName);
16422
16826
  if (!fs3.existsSync(destPath)) {
16423
16827
  fs3.copyFileSync(sourcePath, destPath);
16424
16828
  }
@@ -16430,14 +16834,14 @@ function sanitize(name) {
16430
16834
 
16431
16835
  // src/bundler/bundle-assets.ts
16432
16836
  function bundleAssets(htmlPath, options = {}) {
16433
- const htmlDir = path4.dirname(htmlPath);
16434
- const assetsDir = path4.join(htmlDir, "assets");
16837
+ const htmlDir = path5.dirname(htmlPath);
16838
+ const assetsDir = path5.join(htmlDir, "assets");
16435
16839
  let html = fs4.readFileSync(htmlPath, "utf8");
16436
16840
  const refs = scanHtmlAssets(html);
16437
16841
  let copiedCount = 0;
16438
16842
  const missing = [];
16439
16843
  for (const ref of refs) {
16440
- const absolutePath = path4.resolve(htmlDir, ref);
16844
+ const absolutePath = path5.resolve(htmlDir, ref);
16441
16845
  if (!fs4.existsSync(absolutePath)) {
16442
16846
  missing.push(ref);
16443
16847
  continue;
@@ -16935,14 +17339,14 @@ function groupBy7(items, keyFn) {
16935
17339
 
16936
17340
  // src/formatters/astro-assets.ts
16937
17341
  var fs5 = __toESM(require("fs"), 1);
16938
- var path5 = __toESM(require("path"), 1);
17342
+ var path6 = __toESM(require("path"), 1);
16939
17343
  var SKIP_PREFIXES = ["http://", "https://", "data:", "#"];
16940
17344
  function isLocalPath(src) {
16941
17345
  const trimmed = src.trim();
16942
17346
  if (SKIP_PREFIXES.some((prefix) => trimmed.startsWith(prefix))) {
16943
17347
  return false;
16944
17348
  }
16945
- return !path5.posix.isAbsolute(trimmed) && !path5.win32.isAbsolute(trimmed);
17349
+ return !path6.posix.isAbsolute(trimmed) && !path6.win32.isAbsolute(trimmed);
16946
17350
  }
16947
17351
  function stripCodeContent(markdown) {
16948
17352
  let result = markdown.replace(/^[ \t]*(`{3,}|~{3,})[^\n]*\n[\s\S]*?^[ \t]*\1\s*$/gm, "");
@@ -17036,7 +17440,7 @@ function copyMarkdownAssets(options) {
17036
17440
  const pathMap = /* @__PURE__ */ new Map();
17037
17441
  const missing = [];
17038
17442
  for (const ref of refs) {
17039
- const absPath = path5.resolve(markdownDir, ref);
17443
+ const absPath = path6.resolve(markdownDir, ref);
17040
17444
  if (!fs5.existsSync(absPath)) {
17041
17445
  if (!allowMissing) {
17042
17446
  throw new Error(`Asset not found: ${absPath}`);
@@ -18061,24 +18465,24 @@ function pickleStepArgumentToDocs(ps) {
18061
18465
 
18062
18466
  // src/utils/git-info.ts
18063
18467
  var fs6 = __toESM(require("fs"), 1);
18064
- var path6 = __toESM(require("path"), 1);
18468
+ var path7 = __toESM(require("path"), 1);
18065
18469
  function readGitSha(cwd = process.cwd()) {
18066
18470
  const envSha = process.env.GITHUB_SHA || process.env.GIT_COMMIT || process.env.CI_COMMIT_SHA;
18067
18471
  if (envSha) return envSha;
18068
18472
  const gitDir = findGitDir(cwd);
18069
18473
  if (!gitDir) return void 0;
18070
18474
  try {
18071
- const headPath = path6.join(gitDir, "HEAD");
18475
+ const headPath = path7.join(gitDir, "HEAD");
18072
18476
  const head = fs6.readFileSync(headPath, "utf8").trim();
18073
18477
  if (!head.startsWith("ref:")) {
18074
18478
  return head;
18075
18479
  }
18076
18480
  const refPath = head.replace("ref:", "").trim();
18077
- const refFile = path6.join(gitDir, refPath);
18481
+ const refFile = path7.join(gitDir, refPath);
18078
18482
  if (fs6.existsSync(refFile)) {
18079
18483
  return fs6.readFileSync(refFile, "utf8").trim();
18080
18484
  }
18081
- const packedRefs = path6.join(gitDir, "packed-refs");
18485
+ const packedRefs = path7.join(gitDir, "packed-refs");
18082
18486
  if (fs6.existsSync(packedRefs)) {
18083
18487
  const content = fs6.readFileSync(packedRefs, "utf8");
18084
18488
  for (const line of content.split("\n")) {
@@ -18095,19 +18499,19 @@ function readGitSha(cwd = process.cwd()) {
18095
18499
  function findGitDir(start) {
18096
18500
  let current = start;
18097
18501
  while (true) {
18098
- const candidate = path6.join(current, ".git");
18502
+ const candidate = path7.join(current, ".git");
18099
18503
  if (fs6.existsSync(candidate)) {
18100
18504
  const stat = fs6.statSync(candidate);
18101
18505
  if (stat.isFile()) {
18102
18506
  const content = fs6.readFileSync(candidate, "utf8").trim();
18103
18507
  const match = content.match(/^gitdir: (.+)$/);
18104
18508
  if (match) {
18105
- return path6.resolve(current, match[1]);
18509
+ return path7.resolve(current, match[1]);
18106
18510
  }
18107
18511
  }
18108
18512
  return candidate;
18109
18513
  }
18110
- const parent = path6.dirname(current);
18514
+ const parent = path7.dirname(current);
18111
18515
  if (parent === current) return void 0;
18112
18516
  current = parent;
18113
18517
  }
@@ -18118,7 +18522,7 @@ function readBranchName(cwd = process.cwd()) {
18118
18522
  const gitDir = findGitDir(cwd);
18119
18523
  if (!gitDir) return void 0;
18120
18524
  try {
18121
- const headPath = path6.join(gitDir, "HEAD");
18525
+ const headPath = path7.join(gitDir, "HEAD");
18122
18526
  const head = fs6.readFileSync(headPath, "utf8").trim();
18123
18527
  if (head.startsWith("ref:")) {
18124
18528
  const refPath = head.replace("ref:", "").trim();
@@ -18157,7 +18561,7 @@ function nanosecondsToMs(ns) {
18157
18561
 
18158
18562
  // src/utils/metadata.ts
18159
18563
  var fs7 = __toESM(require("fs"), 1);
18160
- var path7 = __toESM(require("path"), 1);
18564
+ var path8 = __toESM(require("path"), 1);
18161
18565
  var versionCache = /* @__PURE__ */ new Map();
18162
18566
  function readPackageVersion(root) {
18163
18567
  if (versionCache.has(root)) {
@@ -18168,9 +18572,9 @@ function readPackageVersion(root) {
18168
18572
  return version;
18169
18573
  }
18170
18574
  function findPackageVersion(startDir) {
18171
- let current = path7.resolve(startDir);
18575
+ let current = path8.resolve(startDir);
18172
18576
  while (true) {
18173
- const pkgPath = path7.join(current, "package.json");
18577
+ const pkgPath = path8.join(current, "package.json");
18174
18578
  try {
18175
18579
  if (fs7.existsSync(pkgPath)) {
18176
18580
  const raw = fs7.readFileSync(pkgPath, "utf8");
@@ -18179,7 +18583,7 @@ function findPackageVersion(startDir) {
18179
18583
  }
18180
18584
  } catch {
18181
18585
  }
18182
- const parent = path7.dirname(current);
18586
+ const parent = path8.dirname(current);
18183
18587
  if (parent === current) {
18184
18588
  return void 0;
18185
18589
  }
@@ -19138,7 +19542,8 @@ var FORMAT_EXTENSIONS = {
19138
19542
  junit: ".junit.xml",
19139
19543
  "cucumber-json": ".cucumber.json",
19140
19544
  "cucumber-messages": ".ndjson",
19141
- confluence: ".adf.json"
19545
+ confluence: ".adf.json",
19546
+ "story-report-json": ".story-report.json"
19142
19547
  };
19143
19548
  var TEST_EXTENSIONS = [
19144
19549
  ".test.ts",
@@ -19165,11 +19570,11 @@ function computeOutputPath(sourceFile, format, mode, colocatedStyle, baseOutputD
19165
19570
  const ext = FORMAT_EXTENSIONS[format];
19166
19571
  const effectiveName = outputName + (outputNameSuffix ?? "");
19167
19572
  if (mode === "aggregated") {
19168
- return toPosix(path8.join(baseOutputDir, `${effectiveName}${ext}`));
19573
+ return toPosix(path9.join(baseOutputDir, `${effectiveName}${ext}`));
19169
19574
  }
19170
19575
  const normalizedSource = toPosix(sourceFile);
19171
- const dirOfSource = path8.posix.dirname(normalizedSource);
19172
- let baseName = path8.posix.basename(normalizedSource);
19576
+ const dirOfSource = path9.posix.dirname(normalizedSource);
19577
+ let baseName = path9.posix.basename(normalizedSource);
19173
19578
  for (const testExt of TEST_EXTENSIONS) {
19174
19579
  if (baseName.endsWith(testExt)) {
19175
19580
  baseName = baseName.slice(0, -testExt.length);
@@ -19178,9 +19583,9 @@ function computeOutputPath(sourceFile, format, mode, colocatedStyle, baseOutputD
19178
19583
  }
19179
19584
  const fileName = `${baseName}.${effectiveName}${ext}`;
19180
19585
  if (colocatedStyle === "adjacent") {
19181
- return toPosix(path8.posix.join(dirOfSource, fileName));
19586
+ return toPosix(path9.posix.join(dirOfSource, fileName));
19182
19587
  }
19183
- return toPosix(path8.posix.join(baseOutputDir, dirOfSource, fileName));
19588
+ return toPosix(path9.posix.join(baseOutputDir, dirOfSource, fileName));
19184
19589
  }
19185
19590
  function groupTestCasesByOutput(testCases, format, options, logger, outputNameSuffix) {
19186
19591
  const groups = /* @__PURE__ */ new Map();
@@ -19263,6 +19668,9 @@ var ReportGenerator = class {
19263
19668
  cucumberJson: {
19264
19669
  pretty: options.cucumberJson?.pretty ?? false
19265
19670
  },
19671
+ storyReportJson: {
19672
+ pretty: options.storyReportJson?.pretty ?? true
19673
+ },
19266
19674
  cucumberMessages: {
19267
19675
  uriStrategy: options.cucumberMessages?.uriStrategy ?? "sourceFile",
19268
19676
  includeSynthetics: options.cucumberMessages?.includeSynthetics ?? true,
@@ -19378,8 +19786,8 @@ var ReportGenerator = class {
19378
19786
  if (astroPaths) {
19379
19787
  for (const mdPath of astroPaths) {
19380
19788
  const content = await fsPromises.readFile(mdPath, "utf8");
19381
- const mdDir = path8.dirname(mdPath);
19382
- const assetsDir = path8.resolve(this.options.astro.assetsDir);
19789
+ const mdDir = path9.dirname(mdPath);
19790
+ const assetsDir = path9.resolve(this.options.astro.assetsDir);
19383
19791
  const result = copyMarkdownAssets({
19384
19792
  markdown: content,
19385
19793
  markdownDir: mdDir,
@@ -19410,9 +19818,9 @@ var ReportGenerator = class {
19410
19818
  if (groups.size === 0 && this.options.output.mode === "aggregated") {
19411
19819
  const ext = FORMAT_EXTENSIONS[format];
19412
19820
  const effectiveName = this.options.outputName + (outputNameSuffix ?? "");
19413
- const outputPath = toPosix(path8.join(this.options.outputDir, `${effectiveName}${ext}`));
19821
+ const outputPath = toPosix(path9.join(this.options.outputDir, `${effectiveName}${ext}`));
19414
19822
  const content = await this.formatContent(run, format);
19415
- const dir = path8.dirname(outputPath);
19823
+ const dir = path9.dirname(outputPath);
19416
19824
  await fsPromises.mkdir(dir, { recursive: true });
19417
19825
  await this.deps.writeFile(outputPath, content);
19418
19826
  return [outputPath];
@@ -19424,7 +19832,7 @@ var ReportGenerator = class {
19424
19832
  testCases
19425
19833
  };
19426
19834
  const content = await this.formatContent(groupRun, format);
19427
- const dir = path8.dirname(outputPath);
19835
+ const dir = path9.dirname(outputPath);
19428
19836
  await fsPromises.mkdir(dir, { recursive: true });
19429
19837
  await this.deps.writeFile(outputPath, content);
19430
19838
  writtenPaths.push(outputPath);
@@ -19531,6 +19939,12 @@ var ReportGenerator = class {
19531
19939
  });
19532
19940
  return formatter.format(run);
19533
19941
  }
19942
+ case "story-report-json": {
19943
+ const formatter = new StoryReportJsonFormatter({
19944
+ pretty: this.options.storyReportJson.pretty
19945
+ });
19946
+ return formatter.format(run);
19947
+ }
19534
19948
  default:
19535
19949
  throw new Error(`Unknown format: ${format}`);
19536
19950
  }
@@ -19547,7 +19961,7 @@ async function generateRunComparison(args) {
19547
19961
  await fsPromises.mkdir(outputDir, { recursive: true });
19548
19962
  for (const format of args.formats) {
19549
19963
  const ext = format === "html" ? ".html" : ".md";
19550
- const outputPath = toPosix(path8.join(outputDir, `${outputName}${ext}`));
19964
+ const outputPath = toPosix(path9.join(outputDir, `${outputName}${ext}`));
19551
19965
  const content = format === "html" ? new RunDiffHtmlFormatter({ title: args.title }).format(diff) : new RunDiffMarkdownFormatter({ title: args.title }).format(diff);
19552
19966
  await fsPromises.writeFile(outputPath, content, "utf8");
19553
19967
  files.push(outputPath);
@@ -19573,6 +19987,8 @@ function normalizePlaywrightResults(testResults, adapterOptions, canonicalizeOpt
19573
19987
  CucumberHtmlFormatter,
19574
19988
  CucumberJsonFormatter,
19575
19989
  CucumberMessagesFormatter,
19990
+ ES_THEME_TOKENS_CSS,
19991
+ ES_THEME_TOKEN_VALUES,
19576
19992
  HtmlFormatter,
19577
19993
  JUnitFormatter,
19578
19994
  MIN_FLAKINESS_SAMPLES,
@@ -19583,6 +19999,9 @@ function normalizePlaywrightResults(testResults, adapterOptions, canonicalizeOpt
19583
19999
  RunDiffHtmlFormatter,
19584
20000
  RunDiffMarkdownFormatter,
19585
20001
  STORY_META_KEY,
20002
+ STORY_REPORT_SCHEMA_MAJOR,
20003
+ STORY_REPORT_SCHEMA_VERSION,
20004
+ StoryReportJsonFormatter,
19586
20005
  adaptJestRun,
19587
20006
  adaptPlaywrightRun,
19588
20007
  adaptVitestRun,
@@ -19639,6 +20058,7 @@ function normalizePlaywrightResults(testResults, adapterOptions, canonicalizeOpt
19639
20058
  stripAnsi,
19640
20059
  toCIInfo,
19641
20060
  toRawCIInfo,
20061
+ toStoryReport,
19642
20062
  tryGetActiveOtelContext,
19643
20063
  updateHistory,
19644
20064
  validateCanonicalRun