executable-stories-formatters 0.10.0 → 0.11.1
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 +1667 -149
- package/dist/cli.js.map +1 -1
- package/dist/{index-CbWFyoTx.d.cts → index-DF16Xl5i.d.cts} +7 -0
- package/dist/{index-CbWFyoTx.d.ts → index-DF16Xl5i.d.ts} +7 -0
- package/dist/index.cjs +372 -45
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +129 -7
- package/dist/index.d.ts +129 -7
- package/dist/index.js +363 -42
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/schemas/raw-run.schema.json +19 -0
- package/schemas/story-report-v1.json +17 -0
package/dist/index.cjs
CHANGED
|
@@ -44,6 +44,7 @@ __export(src_exports, {
|
|
|
44
44
|
MIN_METRIC_SAMPLES: () => MIN_METRIC_SAMPLES,
|
|
45
45
|
MIN_PERF_SAMPLES: () => MIN_PERF_SAMPLES,
|
|
46
46
|
MarkdownFormatter: () => MarkdownFormatter,
|
|
47
|
+
ReleaseManifestFormatter: () => ReleaseManifestFormatter,
|
|
47
48
|
ReportGenerator: () => ReportGenerator,
|
|
48
49
|
ReviewHtmlFormatter: () => ReviewHtmlFormatter,
|
|
49
50
|
ReviewMarkdownFormatter: () => ReviewMarkdownFormatter,
|
|
@@ -83,10 +84,13 @@ __export(src_exports, {
|
|
|
83
84
|
generateTestCaseId: () => generateTestCaseId,
|
|
84
85
|
getAvailableThemes: () => getAvailableThemes,
|
|
85
86
|
getCssOnlyThemes: () => getCssOnlyThemes,
|
|
87
|
+
getDeploymentStatus: () => getDeploymentStatus,
|
|
88
|
+
getEnvironmentDrift: () => getEnvironmentDrift,
|
|
86
89
|
gradeEvidence: () => gradeEvidence,
|
|
87
90
|
hasSufficientHistory: () => hasSufficientHistory,
|
|
88
91
|
isReviewableSource: () => isReviewableSource,
|
|
89
92
|
isTestFile: () => isTestFile,
|
|
93
|
+
joinNameAndExt: () => joinNameAndExt,
|
|
90
94
|
listScenarios: () => listScenarios,
|
|
91
95
|
loadHistory: () => loadHistory,
|
|
92
96
|
mergeStepResults: () => mergeStepResults,
|
|
@@ -103,6 +107,7 @@ __export(src_exports, {
|
|
|
103
107
|
readBranchName: () => readBranchName,
|
|
104
108
|
readGitSha: () => readGitSha,
|
|
105
109
|
readPackageVersion: () => readPackageVersion,
|
|
110
|
+
recordDeployment: () => recordDeployment,
|
|
106
111
|
regenerateArtifacts: () => regenerateArtifacts,
|
|
107
112
|
resolveAttachment: () => resolveAttachment,
|
|
108
113
|
resolveAttachments: () => resolveAttachments,
|
|
@@ -122,6 +127,7 @@ __export(src_exports, {
|
|
|
122
127
|
toBehaviorManifest: () => toBehaviorManifest,
|
|
123
128
|
toCIInfo: () => toCIInfo,
|
|
124
129
|
toRawCIInfo: () => toRawCIInfo,
|
|
130
|
+
toReleaseManifest: () => toReleaseManifest,
|
|
125
131
|
toScenarioIndex: () => toScenarioIndex,
|
|
126
132
|
toStoryReport: () => toStoryReport,
|
|
127
133
|
tryGetActiveOtelContext: () => tryGetActiveOtelContext,
|
|
@@ -129,8 +135,8 @@ __export(src_exports, {
|
|
|
129
135
|
validateCanonicalRun: () => validateCanonicalRun
|
|
130
136
|
});
|
|
131
137
|
module.exports = __toCommonJS(src_exports);
|
|
132
|
-
var
|
|
133
|
-
var
|
|
138
|
+
var fs10 = require("fs");
|
|
139
|
+
var path11 = __toESM(require("path"), 1);
|
|
134
140
|
var fsPromises = __toESM(require("fs/promises"), 1);
|
|
135
141
|
|
|
136
142
|
// src/converters/acl/status.ts
|
|
@@ -893,6 +899,15 @@ function copyDocEntry(entry) {
|
|
|
893
899
|
phase: entry.phase,
|
|
894
900
|
...children
|
|
895
901
|
};
|
|
902
|
+
case "video":
|
|
903
|
+
return {
|
|
904
|
+
kind: "video",
|
|
905
|
+
path: entry.path,
|
|
906
|
+
...entry.caption ? { caption: entry.caption } : {},
|
|
907
|
+
...entry.poster ? { poster: entry.poster } : {},
|
|
908
|
+
phase: entry.phase,
|
|
909
|
+
...children
|
|
910
|
+
};
|
|
896
911
|
case "custom":
|
|
897
912
|
return {
|
|
898
913
|
kind: "custom",
|
|
@@ -1010,23 +1025,23 @@ function buildFeature(relSourceFile, group) {
|
|
|
1010
1025
|
function ensureUniqueFeatureIds(features) {
|
|
1011
1026
|
const seen = /* @__PURE__ */ new Map();
|
|
1012
1027
|
for (const f of features) {
|
|
1013
|
-
const
|
|
1014
|
-
if (
|
|
1015
|
-
seen.set(f.id,
|
|
1028
|
+
const count2 = seen.get(f.id) ?? 0;
|
|
1029
|
+
if (count2 > 0) f.id = `${f.id}-${count2 + 1}`;
|
|
1030
|
+
seen.set(f.id, count2 + 1);
|
|
1016
1031
|
}
|
|
1017
1032
|
}
|
|
1018
1033
|
function ensureUniqueScenarioIds(feature) {
|
|
1019
1034
|
const seen = /* @__PURE__ */ new Map();
|
|
1020
1035
|
for (const s of feature.scenarios) {
|
|
1021
|
-
const
|
|
1022
|
-
if (
|
|
1023
|
-
const newId = `${s.id}-${
|
|
1036
|
+
const count2 = seen.get(s.id) ?? 0;
|
|
1037
|
+
if (count2 > 0) {
|
|
1038
|
+
const newId = `${s.id}-${count2 + 1}`;
|
|
1024
1039
|
for (const step of s.steps) {
|
|
1025
1040
|
step.id = step.id.replace(s.id, newId);
|
|
1026
1041
|
}
|
|
1027
1042
|
s.id = newId;
|
|
1028
1043
|
}
|
|
1029
|
-
seen.set(s.id,
|
|
1044
|
+
seen.set(s.id, count2 + 1);
|
|
1030
1045
|
}
|
|
1031
1046
|
}
|
|
1032
1047
|
function toStoryReport(run) {
|
|
@@ -14054,6 +14069,23 @@ function renderDocScreenshot(entry, deps) {
|
|
|
14054
14069
|
${entry.alt ? `<div class="doc-screenshot-caption">${deps.escapeHtml(entry.alt)}</div>` : ""}
|
|
14055
14070
|
</div>`;
|
|
14056
14071
|
}
|
|
14072
|
+
function renderDocVideo(entry, deps) {
|
|
14073
|
+
const isRemote = /^(?:https?:|data:)/i.test(entry.path);
|
|
14074
|
+
const isAbsoluteFsPath = !isRemote && /^(?:[/\\]|[A-Za-z]:[/\\])/.test(entry.path);
|
|
14075
|
+
const captionHtml = entry.caption ? `<div class="doc-video-caption">${deps.escapeHtml(entry.caption)}</div>` : "";
|
|
14076
|
+
if ((deps.embedScreenshots ?? true) && isAbsoluteFsPath) {
|
|
14077
|
+
return `<div class="doc-video doc-video-missing">
|
|
14078
|
+
<div class="doc-video-missing-label">Video unavailable</div>
|
|
14079
|
+
<div class="doc-video-missing-path">${deps.escapeHtml(entry.path)}</div>
|
|
14080
|
+
${captionHtml}
|
|
14081
|
+
</div>`;
|
|
14082
|
+
}
|
|
14083
|
+
const poster = entry.poster ? ` poster="${deps.escapeHtml(entry.poster)}"` : "";
|
|
14084
|
+
return `<div class="doc-video">
|
|
14085
|
+
<video class="doc-video-player" controls preload="metadata"${poster} src="${deps.escapeHtml(entry.path)}"></video>
|
|
14086
|
+
${captionHtml}
|
|
14087
|
+
</div>`;
|
|
14088
|
+
}
|
|
14057
14089
|
function renderDocCustom(entry, deps) {
|
|
14058
14090
|
if (entry.type === "visual" && entry.data && typeof entry.data === "object") {
|
|
14059
14091
|
const data = entry.data;
|
|
@@ -14107,6 +14139,9 @@ function renderDocEntry(entry, deps) {
|
|
|
14107
14139
|
case "screenshot":
|
|
14108
14140
|
html = renderDocScreenshot(entry, deps);
|
|
14109
14141
|
break;
|
|
14142
|
+
case "video":
|
|
14143
|
+
html = renderDocVideo(entry, deps);
|
|
14144
|
+
break;
|
|
14110
14145
|
case "custom":
|
|
14111
14146
|
html = renderDocCustom(entry, deps);
|
|
14112
14147
|
break;
|
|
@@ -15433,6 +15468,19 @@ var MarkdownFormatter = class {
|
|
|
15433
15468
|
case "screenshot":
|
|
15434
15469
|
lines.push(`${indent}`);
|
|
15435
15470
|
break;
|
|
15471
|
+
case "video": {
|
|
15472
|
+
const poster = entry.poster ? ` poster="${entry.poster}"` : "";
|
|
15473
|
+
lines.push(`${indent}`);
|
|
15474
|
+
lines.push(`${indent}<video controls preload="metadata"${poster} class="doc-video">`);
|
|
15475
|
+
lines.push(`${indent} <source src="${entry.path}" />`);
|
|
15476
|
+
lines.push(`${indent}</video>`);
|
|
15477
|
+
if (entry.caption) {
|
|
15478
|
+
lines.push(`${indent}`);
|
|
15479
|
+
lines.push(`${indent}*${entry.caption}*`);
|
|
15480
|
+
}
|
|
15481
|
+
lines.push(`${indent}`);
|
|
15482
|
+
break;
|
|
15483
|
+
}
|
|
15436
15484
|
case "custom":
|
|
15437
15485
|
if (entry.type === "visual" && entry.data && typeof entry.data === "object") {
|
|
15438
15486
|
const data = entry.data;
|
|
@@ -15525,6 +15573,88 @@ function groupBy6(items, keyFn) {
|
|
|
15525
15573
|
return map;
|
|
15526
15574
|
}
|
|
15527
15575
|
|
|
15576
|
+
// src/formatters/release-manifest.ts
|
|
15577
|
+
var import_node_crypto2 = require("crypto");
|
|
15578
|
+
var ReleaseManifestFormatter = class {
|
|
15579
|
+
format(run) {
|
|
15580
|
+
const manifest = toReleaseManifest(run);
|
|
15581
|
+
const lines = [];
|
|
15582
|
+
lines.push("# Release Manifest");
|
|
15583
|
+
lines.push("");
|
|
15584
|
+
lines.push(`Generated: ${manifest.generatedAt}`);
|
|
15585
|
+
lines.push(`Run: ${manifest.run.startedAt} to ${manifest.run.finishedAt}`);
|
|
15586
|
+
if (manifest.run.branch) lines.push(`Branch: ${manifest.run.branch}`);
|
|
15587
|
+
if (manifest.run.gitSha) lines.push(`Commit: ${manifest.run.gitSha}`);
|
|
15588
|
+
lines.push(`Tested-together hash: \`${manifest.testedTogetherHash}\``);
|
|
15589
|
+
lines.push("");
|
|
15590
|
+
lines.push("| Scenarios | Passed | Failed | Skipped | Pending |");
|
|
15591
|
+
lines.push("| ---: | ---: | ---: | ---: | ---: |");
|
|
15592
|
+
lines.push(`| ${manifest.run.total} | ${manifest.run.passed} | ${manifest.run.failed} | ${manifest.run.skipped} | ${manifest.run.pending} |`);
|
|
15593
|
+
lines.push("");
|
|
15594
|
+
lines.push("## Scenarios");
|
|
15595
|
+
lines.push("");
|
|
15596
|
+
lines.push("| Status | Scenario | Source | Tags |");
|
|
15597
|
+
lines.push("| --- | --- | --- | --- |");
|
|
15598
|
+
for (const scenario of manifest.scenarios) {
|
|
15599
|
+
const source = `${scenario.sourceFile}:${scenario.sourceLine}`;
|
|
15600
|
+
const tags = scenario.tags.length > 0 ? scenario.tags.map((tag) => `\`${tag}\``).join(", ") : "";
|
|
15601
|
+
lines.push(`| ${renderStatus(scenario.status)} | ${escapePipe(scenario.title)} | \`${source}\` | ${tags} |`);
|
|
15602
|
+
}
|
|
15603
|
+
return lines.join("\n");
|
|
15604
|
+
}
|
|
15605
|
+
};
|
|
15606
|
+
function toReleaseManifest(run) {
|
|
15607
|
+
const scenarios = [...run.testCases].sort((a, b) => a.id.localeCompare(b.id)).map((tc) => ({
|
|
15608
|
+
id: tc.id,
|
|
15609
|
+
title: tc.story.scenario,
|
|
15610
|
+
status: tc.status,
|
|
15611
|
+
sourceFile: tc.sourceFile,
|
|
15612
|
+
sourceLine: tc.sourceLine,
|
|
15613
|
+
tags: tc.tags
|
|
15614
|
+
}));
|
|
15615
|
+
const fingerprint = scenarios.map((scenario) => `${scenario.id}:${scenario.status}`).join("\n");
|
|
15616
|
+
return {
|
|
15617
|
+
schemaVersion: "1.0",
|
|
15618
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15619
|
+
run: {
|
|
15620
|
+
startedAt: new Date(run.startedAtMs).toISOString(),
|
|
15621
|
+
finishedAt: new Date(run.finishedAtMs).toISOString(),
|
|
15622
|
+
gitSha: run.gitSha,
|
|
15623
|
+
branch: getBranch(run),
|
|
15624
|
+
total: run.testCases.length,
|
|
15625
|
+
passed: count(run.testCases, "passed"),
|
|
15626
|
+
failed: count(run.testCases, "failed"),
|
|
15627
|
+
skipped: count(run.testCases, "skipped"),
|
|
15628
|
+
pending: count(run.testCases, "pending")
|
|
15629
|
+
},
|
|
15630
|
+
testedTogetherHash: (0, import_node_crypto2.createHash)("sha256").update(fingerprint).digest("hex"),
|
|
15631
|
+
scenarios
|
|
15632
|
+
};
|
|
15633
|
+
}
|
|
15634
|
+
function getBranch(run) {
|
|
15635
|
+
return run.ci?.branch;
|
|
15636
|
+
}
|
|
15637
|
+
function count(testCases, status) {
|
|
15638
|
+
return testCases.filter((tc) => tc.status === status).length;
|
|
15639
|
+
}
|
|
15640
|
+
function renderStatus(status) {
|
|
15641
|
+
switch (status) {
|
|
15642
|
+
case "passed":
|
|
15643
|
+
return "passed";
|
|
15644
|
+
case "failed":
|
|
15645
|
+
return "failed";
|
|
15646
|
+
case "skipped":
|
|
15647
|
+
return "skipped";
|
|
15648
|
+
case "pending":
|
|
15649
|
+
return "pending";
|
|
15650
|
+
default:
|
|
15651
|
+
return status;
|
|
15652
|
+
}
|
|
15653
|
+
}
|
|
15654
|
+
function escapePipe(value) {
|
|
15655
|
+
return value.replace(/\|/g, "\\|");
|
|
15656
|
+
}
|
|
15657
|
+
|
|
15528
15658
|
// src/formatters/cucumber-messages/synthesize-feature.ts
|
|
15529
15659
|
function extractFeatureName(testCases, uri) {
|
|
15530
15660
|
for (const tc of testCases) {
|
|
@@ -15591,7 +15721,7 @@ function synthesizeFeature(uri, testCases) {
|
|
|
15591
15721
|
}
|
|
15592
15722
|
|
|
15593
15723
|
// src/utils/cucumber-messages.ts
|
|
15594
|
-
var
|
|
15724
|
+
var import_node_crypto3 = require("crypto");
|
|
15595
15725
|
function msToTimestamp(ms) {
|
|
15596
15726
|
const seconds = Math.floor(ms / 1e3);
|
|
15597
15727
|
const nanos = Math.round(ms % 1e3 * 1e6);
|
|
@@ -15657,7 +15787,7 @@ function statusToCucumberStatus(status) {
|
|
|
15657
15787
|
}
|
|
15658
15788
|
function deterministicId(kind, salt, ...parts) {
|
|
15659
15789
|
const input = [salt, kind, ...parts].join("::");
|
|
15660
|
-
return (0,
|
|
15790
|
+
return (0, import_node_crypto3.createHash)("sha1").update(input).digest("hex").slice(0, 36);
|
|
15661
15791
|
}
|
|
15662
15792
|
|
|
15663
15793
|
// src/formatters/cucumber-messages/build-gherkin-document.ts
|
|
@@ -16145,8 +16275,8 @@ function extractDocAttachments(step) {
|
|
|
16145
16275
|
}
|
|
16146
16276
|
return attachments;
|
|
16147
16277
|
}
|
|
16148
|
-
function guessMediaType(
|
|
16149
|
-
const lower =
|
|
16278
|
+
function guessMediaType(path12) {
|
|
16279
|
+
const lower = path12.toLowerCase();
|
|
16150
16280
|
if (lower.endsWith(".png")) return "image/png";
|
|
16151
16281
|
if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg";
|
|
16152
16282
|
if (lower.endsWith(".gif")) return "image/gif";
|
|
@@ -16287,11 +16417,11 @@ var CucumberHtmlFormatter = class {
|
|
|
16287
16417
|
for (const envelope of envelopes) {
|
|
16288
16418
|
const accepted = htmlStream.write(envelope);
|
|
16289
16419
|
if (!accepted) {
|
|
16290
|
-
await new Promise((
|
|
16420
|
+
await new Promise((resolve9) => htmlStream.once("drain", resolve9));
|
|
16291
16421
|
}
|
|
16292
16422
|
}
|
|
16293
|
-
await new Promise((
|
|
16294
|
-
collector.on("finish",
|
|
16423
|
+
await new Promise((resolve9, reject) => {
|
|
16424
|
+
collector.on("finish", resolve9);
|
|
16295
16425
|
collector.on("error", reject);
|
|
16296
16426
|
htmlStream.end();
|
|
16297
16427
|
});
|
|
@@ -16592,6 +16722,8 @@ function formatDocEntry(doc) {
|
|
|
16592
16722
|
return `${escapeHtml2(doc.title ?? "mermaid diagram")}: <code>${escapeHtml2(doc.code)}</code>`;
|
|
16593
16723
|
case "screenshot":
|
|
16594
16724
|
return `${doc.alt ? `${escapeHtml2(doc.alt)}: ` : ""}${escapeHtml2(doc.path)}`;
|
|
16725
|
+
case "video":
|
|
16726
|
+
return `${doc.caption ? `${escapeHtml2(doc.caption)}: ` : ""}${escapeHtml2(doc.path)}`;
|
|
16595
16727
|
case "custom":
|
|
16596
16728
|
return `${escapeHtml2(doc.type)}: ${escapeHtml2(JSON.stringify(doc.data))}`;
|
|
16597
16729
|
}
|
|
@@ -17048,6 +17180,8 @@ function formatDocEntry2(doc) {
|
|
|
17048
17180
|
return `${doc.title ?? "mermaid diagram"}: \`${doc.code}\``;
|
|
17049
17181
|
case "screenshot":
|
|
17050
17182
|
return `${doc.alt ? `${doc.alt}: ` : ""}${doc.path}`;
|
|
17183
|
+
case "video":
|
|
17184
|
+
return `${doc.caption ? `${doc.caption}: ` : ""}${doc.path}`;
|
|
17051
17185
|
case "custom":
|
|
17052
17186
|
return `${doc.type}: ${JSON.stringify(doc.data)}`;
|
|
17053
17187
|
}
|
|
@@ -17293,19 +17427,35 @@ function replaceAssetRef(html, original, replacement) {
|
|
|
17293
17427
|
return html;
|
|
17294
17428
|
}
|
|
17295
17429
|
|
|
17430
|
+
// src/utils/source-file.ts
|
|
17431
|
+
function cleanTestStem(fileName) {
|
|
17432
|
+
const base = fileName.split(/[\\/]/).pop() ?? fileName;
|
|
17433
|
+
const stripped = base.replace(/\.(story\.)?(test|spec|cy)\.[cm]?[jt]sx?$/i, "");
|
|
17434
|
+
if (stripped !== base) return stripped;
|
|
17435
|
+
return base.replace(/\.[^.]+$/, "");
|
|
17436
|
+
}
|
|
17437
|
+
function humanizeSourceFile(fileName) {
|
|
17438
|
+
return cleanTestStem(fileName).split(/[-_.\s]+/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
17439
|
+
}
|
|
17440
|
+
|
|
17296
17441
|
// src/formatters/astro.ts
|
|
17297
17442
|
var AstroFormatter = class _AstroFormatter {
|
|
17298
17443
|
markdownFormatter;
|
|
17299
17444
|
title;
|
|
17445
|
+
perFileTitle;
|
|
17300
17446
|
constructor(options = {}) {
|
|
17301
17447
|
this.title = options.markdown?.title ?? "User Stories";
|
|
17448
|
+
this.perFileTitle = options.perFileTitle ?? false;
|
|
17302
17449
|
this.markdownFormatter = new MarkdownFormatter({
|
|
17303
17450
|
...options.markdown,
|
|
17304
17451
|
title: this.title,
|
|
17305
17452
|
stepStyle: "gherkin",
|
|
17306
17453
|
includeFrontMatter: false,
|
|
17307
17454
|
includeSummaryTable: false,
|
|
17308
|
-
includeMetadata: false
|
|
17455
|
+
includeMetadata: false,
|
|
17456
|
+
// A per-file page is one file already — group by suite/describe so the
|
|
17457
|
+
// body shows clean section headings, not the redundant source path.
|
|
17458
|
+
groupBy: this.perFileTitle ? "suite" : options.markdown?.groupBy ?? "file"
|
|
17309
17459
|
});
|
|
17310
17460
|
}
|
|
17311
17461
|
format(run) {
|
|
@@ -17315,13 +17465,31 @@ var AstroFormatter = class _AstroFormatter {
|
|
|
17315
17465
|
return `${frontmatter}
|
|
17316
17466
|
${body}`;
|
|
17317
17467
|
}
|
|
17468
|
+
/**
|
|
17469
|
+
* Title for the page. A per-file page (one source file — i.e. colocated mode)
|
|
17470
|
+
* is titled by its suite/describe name, falling back to a humanized filename,
|
|
17471
|
+
* so the docs nav reads "Convert Currency" not "User Stories" six times over.
|
|
17472
|
+
* Multi-file (aggregated) pages keep the configured title.
|
|
17473
|
+
*/
|
|
17474
|
+
deriveTitle(run) {
|
|
17475
|
+
if (!this.perFileTitle) return this.title;
|
|
17476
|
+
const sourceFiles = new Set(
|
|
17477
|
+
run.testCases.map((tc) => tc.sourceFile).filter((f) => f && f !== "unknown")
|
|
17478
|
+
);
|
|
17479
|
+
if (sourceFiles.size !== 1) return this.title;
|
|
17480
|
+
const suites = new Set(
|
|
17481
|
+
run.testCases.map((tc) => tc.titlePath?.[0]).filter((s) => Boolean(s))
|
|
17482
|
+
);
|
|
17483
|
+
if (suites.size === 1) return [...suites][0];
|
|
17484
|
+
return humanizeSourceFile([...sourceFiles][0]) || this.title;
|
|
17485
|
+
}
|
|
17318
17486
|
buildFrontmatter(run) {
|
|
17319
17487
|
const badge = _AstroFormatter.computeBadge(run.testCases);
|
|
17320
|
-
const
|
|
17321
|
-
const description = `${
|
|
17488
|
+
const count2 = run.testCases.length;
|
|
17489
|
+
const description = `${count2} scenario${count2 !== 1 ? "s" : ""} \u2014 ${badge.text.toLowerCase()}`;
|
|
17322
17490
|
const lines = [
|
|
17323
17491
|
"---",
|
|
17324
|
-
`title: ${this.
|
|
17492
|
+
`title: ${yamlScalar(this.deriveTitle(run))}`,
|
|
17325
17493
|
`description: ${description}`,
|
|
17326
17494
|
"sidebar:",
|
|
17327
17495
|
" badge:",
|
|
@@ -17339,6 +17507,12 @@ ${body}`;
|
|
|
17339
17507
|
return { text: "Passed", variant: "success" };
|
|
17340
17508
|
}
|
|
17341
17509
|
};
|
|
17510
|
+
function yamlScalar(value) {
|
|
17511
|
+
if (/[:#[\]{}&*!|>'"%@`]|^[\s-]|\s$/.test(value)) {
|
|
17512
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
17513
|
+
}
|
|
17514
|
+
return value;
|
|
17515
|
+
}
|
|
17342
17516
|
|
|
17343
17517
|
// src/formatters/confluence.ts
|
|
17344
17518
|
var ConfluenceFormatter = class {
|
|
@@ -17615,6 +17789,15 @@ ${tc.errorStack}` : "");
|
|
|
17615
17789
|
])
|
|
17616
17790
|
);
|
|
17617
17791
|
break;
|
|
17792
|
+
case "video":
|
|
17793
|
+
content.push(
|
|
17794
|
+
paragraph([
|
|
17795
|
+
text(entry.caption ?? "Video", strong()),
|
|
17796
|
+
text(": "),
|
|
17797
|
+
link(entry.path, entry.path)
|
|
17798
|
+
])
|
|
17799
|
+
);
|
|
17800
|
+
break;
|
|
17618
17801
|
case "custom":
|
|
17619
17802
|
content.push(paragraph([text(`[${entry.type}]`, strong())]));
|
|
17620
17803
|
content.push(codeBlock(JSON.stringify(entry.data ?? null, null, 2), "json"));
|
|
@@ -17784,6 +17967,13 @@ function scanMarkdownAssets(markdown) {
|
|
|
17784
17967
|
found.add(src);
|
|
17785
17968
|
}
|
|
17786
17969
|
}
|
|
17970
|
+
const posterRe = /<video[^>]+\bposter=["']([^"']+)["'][^>]*>/gi;
|
|
17971
|
+
while ((match = posterRe.exec(stripped)) !== null) {
|
|
17972
|
+
const src = match[1].trim();
|
|
17973
|
+
if (isLocalPath(src)) {
|
|
17974
|
+
found.add(src);
|
|
17975
|
+
}
|
|
17976
|
+
}
|
|
17787
17977
|
return Array.from(found);
|
|
17788
17978
|
}
|
|
17789
17979
|
function splitByCode(markdown) {
|
|
@@ -17834,6 +18024,19 @@ function rewriteProseSegment(prose, assetsBaseUrl, pathMap) {
|
|
|
17834
18024
|
return `${pre}${assetsBaseUrl}/${trimmed}${post}`;
|
|
17835
18025
|
}
|
|
17836
18026
|
);
|
|
18027
|
+
result = result.replace(
|
|
18028
|
+
/(<video[^>]+\bposter=["'])([^"']+)(["'][^>]*>)/gi,
|
|
18029
|
+
(full, pre, src, post) => {
|
|
18030
|
+
const trimmed = src.trim();
|
|
18031
|
+
if (!isLocalPath(trimmed)) return full;
|
|
18032
|
+
if (pathMap) {
|
|
18033
|
+
const mapped = pathMap.get(trimmed);
|
|
18034
|
+
if (mapped === void 0) return full;
|
|
18035
|
+
return `${pre}${assetsBaseUrl}/${mapped}${post}`;
|
|
18036
|
+
}
|
|
18037
|
+
return `${pre}${assetsBaseUrl}/${trimmed}${post}`;
|
|
18038
|
+
}
|
|
18039
|
+
);
|
|
17837
18040
|
return result;
|
|
17838
18041
|
}
|
|
17839
18042
|
function rewriteAssetPaths(markdown, assetsBaseUrl, pathMap) {
|
|
@@ -18281,14 +18484,14 @@ ${result.errors.join("\n")}`);
|
|
|
18281
18484
|
}
|
|
18282
18485
|
|
|
18283
18486
|
// src/coverage-index.ts
|
|
18284
|
-
function normalizePath(
|
|
18285
|
-
return
|
|
18487
|
+
function normalizePath(path12) {
|
|
18488
|
+
return path12.replace(/^\.\//, "");
|
|
18286
18489
|
}
|
|
18287
18490
|
function scenariosCoveringPaths(index, paths) {
|
|
18288
18491
|
const queries = paths.map(normalizePath);
|
|
18289
18492
|
return index.scenarios.filter(
|
|
18290
18493
|
(scenario) => scenario.covers.some(
|
|
18291
|
-
(glob) => queries.some((
|
|
18494
|
+
(glob) => queries.some((path12) => matchesPattern(normalizePath(glob), path12))
|
|
18292
18495
|
)
|
|
18293
18496
|
);
|
|
18294
18497
|
}
|
|
@@ -19600,7 +19803,7 @@ async function sendTeamsNotification(args, deps) {
|
|
|
19600
19803
|
}
|
|
19601
19804
|
|
|
19602
19805
|
// src/notifiers/hmac.ts
|
|
19603
|
-
var
|
|
19806
|
+
var import_node_crypto4 = require("crypto");
|
|
19604
19807
|
function signBody(args) {
|
|
19605
19808
|
let input;
|
|
19606
19809
|
let timestamp;
|
|
@@ -19610,7 +19813,7 @@ function signBody(args) {
|
|
|
19610
19813
|
} else {
|
|
19611
19814
|
input = args.body;
|
|
19612
19815
|
}
|
|
19613
|
-
const hex = (0,
|
|
19816
|
+
const hex = (0, import_node_crypto4.createHmac)("sha256", args.secret).update(input, "utf8").digest("hex");
|
|
19614
19817
|
return {
|
|
19615
19818
|
signature: `sha256=${hex}`,
|
|
19616
19819
|
timestamp
|
|
@@ -20206,18 +20409,18 @@ function deriveChangeType(tags) {
|
|
|
20206
20409
|
}
|
|
20207
20410
|
return "unknown";
|
|
20208
20411
|
}
|
|
20209
|
-
function extensionOf(
|
|
20210
|
-
const base =
|
|
20412
|
+
function extensionOf(path12) {
|
|
20413
|
+
const base = path12.split("/").pop() ?? path12;
|
|
20211
20414
|
const dot = base.lastIndexOf(".");
|
|
20212
20415
|
return dot === -1 ? "" : base.slice(dot + 1).toLowerCase();
|
|
20213
20416
|
}
|
|
20214
|
-
function isTestFile(
|
|
20215
|
-
return TEST_INFIX.test(
|
|
20417
|
+
function isTestFile(path12) {
|
|
20418
|
+
return TEST_INFIX.test(path12);
|
|
20216
20419
|
}
|
|
20217
|
-
function isReviewableSource(
|
|
20218
|
-
if (isTestFile(
|
|
20219
|
-
if (
|
|
20220
|
-
return CODE_EXTENSIONS.has(extensionOf(
|
|
20420
|
+
function isReviewableSource(path12) {
|
|
20421
|
+
if (isTestFile(path12)) return false;
|
|
20422
|
+
if (path12.endsWith(".d.ts")) return false;
|
|
20423
|
+
return CODE_EXTENSIONS.has(extensionOf(path12));
|
|
20221
20424
|
}
|
|
20222
20425
|
function testBaseKey(testFile) {
|
|
20223
20426
|
return testFile.replace(TEST_INFIX, "");
|
|
@@ -20321,7 +20524,7 @@ function toClaim(testCase, changedSourcePaths) {
|
|
|
20321
20524
|
const { strength, reasons } = gradeEvidence(testCase, audience);
|
|
20322
20525
|
const key = testBaseKey(testCase.sourceFile);
|
|
20323
20526
|
const coversFiles = changedSourcePaths.filter(
|
|
20324
|
-
(
|
|
20527
|
+
(path12) => sourceBaseKey(path12) === key
|
|
20325
20528
|
);
|
|
20326
20529
|
return {
|
|
20327
20530
|
id: testCase.id,
|
|
@@ -20851,11 +21054,116 @@ applyTheme(getEffectiveTheme());` : "";
|
|
|
20851
21054
|
}
|
|
20852
21055
|
};
|
|
20853
21056
|
|
|
21057
|
+
// src/deploy/ledger.ts
|
|
21058
|
+
var fs9 = __toESM(require("fs"), 1);
|
|
21059
|
+
var path10 = __toESM(require("path"), 1);
|
|
21060
|
+
function createEmptyLedger() {
|
|
21061
|
+
return {
|
|
21062
|
+
deployments: [],
|
|
21063
|
+
schemaVersion: 1
|
|
21064
|
+
};
|
|
21065
|
+
}
|
|
21066
|
+
function loadLedger(ledgerPath) {
|
|
21067
|
+
const resolved = path10.resolve(ledgerPath);
|
|
21068
|
+
if (!fs9.existsSync(resolved)) {
|
|
21069
|
+
return createEmptyLedger();
|
|
21070
|
+
}
|
|
21071
|
+
try {
|
|
21072
|
+
const raw = JSON.parse(fs9.readFileSync(resolved, "utf8"));
|
|
21073
|
+
if (raw.schemaVersion !== 1) {
|
|
21074
|
+
throw new Error(`Unsupported ledger schemaVersion: ${raw.schemaVersion}`);
|
|
21075
|
+
}
|
|
21076
|
+
return raw;
|
|
21077
|
+
} catch (err) {
|
|
21078
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
21079
|
+
throw new Error(`Failed to load deployment ledger at ${resolved}: ${msg}`);
|
|
21080
|
+
}
|
|
21081
|
+
}
|
|
21082
|
+
function saveLedger(ledger, ledgerPath) {
|
|
21083
|
+
const resolved = path10.resolve(ledgerPath);
|
|
21084
|
+
const dir = path10.dirname(resolved);
|
|
21085
|
+
fs9.mkdirSync(dir, { recursive: true });
|
|
21086
|
+
fs9.writeFileSync(resolved, JSON.stringify(ledger, null, 2), "utf8");
|
|
21087
|
+
}
|
|
21088
|
+
function getLatestDeployment(ledger, environment) {
|
|
21089
|
+
return [...ledger.deployments].reverse().find((d) => d.environment === environment);
|
|
21090
|
+
}
|
|
21091
|
+
|
|
21092
|
+
// src/deploy/index.ts
|
|
21093
|
+
function recordDeployment(args) {
|
|
21094
|
+
const ledger = loadLedger(args.ledgerPath);
|
|
21095
|
+
const previous = getLatestDeployment(ledger, args.environment);
|
|
21096
|
+
const entry = {
|
|
21097
|
+
environment: args.environment,
|
|
21098
|
+
tag: args.tag,
|
|
21099
|
+
sha: args.run.gitSha,
|
|
21100
|
+
runFile: args.runFilePath,
|
|
21101
|
+
scenarioIds: args.run.testCases.map((tc) => tc.id),
|
|
21102
|
+
scenarioStatuses: Object.fromEntries(args.run.testCases.map((tc) => [tc.id, tc.status])),
|
|
21103
|
+
timestamp: new Date(args.run.finishedAtMs).toISOString(),
|
|
21104
|
+
summary: countStatuses(args.run)
|
|
21105
|
+
};
|
|
21106
|
+
ledger.deployments.push(entry);
|
|
21107
|
+
if (previous) {
|
|
21108
|
+
const previousIds = new Set(previous.scenarioIds);
|
|
21109
|
+
const added = entry.scenarioIds.filter((id) => !previousIds.has(id)).length;
|
|
21110
|
+
const removed = previous.scenarioIds.filter((id) => !entry.scenarioIds.includes(id)).length;
|
|
21111
|
+
if (added > 0 || removed > 0) {
|
|
21112
|
+
}
|
|
21113
|
+
}
|
|
21114
|
+
saveLedger(ledger, args.ledgerPath);
|
|
21115
|
+
return { entry, ledgerPath: args.ledgerPath };
|
|
21116
|
+
}
|
|
21117
|
+
function getDeploymentStatus(ledgerPath) {
|
|
21118
|
+
const ledger = loadLedger(ledgerPath);
|
|
21119
|
+
const environments = {};
|
|
21120
|
+
for (const entry of ledger.deployments) {
|
|
21121
|
+
environments[entry.environment] = {
|
|
21122
|
+
latest: entry,
|
|
21123
|
+
previousDeployment: environments[entry.environment]?.latest
|
|
21124
|
+
};
|
|
21125
|
+
}
|
|
21126
|
+
return { environments, ledgerPath };
|
|
21127
|
+
}
|
|
21128
|
+
function getEnvironmentDrift(ledgerPath, envA, envB) {
|
|
21129
|
+
const ledger = loadLedger(ledgerPath);
|
|
21130
|
+
const aEntry = getLatestDeployment(ledger, envA);
|
|
21131
|
+
const bEntry = getLatestDeployment(ledger, envB);
|
|
21132
|
+
if (!aEntry) {
|
|
21133
|
+
throw new Error(`No deployment found for environment "${envA}"`);
|
|
21134
|
+
}
|
|
21135
|
+
if (!bEntry) {
|
|
21136
|
+
throw new Error(`No deployment found for environment "${envB}"`);
|
|
21137
|
+
}
|
|
21138
|
+
const aIds = new Set(aEntry.scenarioIds);
|
|
21139
|
+
const bIds = new Set(bEntry.scenarioIds);
|
|
21140
|
+
const onlyInA = aEntry.scenarioIds.filter((id) => !bIds.has(id));
|
|
21141
|
+
const onlyInB = bEntry.scenarioIds.filter((id) => !aIds.has(id));
|
|
21142
|
+
const inBoth = aEntry.scenarioIds.filter((id) => bIds.has(id));
|
|
21143
|
+
const statusChanged = inBoth.map((id) => ({
|
|
21144
|
+
id,
|
|
21145
|
+
statusA: aEntry.scenarioStatuses?.[id] ?? "unknown",
|
|
21146
|
+
statusB: bEntry.scenarioStatuses?.[id] ?? "unknown"
|
|
21147
|
+
})).filter((item) => item.statusA !== item.statusB);
|
|
21148
|
+
return { environmentA: envA, environmentB: envB, onlyInA, onlyInB, inBoth, statusChanged, aEntry, bEntry };
|
|
21149
|
+
}
|
|
21150
|
+
function countStatuses(run) {
|
|
21151
|
+
const summary = { total: 0, passed: 0, failed: 0, skipped: 0, pending: 0 };
|
|
21152
|
+
for (const tc of run.testCases) {
|
|
21153
|
+
summary.total++;
|
|
21154
|
+
if (tc.status in summary) {
|
|
21155
|
+
summary[tc.status]++;
|
|
21156
|
+
}
|
|
21157
|
+
}
|
|
21158
|
+
return summary;
|
|
21159
|
+
}
|
|
21160
|
+
|
|
20854
21161
|
// src/index.ts
|
|
20855
21162
|
var FORMAT_EXTENSIONS = {
|
|
20856
21163
|
astro: ".md",
|
|
20857
21164
|
"behavior-manifest-json": ".behavior-manifest.json",
|
|
20858
21165
|
markdown: ".md",
|
|
21166
|
+
"release-manifest": ".release-manifest.md",
|
|
20859
21167
|
html: ".html",
|
|
20860
21168
|
"cucumber-html": ".cucumber.html",
|
|
20861
21169
|
junit: ".junit.xml",
|
|
@@ -20865,6 +21173,10 @@ var FORMAT_EXTENSIONS = {
|
|
|
20865
21173
|
"scenario-index-json": ".scenarios-index.json",
|
|
20866
21174
|
"story-report-json": ".story-report.json"
|
|
20867
21175
|
};
|
|
21176
|
+
function joinNameAndExt(name, ext) {
|
|
21177
|
+
const stutter = `.${name}.`;
|
|
21178
|
+
return ext.startsWith(stutter) ? `${name}.${ext.slice(stutter.length)}` : `${name}${ext}`;
|
|
21179
|
+
}
|
|
20868
21180
|
var TEST_EXTENSIONS = [
|
|
20869
21181
|
".test.ts",
|
|
20870
21182
|
".test.tsx",
|
|
@@ -20890,11 +21202,11 @@ function computeOutputPath(sourceFile, format, mode, colocatedStyle, baseOutputD
|
|
|
20890
21202
|
const ext = FORMAT_EXTENSIONS[format];
|
|
20891
21203
|
const effectiveName = outputName + (outputNameSuffix ?? "");
|
|
20892
21204
|
if (mode === "aggregated") {
|
|
20893
|
-
return toPosix(
|
|
21205
|
+
return toPosix(path11.join(baseOutputDir, joinNameAndExt(effectiveName, ext)));
|
|
20894
21206
|
}
|
|
20895
21207
|
const normalizedSource = toPosix(sourceFile);
|
|
20896
|
-
const dirOfSource =
|
|
20897
|
-
let baseName =
|
|
21208
|
+
const dirOfSource = path11.posix.dirname(normalizedSource);
|
|
21209
|
+
let baseName = path11.posix.basename(normalizedSource);
|
|
20898
21210
|
for (const testExt of TEST_EXTENSIONS) {
|
|
20899
21211
|
if (baseName.endsWith(testExt)) {
|
|
20900
21212
|
baseName = baseName.slice(0, -testExt.length);
|
|
@@ -20903,9 +21215,12 @@ function computeOutputPath(sourceFile, format, mode, colocatedStyle, baseOutputD
|
|
|
20903
21215
|
}
|
|
20904
21216
|
const fileName = `${baseName}.${effectiveName}${ext}`;
|
|
20905
21217
|
if (colocatedStyle === "adjacent") {
|
|
20906
|
-
return toPosix(
|
|
21218
|
+
return toPosix(path11.posix.join(dirOfSource, fileName));
|
|
20907
21219
|
}
|
|
20908
|
-
|
|
21220
|
+
if (colocatedStyle === "flat") {
|
|
21221
|
+
return toPosix(path11.posix.join(baseOutputDir, `${cleanTestStem(normalizedSource)}${ext}`));
|
|
21222
|
+
}
|
|
21223
|
+
return toPosix(path11.posix.join(baseOutputDir, dirOfSource, fileName));
|
|
20909
21224
|
}
|
|
20910
21225
|
function groupTestCasesByOutput(testCases, format, options, logger, outputNameSuffix) {
|
|
20911
21226
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -21112,8 +21427,8 @@ var ReportGenerator = class {
|
|
|
21112
21427
|
if (astroPaths) {
|
|
21113
21428
|
for (const mdPath of astroPaths) {
|
|
21114
21429
|
const content = await fsPromises.readFile(mdPath, "utf8");
|
|
21115
|
-
const mdDir =
|
|
21116
|
-
const assetsDir =
|
|
21430
|
+
const mdDir = path11.dirname(mdPath);
|
|
21431
|
+
const assetsDir = path11.resolve(this.options.astro.assetsDir);
|
|
21117
21432
|
const result = copyMarkdownAssets({
|
|
21118
21433
|
markdown: content,
|
|
21119
21434
|
markdownDir: mdDir,
|
|
@@ -21144,9 +21459,9 @@ var ReportGenerator = class {
|
|
|
21144
21459
|
if (groups.size === 0 && this.options.output.mode === "aggregated") {
|
|
21145
21460
|
const ext = FORMAT_EXTENSIONS[format];
|
|
21146
21461
|
const effectiveName = this.options.outputName + (outputNameSuffix ?? "");
|
|
21147
|
-
const outputPath = toPosix(
|
|
21462
|
+
const outputPath = toPosix(path11.join(this.options.outputDir, joinNameAndExt(effectiveName, ext)));
|
|
21148
21463
|
const content = await this.formatContent(run, format);
|
|
21149
|
-
const dir =
|
|
21464
|
+
const dir = path11.dirname(outputPath);
|
|
21150
21465
|
await fsPromises.mkdir(dir, { recursive: true });
|
|
21151
21466
|
await this.deps.writeFile(outputPath, content);
|
|
21152
21467
|
return [outputPath];
|
|
@@ -21158,7 +21473,7 @@ var ReportGenerator = class {
|
|
|
21158
21473
|
testCases
|
|
21159
21474
|
};
|
|
21160
21475
|
const content = await this.formatContent(groupRun, format);
|
|
21161
|
-
const dir =
|
|
21476
|
+
const dir = path11.dirname(outputPath);
|
|
21162
21477
|
await fsPromises.mkdir(dir, { recursive: true });
|
|
21163
21478
|
await this.deps.writeFile(outputPath, content);
|
|
21164
21479
|
writtenPaths.push(outputPath);
|
|
@@ -21224,6 +21539,8 @@ var ReportGenerator = class {
|
|
|
21224
21539
|
case "astro": {
|
|
21225
21540
|
const formatter = new AstroFormatter({
|
|
21226
21541
|
assetsBaseUrl: this.options.astro.assetsBaseUrl,
|
|
21542
|
+
// Colocated = one page per file, so title each by its own suite/file.
|
|
21543
|
+
perFileTitle: this.options.output.mode === "colocated",
|
|
21227
21544
|
markdown: this.options.astro.markdown
|
|
21228
21545
|
});
|
|
21229
21546
|
return formatter.format(run);
|
|
@@ -21265,6 +21582,10 @@ var ReportGenerator = class {
|
|
|
21265
21582
|
});
|
|
21266
21583
|
return formatter.format(run);
|
|
21267
21584
|
}
|
|
21585
|
+
case "release-manifest": {
|
|
21586
|
+
const formatter = new ReleaseManifestFormatter();
|
|
21587
|
+
return formatter.format(run);
|
|
21588
|
+
}
|
|
21268
21589
|
case "story-report-json": {
|
|
21269
21590
|
const formatter = new StoryReportJsonFormatter({
|
|
21270
21591
|
pretty: this.options.storyReportJson.pretty
|
|
@@ -21299,7 +21620,7 @@ async function generateRunComparison(args) {
|
|
|
21299
21620
|
await fsPromises.mkdir(outputDir, { recursive: true });
|
|
21300
21621
|
for (const format of args.formats) {
|
|
21301
21622
|
const ext = format === "html" ? ".html" : ".md";
|
|
21302
|
-
const outputPath = toPosix(
|
|
21623
|
+
const outputPath = toPosix(path11.join(outputDir, `${outputName}${ext}`));
|
|
21303
21624
|
const content = format === "html" ? new RunDiffHtmlFormatter({ title: args.title }).format(diff) : new RunDiffMarkdownFormatter({ title: args.title }).format(diff);
|
|
21304
21625
|
await fsPromises.writeFile(outputPath, content, "utf8");
|
|
21305
21626
|
files.push(outputPath);
|
|
@@ -21334,6 +21655,7 @@ function normalizePlaywrightResults(testResults, adapterOptions, canonicalizeOpt
|
|
|
21334
21655
|
MIN_METRIC_SAMPLES,
|
|
21335
21656
|
MIN_PERF_SAMPLES,
|
|
21336
21657
|
MarkdownFormatter,
|
|
21658
|
+
ReleaseManifestFormatter,
|
|
21337
21659
|
ReportGenerator,
|
|
21338
21660
|
ReviewHtmlFormatter,
|
|
21339
21661
|
ReviewMarkdownFormatter,
|
|
@@ -21373,10 +21695,13 @@ function normalizePlaywrightResults(testResults, adapterOptions, canonicalizeOpt
|
|
|
21373
21695
|
generateTestCaseId,
|
|
21374
21696
|
getAvailableThemes,
|
|
21375
21697
|
getCssOnlyThemes,
|
|
21698
|
+
getDeploymentStatus,
|
|
21699
|
+
getEnvironmentDrift,
|
|
21376
21700
|
gradeEvidence,
|
|
21377
21701
|
hasSufficientHistory,
|
|
21378
21702
|
isReviewableSource,
|
|
21379
21703
|
isTestFile,
|
|
21704
|
+
joinNameAndExt,
|
|
21380
21705
|
listScenarios,
|
|
21381
21706
|
loadHistory,
|
|
21382
21707
|
mergeStepResults,
|
|
@@ -21393,6 +21718,7 @@ function normalizePlaywrightResults(testResults, adapterOptions, canonicalizeOpt
|
|
|
21393
21718
|
readBranchName,
|
|
21394
21719
|
readGitSha,
|
|
21395
21720
|
readPackageVersion,
|
|
21721
|
+
recordDeployment,
|
|
21396
21722
|
regenerateArtifacts,
|
|
21397
21723
|
resolveAttachment,
|
|
21398
21724
|
resolveAttachments,
|
|
@@ -21412,6 +21738,7 @@ function normalizePlaywrightResults(testResults, adapterOptions, canonicalizeOpt
|
|
|
21412
21738
|
toBehaviorManifest,
|
|
21413
21739
|
toCIInfo,
|
|
21414
21740
|
toRawCIInfo,
|
|
21741
|
+
toReleaseManifest,
|
|
21415
21742
|
toScenarioIndex,
|
|
21416
21743
|
toStoryReport,
|
|
21417
21744
|
tryGetActiveOtelContext,
|