executable-stories-formatters 0.9.0 → 0.11.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/README.md +35 -0
- package/dist/adapters.d.cts +1 -1
- package/dist/adapters.d.ts +1 -1
- package/dist/cli.js +1437 -126
- package/dist/cli.js.map +1 -1
- package/dist/{index-it3Pkmqv.d.cts → index-DF16Xl5i.d.cts} +9 -0
- package/dist/{index-it3Pkmqv.d.ts → index-DF16Xl5i.d.ts} +9 -0
- package/dist/index.cjs +613 -55
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +240 -7
- package/dist/index.d.ts +240 -7
- package/dist/index.js +602 -54
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/schemas/behavior-manifest-v1.json +65 -0
- package/schemas/examples/dotnet.json +84 -20
- package/schemas/examples/go.json +77 -20
- package/schemas/examples/junit5.json +84 -20
- package/schemas/examples/pytest.json +92 -20
- package/schemas/examples/rust.json +84 -20
- package/schemas/raw-run.schema.json +19 -0
- package/schemas/scenario-index-v1.json +88 -0
- package/schemas/story-report-v1.json +22 -0
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import "fs";
|
|
3
|
-
import * as
|
|
3
|
+
import * as path10 from "path";
|
|
4
4
|
import * as fsPromises from "fs/promises";
|
|
5
5
|
|
|
6
6
|
// src/converters/acl/status.ts
|
|
@@ -763,6 +763,15 @@ function copyDocEntry(entry) {
|
|
|
763
763
|
phase: entry.phase,
|
|
764
764
|
...children
|
|
765
765
|
};
|
|
766
|
+
case "video":
|
|
767
|
+
return {
|
|
768
|
+
kind: "video",
|
|
769
|
+
path: entry.path,
|
|
770
|
+
...entry.caption ? { caption: entry.caption } : {},
|
|
771
|
+
...entry.poster ? { poster: entry.poster } : {},
|
|
772
|
+
phase: entry.phase,
|
|
773
|
+
...children
|
|
774
|
+
};
|
|
766
775
|
case "custom":
|
|
767
776
|
return {
|
|
768
777
|
kind: "custom",
|
|
@@ -846,6 +855,9 @@ function buildScenario(tc, featureId) {
|
|
|
846
855
|
if (tickets && tickets.length > 0) {
|
|
847
856
|
scenario.tickets = tickets.map((t) => t.url ? { id: t.id, url: t.url } : { id: t.id });
|
|
848
857
|
}
|
|
858
|
+
if (tc.story.covers && tc.story.covers.length > 0) {
|
|
859
|
+
scenario.covers = [...tc.story.covers];
|
|
860
|
+
}
|
|
849
861
|
return scenario;
|
|
850
862
|
}
|
|
851
863
|
function deriveFeatureTitle(group, relSourceFile) {
|
|
@@ -969,6 +981,181 @@ var StoryReportJsonFormatter = class {
|
|
|
969
981
|
}
|
|
970
982
|
};
|
|
971
983
|
|
|
984
|
+
// src/formatters/scenario-index-json.ts
|
|
985
|
+
var ScenarioIndexJsonFormatter = class {
|
|
986
|
+
options;
|
|
987
|
+
constructor(options = {}) {
|
|
988
|
+
this.options = {
|
|
989
|
+
pretty: options.pretty ?? true,
|
|
990
|
+
filters: options.filters
|
|
991
|
+
};
|
|
992
|
+
}
|
|
993
|
+
toIndex(run) {
|
|
994
|
+
return toScenarioIndex(toStoryReport(run), this.options.filters);
|
|
995
|
+
}
|
|
996
|
+
format(run) {
|
|
997
|
+
const index = this.toIndex(run);
|
|
998
|
+
return this.options.pretty ? JSON.stringify(index, null, 2) : JSON.stringify(index);
|
|
999
|
+
}
|
|
1000
|
+
};
|
|
1001
|
+
function toScenarioIndex(report, filters = {}) {
|
|
1002
|
+
const scenarios = report.features.flatMap(
|
|
1003
|
+
(feature) => feature.scenarios.map((scenario) => toScenarioIndexItem(feature, scenario))
|
|
1004
|
+
).filter((scenario) => matchesFilters(scenario, filters));
|
|
1005
|
+
return {
|
|
1006
|
+
schemaVersion: "1.0",
|
|
1007
|
+
runId: report.runId,
|
|
1008
|
+
generatedAtMs: report.finishedAtMs,
|
|
1009
|
+
summary: summarize(scenarios),
|
|
1010
|
+
scenarios
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
1013
|
+
function toScenarioIndexItem(feature, scenario) {
|
|
1014
|
+
return {
|
|
1015
|
+
id: scenario.id,
|
|
1016
|
+
title: scenario.title,
|
|
1017
|
+
status: scenario.status,
|
|
1018
|
+
feature: feature.title,
|
|
1019
|
+
sourceFile: feature.sourceFile,
|
|
1020
|
+
sourceLine: scenario.sourceLine,
|
|
1021
|
+
tags: scenario.tags,
|
|
1022
|
+
tickets: scenario.tickets ?? [],
|
|
1023
|
+
covers: scenario.covers ?? [],
|
|
1024
|
+
durationMs: scenario.durationMs,
|
|
1025
|
+
steps: scenario.steps.map((step) => ({
|
|
1026
|
+
id: step.id,
|
|
1027
|
+
index: step.index,
|
|
1028
|
+
keyword: step.keyword,
|
|
1029
|
+
text: step.text,
|
|
1030
|
+
status: step.status,
|
|
1031
|
+
durationMs: step.durationMs,
|
|
1032
|
+
errorMessage: step.errorMessage,
|
|
1033
|
+
docKinds: step.docEntries.map((entry) => entry.kind)
|
|
1034
|
+
})),
|
|
1035
|
+
docKinds: scenario.docEntries.map((entry) => entry.kind),
|
|
1036
|
+
error: scenario.errorMessage ? { message: scenario.errorMessage, stack: scenario.errorStack } : void 0
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
function matchesFilters(scenario, filters) {
|
|
1040
|
+
if (filters.statuses?.length && !filters.statuses.includes(scenario.status)) {
|
|
1041
|
+
return false;
|
|
1042
|
+
}
|
|
1043
|
+
if (filters.tags?.length && !filters.tags.some((tag) => scenario.tags.includes(tag))) {
|
|
1044
|
+
return false;
|
|
1045
|
+
}
|
|
1046
|
+
if (filters.sourceFiles?.length && !filters.sourceFiles.some((sourceFile) => scenario.sourceFile.includes(sourceFile))) {
|
|
1047
|
+
return false;
|
|
1048
|
+
}
|
|
1049
|
+
return true;
|
|
1050
|
+
}
|
|
1051
|
+
function summarize(scenarios) {
|
|
1052
|
+
return {
|
|
1053
|
+
total: scenarios.length,
|
|
1054
|
+
passed: scenarios.filter((scenario) => scenario.status === "passed").length,
|
|
1055
|
+
failed: scenarios.filter((scenario) => scenario.status === "failed").length,
|
|
1056
|
+
skipped: scenarios.filter((scenario) => scenario.status === "skipped").length,
|
|
1057
|
+
pending: scenarios.filter((scenario) => scenario.status === "pending").length,
|
|
1058
|
+
durationMs: scenarios.reduce((total, scenario) => total + scenario.durationMs, 0)
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
// src/formatters/behavior-manifest-json.ts
|
|
1063
|
+
var BehaviorManifestJsonFormatter = class {
|
|
1064
|
+
pretty;
|
|
1065
|
+
constructor(options = {}) {
|
|
1066
|
+
this.pretty = options.pretty ?? true;
|
|
1067
|
+
}
|
|
1068
|
+
toManifest(run) {
|
|
1069
|
+
return toBehaviorManifest(toStoryReport(run));
|
|
1070
|
+
}
|
|
1071
|
+
format(run) {
|
|
1072
|
+
const manifest = this.toManifest(run);
|
|
1073
|
+
return this.pretty ? JSON.stringify(manifest, null, 2) : JSON.stringify(manifest);
|
|
1074
|
+
}
|
|
1075
|
+
};
|
|
1076
|
+
function toBehaviorManifest(report) {
|
|
1077
|
+
const index = toScenarioIndex(report);
|
|
1078
|
+
const bySource = /* @__PURE__ */ new Map();
|
|
1079
|
+
const byTag = /* @__PURE__ */ new Map();
|
|
1080
|
+
const docKinds = /* @__PURE__ */ new Set();
|
|
1081
|
+
const debuggerIssues = [];
|
|
1082
|
+
for (const scenario of index.scenarios) {
|
|
1083
|
+
const source = bySource.get(scenario.sourceFile) ?? {
|
|
1084
|
+
path: scenario.sourceFile,
|
|
1085
|
+
scenarioCount: 0,
|
|
1086
|
+
failed: 0,
|
|
1087
|
+
tags: []
|
|
1088
|
+
};
|
|
1089
|
+
source.scenarioCount += 1;
|
|
1090
|
+
if (scenario.status === "failed") source.failed += 1;
|
|
1091
|
+
source.tags = [.../* @__PURE__ */ new Set([...source.tags, ...scenario.tags])].sort();
|
|
1092
|
+
bySource.set(scenario.sourceFile, source);
|
|
1093
|
+
for (const tag of scenario.tags) {
|
|
1094
|
+
const tagEntry = byTag.get(tag) ?? { name: tag, scenarioCount: 0 };
|
|
1095
|
+
tagEntry.scenarioCount += 1;
|
|
1096
|
+
byTag.set(tag, tagEntry);
|
|
1097
|
+
}
|
|
1098
|
+
for (const kind of scenario.docKinds) docKinds.add(kind);
|
|
1099
|
+
for (const step of scenario.steps) {
|
|
1100
|
+
for (const kind of step.docKinds) docKinds.add(kind);
|
|
1101
|
+
}
|
|
1102
|
+
if (!scenarioHasDocs(scenario)) {
|
|
1103
|
+
debuggerIssues.push({
|
|
1104
|
+
severity: "warning",
|
|
1105
|
+
code: "missing-docs",
|
|
1106
|
+
scenarioId: scenario.id,
|
|
1107
|
+
title: scenario.title,
|
|
1108
|
+
message: "Scenario has no doc entries."
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
if (scenario.tags.length === 0) {
|
|
1112
|
+
debuggerIssues.push({
|
|
1113
|
+
severity: "warning",
|
|
1114
|
+
code: "missing-tags",
|
|
1115
|
+
scenarioId: scenario.id,
|
|
1116
|
+
title: scenario.title,
|
|
1117
|
+
message: "Scenario has no tags."
|
|
1118
|
+
});
|
|
1119
|
+
}
|
|
1120
|
+
if (scenario.covers.length === 0) {
|
|
1121
|
+
debuggerIssues.push({
|
|
1122
|
+
severity: "warning",
|
|
1123
|
+
code: "missing-covers",
|
|
1124
|
+
scenarioId: scenario.id,
|
|
1125
|
+
title: scenario.title,
|
|
1126
|
+
message: "Scenario declares no covers (product-code paths), so code\u2192scenario lookup cannot find it."
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1129
|
+
if (scenario.sourceLine === void 0) {
|
|
1130
|
+
debuggerIssues.push({
|
|
1131
|
+
severity: "warning",
|
|
1132
|
+
code: "missing-source-line",
|
|
1133
|
+
scenarioId: scenario.id,
|
|
1134
|
+
title: scenario.title,
|
|
1135
|
+
message: "Scenario has no source line."
|
|
1136
|
+
});
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
const scenariosWithDocs = index.scenarios.filter(scenarioHasDocs).length;
|
|
1140
|
+
return {
|
|
1141
|
+
schemaVersion: "1.0",
|
|
1142
|
+
runId: report.runId,
|
|
1143
|
+
generatedAtMs: report.finishedAtMs,
|
|
1144
|
+
summary: index.summary,
|
|
1145
|
+
sourceFiles: [...bySource.values()].sort((a, b) => a.path.localeCompare(b.path)),
|
|
1146
|
+
tags: [...byTag.values()].sort((a, b) => a.name.localeCompare(b.name)),
|
|
1147
|
+
docCoverage: {
|
|
1148
|
+
scenariosWithDocs,
|
|
1149
|
+
scenariosWithoutDocs: index.scenarios.length - scenariosWithDocs,
|
|
1150
|
+
docKinds: [...docKinds].sort()
|
|
1151
|
+
},
|
|
1152
|
+
debugger: debuggerIssues
|
|
1153
|
+
};
|
|
1154
|
+
}
|
|
1155
|
+
function scenarioHasDocs(scenario) {
|
|
1156
|
+
return scenario.docKinds.length > 0 || scenario.steps.some((step) => step.docKinds.length > 0);
|
|
1157
|
+
}
|
|
1158
|
+
|
|
972
1159
|
// src/formatters/html/renderers/index.ts
|
|
973
1160
|
import * as fs2 from "fs";
|
|
974
1161
|
import * as path3 from "path";
|
|
@@ -13746,6 +13933,23 @@ function renderDocScreenshot(entry, deps) {
|
|
|
13746
13933
|
${entry.alt ? `<div class="doc-screenshot-caption">${deps.escapeHtml(entry.alt)}</div>` : ""}
|
|
13747
13934
|
</div>`;
|
|
13748
13935
|
}
|
|
13936
|
+
function renderDocVideo(entry, deps) {
|
|
13937
|
+
const isRemote = /^(?:https?:|data:)/i.test(entry.path);
|
|
13938
|
+
const isAbsoluteFsPath = !isRemote && /^(?:[/\\]|[A-Za-z]:[/\\])/.test(entry.path);
|
|
13939
|
+
const captionHtml = entry.caption ? `<div class="doc-video-caption">${deps.escapeHtml(entry.caption)}</div>` : "";
|
|
13940
|
+
if ((deps.embedScreenshots ?? true) && isAbsoluteFsPath) {
|
|
13941
|
+
return `<div class="doc-video doc-video-missing">
|
|
13942
|
+
<div class="doc-video-missing-label">Video unavailable</div>
|
|
13943
|
+
<div class="doc-video-missing-path">${deps.escapeHtml(entry.path)}</div>
|
|
13944
|
+
${captionHtml}
|
|
13945
|
+
</div>`;
|
|
13946
|
+
}
|
|
13947
|
+
const poster = entry.poster ? ` poster="${deps.escapeHtml(entry.poster)}"` : "";
|
|
13948
|
+
return `<div class="doc-video">
|
|
13949
|
+
<video class="doc-video-player" controls preload="metadata"${poster} src="${deps.escapeHtml(entry.path)}"></video>
|
|
13950
|
+
${captionHtml}
|
|
13951
|
+
</div>`;
|
|
13952
|
+
}
|
|
13749
13953
|
function renderDocCustom(entry, deps) {
|
|
13750
13954
|
if (entry.type === "visual" && entry.data && typeof entry.data === "object") {
|
|
13751
13955
|
const data = entry.data;
|
|
@@ -13799,6 +14003,9 @@ function renderDocEntry(entry, deps) {
|
|
|
13799
14003
|
case "screenshot":
|
|
13800
14004
|
html = renderDocScreenshot(entry, deps);
|
|
13801
14005
|
break;
|
|
14006
|
+
case "video":
|
|
14007
|
+
html = renderDocVideo(entry, deps);
|
|
14008
|
+
break;
|
|
13802
14009
|
case "custom":
|
|
13803
14010
|
html = renderDocCustom(entry, deps);
|
|
13804
14011
|
break;
|
|
@@ -15125,6 +15332,19 @@ var MarkdownFormatter = class {
|
|
|
15125
15332
|
case "screenshot":
|
|
15126
15333
|
lines.push(`${indent}`);
|
|
15127
15334
|
break;
|
|
15335
|
+
case "video": {
|
|
15336
|
+
const poster = entry.poster ? ` poster="${entry.poster}"` : "";
|
|
15337
|
+
lines.push(`${indent}`);
|
|
15338
|
+
lines.push(`${indent}<video controls preload="metadata"${poster} class="doc-video">`);
|
|
15339
|
+
lines.push(`${indent} <source src="${entry.path}" />`);
|
|
15340
|
+
lines.push(`${indent}</video>`);
|
|
15341
|
+
if (entry.caption) {
|
|
15342
|
+
lines.push(`${indent}`);
|
|
15343
|
+
lines.push(`${indent}*${entry.caption}*`);
|
|
15344
|
+
}
|
|
15345
|
+
lines.push(`${indent}`);
|
|
15346
|
+
break;
|
|
15347
|
+
}
|
|
15128
15348
|
case "custom":
|
|
15129
15349
|
if (entry.type === "visual" && entry.data && typeof entry.data === "object") {
|
|
15130
15350
|
const data = entry.data;
|
|
@@ -15837,8 +16057,8 @@ function extractDocAttachments(step) {
|
|
|
15837
16057
|
}
|
|
15838
16058
|
return attachments;
|
|
15839
16059
|
}
|
|
15840
|
-
function guessMediaType(
|
|
15841
|
-
const lower =
|
|
16060
|
+
function guessMediaType(path11) {
|
|
16061
|
+
const lower = path11.toLowerCase();
|
|
15842
16062
|
if (lower.endsWith(".png")) return "image/png";
|
|
15843
16063
|
if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg";
|
|
15844
16064
|
if (lower.endsWith(".gif")) return "image/gif";
|
|
@@ -15979,11 +16199,11 @@ var CucumberHtmlFormatter = class {
|
|
|
15979
16199
|
for (const envelope of envelopes) {
|
|
15980
16200
|
const accepted = htmlStream.write(envelope);
|
|
15981
16201
|
if (!accepted) {
|
|
15982
|
-
await new Promise((
|
|
16202
|
+
await new Promise((resolve8) => htmlStream.once("drain", resolve8));
|
|
15983
16203
|
}
|
|
15984
16204
|
}
|
|
15985
|
-
await new Promise((
|
|
15986
|
-
collector.on("finish",
|
|
16205
|
+
await new Promise((resolve8, reject) => {
|
|
16206
|
+
collector.on("finish", resolve8);
|
|
15987
16207
|
collector.on("error", reject);
|
|
15988
16208
|
htmlStream.end();
|
|
15989
16209
|
});
|
|
@@ -16284,6 +16504,8 @@ function formatDocEntry(doc) {
|
|
|
16284
16504
|
return `${escapeHtml2(doc.title ?? "mermaid diagram")}: <code>${escapeHtml2(doc.code)}</code>`;
|
|
16285
16505
|
case "screenshot":
|
|
16286
16506
|
return `${doc.alt ? `${escapeHtml2(doc.alt)}: ` : ""}${escapeHtml2(doc.path)}`;
|
|
16507
|
+
case "video":
|
|
16508
|
+
return `${doc.caption ? `${escapeHtml2(doc.caption)}: ` : ""}${escapeHtml2(doc.path)}`;
|
|
16287
16509
|
case "custom":
|
|
16288
16510
|
return `${escapeHtml2(doc.type)}: ${escapeHtml2(JSON.stringify(doc.data))}`;
|
|
16289
16511
|
}
|
|
@@ -16740,6 +16962,8 @@ function formatDocEntry2(doc) {
|
|
|
16740
16962
|
return `${doc.title ?? "mermaid diagram"}: \`${doc.code}\``;
|
|
16741
16963
|
case "screenshot":
|
|
16742
16964
|
return `${doc.alt ? `${doc.alt}: ` : ""}${doc.path}`;
|
|
16965
|
+
case "video":
|
|
16966
|
+
return `${doc.caption ? `${doc.caption}: ` : ""}${doc.path}`;
|
|
16743
16967
|
case "custom":
|
|
16744
16968
|
return `${doc.type}: ${JSON.stringify(doc.data)}`;
|
|
16745
16969
|
}
|
|
@@ -16985,19 +17209,35 @@ function replaceAssetRef(html, original, replacement) {
|
|
|
16985
17209
|
return html;
|
|
16986
17210
|
}
|
|
16987
17211
|
|
|
17212
|
+
// src/utils/source-file.ts
|
|
17213
|
+
function cleanTestStem(fileName) {
|
|
17214
|
+
const base = fileName.split(/[\\/]/).pop() ?? fileName;
|
|
17215
|
+
const stripped = base.replace(/\.(story\.)?(test|spec|cy)\.[cm]?[jt]sx?$/i, "");
|
|
17216
|
+
if (stripped !== base) return stripped;
|
|
17217
|
+
return base.replace(/\.[^.]+$/, "");
|
|
17218
|
+
}
|
|
17219
|
+
function humanizeSourceFile(fileName) {
|
|
17220
|
+
return cleanTestStem(fileName).split(/[-_.\s]+/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
17221
|
+
}
|
|
17222
|
+
|
|
16988
17223
|
// src/formatters/astro.ts
|
|
16989
17224
|
var AstroFormatter = class _AstroFormatter {
|
|
16990
17225
|
markdownFormatter;
|
|
16991
17226
|
title;
|
|
17227
|
+
perFileTitle;
|
|
16992
17228
|
constructor(options = {}) {
|
|
16993
17229
|
this.title = options.markdown?.title ?? "User Stories";
|
|
17230
|
+
this.perFileTitle = options.perFileTitle ?? false;
|
|
16994
17231
|
this.markdownFormatter = new MarkdownFormatter({
|
|
16995
17232
|
...options.markdown,
|
|
16996
17233
|
title: this.title,
|
|
16997
17234
|
stepStyle: "gherkin",
|
|
16998
17235
|
includeFrontMatter: false,
|
|
16999
17236
|
includeSummaryTable: false,
|
|
17000
|
-
includeMetadata: false
|
|
17237
|
+
includeMetadata: false,
|
|
17238
|
+
// A per-file page is one file already — group by suite/describe so the
|
|
17239
|
+
// body shows clean section headings, not the redundant source path.
|
|
17240
|
+
groupBy: this.perFileTitle ? "suite" : options.markdown?.groupBy ?? "file"
|
|
17001
17241
|
});
|
|
17002
17242
|
}
|
|
17003
17243
|
format(run) {
|
|
@@ -17007,13 +17247,31 @@ var AstroFormatter = class _AstroFormatter {
|
|
|
17007
17247
|
return `${frontmatter}
|
|
17008
17248
|
${body}`;
|
|
17009
17249
|
}
|
|
17250
|
+
/**
|
|
17251
|
+
* Title for the page. A per-file page (one source file — i.e. colocated mode)
|
|
17252
|
+
* is titled by its suite/describe name, falling back to a humanized filename,
|
|
17253
|
+
* so the docs nav reads "Convert Currency" not "User Stories" six times over.
|
|
17254
|
+
* Multi-file (aggregated) pages keep the configured title.
|
|
17255
|
+
*/
|
|
17256
|
+
deriveTitle(run) {
|
|
17257
|
+
if (!this.perFileTitle) return this.title;
|
|
17258
|
+
const sourceFiles = new Set(
|
|
17259
|
+
run.testCases.map((tc) => tc.sourceFile).filter((f) => f && f !== "unknown")
|
|
17260
|
+
);
|
|
17261
|
+
if (sourceFiles.size !== 1) return this.title;
|
|
17262
|
+
const suites = new Set(
|
|
17263
|
+
run.testCases.map((tc) => tc.titlePath?.[0]).filter((s) => Boolean(s))
|
|
17264
|
+
);
|
|
17265
|
+
if (suites.size === 1) return [...suites][0];
|
|
17266
|
+
return humanizeSourceFile([...sourceFiles][0]) || this.title;
|
|
17267
|
+
}
|
|
17010
17268
|
buildFrontmatter(run) {
|
|
17011
17269
|
const badge = _AstroFormatter.computeBadge(run.testCases);
|
|
17012
17270
|
const count = run.testCases.length;
|
|
17013
17271
|
const description = `${count} scenario${count !== 1 ? "s" : ""} \u2014 ${badge.text.toLowerCase()}`;
|
|
17014
17272
|
const lines = [
|
|
17015
17273
|
"---",
|
|
17016
|
-
`title: ${this.
|
|
17274
|
+
`title: ${yamlScalar(this.deriveTitle(run))}`,
|
|
17017
17275
|
`description: ${description}`,
|
|
17018
17276
|
"sidebar:",
|
|
17019
17277
|
" badge:",
|
|
@@ -17031,6 +17289,12 @@ ${body}`;
|
|
|
17031
17289
|
return { text: "Passed", variant: "success" };
|
|
17032
17290
|
}
|
|
17033
17291
|
};
|
|
17292
|
+
function yamlScalar(value) {
|
|
17293
|
+
if (/[:#[\]{}&*!|>'"%@`]|^[\s-]|\s$/.test(value)) {
|
|
17294
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
17295
|
+
}
|
|
17296
|
+
return value;
|
|
17297
|
+
}
|
|
17034
17298
|
|
|
17035
17299
|
// src/formatters/confluence.ts
|
|
17036
17300
|
var ConfluenceFormatter = class {
|
|
@@ -17307,6 +17571,15 @@ ${tc.errorStack}` : "");
|
|
|
17307
17571
|
])
|
|
17308
17572
|
);
|
|
17309
17573
|
break;
|
|
17574
|
+
case "video":
|
|
17575
|
+
content.push(
|
|
17576
|
+
paragraph([
|
|
17577
|
+
text(entry.caption ?? "Video", strong()),
|
|
17578
|
+
text(": "),
|
|
17579
|
+
link(entry.path, entry.path)
|
|
17580
|
+
])
|
|
17581
|
+
);
|
|
17582
|
+
break;
|
|
17310
17583
|
case "custom":
|
|
17311
17584
|
content.push(paragraph([text(`[${entry.type}]`, strong())]));
|
|
17312
17585
|
content.push(codeBlock(JSON.stringify(entry.data ?? null, null, 2), "json"));
|
|
@@ -17476,6 +17749,13 @@ function scanMarkdownAssets(markdown) {
|
|
|
17476
17749
|
found.add(src);
|
|
17477
17750
|
}
|
|
17478
17751
|
}
|
|
17752
|
+
const posterRe = /<video[^>]+\bposter=["']([^"']+)["'][^>]*>/gi;
|
|
17753
|
+
while ((match = posterRe.exec(stripped)) !== null) {
|
|
17754
|
+
const src = match[1].trim();
|
|
17755
|
+
if (isLocalPath(src)) {
|
|
17756
|
+
found.add(src);
|
|
17757
|
+
}
|
|
17758
|
+
}
|
|
17479
17759
|
return Array.from(found);
|
|
17480
17760
|
}
|
|
17481
17761
|
function splitByCode(markdown) {
|
|
@@ -17526,6 +17806,19 @@ function rewriteProseSegment(prose, assetsBaseUrl, pathMap) {
|
|
|
17526
17806
|
return `${pre}${assetsBaseUrl}/${trimmed}${post}`;
|
|
17527
17807
|
}
|
|
17528
17808
|
);
|
|
17809
|
+
result = result.replace(
|
|
17810
|
+
/(<video[^>]+\bposter=["'])([^"']+)(["'][^>]*>)/gi,
|
|
17811
|
+
(full, pre, src, post) => {
|
|
17812
|
+
const trimmed = src.trim();
|
|
17813
|
+
if (!isLocalPath(trimmed)) return full;
|
|
17814
|
+
if (pathMap) {
|
|
17815
|
+
const mapped = pathMap.get(trimmed);
|
|
17816
|
+
if (mapped === void 0) return full;
|
|
17817
|
+
return `${pre}${assetsBaseUrl}/${mapped}${post}`;
|
|
17818
|
+
}
|
|
17819
|
+
return `${pre}${assetsBaseUrl}/${trimmed}${post}`;
|
|
17820
|
+
}
|
|
17821
|
+
);
|
|
17529
17822
|
return result;
|
|
17530
17823
|
}
|
|
17531
17824
|
function rewriteAssetPaths(markdown, assetsBaseUrl, pathMap) {
|
|
@@ -17972,6 +18265,184 @@ ${result.errors.join("\n")}`);
|
|
|
17972
18265
|
}
|
|
17973
18266
|
}
|
|
17974
18267
|
|
|
18268
|
+
// src/coverage-index.ts
|
|
18269
|
+
function normalizePath(path11) {
|
|
18270
|
+
return path11.replace(/^\.\//, "");
|
|
18271
|
+
}
|
|
18272
|
+
function scenariosCoveringPaths(index, paths) {
|
|
18273
|
+
const queries = paths.map(normalizePath);
|
|
18274
|
+
return index.scenarios.filter(
|
|
18275
|
+
(scenario) => scenario.covers.some(
|
|
18276
|
+
(glob) => queries.some((path11) => matchesPattern(normalizePath(glob), path11))
|
|
18277
|
+
)
|
|
18278
|
+
);
|
|
18279
|
+
}
|
|
18280
|
+
|
|
18281
|
+
// src/watch.ts
|
|
18282
|
+
import * as fs6 from "fs";
|
|
18283
|
+
import * as path7 from "path";
|
|
18284
|
+
|
|
18285
|
+
// src/converters/synthesize.ts
|
|
18286
|
+
var KEYWORD_MAP = {
|
|
18287
|
+
given: "Given",
|
|
18288
|
+
when: "When",
|
|
18289
|
+
then: "Then",
|
|
18290
|
+
and: "And",
|
|
18291
|
+
but: "But"
|
|
18292
|
+
};
|
|
18293
|
+
function normalizeKeyword(keyword) {
|
|
18294
|
+
return KEYWORD_MAP[keyword.toLowerCase()] ?? keyword;
|
|
18295
|
+
}
|
|
18296
|
+
function normalizeStepKeywords(steps) {
|
|
18297
|
+
return steps.map((step) => ({
|
|
18298
|
+
...step,
|
|
18299
|
+
keyword: normalizeKeyword(step.keyword)
|
|
18300
|
+
}));
|
|
18301
|
+
}
|
|
18302
|
+
function deriveScenarioName(tc) {
|
|
18303
|
+
if (tc.title) return tc.title;
|
|
18304
|
+
if (tc.titlePath && tc.titlePath.length > 0) {
|
|
18305
|
+
return tc.titlePath[tc.titlePath.length - 1];
|
|
18306
|
+
}
|
|
18307
|
+
return "Untitled";
|
|
18308
|
+
}
|
|
18309
|
+
function synthesizeStories(raw) {
|
|
18310
|
+
return {
|
|
18311
|
+
...raw,
|
|
18312
|
+
testCases: raw.testCases.map(synthesizeTestCase)
|
|
18313
|
+
};
|
|
18314
|
+
}
|
|
18315
|
+
function synthesizeTestCase(tc) {
|
|
18316
|
+
if (tc.story == null) {
|
|
18317
|
+
const scenario = deriveScenarioName(tc);
|
|
18318
|
+
return {
|
|
18319
|
+
...tc,
|
|
18320
|
+
story: {
|
|
18321
|
+
scenario,
|
|
18322
|
+
steps: [{ keyword: "Then", text: scenario }]
|
|
18323
|
+
}
|
|
18324
|
+
};
|
|
18325
|
+
}
|
|
18326
|
+
const steps = tc.story.steps;
|
|
18327
|
+
if (!steps || steps.length === 0) {
|
|
18328
|
+
return {
|
|
18329
|
+
...tc,
|
|
18330
|
+
story: {
|
|
18331
|
+
...tc.story,
|
|
18332
|
+
steps: [{ keyword: "Then", text: tc.story.scenario }]
|
|
18333
|
+
}
|
|
18334
|
+
};
|
|
18335
|
+
}
|
|
18336
|
+
return {
|
|
18337
|
+
...tc,
|
|
18338
|
+
story: {
|
|
18339
|
+
...tc.story,
|
|
18340
|
+
steps: normalizeStepKeywords(steps)
|
|
18341
|
+
}
|
|
18342
|
+
};
|
|
18343
|
+
}
|
|
18344
|
+
|
|
18345
|
+
// src/watch.ts
|
|
18346
|
+
function toRun(data, inputType, synthesize) {
|
|
18347
|
+
if (inputType === "canonical") return data;
|
|
18348
|
+
let raw = data;
|
|
18349
|
+
if (synthesize) raw = synthesizeStories(raw);
|
|
18350
|
+
return canonicalizeRun(raw);
|
|
18351
|
+
}
|
|
18352
|
+
async function regenerateArtifacts(options, deps = {}) {
|
|
18353
|
+
const read = deps.readFile ?? ((filePath) => fs6.readFileSync(filePath, "utf8"));
|
|
18354
|
+
const data = JSON.parse(read(path7.resolve(options.input)));
|
|
18355
|
+
const run = toRun(data, options.inputType ?? "raw", options.synthesize !== false);
|
|
18356
|
+
const generator = new ReportGenerator({
|
|
18357
|
+
formats: options.formats,
|
|
18358
|
+
outputDir: options.outputDir,
|
|
18359
|
+
outputName: options.outputName
|
|
18360
|
+
});
|
|
18361
|
+
const result = await generator.generate(run);
|
|
18362
|
+
return [...result.values()].flat();
|
|
18363
|
+
}
|
|
18364
|
+
function startWatch(options, deps = {}) {
|
|
18365
|
+
const log = deps.log ?? ((message) => console.log(message));
|
|
18366
|
+
const regenerate = deps.regenerate ?? ((input) => regenerateArtifacts({ ...options, input }, deps));
|
|
18367
|
+
const watchFn = deps.watch ?? ((filePath, listener) => fs6.watch(filePath, listener));
|
|
18368
|
+
const debounceMs = options.debounceMs ?? 150;
|
|
18369
|
+
let timer;
|
|
18370
|
+
let running = false;
|
|
18371
|
+
let pending = false;
|
|
18372
|
+
const run = async () => {
|
|
18373
|
+
if (running) {
|
|
18374
|
+
pending = true;
|
|
18375
|
+
return;
|
|
18376
|
+
}
|
|
18377
|
+
running = true;
|
|
18378
|
+
try {
|
|
18379
|
+
const files = await regenerate(options.input);
|
|
18380
|
+
log(`Regenerated ${files.length} artifact file(s) from ${options.input}`);
|
|
18381
|
+
} catch (error) {
|
|
18382
|
+
log(`Watch regeneration failed: ${error.message}`);
|
|
18383
|
+
} finally {
|
|
18384
|
+
running = false;
|
|
18385
|
+
if (pending) {
|
|
18386
|
+
pending = false;
|
|
18387
|
+
trigger();
|
|
18388
|
+
}
|
|
18389
|
+
}
|
|
18390
|
+
};
|
|
18391
|
+
const trigger = () => {
|
|
18392
|
+
if (timer) clearTimeout(timer);
|
|
18393
|
+
timer = setTimeout(() => void run(), debounceMs);
|
|
18394
|
+
};
|
|
18395
|
+
trigger();
|
|
18396
|
+
const watcher = watchFn(path7.resolve(options.input), trigger);
|
|
18397
|
+
return {
|
|
18398
|
+
close: () => {
|
|
18399
|
+
if (timer) clearTimeout(timer);
|
|
18400
|
+
watcher.close();
|
|
18401
|
+
}
|
|
18402
|
+
};
|
|
18403
|
+
}
|
|
18404
|
+
|
|
18405
|
+
// src/behavior-diff.ts
|
|
18406
|
+
function classifyStatusChange(baseline, current) {
|
|
18407
|
+
if (baseline === void 0) return "added";
|
|
18408
|
+
if (current === void 0) return "removed";
|
|
18409
|
+
if (baseline === current) return "unchanged";
|
|
18410
|
+
if (baseline === "passed" && current === "failed") return "regressed";
|
|
18411
|
+
if (baseline === "failed" && current === "passed") return "fixed";
|
|
18412
|
+
return "changed";
|
|
18413
|
+
}
|
|
18414
|
+
function scenarioMap(report) {
|
|
18415
|
+
const map = /* @__PURE__ */ new Map();
|
|
18416
|
+
for (const feature of report.features) {
|
|
18417
|
+
for (const scenario of feature.scenarios) {
|
|
18418
|
+
map.set(scenario.id, { scenario, sourceFile: feature.sourceFile });
|
|
18419
|
+
}
|
|
18420
|
+
}
|
|
18421
|
+
return map;
|
|
18422
|
+
}
|
|
18423
|
+
function diffStoryReports(baseline, current) {
|
|
18424
|
+
const base = scenarioMap(baseline);
|
|
18425
|
+
const curr = scenarioMap(current);
|
|
18426
|
+
const ids = [.../* @__PURE__ */ new Set([...base.keys(), ...curr.keys()])];
|
|
18427
|
+
const scenarios = ids.map((id) => {
|
|
18428
|
+
const b = base.get(id);
|
|
18429
|
+
const c = curr.get(id);
|
|
18430
|
+
const kind = classifyStatusChange(b?.scenario.status, c?.scenario.status);
|
|
18431
|
+
const meta = c ?? b;
|
|
18432
|
+
return {
|
|
18433
|
+
id,
|
|
18434
|
+
title: meta.scenario.title,
|
|
18435
|
+
sourceFile: meta.sourceFile,
|
|
18436
|
+
kind,
|
|
18437
|
+
baselineStatus: b?.scenario.status,
|
|
18438
|
+
currentStatus: c?.scenario.status
|
|
18439
|
+
};
|
|
18440
|
+
});
|
|
18441
|
+
const summary = { added: 0, removed: 0, regressed: 0, fixed: 0, changed: 0, unchanged: 0 };
|
|
18442
|
+
for (const s of scenarios) summary[s.kind] += 1;
|
|
18443
|
+
return { schemaVersion: "1.0", summary, scenarios };
|
|
18444
|
+
}
|
|
18445
|
+
|
|
17975
18446
|
// src/publishers/confluence.ts
|
|
17976
18447
|
function parseAdf(adf) {
|
|
17977
18448
|
let parsed;
|
|
@@ -18567,27 +19038,27 @@ function pickleStepArgumentToDocs(ps) {
|
|
|
18567
19038
|
}
|
|
18568
19039
|
|
|
18569
19040
|
// src/utils/git-info.ts
|
|
18570
|
-
import * as
|
|
18571
|
-
import * as
|
|
19041
|
+
import * as fs7 from "fs";
|
|
19042
|
+
import * as path8 from "path";
|
|
18572
19043
|
function readGitSha(cwd = process.cwd()) {
|
|
18573
19044
|
const envSha = process.env.GITHUB_SHA || process.env.GIT_COMMIT || process.env.CI_COMMIT_SHA;
|
|
18574
19045
|
if (envSha) return envSha;
|
|
18575
19046
|
const gitDir = findGitDir(cwd);
|
|
18576
19047
|
if (!gitDir) return void 0;
|
|
18577
19048
|
try {
|
|
18578
|
-
const headPath =
|
|
18579
|
-
const head =
|
|
19049
|
+
const headPath = path8.join(gitDir, "HEAD");
|
|
19050
|
+
const head = fs7.readFileSync(headPath, "utf8").trim();
|
|
18580
19051
|
if (!head.startsWith("ref:")) {
|
|
18581
19052
|
return head;
|
|
18582
19053
|
}
|
|
18583
19054
|
const refPath = head.replace("ref:", "").trim();
|
|
18584
|
-
const refFile =
|
|
18585
|
-
if (
|
|
18586
|
-
return
|
|
19055
|
+
const refFile = path8.join(gitDir, refPath);
|
|
19056
|
+
if (fs7.existsSync(refFile)) {
|
|
19057
|
+
return fs7.readFileSync(refFile, "utf8").trim();
|
|
18587
19058
|
}
|
|
18588
|
-
const packedRefs =
|
|
18589
|
-
if (
|
|
18590
|
-
const content =
|
|
19059
|
+
const packedRefs = path8.join(gitDir, "packed-refs");
|
|
19060
|
+
if (fs7.existsSync(packedRefs)) {
|
|
19061
|
+
const content = fs7.readFileSync(packedRefs, "utf8");
|
|
18591
19062
|
for (const line of content.split("\n")) {
|
|
18592
19063
|
if (!line || line.startsWith("#") || line.startsWith("^")) continue;
|
|
18593
19064
|
const [sha, ref] = line.split(" ");
|
|
@@ -18602,19 +19073,19 @@ function readGitSha(cwd = process.cwd()) {
|
|
|
18602
19073
|
function findGitDir(start) {
|
|
18603
19074
|
let current = start;
|
|
18604
19075
|
while (true) {
|
|
18605
|
-
const candidate =
|
|
18606
|
-
if (
|
|
18607
|
-
const stat =
|
|
19076
|
+
const candidate = path8.join(current, ".git");
|
|
19077
|
+
if (fs7.existsSync(candidate)) {
|
|
19078
|
+
const stat = fs7.statSync(candidate);
|
|
18608
19079
|
if (stat.isFile()) {
|
|
18609
|
-
const content =
|
|
19080
|
+
const content = fs7.readFileSync(candidate, "utf8").trim();
|
|
18610
19081
|
const match = content.match(/^gitdir: (.+)$/);
|
|
18611
19082
|
if (match) {
|
|
18612
|
-
return
|
|
19083
|
+
return path8.resolve(current, match[1]);
|
|
18613
19084
|
}
|
|
18614
19085
|
}
|
|
18615
19086
|
return candidate;
|
|
18616
19087
|
}
|
|
18617
|
-
const parent =
|
|
19088
|
+
const parent = path8.dirname(current);
|
|
18618
19089
|
if (parent === current) return void 0;
|
|
18619
19090
|
current = parent;
|
|
18620
19091
|
}
|
|
@@ -18625,8 +19096,8 @@ function readBranchName(cwd = process.cwd()) {
|
|
|
18625
19096
|
const gitDir = findGitDir(cwd);
|
|
18626
19097
|
if (!gitDir) return void 0;
|
|
18627
19098
|
try {
|
|
18628
|
-
const headPath =
|
|
18629
|
-
const head =
|
|
19099
|
+
const headPath = path8.join(gitDir, "HEAD");
|
|
19100
|
+
const head = fs7.readFileSync(headPath, "utf8").trim();
|
|
18630
19101
|
if (head.startsWith("ref:")) {
|
|
18631
19102
|
const refPath = head.replace("ref:", "").trim();
|
|
18632
19103
|
const match = refPath.match(/^refs\/heads\/(.+)$/);
|
|
@@ -18663,8 +19134,8 @@ function nanosecondsToMs(ns) {
|
|
|
18663
19134
|
}
|
|
18664
19135
|
|
|
18665
19136
|
// src/utils/metadata.ts
|
|
18666
|
-
import * as
|
|
18667
|
-
import * as
|
|
19137
|
+
import * as fs8 from "fs";
|
|
19138
|
+
import * as path9 from "path";
|
|
18668
19139
|
var versionCache = /* @__PURE__ */ new Map();
|
|
18669
19140
|
function readPackageVersion(root) {
|
|
18670
19141
|
if (versionCache.has(root)) {
|
|
@@ -18675,18 +19146,18 @@ function readPackageVersion(root) {
|
|
|
18675
19146
|
return version;
|
|
18676
19147
|
}
|
|
18677
19148
|
function findPackageVersion(startDir) {
|
|
18678
|
-
let current =
|
|
19149
|
+
let current = path9.resolve(startDir);
|
|
18679
19150
|
while (true) {
|
|
18680
|
-
const pkgPath =
|
|
19151
|
+
const pkgPath = path9.join(current, "package.json");
|
|
18681
19152
|
try {
|
|
18682
|
-
if (
|
|
18683
|
-
const raw =
|
|
19153
|
+
if (fs8.existsSync(pkgPath)) {
|
|
19154
|
+
const raw = fs8.readFileSync(pkgPath, "utf8");
|
|
18684
19155
|
const parsed = JSON.parse(raw);
|
|
18685
19156
|
return parsed.version;
|
|
18686
19157
|
}
|
|
18687
19158
|
} catch {
|
|
18688
19159
|
}
|
|
18689
|
-
const parent =
|
|
19160
|
+
const parent = path9.dirname(current);
|
|
18690
19161
|
if (parent === current) {
|
|
18691
19162
|
return void 0;
|
|
18692
19163
|
}
|
|
@@ -19556,12 +20027,22 @@ function listScenarios(args, _deps) {
|
|
|
19556
20027
|
const { testCases, format } = args;
|
|
19557
20028
|
if (format === "json") {
|
|
19558
20029
|
const items = testCases.map((tc) => ({
|
|
20030
|
+
id: tc.id,
|
|
19559
20031
|
scenario: tc.story.scenario,
|
|
19560
20032
|
status: tc.status,
|
|
19561
20033
|
sourceFile: tc.sourceFile,
|
|
19562
20034
|
sourceLine: tc.sourceLine,
|
|
20035
|
+
suitePath: tc.story.suitePath ?? tc.titlePath.slice(0, -1),
|
|
19563
20036
|
tags: tc.tags,
|
|
19564
|
-
|
|
20037
|
+
tickets: tc.story.tickets ?? [],
|
|
20038
|
+
covers: tc.story.covers ?? [],
|
|
20039
|
+
durationMs: tc.durationMs,
|
|
20040
|
+
error: tc.errorMessage ? {
|
|
20041
|
+
message: tc.errorMessage,
|
|
20042
|
+
stack: tc.errorStack
|
|
20043
|
+
} : void 0,
|
|
20044
|
+
steps: tc.story.steps.map((step, index) => toScenarioStep(step, index, tc)),
|
|
20045
|
+
docKinds: collectDocKinds(tc)
|
|
19565
20046
|
}));
|
|
19566
20047
|
return JSON.stringify(items, null, 2);
|
|
19567
20048
|
}
|
|
@@ -19634,6 +20115,34 @@ function listScenarios(args, _deps) {
|
|
|
19634
20115
|
];
|
|
19635
20116
|
return lines.join("\n");
|
|
19636
20117
|
}
|
|
20118
|
+
function toScenarioStep(step, index, testCase) {
|
|
20119
|
+
const result = testCase.stepResults.find(
|
|
20120
|
+
(candidate) => candidate.index === index || candidate.stepId === step.id
|
|
20121
|
+
);
|
|
20122
|
+
return {
|
|
20123
|
+
id: step.id,
|
|
20124
|
+
index,
|
|
20125
|
+
keyword: step.keyword,
|
|
20126
|
+
text: step.text,
|
|
20127
|
+
status: result?.status ?? testCase.status,
|
|
20128
|
+
durationMs: result?.durationMs ?? step.durationMs ?? 0,
|
|
20129
|
+
errorMessage: result?.errorMessage,
|
|
20130
|
+
mode: step.mode,
|
|
20131
|
+
docKinds: (step.docs ?? []).map((doc) => doc.kind)
|
|
20132
|
+
};
|
|
20133
|
+
}
|
|
20134
|
+
function collectDocKinds(testCase) {
|
|
20135
|
+
const kinds = /* @__PURE__ */ new Set();
|
|
20136
|
+
for (const doc of testCase.story.docs ?? []) {
|
|
20137
|
+
kinds.add(doc.kind);
|
|
20138
|
+
}
|
|
20139
|
+
for (const step of testCase.story.steps) {
|
|
20140
|
+
for (const doc of step.docs ?? []) {
|
|
20141
|
+
kinds.add(doc.kind);
|
|
20142
|
+
}
|
|
20143
|
+
}
|
|
20144
|
+
return [...kinds].sort();
|
|
20145
|
+
}
|
|
19637
20146
|
|
|
19638
20147
|
// src/review/conventions.ts
|
|
19639
20148
|
var CHANGE_TAG_PREFIX = "change:";
|
|
@@ -19681,18 +20190,18 @@ function deriveChangeType(tags) {
|
|
|
19681
20190
|
}
|
|
19682
20191
|
return "unknown";
|
|
19683
20192
|
}
|
|
19684
|
-
function extensionOf(
|
|
19685
|
-
const base =
|
|
20193
|
+
function extensionOf(path11) {
|
|
20194
|
+
const base = path11.split("/").pop() ?? path11;
|
|
19686
20195
|
const dot = base.lastIndexOf(".");
|
|
19687
20196
|
return dot === -1 ? "" : base.slice(dot + 1).toLowerCase();
|
|
19688
20197
|
}
|
|
19689
|
-
function isTestFile(
|
|
19690
|
-
return TEST_INFIX.test(
|
|
20198
|
+
function isTestFile(path11) {
|
|
20199
|
+
return TEST_INFIX.test(path11);
|
|
19691
20200
|
}
|
|
19692
|
-
function isReviewableSource(
|
|
19693
|
-
if (isTestFile(
|
|
19694
|
-
if (
|
|
19695
|
-
return CODE_EXTENSIONS.has(extensionOf(
|
|
20201
|
+
function isReviewableSource(path11) {
|
|
20202
|
+
if (isTestFile(path11)) return false;
|
|
20203
|
+
if (path11.endsWith(".d.ts")) return false;
|
|
20204
|
+
return CODE_EXTENSIONS.has(extensionOf(path11));
|
|
19696
20205
|
}
|
|
19697
20206
|
function testBaseKey(testFile) {
|
|
19698
20207
|
return testFile.replace(TEST_INFIX, "");
|
|
@@ -19796,7 +20305,7 @@ function toClaim(testCase, changedSourcePaths) {
|
|
|
19796
20305
|
const { strength, reasons } = gradeEvidence(testCase, audience);
|
|
19797
20306
|
const key = testBaseKey(testCase.sourceFile);
|
|
19798
20307
|
const coversFiles = changedSourcePaths.filter(
|
|
19799
|
-
(
|
|
20308
|
+
(path11) => sourceBaseKey(path11) === key
|
|
19800
20309
|
);
|
|
19801
20310
|
return {
|
|
19802
20311
|
id: testCase.id,
|
|
@@ -20329,6 +20838,7 @@ applyTheme(getEffectiveTheme());` : "";
|
|
|
20329
20838
|
// src/index.ts
|
|
20330
20839
|
var FORMAT_EXTENSIONS = {
|
|
20331
20840
|
astro: ".md",
|
|
20841
|
+
"behavior-manifest-json": ".behavior-manifest.json",
|
|
20332
20842
|
markdown: ".md",
|
|
20333
20843
|
html: ".html",
|
|
20334
20844
|
"cucumber-html": ".cucumber.html",
|
|
@@ -20336,8 +20846,13 @@ var FORMAT_EXTENSIONS = {
|
|
|
20336
20846
|
"cucumber-json": ".cucumber.json",
|
|
20337
20847
|
"cucumber-messages": ".ndjson",
|
|
20338
20848
|
confluence: ".adf.json",
|
|
20849
|
+
"scenario-index-json": ".scenarios-index.json",
|
|
20339
20850
|
"story-report-json": ".story-report.json"
|
|
20340
20851
|
};
|
|
20852
|
+
function joinNameAndExt(name, ext) {
|
|
20853
|
+
const stutter = `.${name}.`;
|
|
20854
|
+
return ext.startsWith(stutter) ? `${name}.${ext.slice(stutter.length)}` : `${name}${ext}`;
|
|
20855
|
+
}
|
|
20341
20856
|
var TEST_EXTENSIONS = [
|
|
20342
20857
|
".test.ts",
|
|
20343
20858
|
".test.tsx",
|
|
@@ -20363,11 +20878,11 @@ function computeOutputPath(sourceFile, format, mode, colocatedStyle, baseOutputD
|
|
|
20363
20878
|
const ext = FORMAT_EXTENSIONS[format];
|
|
20364
20879
|
const effectiveName = outputName + (outputNameSuffix ?? "");
|
|
20365
20880
|
if (mode === "aggregated") {
|
|
20366
|
-
return toPosix(
|
|
20881
|
+
return toPosix(path10.join(baseOutputDir, joinNameAndExt(effectiveName, ext)));
|
|
20367
20882
|
}
|
|
20368
20883
|
const normalizedSource = toPosix(sourceFile);
|
|
20369
|
-
const dirOfSource =
|
|
20370
|
-
let baseName =
|
|
20884
|
+
const dirOfSource = path10.posix.dirname(normalizedSource);
|
|
20885
|
+
let baseName = path10.posix.basename(normalizedSource);
|
|
20371
20886
|
for (const testExt of TEST_EXTENSIONS) {
|
|
20372
20887
|
if (baseName.endsWith(testExt)) {
|
|
20373
20888
|
baseName = baseName.slice(0, -testExt.length);
|
|
@@ -20376,9 +20891,12 @@ function computeOutputPath(sourceFile, format, mode, colocatedStyle, baseOutputD
|
|
|
20376
20891
|
}
|
|
20377
20892
|
const fileName = `${baseName}.${effectiveName}${ext}`;
|
|
20378
20893
|
if (colocatedStyle === "adjacent") {
|
|
20379
|
-
return toPosix(
|
|
20894
|
+
return toPosix(path10.posix.join(dirOfSource, fileName));
|
|
20380
20895
|
}
|
|
20381
|
-
|
|
20896
|
+
if (colocatedStyle === "flat") {
|
|
20897
|
+
return toPosix(path10.posix.join(baseOutputDir, `${cleanTestStem(normalizedSource)}${ext}`));
|
|
20898
|
+
}
|
|
20899
|
+
return toPosix(path10.posix.join(baseOutputDir, dirOfSource, fileName));
|
|
20382
20900
|
}
|
|
20383
20901
|
function groupTestCasesByOutput(testCases, format, options, logger, outputNameSuffix) {
|
|
20384
20902
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -20464,6 +20982,12 @@ var ReportGenerator = class {
|
|
|
20464
20982
|
storyReportJson: {
|
|
20465
20983
|
pretty: options.storyReportJson?.pretty ?? true
|
|
20466
20984
|
},
|
|
20985
|
+
scenarioIndexJson: {
|
|
20986
|
+
pretty: options.scenarioIndexJson?.pretty ?? true
|
|
20987
|
+
},
|
|
20988
|
+
behaviorManifestJson: {
|
|
20989
|
+
pretty: options.behaviorManifestJson?.pretty ?? true
|
|
20990
|
+
},
|
|
20467
20991
|
cucumberMessages: {
|
|
20468
20992
|
uriStrategy: options.cucumberMessages?.uriStrategy ?? "sourceFile",
|
|
20469
20993
|
includeSynthetics: options.cucumberMessages?.includeSynthetics ?? true,
|
|
@@ -20579,8 +21103,8 @@ var ReportGenerator = class {
|
|
|
20579
21103
|
if (astroPaths) {
|
|
20580
21104
|
for (const mdPath of astroPaths) {
|
|
20581
21105
|
const content = await fsPromises.readFile(mdPath, "utf8");
|
|
20582
|
-
const mdDir =
|
|
20583
|
-
const assetsDir =
|
|
21106
|
+
const mdDir = path10.dirname(mdPath);
|
|
21107
|
+
const assetsDir = path10.resolve(this.options.astro.assetsDir);
|
|
20584
21108
|
const result = copyMarkdownAssets({
|
|
20585
21109
|
markdown: content,
|
|
20586
21110
|
markdownDir: mdDir,
|
|
@@ -20611,9 +21135,9 @@ var ReportGenerator = class {
|
|
|
20611
21135
|
if (groups.size === 0 && this.options.output.mode === "aggregated") {
|
|
20612
21136
|
const ext = FORMAT_EXTENSIONS[format];
|
|
20613
21137
|
const effectiveName = this.options.outputName + (outputNameSuffix ?? "");
|
|
20614
|
-
const outputPath = toPosix(
|
|
21138
|
+
const outputPath = toPosix(path10.join(this.options.outputDir, joinNameAndExt(effectiveName, ext)));
|
|
20615
21139
|
const content = await this.formatContent(run, format);
|
|
20616
|
-
const dir =
|
|
21140
|
+
const dir = path10.dirname(outputPath);
|
|
20617
21141
|
await fsPromises.mkdir(dir, { recursive: true });
|
|
20618
21142
|
await this.deps.writeFile(outputPath, content);
|
|
20619
21143
|
return [outputPath];
|
|
@@ -20625,7 +21149,7 @@ var ReportGenerator = class {
|
|
|
20625
21149
|
testCases
|
|
20626
21150
|
};
|
|
20627
21151
|
const content = await this.formatContent(groupRun, format);
|
|
20628
|
-
const dir =
|
|
21152
|
+
const dir = path10.dirname(outputPath);
|
|
20629
21153
|
await fsPromises.mkdir(dir, { recursive: true });
|
|
20630
21154
|
await this.deps.writeFile(outputPath, content);
|
|
20631
21155
|
writtenPaths.push(outputPath);
|
|
@@ -20691,6 +21215,8 @@ var ReportGenerator = class {
|
|
|
20691
21215
|
case "astro": {
|
|
20692
21216
|
const formatter = new AstroFormatter({
|
|
20693
21217
|
assetsBaseUrl: this.options.astro.assetsBaseUrl,
|
|
21218
|
+
// Colocated = one page per file, so title each by its own suite/file.
|
|
21219
|
+
perFileTitle: this.options.output.mode === "colocated",
|
|
20694
21220
|
markdown: this.options.astro.markdown
|
|
20695
21221
|
});
|
|
20696
21222
|
return formatter.format(run);
|
|
@@ -20738,6 +21264,18 @@ var ReportGenerator = class {
|
|
|
20738
21264
|
});
|
|
20739
21265
|
return formatter.format(run);
|
|
20740
21266
|
}
|
|
21267
|
+
case "scenario-index-json": {
|
|
21268
|
+
const formatter = new ScenarioIndexJsonFormatter({
|
|
21269
|
+
pretty: this.options.scenarioIndexJson.pretty
|
|
21270
|
+
});
|
|
21271
|
+
return formatter.format(run);
|
|
21272
|
+
}
|
|
21273
|
+
case "behavior-manifest-json": {
|
|
21274
|
+
const formatter = new BehaviorManifestJsonFormatter({
|
|
21275
|
+
pretty: this.options.behaviorManifestJson.pretty
|
|
21276
|
+
});
|
|
21277
|
+
return formatter.format(run);
|
|
21278
|
+
}
|
|
20741
21279
|
default:
|
|
20742
21280
|
throw new Error(`Unknown format: ${format}`);
|
|
20743
21281
|
}
|
|
@@ -20754,7 +21292,7 @@ async function generateRunComparison(args) {
|
|
|
20754
21292
|
await fsPromises.mkdir(outputDir, { recursive: true });
|
|
20755
21293
|
for (const format of args.formats) {
|
|
20756
21294
|
const ext = format === "html" ? ".html" : ".md";
|
|
20757
|
-
const outputPath = toPosix(
|
|
21295
|
+
const outputPath = toPosix(path10.join(outputDir, `${outputName}${ext}`));
|
|
20758
21296
|
const content = format === "html" ? new RunDiffHtmlFormatter({ title: args.title }).format(diff) : new RunDiffMarkdownFormatter({ title: args.title }).format(diff);
|
|
20759
21297
|
await fsPromises.writeFile(outputPath, content, "utf8");
|
|
20760
21298
|
files.push(outputPath);
|
|
@@ -20775,6 +21313,7 @@ function normalizePlaywrightResults(testResults, adapterOptions, canonicalizeOpt
|
|
|
20775
21313
|
}
|
|
20776
21314
|
export {
|
|
20777
21315
|
AstroFormatter,
|
|
21316
|
+
BehaviorManifestJsonFormatter,
|
|
20778
21317
|
ConfluenceFormatter,
|
|
20779
21318
|
CucumberHtmlFormatter,
|
|
20780
21319
|
CucumberJsonFormatter,
|
|
@@ -20795,6 +21334,7 @@ export {
|
|
|
20795
21334
|
STORY_META_KEY,
|
|
20796
21335
|
STORY_REPORT_SCHEMA_MAJOR,
|
|
20797
21336
|
STORY_REPORT_SCHEMA_VERSION,
|
|
21337
|
+
ScenarioIndexJsonFormatter,
|
|
20798
21338
|
StoryReportJsonFormatter,
|
|
20799
21339
|
adaptJestRun,
|
|
20800
21340
|
adaptPlaywrightRun,
|
|
@@ -20805,6 +21345,7 @@ export {
|
|
|
20805
21345
|
calculateFlakiness,
|
|
20806
21346
|
calculateStability,
|
|
20807
21347
|
canonicalizeRun,
|
|
21348
|
+
classifyStatusChange,
|
|
20808
21349
|
clearVersionCache,
|
|
20809
21350
|
computeTestMetrics,
|
|
20810
21351
|
copyMarkdownAssets,
|
|
@@ -20816,6 +21357,7 @@ export {
|
|
|
20816
21357
|
detectCI4 as detectCI,
|
|
20817
21358
|
detectPerformanceTrend,
|
|
20818
21359
|
diffRuns,
|
|
21360
|
+
diffStoryReports,
|
|
20819
21361
|
findGitDir,
|
|
20820
21362
|
formatDuration3 as formatDuration,
|
|
20821
21363
|
generateRunComparison,
|
|
@@ -20827,6 +21369,7 @@ export {
|
|
|
20827
21369
|
hasSufficientHistory,
|
|
20828
21370
|
isReviewableSource,
|
|
20829
21371
|
isTestFile,
|
|
21372
|
+
joinNameAndExt,
|
|
20830
21373
|
listScenarios,
|
|
20831
21374
|
loadHistory,
|
|
20832
21375
|
mergeStepResults,
|
|
@@ -20843,21 +21386,26 @@ export {
|
|
|
20843
21386
|
readBranchName,
|
|
20844
21387
|
readGitSha,
|
|
20845
21388
|
readPackageVersion,
|
|
21389
|
+
regenerateArtifacts,
|
|
20846
21390
|
resolveAttachment,
|
|
20847
21391
|
resolveAttachments,
|
|
20848
21392
|
resolveTheme,
|
|
20849
21393
|
resolveTraceUrl,
|
|
20850
21394
|
rewriteAssetPaths,
|
|
20851
21395
|
saveHistory,
|
|
21396
|
+
scenariosCoveringPaths,
|
|
20852
21397
|
sendNotifications,
|
|
20853
21398
|
sendSlackNotification,
|
|
20854
21399
|
sendTeamsNotification,
|
|
20855
21400
|
sendWebhookNotification,
|
|
20856
21401
|
signBody,
|
|
20857
21402
|
slugify,
|
|
21403
|
+
startWatch,
|
|
20858
21404
|
stripAnsi,
|
|
21405
|
+
toBehaviorManifest,
|
|
20859
21406
|
toCIInfo,
|
|
20860
21407
|
toRawCIInfo,
|
|
21408
|
+
toScenarioIndex,
|
|
20861
21409
|
toStoryReport,
|
|
20862
21410
|
tryGetActiveOtelContext,
|
|
20863
21411
|
updateHistory,
|