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.cjs
CHANGED
|
@@ -31,6 +31,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var src_exports = {};
|
|
32
32
|
__export(src_exports, {
|
|
33
33
|
AstroFormatter: () => AstroFormatter,
|
|
34
|
+
BehaviorManifestJsonFormatter: () => BehaviorManifestJsonFormatter,
|
|
34
35
|
ConfluenceFormatter: () => ConfluenceFormatter,
|
|
35
36
|
CucumberHtmlFormatter: () => CucumberHtmlFormatter,
|
|
36
37
|
CucumberJsonFormatter: () => CucumberJsonFormatter,
|
|
@@ -51,6 +52,7 @@ __export(src_exports, {
|
|
|
51
52
|
STORY_META_KEY: () => STORY_META_KEY,
|
|
52
53
|
STORY_REPORT_SCHEMA_MAJOR: () => STORY_REPORT_SCHEMA_MAJOR,
|
|
53
54
|
STORY_REPORT_SCHEMA_VERSION: () => STORY_REPORT_SCHEMA_VERSION,
|
|
55
|
+
ScenarioIndexJsonFormatter: () => ScenarioIndexJsonFormatter,
|
|
54
56
|
StoryReportJsonFormatter: () => StoryReportJsonFormatter,
|
|
55
57
|
adaptJestRun: () => adaptJestRun,
|
|
56
58
|
adaptPlaywrightRun: () => adaptPlaywrightRun,
|
|
@@ -61,6 +63,7 @@ __export(src_exports, {
|
|
|
61
63
|
calculateFlakiness: () => calculateFlakiness,
|
|
62
64
|
calculateStability: () => calculateStability,
|
|
63
65
|
canonicalizeRun: () => canonicalizeRun,
|
|
66
|
+
classifyStatusChange: () => classifyStatusChange,
|
|
64
67
|
clearVersionCache: () => clearVersionCache,
|
|
65
68
|
computeTestMetrics: () => computeTestMetrics,
|
|
66
69
|
copyMarkdownAssets: () => copyMarkdownAssets,
|
|
@@ -72,6 +75,7 @@ __export(src_exports, {
|
|
|
72
75
|
detectCI: () => detectCI4,
|
|
73
76
|
detectPerformanceTrend: () => detectPerformanceTrend,
|
|
74
77
|
diffRuns: () => diffRuns,
|
|
78
|
+
diffStoryReports: () => diffStoryReports,
|
|
75
79
|
findGitDir: () => findGitDir,
|
|
76
80
|
formatDuration: () => formatDuration3,
|
|
77
81
|
generateRunComparison: () => generateRunComparison,
|
|
@@ -83,6 +87,7 @@ __export(src_exports, {
|
|
|
83
87
|
hasSufficientHistory: () => hasSufficientHistory,
|
|
84
88
|
isReviewableSource: () => isReviewableSource,
|
|
85
89
|
isTestFile: () => isTestFile,
|
|
90
|
+
joinNameAndExt: () => joinNameAndExt,
|
|
86
91
|
listScenarios: () => listScenarios,
|
|
87
92
|
loadHistory: () => loadHistory,
|
|
88
93
|
mergeStepResults: () => mergeStepResults,
|
|
@@ -99,29 +104,34 @@ __export(src_exports, {
|
|
|
99
104
|
readBranchName: () => readBranchName,
|
|
100
105
|
readGitSha: () => readGitSha,
|
|
101
106
|
readPackageVersion: () => readPackageVersion,
|
|
107
|
+
regenerateArtifacts: () => regenerateArtifacts,
|
|
102
108
|
resolveAttachment: () => resolveAttachment,
|
|
103
109
|
resolveAttachments: () => resolveAttachments,
|
|
104
110
|
resolveTheme: () => resolveTheme,
|
|
105
111
|
resolveTraceUrl: () => resolveTraceUrl,
|
|
106
112
|
rewriteAssetPaths: () => rewriteAssetPaths,
|
|
107
113
|
saveHistory: () => saveHistory,
|
|
114
|
+
scenariosCoveringPaths: () => scenariosCoveringPaths,
|
|
108
115
|
sendNotifications: () => sendNotifications,
|
|
109
116
|
sendSlackNotification: () => sendSlackNotification,
|
|
110
117
|
sendTeamsNotification: () => sendTeamsNotification,
|
|
111
118
|
sendWebhookNotification: () => sendWebhookNotification,
|
|
112
119
|
signBody: () => signBody,
|
|
113
120
|
slugify: () => slugify,
|
|
121
|
+
startWatch: () => startWatch,
|
|
114
122
|
stripAnsi: () => stripAnsi,
|
|
123
|
+
toBehaviorManifest: () => toBehaviorManifest,
|
|
115
124
|
toCIInfo: () => toCIInfo,
|
|
116
125
|
toRawCIInfo: () => toRawCIInfo,
|
|
126
|
+
toScenarioIndex: () => toScenarioIndex,
|
|
117
127
|
toStoryReport: () => toStoryReport,
|
|
118
128
|
tryGetActiveOtelContext: () => tryGetActiveOtelContext,
|
|
119
129
|
updateHistory: () => updateHistory,
|
|
120
130
|
validateCanonicalRun: () => validateCanonicalRun
|
|
121
131
|
});
|
|
122
132
|
module.exports = __toCommonJS(src_exports);
|
|
123
|
-
var
|
|
124
|
-
var
|
|
133
|
+
var fs9 = require("fs");
|
|
134
|
+
var path10 = __toESM(require("path"), 1);
|
|
125
135
|
var fsPromises = __toESM(require("fs/promises"), 1);
|
|
126
136
|
|
|
127
137
|
// src/converters/acl/status.ts
|
|
@@ -884,6 +894,15 @@ function copyDocEntry(entry) {
|
|
|
884
894
|
phase: entry.phase,
|
|
885
895
|
...children
|
|
886
896
|
};
|
|
897
|
+
case "video":
|
|
898
|
+
return {
|
|
899
|
+
kind: "video",
|
|
900
|
+
path: entry.path,
|
|
901
|
+
...entry.caption ? { caption: entry.caption } : {},
|
|
902
|
+
...entry.poster ? { poster: entry.poster } : {},
|
|
903
|
+
phase: entry.phase,
|
|
904
|
+
...children
|
|
905
|
+
};
|
|
887
906
|
case "custom":
|
|
888
907
|
return {
|
|
889
908
|
kind: "custom",
|
|
@@ -967,6 +986,9 @@ function buildScenario(tc, featureId) {
|
|
|
967
986
|
if (tickets && tickets.length > 0) {
|
|
968
987
|
scenario.tickets = tickets.map((t) => t.url ? { id: t.id, url: t.url } : { id: t.id });
|
|
969
988
|
}
|
|
989
|
+
if (tc.story.covers && tc.story.covers.length > 0) {
|
|
990
|
+
scenario.covers = [...tc.story.covers];
|
|
991
|
+
}
|
|
970
992
|
return scenario;
|
|
971
993
|
}
|
|
972
994
|
function deriveFeatureTitle(group, relSourceFile) {
|
|
@@ -1090,6 +1112,181 @@ var StoryReportJsonFormatter = class {
|
|
|
1090
1112
|
}
|
|
1091
1113
|
};
|
|
1092
1114
|
|
|
1115
|
+
// src/formatters/scenario-index-json.ts
|
|
1116
|
+
var ScenarioIndexJsonFormatter = class {
|
|
1117
|
+
options;
|
|
1118
|
+
constructor(options = {}) {
|
|
1119
|
+
this.options = {
|
|
1120
|
+
pretty: options.pretty ?? true,
|
|
1121
|
+
filters: options.filters
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1124
|
+
toIndex(run) {
|
|
1125
|
+
return toScenarioIndex(toStoryReport(run), this.options.filters);
|
|
1126
|
+
}
|
|
1127
|
+
format(run) {
|
|
1128
|
+
const index = this.toIndex(run);
|
|
1129
|
+
return this.options.pretty ? JSON.stringify(index, null, 2) : JSON.stringify(index);
|
|
1130
|
+
}
|
|
1131
|
+
};
|
|
1132
|
+
function toScenarioIndex(report, filters = {}) {
|
|
1133
|
+
const scenarios = report.features.flatMap(
|
|
1134
|
+
(feature) => feature.scenarios.map((scenario) => toScenarioIndexItem(feature, scenario))
|
|
1135
|
+
).filter((scenario) => matchesFilters(scenario, filters));
|
|
1136
|
+
return {
|
|
1137
|
+
schemaVersion: "1.0",
|
|
1138
|
+
runId: report.runId,
|
|
1139
|
+
generatedAtMs: report.finishedAtMs,
|
|
1140
|
+
summary: summarize(scenarios),
|
|
1141
|
+
scenarios
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
function toScenarioIndexItem(feature, scenario) {
|
|
1145
|
+
return {
|
|
1146
|
+
id: scenario.id,
|
|
1147
|
+
title: scenario.title,
|
|
1148
|
+
status: scenario.status,
|
|
1149
|
+
feature: feature.title,
|
|
1150
|
+
sourceFile: feature.sourceFile,
|
|
1151
|
+
sourceLine: scenario.sourceLine,
|
|
1152
|
+
tags: scenario.tags,
|
|
1153
|
+
tickets: scenario.tickets ?? [],
|
|
1154
|
+
covers: scenario.covers ?? [],
|
|
1155
|
+
durationMs: scenario.durationMs,
|
|
1156
|
+
steps: scenario.steps.map((step) => ({
|
|
1157
|
+
id: step.id,
|
|
1158
|
+
index: step.index,
|
|
1159
|
+
keyword: step.keyword,
|
|
1160
|
+
text: step.text,
|
|
1161
|
+
status: step.status,
|
|
1162
|
+
durationMs: step.durationMs,
|
|
1163
|
+
errorMessage: step.errorMessage,
|
|
1164
|
+
docKinds: step.docEntries.map((entry) => entry.kind)
|
|
1165
|
+
})),
|
|
1166
|
+
docKinds: scenario.docEntries.map((entry) => entry.kind),
|
|
1167
|
+
error: scenario.errorMessage ? { message: scenario.errorMessage, stack: scenario.errorStack } : void 0
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1170
|
+
function matchesFilters(scenario, filters) {
|
|
1171
|
+
if (filters.statuses?.length && !filters.statuses.includes(scenario.status)) {
|
|
1172
|
+
return false;
|
|
1173
|
+
}
|
|
1174
|
+
if (filters.tags?.length && !filters.tags.some((tag) => scenario.tags.includes(tag))) {
|
|
1175
|
+
return false;
|
|
1176
|
+
}
|
|
1177
|
+
if (filters.sourceFiles?.length && !filters.sourceFiles.some((sourceFile) => scenario.sourceFile.includes(sourceFile))) {
|
|
1178
|
+
return false;
|
|
1179
|
+
}
|
|
1180
|
+
return true;
|
|
1181
|
+
}
|
|
1182
|
+
function summarize(scenarios) {
|
|
1183
|
+
return {
|
|
1184
|
+
total: scenarios.length,
|
|
1185
|
+
passed: scenarios.filter((scenario) => scenario.status === "passed").length,
|
|
1186
|
+
failed: scenarios.filter((scenario) => scenario.status === "failed").length,
|
|
1187
|
+
skipped: scenarios.filter((scenario) => scenario.status === "skipped").length,
|
|
1188
|
+
pending: scenarios.filter((scenario) => scenario.status === "pending").length,
|
|
1189
|
+
durationMs: scenarios.reduce((total, scenario) => total + scenario.durationMs, 0)
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
// src/formatters/behavior-manifest-json.ts
|
|
1194
|
+
var BehaviorManifestJsonFormatter = class {
|
|
1195
|
+
pretty;
|
|
1196
|
+
constructor(options = {}) {
|
|
1197
|
+
this.pretty = options.pretty ?? true;
|
|
1198
|
+
}
|
|
1199
|
+
toManifest(run) {
|
|
1200
|
+
return toBehaviorManifest(toStoryReport(run));
|
|
1201
|
+
}
|
|
1202
|
+
format(run) {
|
|
1203
|
+
const manifest = this.toManifest(run);
|
|
1204
|
+
return this.pretty ? JSON.stringify(manifest, null, 2) : JSON.stringify(manifest);
|
|
1205
|
+
}
|
|
1206
|
+
};
|
|
1207
|
+
function toBehaviorManifest(report) {
|
|
1208
|
+
const index = toScenarioIndex(report);
|
|
1209
|
+
const bySource = /* @__PURE__ */ new Map();
|
|
1210
|
+
const byTag = /* @__PURE__ */ new Map();
|
|
1211
|
+
const docKinds = /* @__PURE__ */ new Set();
|
|
1212
|
+
const debuggerIssues = [];
|
|
1213
|
+
for (const scenario of index.scenarios) {
|
|
1214
|
+
const source = bySource.get(scenario.sourceFile) ?? {
|
|
1215
|
+
path: scenario.sourceFile,
|
|
1216
|
+
scenarioCount: 0,
|
|
1217
|
+
failed: 0,
|
|
1218
|
+
tags: []
|
|
1219
|
+
};
|
|
1220
|
+
source.scenarioCount += 1;
|
|
1221
|
+
if (scenario.status === "failed") source.failed += 1;
|
|
1222
|
+
source.tags = [.../* @__PURE__ */ new Set([...source.tags, ...scenario.tags])].sort();
|
|
1223
|
+
bySource.set(scenario.sourceFile, source);
|
|
1224
|
+
for (const tag of scenario.tags) {
|
|
1225
|
+
const tagEntry = byTag.get(tag) ?? { name: tag, scenarioCount: 0 };
|
|
1226
|
+
tagEntry.scenarioCount += 1;
|
|
1227
|
+
byTag.set(tag, tagEntry);
|
|
1228
|
+
}
|
|
1229
|
+
for (const kind of scenario.docKinds) docKinds.add(kind);
|
|
1230
|
+
for (const step of scenario.steps) {
|
|
1231
|
+
for (const kind of step.docKinds) docKinds.add(kind);
|
|
1232
|
+
}
|
|
1233
|
+
if (!scenarioHasDocs(scenario)) {
|
|
1234
|
+
debuggerIssues.push({
|
|
1235
|
+
severity: "warning",
|
|
1236
|
+
code: "missing-docs",
|
|
1237
|
+
scenarioId: scenario.id,
|
|
1238
|
+
title: scenario.title,
|
|
1239
|
+
message: "Scenario has no doc entries."
|
|
1240
|
+
});
|
|
1241
|
+
}
|
|
1242
|
+
if (scenario.tags.length === 0) {
|
|
1243
|
+
debuggerIssues.push({
|
|
1244
|
+
severity: "warning",
|
|
1245
|
+
code: "missing-tags",
|
|
1246
|
+
scenarioId: scenario.id,
|
|
1247
|
+
title: scenario.title,
|
|
1248
|
+
message: "Scenario has no tags."
|
|
1249
|
+
});
|
|
1250
|
+
}
|
|
1251
|
+
if (scenario.covers.length === 0) {
|
|
1252
|
+
debuggerIssues.push({
|
|
1253
|
+
severity: "warning",
|
|
1254
|
+
code: "missing-covers",
|
|
1255
|
+
scenarioId: scenario.id,
|
|
1256
|
+
title: scenario.title,
|
|
1257
|
+
message: "Scenario declares no covers (product-code paths), so code\u2192scenario lookup cannot find it."
|
|
1258
|
+
});
|
|
1259
|
+
}
|
|
1260
|
+
if (scenario.sourceLine === void 0) {
|
|
1261
|
+
debuggerIssues.push({
|
|
1262
|
+
severity: "warning",
|
|
1263
|
+
code: "missing-source-line",
|
|
1264
|
+
scenarioId: scenario.id,
|
|
1265
|
+
title: scenario.title,
|
|
1266
|
+
message: "Scenario has no source line."
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
const scenariosWithDocs = index.scenarios.filter(scenarioHasDocs).length;
|
|
1271
|
+
return {
|
|
1272
|
+
schemaVersion: "1.0",
|
|
1273
|
+
runId: report.runId,
|
|
1274
|
+
generatedAtMs: report.finishedAtMs,
|
|
1275
|
+
summary: index.summary,
|
|
1276
|
+
sourceFiles: [...bySource.values()].sort((a, b) => a.path.localeCompare(b.path)),
|
|
1277
|
+
tags: [...byTag.values()].sort((a, b) => a.name.localeCompare(b.name)),
|
|
1278
|
+
docCoverage: {
|
|
1279
|
+
scenariosWithDocs,
|
|
1280
|
+
scenariosWithoutDocs: index.scenarios.length - scenariosWithDocs,
|
|
1281
|
+
docKinds: [...docKinds].sort()
|
|
1282
|
+
},
|
|
1283
|
+
debugger: debuggerIssues
|
|
1284
|
+
};
|
|
1285
|
+
}
|
|
1286
|
+
function scenarioHasDocs(scenario) {
|
|
1287
|
+
return scenario.docKinds.length > 0 || scenario.steps.some((step) => step.docKinds.length > 0);
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1093
1290
|
// src/formatters/html/renderers/index.ts
|
|
1094
1291
|
var fs2 = __toESM(require("fs"), 1);
|
|
1095
1292
|
var path3 = __toESM(require("path"), 1);
|
|
@@ -13867,6 +14064,23 @@ function renderDocScreenshot(entry, deps) {
|
|
|
13867
14064
|
${entry.alt ? `<div class="doc-screenshot-caption">${deps.escapeHtml(entry.alt)}</div>` : ""}
|
|
13868
14065
|
</div>`;
|
|
13869
14066
|
}
|
|
14067
|
+
function renderDocVideo(entry, deps) {
|
|
14068
|
+
const isRemote = /^(?:https?:|data:)/i.test(entry.path);
|
|
14069
|
+
const isAbsoluteFsPath = !isRemote && /^(?:[/\\]|[A-Za-z]:[/\\])/.test(entry.path);
|
|
14070
|
+
const captionHtml = entry.caption ? `<div class="doc-video-caption">${deps.escapeHtml(entry.caption)}</div>` : "";
|
|
14071
|
+
if ((deps.embedScreenshots ?? true) && isAbsoluteFsPath) {
|
|
14072
|
+
return `<div class="doc-video doc-video-missing">
|
|
14073
|
+
<div class="doc-video-missing-label">Video unavailable</div>
|
|
14074
|
+
<div class="doc-video-missing-path">${deps.escapeHtml(entry.path)}</div>
|
|
14075
|
+
${captionHtml}
|
|
14076
|
+
</div>`;
|
|
14077
|
+
}
|
|
14078
|
+
const poster = entry.poster ? ` poster="${deps.escapeHtml(entry.poster)}"` : "";
|
|
14079
|
+
return `<div class="doc-video">
|
|
14080
|
+
<video class="doc-video-player" controls preload="metadata"${poster} src="${deps.escapeHtml(entry.path)}"></video>
|
|
14081
|
+
${captionHtml}
|
|
14082
|
+
</div>`;
|
|
14083
|
+
}
|
|
13870
14084
|
function renderDocCustom(entry, deps) {
|
|
13871
14085
|
if (entry.type === "visual" && entry.data && typeof entry.data === "object") {
|
|
13872
14086
|
const data = entry.data;
|
|
@@ -13920,6 +14134,9 @@ function renderDocEntry(entry, deps) {
|
|
|
13920
14134
|
case "screenshot":
|
|
13921
14135
|
html = renderDocScreenshot(entry, deps);
|
|
13922
14136
|
break;
|
|
14137
|
+
case "video":
|
|
14138
|
+
html = renderDocVideo(entry, deps);
|
|
14139
|
+
break;
|
|
13923
14140
|
case "custom":
|
|
13924
14141
|
html = renderDocCustom(entry, deps);
|
|
13925
14142
|
break;
|
|
@@ -15246,6 +15463,19 @@ var MarkdownFormatter = class {
|
|
|
15246
15463
|
case "screenshot":
|
|
15247
15464
|
lines.push(`${indent}`);
|
|
15248
15465
|
break;
|
|
15466
|
+
case "video": {
|
|
15467
|
+
const poster = entry.poster ? ` poster="${entry.poster}"` : "";
|
|
15468
|
+
lines.push(`${indent}`);
|
|
15469
|
+
lines.push(`${indent}<video controls preload="metadata"${poster} class="doc-video">`);
|
|
15470
|
+
lines.push(`${indent} <source src="${entry.path}" />`);
|
|
15471
|
+
lines.push(`${indent}</video>`);
|
|
15472
|
+
if (entry.caption) {
|
|
15473
|
+
lines.push(`${indent}`);
|
|
15474
|
+
lines.push(`${indent}*${entry.caption}*`);
|
|
15475
|
+
}
|
|
15476
|
+
lines.push(`${indent}`);
|
|
15477
|
+
break;
|
|
15478
|
+
}
|
|
15249
15479
|
case "custom":
|
|
15250
15480
|
if (entry.type === "visual" && entry.data && typeof entry.data === "object") {
|
|
15251
15481
|
const data = entry.data;
|
|
@@ -15958,8 +16188,8 @@ function extractDocAttachments(step) {
|
|
|
15958
16188
|
}
|
|
15959
16189
|
return attachments;
|
|
15960
16190
|
}
|
|
15961
|
-
function guessMediaType(
|
|
15962
|
-
const lower =
|
|
16191
|
+
function guessMediaType(path11) {
|
|
16192
|
+
const lower = path11.toLowerCase();
|
|
15963
16193
|
if (lower.endsWith(".png")) return "image/png";
|
|
15964
16194
|
if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg";
|
|
15965
16195
|
if (lower.endsWith(".gif")) return "image/gif";
|
|
@@ -16100,11 +16330,11 @@ var CucumberHtmlFormatter = class {
|
|
|
16100
16330
|
for (const envelope of envelopes) {
|
|
16101
16331
|
const accepted = htmlStream.write(envelope);
|
|
16102
16332
|
if (!accepted) {
|
|
16103
|
-
await new Promise((
|
|
16333
|
+
await new Promise((resolve8) => htmlStream.once("drain", resolve8));
|
|
16104
16334
|
}
|
|
16105
16335
|
}
|
|
16106
|
-
await new Promise((
|
|
16107
|
-
collector.on("finish",
|
|
16336
|
+
await new Promise((resolve8, reject) => {
|
|
16337
|
+
collector.on("finish", resolve8);
|
|
16108
16338
|
collector.on("error", reject);
|
|
16109
16339
|
htmlStream.end();
|
|
16110
16340
|
});
|
|
@@ -16405,6 +16635,8 @@ function formatDocEntry(doc) {
|
|
|
16405
16635
|
return `${escapeHtml2(doc.title ?? "mermaid diagram")}: <code>${escapeHtml2(doc.code)}</code>`;
|
|
16406
16636
|
case "screenshot":
|
|
16407
16637
|
return `${doc.alt ? `${escapeHtml2(doc.alt)}: ` : ""}${escapeHtml2(doc.path)}`;
|
|
16638
|
+
case "video":
|
|
16639
|
+
return `${doc.caption ? `${escapeHtml2(doc.caption)}: ` : ""}${escapeHtml2(doc.path)}`;
|
|
16408
16640
|
case "custom":
|
|
16409
16641
|
return `${escapeHtml2(doc.type)}: ${escapeHtml2(JSON.stringify(doc.data))}`;
|
|
16410
16642
|
}
|
|
@@ -16861,6 +17093,8 @@ function formatDocEntry2(doc) {
|
|
|
16861
17093
|
return `${doc.title ?? "mermaid diagram"}: \`${doc.code}\``;
|
|
16862
17094
|
case "screenshot":
|
|
16863
17095
|
return `${doc.alt ? `${doc.alt}: ` : ""}${doc.path}`;
|
|
17096
|
+
case "video":
|
|
17097
|
+
return `${doc.caption ? `${doc.caption}: ` : ""}${doc.path}`;
|
|
16864
17098
|
case "custom":
|
|
16865
17099
|
return `${doc.type}: ${JSON.stringify(doc.data)}`;
|
|
16866
17100
|
}
|
|
@@ -17106,19 +17340,35 @@ function replaceAssetRef(html, original, replacement) {
|
|
|
17106
17340
|
return html;
|
|
17107
17341
|
}
|
|
17108
17342
|
|
|
17343
|
+
// src/utils/source-file.ts
|
|
17344
|
+
function cleanTestStem(fileName) {
|
|
17345
|
+
const base = fileName.split(/[\\/]/).pop() ?? fileName;
|
|
17346
|
+
const stripped = base.replace(/\.(story\.)?(test|spec|cy)\.[cm]?[jt]sx?$/i, "");
|
|
17347
|
+
if (stripped !== base) return stripped;
|
|
17348
|
+
return base.replace(/\.[^.]+$/, "");
|
|
17349
|
+
}
|
|
17350
|
+
function humanizeSourceFile(fileName) {
|
|
17351
|
+
return cleanTestStem(fileName).split(/[-_.\s]+/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
17352
|
+
}
|
|
17353
|
+
|
|
17109
17354
|
// src/formatters/astro.ts
|
|
17110
17355
|
var AstroFormatter = class _AstroFormatter {
|
|
17111
17356
|
markdownFormatter;
|
|
17112
17357
|
title;
|
|
17358
|
+
perFileTitle;
|
|
17113
17359
|
constructor(options = {}) {
|
|
17114
17360
|
this.title = options.markdown?.title ?? "User Stories";
|
|
17361
|
+
this.perFileTitle = options.perFileTitle ?? false;
|
|
17115
17362
|
this.markdownFormatter = new MarkdownFormatter({
|
|
17116
17363
|
...options.markdown,
|
|
17117
17364
|
title: this.title,
|
|
17118
17365
|
stepStyle: "gherkin",
|
|
17119
17366
|
includeFrontMatter: false,
|
|
17120
17367
|
includeSummaryTable: false,
|
|
17121
|
-
includeMetadata: false
|
|
17368
|
+
includeMetadata: false,
|
|
17369
|
+
// A per-file page is one file already — group by suite/describe so the
|
|
17370
|
+
// body shows clean section headings, not the redundant source path.
|
|
17371
|
+
groupBy: this.perFileTitle ? "suite" : options.markdown?.groupBy ?? "file"
|
|
17122
17372
|
});
|
|
17123
17373
|
}
|
|
17124
17374
|
format(run) {
|
|
@@ -17128,13 +17378,31 @@ var AstroFormatter = class _AstroFormatter {
|
|
|
17128
17378
|
return `${frontmatter}
|
|
17129
17379
|
${body}`;
|
|
17130
17380
|
}
|
|
17381
|
+
/**
|
|
17382
|
+
* Title for the page. A per-file page (one source file — i.e. colocated mode)
|
|
17383
|
+
* is titled by its suite/describe name, falling back to a humanized filename,
|
|
17384
|
+
* so the docs nav reads "Convert Currency" not "User Stories" six times over.
|
|
17385
|
+
* Multi-file (aggregated) pages keep the configured title.
|
|
17386
|
+
*/
|
|
17387
|
+
deriveTitle(run) {
|
|
17388
|
+
if (!this.perFileTitle) return this.title;
|
|
17389
|
+
const sourceFiles = new Set(
|
|
17390
|
+
run.testCases.map((tc) => tc.sourceFile).filter((f) => f && f !== "unknown")
|
|
17391
|
+
);
|
|
17392
|
+
if (sourceFiles.size !== 1) return this.title;
|
|
17393
|
+
const suites = new Set(
|
|
17394
|
+
run.testCases.map((tc) => tc.titlePath?.[0]).filter((s) => Boolean(s))
|
|
17395
|
+
);
|
|
17396
|
+
if (suites.size === 1) return [...suites][0];
|
|
17397
|
+
return humanizeSourceFile([...sourceFiles][0]) || this.title;
|
|
17398
|
+
}
|
|
17131
17399
|
buildFrontmatter(run) {
|
|
17132
17400
|
const badge = _AstroFormatter.computeBadge(run.testCases);
|
|
17133
17401
|
const count = run.testCases.length;
|
|
17134
17402
|
const description = `${count} scenario${count !== 1 ? "s" : ""} \u2014 ${badge.text.toLowerCase()}`;
|
|
17135
17403
|
const lines = [
|
|
17136
17404
|
"---",
|
|
17137
|
-
`title: ${this.
|
|
17405
|
+
`title: ${yamlScalar(this.deriveTitle(run))}`,
|
|
17138
17406
|
`description: ${description}`,
|
|
17139
17407
|
"sidebar:",
|
|
17140
17408
|
" badge:",
|
|
@@ -17152,6 +17420,12 @@ ${body}`;
|
|
|
17152
17420
|
return { text: "Passed", variant: "success" };
|
|
17153
17421
|
}
|
|
17154
17422
|
};
|
|
17423
|
+
function yamlScalar(value) {
|
|
17424
|
+
if (/[:#[\]{}&*!|>'"%@`]|^[\s-]|\s$/.test(value)) {
|
|
17425
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
17426
|
+
}
|
|
17427
|
+
return value;
|
|
17428
|
+
}
|
|
17155
17429
|
|
|
17156
17430
|
// src/formatters/confluence.ts
|
|
17157
17431
|
var ConfluenceFormatter = class {
|
|
@@ -17428,6 +17702,15 @@ ${tc.errorStack}` : "");
|
|
|
17428
17702
|
])
|
|
17429
17703
|
);
|
|
17430
17704
|
break;
|
|
17705
|
+
case "video":
|
|
17706
|
+
content.push(
|
|
17707
|
+
paragraph([
|
|
17708
|
+
text(entry.caption ?? "Video", strong()),
|
|
17709
|
+
text(": "),
|
|
17710
|
+
link(entry.path, entry.path)
|
|
17711
|
+
])
|
|
17712
|
+
);
|
|
17713
|
+
break;
|
|
17431
17714
|
case "custom":
|
|
17432
17715
|
content.push(paragraph([text(`[${entry.type}]`, strong())]));
|
|
17433
17716
|
content.push(codeBlock(JSON.stringify(entry.data ?? null, null, 2), "json"));
|
|
@@ -17597,6 +17880,13 @@ function scanMarkdownAssets(markdown) {
|
|
|
17597
17880
|
found.add(src);
|
|
17598
17881
|
}
|
|
17599
17882
|
}
|
|
17883
|
+
const posterRe = /<video[^>]+\bposter=["']([^"']+)["'][^>]*>/gi;
|
|
17884
|
+
while ((match = posterRe.exec(stripped)) !== null) {
|
|
17885
|
+
const src = match[1].trim();
|
|
17886
|
+
if (isLocalPath(src)) {
|
|
17887
|
+
found.add(src);
|
|
17888
|
+
}
|
|
17889
|
+
}
|
|
17600
17890
|
return Array.from(found);
|
|
17601
17891
|
}
|
|
17602
17892
|
function splitByCode(markdown) {
|
|
@@ -17647,6 +17937,19 @@ function rewriteProseSegment(prose, assetsBaseUrl, pathMap) {
|
|
|
17647
17937
|
return `${pre}${assetsBaseUrl}/${trimmed}${post}`;
|
|
17648
17938
|
}
|
|
17649
17939
|
);
|
|
17940
|
+
result = result.replace(
|
|
17941
|
+
/(<video[^>]+\bposter=["'])([^"']+)(["'][^>]*>)/gi,
|
|
17942
|
+
(full, pre, src, post) => {
|
|
17943
|
+
const trimmed = src.trim();
|
|
17944
|
+
if (!isLocalPath(trimmed)) return full;
|
|
17945
|
+
if (pathMap) {
|
|
17946
|
+
const mapped = pathMap.get(trimmed);
|
|
17947
|
+
if (mapped === void 0) return full;
|
|
17948
|
+
return `${pre}${assetsBaseUrl}/${mapped}${post}`;
|
|
17949
|
+
}
|
|
17950
|
+
return `${pre}${assetsBaseUrl}/${trimmed}${post}`;
|
|
17951
|
+
}
|
|
17952
|
+
);
|
|
17650
17953
|
return result;
|
|
17651
17954
|
}
|
|
17652
17955
|
function rewriteAssetPaths(markdown, assetsBaseUrl, pathMap) {
|
|
@@ -18093,6 +18396,184 @@ ${result.errors.join("\n")}`);
|
|
|
18093
18396
|
}
|
|
18094
18397
|
}
|
|
18095
18398
|
|
|
18399
|
+
// src/coverage-index.ts
|
|
18400
|
+
function normalizePath(path11) {
|
|
18401
|
+
return path11.replace(/^\.\//, "");
|
|
18402
|
+
}
|
|
18403
|
+
function scenariosCoveringPaths(index, paths) {
|
|
18404
|
+
const queries = paths.map(normalizePath);
|
|
18405
|
+
return index.scenarios.filter(
|
|
18406
|
+
(scenario) => scenario.covers.some(
|
|
18407
|
+
(glob) => queries.some((path11) => matchesPattern(normalizePath(glob), path11))
|
|
18408
|
+
)
|
|
18409
|
+
);
|
|
18410
|
+
}
|
|
18411
|
+
|
|
18412
|
+
// src/watch.ts
|
|
18413
|
+
var fs6 = __toESM(require("fs"), 1);
|
|
18414
|
+
var path7 = __toESM(require("path"), 1);
|
|
18415
|
+
|
|
18416
|
+
// src/converters/synthesize.ts
|
|
18417
|
+
var KEYWORD_MAP = {
|
|
18418
|
+
given: "Given",
|
|
18419
|
+
when: "When",
|
|
18420
|
+
then: "Then",
|
|
18421
|
+
and: "And",
|
|
18422
|
+
but: "But"
|
|
18423
|
+
};
|
|
18424
|
+
function normalizeKeyword(keyword) {
|
|
18425
|
+
return KEYWORD_MAP[keyword.toLowerCase()] ?? keyword;
|
|
18426
|
+
}
|
|
18427
|
+
function normalizeStepKeywords(steps) {
|
|
18428
|
+
return steps.map((step) => ({
|
|
18429
|
+
...step,
|
|
18430
|
+
keyword: normalizeKeyword(step.keyword)
|
|
18431
|
+
}));
|
|
18432
|
+
}
|
|
18433
|
+
function deriveScenarioName(tc) {
|
|
18434
|
+
if (tc.title) return tc.title;
|
|
18435
|
+
if (tc.titlePath && tc.titlePath.length > 0) {
|
|
18436
|
+
return tc.titlePath[tc.titlePath.length - 1];
|
|
18437
|
+
}
|
|
18438
|
+
return "Untitled";
|
|
18439
|
+
}
|
|
18440
|
+
function synthesizeStories(raw) {
|
|
18441
|
+
return {
|
|
18442
|
+
...raw,
|
|
18443
|
+
testCases: raw.testCases.map(synthesizeTestCase)
|
|
18444
|
+
};
|
|
18445
|
+
}
|
|
18446
|
+
function synthesizeTestCase(tc) {
|
|
18447
|
+
if (tc.story == null) {
|
|
18448
|
+
const scenario = deriveScenarioName(tc);
|
|
18449
|
+
return {
|
|
18450
|
+
...tc,
|
|
18451
|
+
story: {
|
|
18452
|
+
scenario,
|
|
18453
|
+
steps: [{ keyword: "Then", text: scenario }]
|
|
18454
|
+
}
|
|
18455
|
+
};
|
|
18456
|
+
}
|
|
18457
|
+
const steps = tc.story.steps;
|
|
18458
|
+
if (!steps || steps.length === 0) {
|
|
18459
|
+
return {
|
|
18460
|
+
...tc,
|
|
18461
|
+
story: {
|
|
18462
|
+
...tc.story,
|
|
18463
|
+
steps: [{ keyword: "Then", text: tc.story.scenario }]
|
|
18464
|
+
}
|
|
18465
|
+
};
|
|
18466
|
+
}
|
|
18467
|
+
return {
|
|
18468
|
+
...tc,
|
|
18469
|
+
story: {
|
|
18470
|
+
...tc.story,
|
|
18471
|
+
steps: normalizeStepKeywords(steps)
|
|
18472
|
+
}
|
|
18473
|
+
};
|
|
18474
|
+
}
|
|
18475
|
+
|
|
18476
|
+
// src/watch.ts
|
|
18477
|
+
function toRun(data, inputType, synthesize) {
|
|
18478
|
+
if (inputType === "canonical") return data;
|
|
18479
|
+
let raw = data;
|
|
18480
|
+
if (synthesize) raw = synthesizeStories(raw);
|
|
18481
|
+
return canonicalizeRun(raw);
|
|
18482
|
+
}
|
|
18483
|
+
async function regenerateArtifacts(options, deps = {}) {
|
|
18484
|
+
const read = deps.readFile ?? ((filePath) => fs6.readFileSync(filePath, "utf8"));
|
|
18485
|
+
const data = JSON.parse(read(path7.resolve(options.input)));
|
|
18486
|
+
const run = toRun(data, options.inputType ?? "raw", options.synthesize !== false);
|
|
18487
|
+
const generator = new ReportGenerator({
|
|
18488
|
+
formats: options.formats,
|
|
18489
|
+
outputDir: options.outputDir,
|
|
18490
|
+
outputName: options.outputName
|
|
18491
|
+
});
|
|
18492
|
+
const result = await generator.generate(run);
|
|
18493
|
+
return [...result.values()].flat();
|
|
18494
|
+
}
|
|
18495
|
+
function startWatch(options, deps = {}) {
|
|
18496
|
+
const log = deps.log ?? ((message) => console.log(message));
|
|
18497
|
+
const regenerate = deps.regenerate ?? ((input) => regenerateArtifacts({ ...options, input }, deps));
|
|
18498
|
+
const watchFn = deps.watch ?? ((filePath, listener) => fs6.watch(filePath, listener));
|
|
18499
|
+
const debounceMs = options.debounceMs ?? 150;
|
|
18500
|
+
let timer;
|
|
18501
|
+
let running = false;
|
|
18502
|
+
let pending = false;
|
|
18503
|
+
const run = async () => {
|
|
18504
|
+
if (running) {
|
|
18505
|
+
pending = true;
|
|
18506
|
+
return;
|
|
18507
|
+
}
|
|
18508
|
+
running = true;
|
|
18509
|
+
try {
|
|
18510
|
+
const files = await regenerate(options.input);
|
|
18511
|
+
log(`Regenerated ${files.length} artifact file(s) from ${options.input}`);
|
|
18512
|
+
} catch (error) {
|
|
18513
|
+
log(`Watch regeneration failed: ${error.message}`);
|
|
18514
|
+
} finally {
|
|
18515
|
+
running = false;
|
|
18516
|
+
if (pending) {
|
|
18517
|
+
pending = false;
|
|
18518
|
+
trigger();
|
|
18519
|
+
}
|
|
18520
|
+
}
|
|
18521
|
+
};
|
|
18522
|
+
const trigger = () => {
|
|
18523
|
+
if (timer) clearTimeout(timer);
|
|
18524
|
+
timer = setTimeout(() => void run(), debounceMs);
|
|
18525
|
+
};
|
|
18526
|
+
trigger();
|
|
18527
|
+
const watcher = watchFn(path7.resolve(options.input), trigger);
|
|
18528
|
+
return {
|
|
18529
|
+
close: () => {
|
|
18530
|
+
if (timer) clearTimeout(timer);
|
|
18531
|
+
watcher.close();
|
|
18532
|
+
}
|
|
18533
|
+
};
|
|
18534
|
+
}
|
|
18535
|
+
|
|
18536
|
+
// src/behavior-diff.ts
|
|
18537
|
+
function classifyStatusChange(baseline, current) {
|
|
18538
|
+
if (baseline === void 0) return "added";
|
|
18539
|
+
if (current === void 0) return "removed";
|
|
18540
|
+
if (baseline === current) return "unchanged";
|
|
18541
|
+
if (baseline === "passed" && current === "failed") return "regressed";
|
|
18542
|
+
if (baseline === "failed" && current === "passed") return "fixed";
|
|
18543
|
+
return "changed";
|
|
18544
|
+
}
|
|
18545
|
+
function scenarioMap(report) {
|
|
18546
|
+
const map = /* @__PURE__ */ new Map();
|
|
18547
|
+
for (const feature of report.features) {
|
|
18548
|
+
for (const scenario of feature.scenarios) {
|
|
18549
|
+
map.set(scenario.id, { scenario, sourceFile: feature.sourceFile });
|
|
18550
|
+
}
|
|
18551
|
+
}
|
|
18552
|
+
return map;
|
|
18553
|
+
}
|
|
18554
|
+
function diffStoryReports(baseline, current) {
|
|
18555
|
+
const base = scenarioMap(baseline);
|
|
18556
|
+
const curr = scenarioMap(current);
|
|
18557
|
+
const ids = [.../* @__PURE__ */ new Set([...base.keys(), ...curr.keys()])];
|
|
18558
|
+
const scenarios = ids.map((id) => {
|
|
18559
|
+
const b = base.get(id);
|
|
18560
|
+
const c = curr.get(id);
|
|
18561
|
+
const kind = classifyStatusChange(b?.scenario.status, c?.scenario.status);
|
|
18562
|
+
const meta = c ?? b;
|
|
18563
|
+
return {
|
|
18564
|
+
id,
|
|
18565
|
+
title: meta.scenario.title,
|
|
18566
|
+
sourceFile: meta.sourceFile,
|
|
18567
|
+
kind,
|
|
18568
|
+
baselineStatus: b?.scenario.status,
|
|
18569
|
+
currentStatus: c?.scenario.status
|
|
18570
|
+
};
|
|
18571
|
+
});
|
|
18572
|
+
const summary = { added: 0, removed: 0, regressed: 0, fixed: 0, changed: 0, unchanged: 0 };
|
|
18573
|
+
for (const s of scenarios) summary[s.kind] += 1;
|
|
18574
|
+
return { schemaVersion: "1.0", summary, scenarios };
|
|
18575
|
+
}
|
|
18576
|
+
|
|
18096
18577
|
// src/publishers/confluence.ts
|
|
18097
18578
|
function parseAdf(adf) {
|
|
18098
18579
|
let parsed;
|
|
@@ -18688,27 +19169,27 @@ function pickleStepArgumentToDocs(ps) {
|
|
|
18688
19169
|
}
|
|
18689
19170
|
|
|
18690
19171
|
// src/utils/git-info.ts
|
|
18691
|
-
var
|
|
18692
|
-
var
|
|
19172
|
+
var fs7 = __toESM(require("fs"), 1);
|
|
19173
|
+
var path8 = __toESM(require("path"), 1);
|
|
18693
19174
|
function readGitSha(cwd = process.cwd()) {
|
|
18694
19175
|
const envSha = process.env.GITHUB_SHA || process.env.GIT_COMMIT || process.env.CI_COMMIT_SHA;
|
|
18695
19176
|
if (envSha) return envSha;
|
|
18696
19177
|
const gitDir = findGitDir(cwd);
|
|
18697
19178
|
if (!gitDir) return void 0;
|
|
18698
19179
|
try {
|
|
18699
|
-
const headPath =
|
|
18700
|
-
const head =
|
|
19180
|
+
const headPath = path8.join(gitDir, "HEAD");
|
|
19181
|
+
const head = fs7.readFileSync(headPath, "utf8").trim();
|
|
18701
19182
|
if (!head.startsWith("ref:")) {
|
|
18702
19183
|
return head;
|
|
18703
19184
|
}
|
|
18704
19185
|
const refPath = head.replace("ref:", "").trim();
|
|
18705
|
-
const refFile =
|
|
18706
|
-
if (
|
|
18707
|
-
return
|
|
19186
|
+
const refFile = path8.join(gitDir, refPath);
|
|
19187
|
+
if (fs7.existsSync(refFile)) {
|
|
19188
|
+
return fs7.readFileSync(refFile, "utf8").trim();
|
|
18708
19189
|
}
|
|
18709
|
-
const packedRefs =
|
|
18710
|
-
if (
|
|
18711
|
-
const content =
|
|
19190
|
+
const packedRefs = path8.join(gitDir, "packed-refs");
|
|
19191
|
+
if (fs7.existsSync(packedRefs)) {
|
|
19192
|
+
const content = fs7.readFileSync(packedRefs, "utf8");
|
|
18712
19193
|
for (const line of content.split("\n")) {
|
|
18713
19194
|
if (!line || line.startsWith("#") || line.startsWith("^")) continue;
|
|
18714
19195
|
const [sha, ref] = line.split(" ");
|
|
@@ -18723,19 +19204,19 @@ function readGitSha(cwd = process.cwd()) {
|
|
|
18723
19204
|
function findGitDir(start) {
|
|
18724
19205
|
let current = start;
|
|
18725
19206
|
while (true) {
|
|
18726
|
-
const candidate =
|
|
18727
|
-
if (
|
|
18728
|
-
const stat =
|
|
19207
|
+
const candidate = path8.join(current, ".git");
|
|
19208
|
+
if (fs7.existsSync(candidate)) {
|
|
19209
|
+
const stat = fs7.statSync(candidate);
|
|
18729
19210
|
if (stat.isFile()) {
|
|
18730
|
-
const content =
|
|
19211
|
+
const content = fs7.readFileSync(candidate, "utf8").trim();
|
|
18731
19212
|
const match = content.match(/^gitdir: (.+)$/);
|
|
18732
19213
|
if (match) {
|
|
18733
|
-
return
|
|
19214
|
+
return path8.resolve(current, match[1]);
|
|
18734
19215
|
}
|
|
18735
19216
|
}
|
|
18736
19217
|
return candidate;
|
|
18737
19218
|
}
|
|
18738
|
-
const parent =
|
|
19219
|
+
const parent = path8.dirname(current);
|
|
18739
19220
|
if (parent === current) return void 0;
|
|
18740
19221
|
current = parent;
|
|
18741
19222
|
}
|
|
@@ -18746,8 +19227,8 @@ function readBranchName(cwd = process.cwd()) {
|
|
|
18746
19227
|
const gitDir = findGitDir(cwd);
|
|
18747
19228
|
if (!gitDir) return void 0;
|
|
18748
19229
|
try {
|
|
18749
|
-
const headPath =
|
|
18750
|
-
const head =
|
|
19230
|
+
const headPath = path8.join(gitDir, "HEAD");
|
|
19231
|
+
const head = fs7.readFileSync(headPath, "utf8").trim();
|
|
18751
19232
|
if (head.startsWith("ref:")) {
|
|
18752
19233
|
const refPath = head.replace("ref:", "").trim();
|
|
18753
19234
|
const match = refPath.match(/^refs\/heads\/(.+)$/);
|
|
@@ -18784,8 +19265,8 @@ function nanosecondsToMs(ns) {
|
|
|
18784
19265
|
}
|
|
18785
19266
|
|
|
18786
19267
|
// src/utils/metadata.ts
|
|
18787
|
-
var
|
|
18788
|
-
var
|
|
19268
|
+
var fs8 = __toESM(require("fs"), 1);
|
|
19269
|
+
var path9 = __toESM(require("path"), 1);
|
|
18789
19270
|
var versionCache = /* @__PURE__ */ new Map();
|
|
18790
19271
|
function readPackageVersion(root) {
|
|
18791
19272
|
if (versionCache.has(root)) {
|
|
@@ -18796,18 +19277,18 @@ function readPackageVersion(root) {
|
|
|
18796
19277
|
return version;
|
|
18797
19278
|
}
|
|
18798
19279
|
function findPackageVersion(startDir) {
|
|
18799
|
-
let current =
|
|
19280
|
+
let current = path9.resolve(startDir);
|
|
18800
19281
|
while (true) {
|
|
18801
|
-
const pkgPath =
|
|
19282
|
+
const pkgPath = path9.join(current, "package.json");
|
|
18802
19283
|
try {
|
|
18803
|
-
if (
|
|
18804
|
-
const raw =
|
|
19284
|
+
if (fs8.existsSync(pkgPath)) {
|
|
19285
|
+
const raw = fs8.readFileSync(pkgPath, "utf8");
|
|
18805
19286
|
const parsed = JSON.parse(raw);
|
|
18806
19287
|
return parsed.version;
|
|
18807
19288
|
}
|
|
18808
19289
|
} catch {
|
|
18809
19290
|
}
|
|
18810
|
-
const parent =
|
|
19291
|
+
const parent = path9.dirname(current);
|
|
18811
19292
|
if (parent === current) {
|
|
18812
19293
|
return void 0;
|
|
18813
19294
|
}
|
|
@@ -19678,12 +20159,22 @@ function listScenarios(args, _deps) {
|
|
|
19678
20159
|
const { testCases, format } = args;
|
|
19679
20160
|
if (format === "json") {
|
|
19680
20161
|
const items = testCases.map((tc) => ({
|
|
20162
|
+
id: tc.id,
|
|
19681
20163
|
scenario: tc.story.scenario,
|
|
19682
20164
|
status: tc.status,
|
|
19683
20165
|
sourceFile: tc.sourceFile,
|
|
19684
20166
|
sourceLine: tc.sourceLine,
|
|
20167
|
+
suitePath: tc.story.suitePath ?? tc.titlePath.slice(0, -1),
|
|
19685
20168
|
tags: tc.tags,
|
|
19686
|
-
|
|
20169
|
+
tickets: tc.story.tickets ?? [],
|
|
20170
|
+
covers: tc.story.covers ?? [],
|
|
20171
|
+
durationMs: tc.durationMs,
|
|
20172
|
+
error: tc.errorMessage ? {
|
|
20173
|
+
message: tc.errorMessage,
|
|
20174
|
+
stack: tc.errorStack
|
|
20175
|
+
} : void 0,
|
|
20176
|
+
steps: tc.story.steps.map((step, index) => toScenarioStep(step, index, tc)),
|
|
20177
|
+
docKinds: collectDocKinds(tc)
|
|
19687
20178
|
}));
|
|
19688
20179
|
return JSON.stringify(items, null, 2);
|
|
19689
20180
|
}
|
|
@@ -19756,6 +20247,34 @@ function listScenarios(args, _deps) {
|
|
|
19756
20247
|
];
|
|
19757
20248
|
return lines.join("\n");
|
|
19758
20249
|
}
|
|
20250
|
+
function toScenarioStep(step, index, testCase) {
|
|
20251
|
+
const result = testCase.stepResults.find(
|
|
20252
|
+
(candidate) => candidate.index === index || candidate.stepId === step.id
|
|
20253
|
+
);
|
|
20254
|
+
return {
|
|
20255
|
+
id: step.id,
|
|
20256
|
+
index,
|
|
20257
|
+
keyword: step.keyword,
|
|
20258
|
+
text: step.text,
|
|
20259
|
+
status: result?.status ?? testCase.status,
|
|
20260
|
+
durationMs: result?.durationMs ?? step.durationMs ?? 0,
|
|
20261
|
+
errorMessage: result?.errorMessage,
|
|
20262
|
+
mode: step.mode,
|
|
20263
|
+
docKinds: (step.docs ?? []).map((doc) => doc.kind)
|
|
20264
|
+
};
|
|
20265
|
+
}
|
|
20266
|
+
function collectDocKinds(testCase) {
|
|
20267
|
+
const kinds = /* @__PURE__ */ new Set();
|
|
20268
|
+
for (const doc of testCase.story.docs ?? []) {
|
|
20269
|
+
kinds.add(doc.kind);
|
|
20270
|
+
}
|
|
20271
|
+
for (const step of testCase.story.steps) {
|
|
20272
|
+
for (const doc of step.docs ?? []) {
|
|
20273
|
+
kinds.add(doc.kind);
|
|
20274
|
+
}
|
|
20275
|
+
}
|
|
20276
|
+
return [...kinds].sort();
|
|
20277
|
+
}
|
|
19759
20278
|
|
|
19760
20279
|
// src/review/conventions.ts
|
|
19761
20280
|
var CHANGE_TAG_PREFIX = "change:";
|
|
@@ -19803,18 +20322,18 @@ function deriveChangeType(tags) {
|
|
|
19803
20322
|
}
|
|
19804
20323
|
return "unknown";
|
|
19805
20324
|
}
|
|
19806
|
-
function extensionOf(
|
|
19807
|
-
const base =
|
|
20325
|
+
function extensionOf(path11) {
|
|
20326
|
+
const base = path11.split("/").pop() ?? path11;
|
|
19808
20327
|
const dot = base.lastIndexOf(".");
|
|
19809
20328
|
return dot === -1 ? "" : base.slice(dot + 1).toLowerCase();
|
|
19810
20329
|
}
|
|
19811
|
-
function isTestFile(
|
|
19812
|
-
return TEST_INFIX.test(
|
|
20330
|
+
function isTestFile(path11) {
|
|
20331
|
+
return TEST_INFIX.test(path11);
|
|
19813
20332
|
}
|
|
19814
|
-
function isReviewableSource(
|
|
19815
|
-
if (isTestFile(
|
|
19816
|
-
if (
|
|
19817
|
-
return CODE_EXTENSIONS.has(extensionOf(
|
|
20333
|
+
function isReviewableSource(path11) {
|
|
20334
|
+
if (isTestFile(path11)) return false;
|
|
20335
|
+
if (path11.endsWith(".d.ts")) return false;
|
|
20336
|
+
return CODE_EXTENSIONS.has(extensionOf(path11));
|
|
19818
20337
|
}
|
|
19819
20338
|
function testBaseKey(testFile) {
|
|
19820
20339
|
return testFile.replace(TEST_INFIX, "");
|
|
@@ -19918,7 +20437,7 @@ function toClaim(testCase, changedSourcePaths) {
|
|
|
19918
20437
|
const { strength, reasons } = gradeEvidence(testCase, audience);
|
|
19919
20438
|
const key = testBaseKey(testCase.sourceFile);
|
|
19920
20439
|
const coversFiles = changedSourcePaths.filter(
|
|
19921
|
-
(
|
|
20440
|
+
(path11) => sourceBaseKey(path11) === key
|
|
19922
20441
|
);
|
|
19923
20442
|
return {
|
|
19924
20443
|
id: testCase.id,
|
|
@@ -20451,6 +20970,7 @@ applyTheme(getEffectiveTheme());` : "";
|
|
|
20451
20970
|
// src/index.ts
|
|
20452
20971
|
var FORMAT_EXTENSIONS = {
|
|
20453
20972
|
astro: ".md",
|
|
20973
|
+
"behavior-manifest-json": ".behavior-manifest.json",
|
|
20454
20974
|
markdown: ".md",
|
|
20455
20975
|
html: ".html",
|
|
20456
20976
|
"cucumber-html": ".cucumber.html",
|
|
@@ -20458,8 +20978,13 @@ var FORMAT_EXTENSIONS = {
|
|
|
20458
20978
|
"cucumber-json": ".cucumber.json",
|
|
20459
20979
|
"cucumber-messages": ".ndjson",
|
|
20460
20980
|
confluence: ".adf.json",
|
|
20981
|
+
"scenario-index-json": ".scenarios-index.json",
|
|
20461
20982
|
"story-report-json": ".story-report.json"
|
|
20462
20983
|
};
|
|
20984
|
+
function joinNameAndExt(name, ext) {
|
|
20985
|
+
const stutter = `.${name}.`;
|
|
20986
|
+
return ext.startsWith(stutter) ? `${name}.${ext.slice(stutter.length)}` : `${name}${ext}`;
|
|
20987
|
+
}
|
|
20463
20988
|
var TEST_EXTENSIONS = [
|
|
20464
20989
|
".test.ts",
|
|
20465
20990
|
".test.tsx",
|
|
@@ -20485,11 +21010,11 @@ function computeOutputPath(sourceFile, format, mode, colocatedStyle, baseOutputD
|
|
|
20485
21010
|
const ext = FORMAT_EXTENSIONS[format];
|
|
20486
21011
|
const effectiveName = outputName + (outputNameSuffix ?? "");
|
|
20487
21012
|
if (mode === "aggregated") {
|
|
20488
|
-
return toPosix(
|
|
21013
|
+
return toPosix(path10.join(baseOutputDir, joinNameAndExt(effectiveName, ext)));
|
|
20489
21014
|
}
|
|
20490
21015
|
const normalizedSource = toPosix(sourceFile);
|
|
20491
|
-
const dirOfSource =
|
|
20492
|
-
let baseName =
|
|
21016
|
+
const dirOfSource = path10.posix.dirname(normalizedSource);
|
|
21017
|
+
let baseName = path10.posix.basename(normalizedSource);
|
|
20493
21018
|
for (const testExt of TEST_EXTENSIONS) {
|
|
20494
21019
|
if (baseName.endsWith(testExt)) {
|
|
20495
21020
|
baseName = baseName.slice(0, -testExt.length);
|
|
@@ -20498,9 +21023,12 @@ function computeOutputPath(sourceFile, format, mode, colocatedStyle, baseOutputD
|
|
|
20498
21023
|
}
|
|
20499
21024
|
const fileName = `${baseName}.${effectiveName}${ext}`;
|
|
20500
21025
|
if (colocatedStyle === "adjacent") {
|
|
20501
|
-
return toPosix(
|
|
21026
|
+
return toPosix(path10.posix.join(dirOfSource, fileName));
|
|
20502
21027
|
}
|
|
20503
|
-
|
|
21028
|
+
if (colocatedStyle === "flat") {
|
|
21029
|
+
return toPosix(path10.posix.join(baseOutputDir, `${cleanTestStem(normalizedSource)}${ext}`));
|
|
21030
|
+
}
|
|
21031
|
+
return toPosix(path10.posix.join(baseOutputDir, dirOfSource, fileName));
|
|
20504
21032
|
}
|
|
20505
21033
|
function groupTestCasesByOutput(testCases, format, options, logger, outputNameSuffix) {
|
|
20506
21034
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -20586,6 +21114,12 @@ var ReportGenerator = class {
|
|
|
20586
21114
|
storyReportJson: {
|
|
20587
21115
|
pretty: options.storyReportJson?.pretty ?? true
|
|
20588
21116
|
},
|
|
21117
|
+
scenarioIndexJson: {
|
|
21118
|
+
pretty: options.scenarioIndexJson?.pretty ?? true
|
|
21119
|
+
},
|
|
21120
|
+
behaviorManifestJson: {
|
|
21121
|
+
pretty: options.behaviorManifestJson?.pretty ?? true
|
|
21122
|
+
},
|
|
20589
21123
|
cucumberMessages: {
|
|
20590
21124
|
uriStrategy: options.cucumberMessages?.uriStrategy ?? "sourceFile",
|
|
20591
21125
|
includeSynthetics: options.cucumberMessages?.includeSynthetics ?? true,
|
|
@@ -20701,8 +21235,8 @@ var ReportGenerator = class {
|
|
|
20701
21235
|
if (astroPaths) {
|
|
20702
21236
|
for (const mdPath of astroPaths) {
|
|
20703
21237
|
const content = await fsPromises.readFile(mdPath, "utf8");
|
|
20704
|
-
const mdDir =
|
|
20705
|
-
const assetsDir =
|
|
21238
|
+
const mdDir = path10.dirname(mdPath);
|
|
21239
|
+
const assetsDir = path10.resolve(this.options.astro.assetsDir);
|
|
20706
21240
|
const result = copyMarkdownAssets({
|
|
20707
21241
|
markdown: content,
|
|
20708
21242
|
markdownDir: mdDir,
|
|
@@ -20733,9 +21267,9 @@ var ReportGenerator = class {
|
|
|
20733
21267
|
if (groups.size === 0 && this.options.output.mode === "aggregated") {
|
|
20734
21268
|
const ext = FORMAT_EXTENSIONS[format];
|
|
20735
21269
|
const effectiveName = this.options.outputName + (outputNameSuffix ?? "");
|
|
20736
|
-
const outputPath = toPosix(
|
|
21270
|
+
const outputPath = toPosix(path10.join(this.options.outputDir, joinNameAndExt(effectiveName, ext)));
|
|
20737
21271
|
const content = await this.formatContent(run, format);
|
|
20738
|
-
const dir =
|
|
21272
|
+
const dir = path10.dirname(outputPath);
|
|
20739
21273
|
await fsPromises.mkdir(dir, { recursive: true });
|
|
20740
21274
|
await this.deps.writeFile(outputPath, content);
|
|
20741
21275
|
return [outputPath];
|
|
@@ -20747,7 +21281,7 @@ var ReportGenerator = class {
|
|
|
20747
21281
|
testCases
|
|
20748
21282
|
};
|
|
20749
21283
|
const content = await this.formatContent(groupRun, format);
|
|
20750
|
-
const dir =
|
|
21284
|
+
const dir = path10.dirname(outputPath);
|
|
20751
21285
|
await fsPromises.mkdir(dir, { recursive: true });
|
|
20752
21286
|
await this.deps.writeFile(outputPath, content);
|
|
20753
21287
|
writtenPaths.push(outputPath);
|
|
@@ -20813,6 +21347,8 @@ var ReportGenerator = class {
|
|
|
20813
21347
|
case "astro": {
|
|
20814
21348
|
const formatter = new AstroFormatter({
|
|
20815
21349
|
assetsBaseUrl: this.options.astro.assetsBaseUrl,
|
|
21350
|
+
// Colocated = one page per file, so title each by its own suite/file.
|
|
21351
|
+
perFileTitle: this.options.output.mode === "colocated",
|
|
20816
21352
|
markdown: this.options.astro.markdown
|
|
20817
21353
|
});
|
|
20818
21354
|
return formatter.format(run);
|
|
@@ -20860,6 +21396,18 @@ var ReportGenerator = class {
|
|
|
20860
21396
|
});
|
|
20861
21397
|
return formatter.format(run);
|
|
20862
21398
|
}
|
|
21399
|
+
case "scenario-index-json": {
|
|
21400
|
+
const formatter = new ScenarioIndexJsonFormatter({
|
|
21401
|
+
pretty: this.options.scenarioIndexJson.pretty
|
|
21402
|
+
});
|
|
21403
|
+
return formatter.format(run);
|
|
21404
|
+
}
|
|
21405
|
+
case "behavior-manifest-json": {
|
|
21406
|
+
const formatter = new BehaviorManifestJsonFormatter({
|
|
21407
|
+
pretty: this.options.behaviorManifestJson.pretty
|
|
21408
|
+
});
|
|
21409
|
+
return formatter.format(run);
|
|
21410
|
+
}
|
|
20863
21411
|
default:
|
|
20864
21412
|
throw new Error(`Unknown format: ${format}`);
|
|
20865
21413
|
}
|
|
@@ -20876,7 +21424,7 @@ async function generateRunComparison(args) {
|
|
|
20876
21424
|
await fsPromises.mkdir(outputDir, { recursive: true });
|
|
20877
21425
|
for (const format of args.formats) {
|
|
20878
21426
|
const ext = format === "html" ? ".html" : ".md";
|
|
20879
|
-
const outputPath = toPosix(
|
|
21427
|
+
const outputPath = toPosix(path10.join(outputDir, `${outputName}${ext}`));
|
|
20880
21428
|
const content = format === "html" ? new RunDiffHtmlFormatter({ title: args.title }).format(diff) : new RunDiffMarkdownFormatter({ title: args.title }).format(diff);
|
|
20881
21429
|
await fsPromises.writeFile(outputPath, content, "utf8");
|
|
20882
21430
|
files.push(outputPath);
|
|
@@ -20898,6 +21446,7 @@ function normalizePlaywrightResults(testResults, adapterOptions, canonicalizeOpt
|
|
|
20898
21446
|
// Annotate the CommonJS export names for ESM import in node:
|
|
20899
21447
|
0 && (module.exports = {
|
|
20900
21448
|
AstroFormatter,
|
|
21449
|
+
BehaviorManifestJsonFormatter,
|
|
20901
21450
|
ConfluenceFormatter,
|
|
20902
21451
|
CucumberHtmlFormatter,
|
|
20903
21452
|
CucumberJsonFormatter,
|
|
@@ -20918,6 +21467,7 @@ function normalizePlaywrightResults(testResults, adapterOptions, canonicalizeOpt
|
|
|
20918
21467
|
STORY_META_KEY,
|
|
20919
21468
|
STORY_REPORT_SCHEMA_MAJOR,
|
|
20920
21469
|
STORY_REPORT_SCHEMA_VERSION,
|
|
21470
|
+
ScenarioIndexJsonFormatter,
|
|
20921
21471
|
StoryReportJsonFormatter,
|
|
20922
21472
|
adaptJestRun,
|
|
20923
21473
|
adaptPlaywrightRun,
|
|
@@ -20928,6 +21478,7 @@ function normalizePlaywrightResults(testResults, adapterOptions, canonicalizeOpt
|
|
|
20928
21478
|
calculateFlakiness,
|
|
20929
21479
|
calculateStability,
|
|
20930
21480
|
canonicalizeRun,
|
|
21481
|
+
classifyStatusChange,
|
|
20931
21482
|
clearVersionCache,
|
|
20932
21483
|
computeTestMetrics,
|
|
20933
21484
|
copyMarkdownAssets,
|
|
@@ -20939,6 +21490,7 @@ function normalizePlaywrightResults(testResults, adapterOptions, canonicalizeOpt
|
|
|
20939
21490
|
detectCI,
|
|
20940
21491
|
detectPerformanceTrend,
|
|
20941
21492
|
diffRuns,
|
|
21493
|
+
diffStoryReports,
|
|
20942
21494
|
findGitDir,
|
|
20943
21495
|
formatDuration,
|
|
20944
21496
|
generateRunComparison,
|
|
@@ -20950,6 +21502,7 @@ function normalizePlaywrightResults(testResults, adapterOptions, canonicalizeOpt
|
|
|
20950
21502
|
hasSufficientHistory,
|
|
20951
21503
|
isReviewableSource,
|
|
20952
21504
|
isTestFile,
|
|
21505
|
+
joinNameAndExt,
|
|
20953
21506
|
listScenarios,
|
|
20954
21507
|
loadHistory,
|
|
20955
21508
|
mergeStepResults,
|
|
@@ -20966,21 +21519,26 @@ function normalizePlaywrightResults(testResults, adapterOptions, canonicalizeOpt
|
|
|
20966
21519
|
readBranchName,
|
|
20967
21520
|
readGitSha,
|
|
20968
21521
|
readPackageVersion,
|
|
21522
|
+
regenerateArtifacts,
|
|
20969
21523
|
resolveAttachment,
|
|
20970
21524
|
resolveAttachments,
|
|
20971
21525
|
resolveTheme,
|
|
20972
21526
|
resolveTraceUrl,
|
|
20973
21527
|
rewriteAssetPaths,
|
|
20974
21528
|
saveHistory,
|
|
21529
|
+
scenariosCoveringPaths,
|
|
20975
21530
|
sendNotifications,
|
|
20976
21531
|
sendSlackNotification,
|
|
20977
21532
|
sendTeamsNotification,
|
|
20978
21533
|
sendWebhookNotification,
|
|
20979
21534
|
signBody,
|
|
20980
21535
|
slugify,
|
|
21536
|
+
startWatch,
|
|
20981
21537
|
stripAnsi,
|
|
21538
|
+
toBehaviorManifest,
|
|
20982
21539
|
toCIInfo,
|
|
20983
21540
|
toRawCIInfo,
|
|
21541
|
+
toScenarioIndex,
|
|
20984
21542
|
toStoryReport,
|
|
20985
21543
|
tryGetActiveOtelContext,
|
|
20986
21544
|
updateHistory,
|