executable-stories-formatters 0.12.0 → 0.13.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 +20 -0
- package/dist/adapters.d.cts +1 -1
- package/dist/adapters.d.ts +1 -1
- package/dist/cli.js +565 -8
- package/dist/cli.js.map +1 -1
- package/dist/{index-mrT6-JSt.d.cts → index-CXrzCk9p.d.cts} +1 -1
- package/dist/{index-mrT6-JSt.d.ts → index-CXrzCk9p.d.ts} +1 -1
- package/dist/index.cjs +423 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +233 -4
- package/dist/index.d.ts +233 -4
- package/dist/index.js +415 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/schemas/raw-run.schema.json +9 -0
|
@@ -625,4 +625,4 @@ interface PlaywrightAdapterOptions {
|
|
|
625
625
|
*/
|
|
626
626
|
declare function adaptPlaywrightRun(testResults: Array<[PlaywrightTestCase, PlaywrightTestResult]>, options?: PlaywrightAdapterOptions): RawRun;
|
|
627
627
|
|
|
628
|
-
export { type Attachment as A,
|
|
628
|
+
export { type Attachment as A, type RawTestCase as B, type CIInfo$1 as C, type DocEntry as D, STORY_META_KEY as E, type StepMode as F, type StoryFileReport as G, type StoryMeta as H, type TestCaseAttempt as I, type JestAdapterOptions as J, type TestCaseEvidence as K, type VitestSerializedError as L, type VitestState as M, type NormalizedTicket as N, type OtelSpan as O, type PlaywrightAdapterOptions as P, type VitestTestCase as Q, type RawStatus as R, type StoryStep as S, type TestRunResult as T, type VitestTestModule as U, type VitestAdapterOptions as V, type VitestTestResult as W, toCIInfo as X, toRawCIInfo as Y, type TestCaseResult as a, type TestStatus as b, type DocPhase as c, type StepResult as d, type CIProvider as e, type RawAttachment as f, type RawRun as g, type RawCIInfo as h, type StepKeyword as i, adaptJestRun as j, adaptPlaywrightRun as k, adaptVitestRun as l, type CIInfo as m, type CoverageSummary as n, type JestAggregatedResult as o, type JestFileResult as p, type JestTestResult as q, type OtelAttributeValue as r, type PlaywrightAnnotation as s, type PlaywrightAttachment as t, type PlaywrightError as u, type PlaywrightLocation as v, type PlaywrightStatus as w, type PlaywrightTestCase as x, type PlaywrightTestResult as y, type RawStepEvent as z };
|
|
@@ -625,4 +625,4 @@ interface PlaywrightAdapterOptions {
|
|
|
625
625
|
*/
|
|
626
626
|
declare function adaptPlaywrightRun(testResults: Array<[PlaywrightTestCase, PlaywrightTestResult]>, options?: PlaywrightAdapterOptions): RawRun;
|
|
627
627
|
|
|
628
|
-
export { type Attachment as A,
|
|
628
|
+
export { type Attachment as A, type RawTestCase as B, type CIInfo$1 as C, type DocEntry as D, STORY_META_KEY as E, type StepMode as F, type StoryFileReport as G, type StoryMeta as H, type TestCaseAttempt as I, type JestAdapterOptions as J, type TestCaseEvidence as K, type VitestSerializedError as L, type VitestState as M, type NormalizedTicket as N, type OtelSpan as O, type PlaywrightAdapterOptions as P, type VitestTestCase as Q, type RawStatus as R, type StoryStep as S, type TestRunResult as T, type VitestTestModule as U, type VitestAdapterOptions as V, type VitestTestResult as W, toCIInfo as X, toRawCIInfo as Y, type TestCaseResult as a, type TestStatus as b, type DocPhase as c, type StepResult as d, type CIProvider as e, type RawAttachment as f, type RawRun as g, type RawCIInfo as h, type StepKeyword as i, adaptJestRun as j, adaptPlaywrightRun as k, adaptVitestRun as l, type CIInfo as m, type CoverageSummary as n, type JestAggregatedResult as o, type JestFileResult as p, type JestTestResult as q, type OtelAttributeValue as r, type PlaywrightAnnotation as s, type PlaywrightAttachment as t, type PlaywrightError as u, type PlaywrightLocation as v, type PlaywrightStatus as w, type PlaywrightTestCase as x, type PlaywrightTestResult as y, type RawStepEvent as z };
|
package/dist/index.cjs
CHANGED
|
@@ -55,12 +55,16 @@ __export(src_exports, {
|
|
|
55
55
|
STORY_REPORT_SCHEMA_VERSION: () => STORY_REPORT_SCHEMA_VERSION,
|
|
56
56
|
ScenarioIndexJsonFormatter: () => ScenarioIndexJsonFormatter,
|
|
57
57
|
StoryReportJsonFormatter: () => StoryReportJsonFormatter,
|
|
58
|
+
TraceabilityMatrixFormatter: () => TraceabilityMatrixFormatter,
|
|
58
59
|
adaptJestRun: () => adaptJestRun,
|
|
59
60
|
adaptPlaywrightRun: () => adaptPlaywrightRun,
|
|
60
61
|
adaptVitestRun: () => adaptVitestRun,
|
|
61
62
|
assertValidRun: () => assertValidRun,
|
|
63
|
+
buildCheck: () => buildCheck,
|
|
64
|
+
buildGoal: () => buildGoal,
|
|
62
65
|
buildHtmlDocEntry: () => buildHtmlDocEntry,
|
|
63
66
|
buildReview: () => buildReview,
|
|
67
|
+
buildTriage: () => buildTriage,
|
|
64
68
|
bundleAssets: () => bundleAssets,
|
|
65
69
|
calculateFlakiness: () => calculateFlakiness,
|
|
66
70
|
calculateStability: () => calculateStability,
|
|
@@ -110,6 +114,9 @@ __export(src_exports, {
|
|
|
110
114
|
readPackageVersion: () => readPackageVersion,
|
|
111
115
|
recordDeployment: () => recordDeployment,
|
|
112
116
|
regenerateArtifacts: () => regenerateArtifacts,
|
|
117
|
+
renderCheck: () => renderCheck,
|
|
118
|
+
renderGoal: () => renderGoal,
|
|
119
|
+
renderTriage: () => renderTriage,
|
|
113
120
|
resolveAttachment: () => resolveAttachment,
|
|
114
121
|
resolveAttachments: () => resolveAttachments,
|
|
115
122
|
resolveTheme: () => resolveTheme,
|
|
@@ -131,6 +138,7 @@ __export(src_exports, {
|
|
|
131
138
|
toReleaseManifest: () => toReleaseManifest,
|
|
132
139
|
toScenarioIndex: () => toScenarioIndex,
|
|
133
140
|
toStoryReport: () => toStoryReport,
|
|
141
|
+
toTraceabilityMatrix: () => toTraceabilityMatrix,
|
|
134
142
|
tryGetActiveOtelContext: () => tryGetActiveOtelContext,
|
|
135
143
|
updateHistory: () => updateHistory,
|
|
136
144
|
validateCanonicalRun: () => validateCanonicalRun
|
|
@@ -663,14 +671,14 @@ var CucumberJsonFormatter = class {
|
|
|
663
671
|
duration: 0
|
|
664
672
|
};
|
|
665
673
|
}
|
|
666
|
-
const
|
|
674
|
+
const statusMap2 = {
|
|
667
675
|
passed: "passed",
|
|
668
676
|
failed: "failed",
|
|
669
677
|
skipped: "skipped",
|
|
670
678
|
pending: "pending"
|
|
671
679
|
};
|
|
672
680
|
const stepResult = {
|
|
673
|
-
status:
|
|
681
|
+
status: statusMap2[result.status] ?? "undefined",
|
|
674
682
|
// Duration in nanoseconds (Cucumber uses nanoseconds)
|
|
675
683
|
duration: result.durationMs * 1e6
|
|
676
684
|
};
|
|
@@ -15854,6 +15862,132 @@ function escapePipe(value) {
|
|
|
15854
15862
|
return value.replace(/\|/g, "\\|");
|
|
15855
15863
|
}
|
|
15856
15864
|
|
|
15865
|
+
// src/formatters/traceability-matrix.ts
|
|
15866
|
+
var TraceabilityMatrixFormatter = class {
|
|
15867
|
+
format(run) {
|
|
15868
|
+
const matrix = toTraceabilityMatrix(run);
|
|
15869
|
+
const lines = [];
|
|
15870
|
+
lines.push("# Traceability Matrix");
|
|
15871
|
+
lines.push("");
|
|
15872
|
+
lines.push(`Generated: ${matrix.generatedAt}`);
|
|
15873
|
+
lines.push(`Run: ${matrix.run.startedAt} to ${matrix.run.finishedAt}`);
|
|
15874
|
+
if (matrix.run.branch) lines.push(`Branch: ${matrix.run.branch}`);
|
|
15875
|
+
if (matrix.run.gitSha) lines.push(`Commit: ${matrix.run.gitSha}`);
|
|
15876
|
+
lines.push("");
|
|
15877
|
+
lines.push("| Requirements | Verified | Failing | Scenarios | Untraced |");
|
|
15878
|
+
lines.push("| ---: | ---: | ---: | ---: | ---: |");
|
|
15879
|
+
lines.push(
|
|
15880
|
+
`| ${matrix.summary.requirements} | ${matrix.summary.requirementsVerified} | ${matrix.summary.requirementsFailing} | ${matrix.summary.scenarios} | ${matrix.summary.untracedScenarios} |`
|
|
15881
|
+
);
|
|
15882
|
+
lines.push("");
|
|
15883
|
+
for (const req of matrix.requirements) {
|
|
15884
|
+
const heading2 = req.url ? `[${req.ticket}](${req.url})` : req.ticket;
|
|
15885
|
+
lines.push(`## ${heading2}`);
|
|
15886
|
+
lines.push("");
|
|
15887
|
+
lines.push(`Status: ${renderRequirementStatus(req.status)}`);
|
|
15888
|
+
if (req.covers.length > 0) {
|
|
15889
|
+
lines.push(`Covers: ${req.covers.map((path12) => `\`${path12}\``).join(", ")}`);
|
|
15890
|
+
}
|
|
15891
|
+
lines.push("");
|
|
15892
|
+
lines.push("| Status | Scenario | Source | Covers |");
|
|
15893
|
+
lines.push("| --- | --- | --- | --- |");
|
|
15894
|
+
for (const scenario of req.scenarios) {
|
|
15895
|
+
const source = `${scenario.sourceFile}:${scenario.sourceLine}`;
|
|
15896
|
+
const covers = scenario.covers.length > 0 ? scenario.covers.map((path12) => `\`${path12}\``).join(", ") : "";
|
|
15897
|
+
lines.push(`| ${scenario.status} | ${escapePipe2(scenario.title)} | \`${source}\` | ${covers} |`);
|
|
15898
|
+
}
|
|
15899
|
+
lines.push("");
|
|
15900
|
+
}
|
|
15901
|
+
if (matrix.untraced.length > 0) {
|
|
15902
|
+
lines.push("## Untraced scenarios");
|
|
15903
|
+
lines.push("");
|
|
15904
|
+
lines.push("Behavior with no requirement link. Add a `ticket` to each so it appears against a requirement.");
|
|
15905
|
+
lines.push("");
|
|
15906
|
+
lines.push("| Status | Scenario | Source |");
|
|
15907
|
+
lines.push("| --- | --- | --- |");
|
|
15908
|
+
for (const scenario of matrix.untraced) {
|
|
15909
|
+
const source = `${scenario.sourceFile}:${scenario.sourceLine}`;
|
|
15910
|
+
lines.push(`| ${scenario.status} | ${escapePipe2(scenario.title)} | \`${source}\` |`);
|
|
15911
|
+
}
|
|
15912
|
+
lines.push("");
|
|
15913
|
+
}
|
|
15914
|
+
return lines.join("\n").trimEnd();
|
|
15915
|
+
}
|
|
15916
|
+
};
|
|
15917
|
+
function toTraceabilityMatrix(run) {
|
|
15918
|
+
const sorted = [...run.testCases].sort((a, b) => a.id.localeCompare(b.id));
|
|
15919
|
+
const byTicket = /* @__PURE__ */ new Map();
|
|
15920
|
+
const untraced = [];
|
|
15921
|
+
for (const tc of sorted) {
|
|
15922
|
+
const tickets = tc.story.tickets ?? [];
|
|
15923
|
+
if (tickets.length === 0) {
|
|
15924
|
+
untraced.push({
|
|
15925
|
+
id: tc.id,
|
|
15926
|
+
title: tc.story.scenario,
|
|
15927
|
+
status: tc.status,
|
|
15928
|
+
sourceFile: tc.sourceFile,
|
|
15929
|
+
sourceLine: tc.sourceLine
|
|
15930
|
+
});
|
|
15931
|
+
continue;
|
|
15932
|
+
}
|
|
15933
|
+
for (const ticket of tickets) {
|
|
15934
|
+
const entry = byTicket.get(ticket.id) ?? { url: ticket.url, cases: [] };
|
|
15935
|
+
if (!entry.url && ticket.url) entry.url = ticket.url;
|
|
15936
|
+
entry.cases.push(tc);
|
|
15937
|
+
byTicket.set(ticket.id, entry);
|
|
15938
|
+
}
|
|
15939
|
+
}
|
|
15940
|
+
const requirements = [...byTicket.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([ticket, entry]) => {
|
|
15941
|
+
const scenarios = entry.cases.map((tc) => ({
|
|
15942
|
+
id: tc.id,
|
|
15943
|
+
title: tc.story.scenario,
|
|
15944
|
+
status: tc.status,
|
|
15945
|
+
sourceFile: tc.sourceFile,
|
|
15946
|
+
sourceLine: tc.sourceLine,
|
|
15947
|
+
covers: tc.story.covers ?? []
|
|
15948
|
+
}));
|
|
15949
|
+
const covers = [...new Set(scenarios.flatMap((s) => s.covers))].sort();
|
|
15950
|
+
return { ticket, url: entry.url, status: requirementStatus(entry.cases), scenarios, covers };
|
|
15951
|
+
});
|
|
15952
|
+
return {
|
|
15953
|
+
schemaVersion: "1.0",
|
|
15954
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15955
|
+
run: {
|
|
15956
|
+
startedAt: new Date(run.startedAtMs).toISOString(),
|
|
15957
|
+
finishedAt: new Date(run.finishedAtMs).toISOString(),
|
|
15958
|
+
gitSha: run.gitSha,
|
|
15959
|
+
branch: run.ci?.branch
|
|
15960
|
+
},
|
|
15961
|
+
summary: {
|
|
15962
|
+
requirements: requirements.length,
|
|
15963
|
+
requirementsVerified: requirements.filter((r) => r.status === "verified").length,
|
|
15964
|
+
requirementsFailing: requirements.filter((r) => r.status === "failing").length,
|
|
15965
|
+
scenarios: run.testCases.length,
|
|
15966
|
+
untracedScenarios: untraced.length
|
|
15967
|
+
},
|
|
15968
|
+
requirements,
|
|
15969
|
+
untraced
|
|
15970
|
+
};
|
|
15971
|
+
}
|
|
15972
|
+
function requirementStatus(cases) {
|
|
15973
|
+
if (cases.some((tc) => tc.status === "failed")) return "failing";
|
|
15974
|
+
if (cases.some((tc) => tc.status === "passed")) return "verified";
|
|
15975
|
+
return "incomplete";
|
|
15976
|
+
}
|
|
15977
|
+
function renderRequirementStatus(status) {
|
|
15978
|
+
switch (status) {
|
|
15979
|
+
case "verified":
|
|
15980
|
+
return "verified (all scenarios passed)";
|
|
15981
|
+
case "failing":
|
|
15982
|
+
return "failing (a scenario failed)";
|
|
15983
|
+
default:
|
|
15984
|
+
return "incomplete (no scenario passed yet)";
|
|
15985
|
+
}
|
|
15986
|
+
}
|
|
15987
|
+
function escapePipe2(value) {
|
|
15988
|
+
return value.replace(/\|/g, "\\|");
|
|
15989
|
+
}
|
|
15990
|
+
|
|
15857
15991
|
// src/formatters/cucumber-messages/synthesize-feature.ts
|
|
15858
15992
|
function extractFeatureName(testCases, uri) {
|
|
15859
15993
|
for (const tc of testCases) {
|
|
@@ -20602,6 +20736,280 @@ function collectDocKinds(testCase) {
|
|
|
20602
20736
|
return [...kinds].sort();
|
|
20603
20737
|
}
|
|
20604
20738
|
|
|
20739
|
+
// src/scenario-failure.ts
|
|
20740
|
+
function failingScenarioMessage(tc) {
|
|
20741
|
+
const failingStep = tc.stepResults.find((s) => s.status === "failed" && s.errorMessage);
|
|
20742
|
+
return failingStep?.errorMessage ?? tc.errorMessage;
|
|
20743
|
+
}
|
|
20744
|
+
|
|
20745
|
+
// src/check.ts
|
|
20746
|
+
var ICON_PASS = "\u2713";
|
|
20747
|
+
var ICON_FAIL = "\u2717";
|
|
20748
|
+
var ICON_SKIP = "\u2298";
|
|
20749
|
+
var ICON_PENDING = "\u23F3";
|
|
20750
|
+
var ICON_WARN = "\u26A0";
|
|
20751
|
+
function buildCheck(args, _deps = {}) {
|
|
20752
|
+
const { testCases, baseline } = args;
|
|
20753
|
+
const summary = {
|
|
20754
|
+
total: testCases.length,
|
|
20755
|
+
passed: testCases.filter((tc) => tc.status === "passed").length,
|
|
20756
|
+
failed: testCases.filter((tc) => tc.status === "failed").length,
|
|
20757
|
+
skipped: testCases.filter((tc) => tc.status === "skipped").length,
|
|
20758
|
+
pending: testCases.filter((tc) => tc.status === "pending").length
|
|
20759
|
+
};
|
|
20760
|
+
let regressed = 0;
|
|
20761
|
+
let fixed = 0;
|
|
20762
|
+
if (baseline) {
|
|
20763
|
+
for (const tc of testCases) {
|
|
20764
|
+
const before = baseline.get(tc.id);
|
|
20765
|
+
if (before === "passed" && tc.status === "failed") regressed += 1;
|
|
20766
|
+
if (before === "failed" && tc.status === "passed") fixed += 1;
|
|
20767
|
+
}
|
|
20768
|
+
}
|
|
20769
|
+
const failures = testCases.filter((tc) => tc.status === "failed").map((tc) => toFailure(tc, baseline)).sort((a, b) => {
|
|
20770
|
+
if (a.regressed !== b.regressed) return a.regressed ? -1 : 1;
|
|
20771
|
+
return a.location.localeCompare(b.location);
|
|
20772
|
+
});
|
|
20773
|
+
return {
|
|
20774
|
+
summary,
|
|
20775
|
+
failures,
|
|
20776
|
+
regressed,
|
|
20777
|
+
fixed,
|
|
20778
|
+
comparedToBaseline: baseline !== void 0
|
|
20779
|
+
};
|
|
20780
|
+
}
|
|
20781
|
+
function toFailure(tc, baseline) {
|
|
20782
|
+
const failedIndexes = new Set(
|
|
20783
|
+
tc.stepResults.filter((s) => s.status === "failed").map((s) => s.index)
|
|
20784
|
+
);
|
|
20785
|
+
const steps = tc.story.steps.map((step, index) => ({
|
|
20786
|
+
keyword: step.keyword,
|
|
20787
|
+
text: step.text,
|
|
20788
|
+
failed: failedIndexes.has(index)
|
|
20789
|
+
}));
|
|
20790
|
+
return {
|
|
20791
|
+
id: tc.id,
|
|
20792
|
+
scenario: tc.story.scenario,
|
|
20793
|
+
location: `${tc.sourceFile}:${tc.sourceLine}`,
|
|
20794
|
+
steps,
|
|
20795
|
+
errorMessage: failingScenarioMessage(tc),
|
|
20796
|
+
covers: tc.story.covers ?? [],
|
|
20797
|
+
tickets: (tc.story.tickets ?? []).map((t) => t.id),
|
|
20798
|
+
regressed: baseline?.get(tc.id) === "passed"
|
|
20799
|
+
};
|
|
20800
|
+
}
|
|
20801
|
+
function renderCheck(report, format) {
|
|
20802
|
+
return format === "json" ? JSON.stringify(report, null, 2) : renderCheckText(report);
|
|
20803
|
+
}
|
|
20804
|
+
function renderCheckText(report) {
|
|
20805
|
+
const { summary, failures } = report;
|
|
20806
|
+
const headlineParts = [`${ICON_PASS} ${summary.passed} passed`];
|
|
20807
|
+
if (summary.failed > 0) headlineParts.push(`${ICON_FAIL} ${summary.failed} failed`);
|
|
20808
|
+
if (summary.skipped > 0) headlineParts.push(`${ICON_SKIP} ${summary.skipped} skipped`);
|
|
20809
|
+
if (summary.pending > 0) headlineParts.push(`${ICON_PENDING} ${summary.pending} pending`);
|
|
20810
|
+
const headline = `${headlineParts.join(" ")} (${summary.total} scenarios)`;
|
|
20811
|
+
if (failures.length === 0) {
|
|
20812
|
+
const lines2 = [headline];
|
|
20813
|
+
if (report.comparedToBaseline && report.fixed > 0) {
|
|
20814
|
+
lines2.push(`${ICON_PASS} ${report.fixed} fixed since baseline.`);
|
|
20815
|
+
}
|
|
20816
|
+
lines2.push("All scenarios green.");
|
|
20817
|
+
return lines2.join("\n");
|
|
20818
|
+
}
|
|
20819
|
+
const lines = [headline, ""];
|
|
20820
|
+
for (const f of failures) {
|
|
20821
|
+
lines.push(`${ICON_FAIL} ${f.scenario}${f.regressed ? " (regressed)" : ""}`);
|
|
20822
|
+
lines.push(` ${f.location}`);
|
|
20823
|
+
for (const step of f.steps) {
|
|
20824
|
+
const marker = step.failed ? ` ${ICON_FAIL} ` : " ";
|
|
20825
|
+
lines.push(`${marker}${step.keyword} ${step.text}`);
|
|
20826
|
+
}
|
|
20827
|
+
if (f.errorMessage) {
|
|
20828
|
+
const firstLine = f.errorMessage.split("\n")[0];
|
|
20829
|
+
lines.push(` \u2192 ${firstLine}`);
|
|
20830
|
+
}
|
|
20831
|
+
if (f.covers.length > 0) {
|
|
20832
|
+
lines.push(` covers: ${f.covers.join(", ")}`);
|
|
20833
|
+
}
|
|
20834
|
+
if (f.tickets.length > 0) {
|
|
20835
|
+
lines.push(` ticket: ${f.tickets.join(", ")}`);
|
|
20836
|
+
}
|
|
20837
|
+
lines.push("");
|
|
20838
|
+
}
|
|
20839
|
+
if (report.comparedToBaseline) {
|
|
20840
|
+
if (report.regressed > 0) {
|
|
20841
|
+
lines.push(`${ICON_WARN} ${report.regressed} regressed since baseline (was passing).`);
|
|
20842
|
+
}
|
|
20843
|
+
if (report.fixed > 0) {
|
|
20844
|
+
lines.push(`${ICON_PASS} ${report.fixed} fixed since baseline.`);
|
|
20845
|
+
}
|
|
20846
|
+
if (report.regressed === 0 && report.fixed === 0) {
|
|
20847
|
+
lines.push("No status changes vs. baseline.");
|
|
20848
|
+
}
|
|
20849
|
+
}
|
|
20850
|
+
return lines.join("\n").trimEnd();
|
|
20851
|
+
}
|
|
20852
|
+
|
|
20853
|
+
// src/goal.ts
|
|
20854
|
+
var ACTIVE = ["passed", "failed"];
|
|
20855
|
+
function buildGoal(args, _deps = {}) {
|
|
20856
|
+
const { run, baseline } = args;
|
|
20857
|
+
const cases = run.testCases;
|
|
20858
|
+
const selectors = [
|
|
20859
|
+
...args.requireTags.map((tag) => ({ label: `tag:${tag}`, match: (tc) => tc.tags.includes(tag) })),
|
|
20860
|
+
...args.requireTickets.map((id) => ({ label: `ticket:${id}`, match: (tc) => (tc.story.tickets ?? []).some((t) => t.id === id) })),
|
|
20861
|
+
...args.requireScenarios.map((sel) => ({ label: `scenario:${sel}`, match: (tc) => tc.id === sel || tc.story.scenario === sel }))
|
|
20862
|
+
];
|
|
20863
|
+
const requirements = selectors.length === 0 ? [evaluate("all scenarios", cases)] : selectors.map((s) => evaluate(s.label, cases.filter(s.match)));
|
|
20864
|
+
const regressions = [];
|
|
20865
|
+
if (baseline && args.enforceNoRegressions) {
|
|
20866
|
+
const before = statusMap(baseline);
|
|
20867
|
+
for (const tc of cases) {
|
|
20868
|
+
if (before.get(tc.id) === "passed" && tc.status === "failed") {
|
|
20869
|
+
regressions.push({ id: tc.id, title: tc.story.scenario });
|
|
20870
|
+
}
|
|
20871
|
+
}
|
|
20872
|
+
}
|
|
20873
|
+
const violations = [];
|
|
20874
|
+
if (baseline && args.enforceRatchet) {
|
|
20875
|
+
const current = new Map(cases.map((tc) => [tc.id, tc]));
|
|
20876
|
+
for (const base of baseline.testCases) {
|
|
20877
|
+
const now = current.get(base.id);
|
|
20878
|
+
if (!now) {
|
|
20879
|
+
violations.push({ id: base.id, title: base.story.scenario, kind: "removed", detail: "scenario no longer present" });
|
|
20880
|
+
continue;
|
|
20881
|
+
}
|
|
20882
|
+
if (ACTIVE.includes(base.status) && (now.status === "skipped" || now.status === "pending")) {
|
|
20883
|
+
violations.push({ id: base.id, title: base.story.scenario, kind: "disabled", detail: `${base.status} -> ${now.status}` });
|
|
20884
|
+
}
|
|
20885
|
+
const baseSteps = base.story.steps.length;
|
|
20886
|
+
const nowSteps = now.story.steps.length;
|
|
20887
|
+
if (nowSteps < baseSteps) {
|
|
20888
|
+
violations.push({ id: base.id, title: base.story.scenario, kind: "weakened", detail: `${baseSteps} steps -> ${nowSteps} steps` });
|
|
20889
|
+
}
|
|
20890
|
+
}
|
|
20891
|
+
}
|
|
20892
|
+
const met = requirements.every((r) => r.met) && regressions.length === 0 && violations.length === 0;
|
|
20893
|
+
return {
|
|
20894
|
+
met,
|
|
20895
|
+
requirements,
|
|
20896
|
+
regressions,
|
|
20897
|
+
regressionsEnforced: Boolean(baseline && args.enforceNoRegressions),
|
|
20898
|
+
ratchet: { enforced: Boolean(baseline && args.enforceRatchet), violations }
|
|
20899
|
+
};
|
|
20900
|
+
}
|
|
20901
|
+
function evaluate(selector, matched) {
|
|
20902
|
+
const passed = matched.filter((tc) => tc.status === "passed").length;
|
|
20903
|
+
const failing = matched.filter((tc) => tc.status !== "passed").map((tc) => tc.story.scenario);
|
|
20904
|
+
return {
|
|
20905
|
+
selector,
|
|
20906
|
+
matched: matched.length,
|
|
20907
|
+
passed,
|
|
20908
|
+
failing,
|
|
20909
|
+
met: matched.length > 0 && failing.length === 0
|
|
20910
|
+
};
|
|
20911
|
+
}
|
|
20912
|
+
function statusMap(run) {
|
|
20913
|
+
return new Map(run.testCases.map((tc) => [tc.id, tc.status]));
|
|
20914
|
+
}
|
|
20915
|
+
function renderGoal(report, format) {
|
|
20916
|
+
if (format === "json") return JSON.stringify(report, null, 2);
|
|
20917
|
+
const lines = [`GOAL: ${report.met ? "met" : "not met"}`];
|
|
20918
|
+
for (const req of report.requirements) {
|
|
20919
|
+
if (req.matched === 0) {
|
|
20920
|
+
lines.push(` ${req.selector}: no matching scenario (no proof)`);
|
|
20921
|
+
continue;
|
|
20922
|
+
}
|
|
20923
|
+
const tail = req.failing.length > 0 ? ` (${req.failing.length} failing)` : "";
|
|
20924
|
+
lines.push(` ${req.selector}: ${req.passed}/${req.matched} scenarios pass${tail}`);
|
|
20925
|
+
}
|
|
20926
|
+
if (report.regressionsEnforced) {
|
|
20927
|
+
if (report.regressions.length === 0) {
|
|
20928
|
+
lines.push(" regressions: 0");
|
|
20929
|
+
} else {
|
|
20930
|
+
lines.push(` regressions: ${report.regressions.length} (${report.regressions.map((r) => r.title).join(", ")})`);
|
|
20931
|
+
}
|
|
20932
|
+
}
|
|
20933
|
+
if (report.ratchet.enforced) {
|
|
20934
|
+
if (report.ratchet.violations.length === 0) {
|
|
20935
|
+
lines.push(" ratchet: clean (0 scenarios removed/weakened)");
|
|
20936
|
+
} else {
|
|
20937
|
+
lines.push(` ratchet: ${report.ratchet.violations.length} removed/weakened`);
|
|
20938
|
+
for (const v of report.ratchet.violations) {
|
|
20939
|
+
lines.push(` ${v.kind}: ${v.title} (${v.detail})`);
|
|
20940
|
+
}
|
|
20941
|
+
}
|
|
20942
|
+
}
|
|
20943
|
+
return lines.join("\n");
|
|
20944
|
+
}
|
|
20945
|
+
|
|
20946
|
+
// src/triage.ts
|
|
20947
|
+
function buildTriage(args, _deps = {}) {
|
|
20948
|
+
const { testCases, baseline } = args;
|
|
20949
|
+
const failing = testCases.filter((tc) => tc.status === "failed");
|
|
20950
|
+
const ranked = failing.map((tc) => {
|
|
20951
|
+
const regressed = baseline?.get(tc.id) === "passed";
|
|
20952
|
+
return {
|
|
20953
|
+
tc,
|
|
20954
|
+
regressed,
|
|
20955
|
+
covers: tc.story.covers ?? []
|
|
20956
|
+
};
|
|
20957
|
+
}).sort((a, b) => {
|
|
20958
|
+
if (a.regressed !== b.regressed) return a.regressed ? -1 : 1;
|
|
20959
|
+
const la = `${a.tc.sourceFile}:${a.tc.sourceLine}`;
|
|
20960
|
+
const lb = `${b.tc.sourceFile}:${b.tc.sourceLine}`;
|
|
20961
|
+
return la.localeCompare(lb);
|
|
20962
|
+
});
|
|
20963
|
+
const items = ranked.map((entry, index) => ({
|
|
20964
|
+
rank: index + 1,
|
|
20965
|
+
id: entry.tc.id,
|
|
20966
|
+
scenario: entry.tc.story.scenario,
|
|
20967
|
+
status: entry.tc.status,
|
|
20968
|
+
location: `${entry.tc.sourceFile}:${entry.tc.sourceLine}`,
|
|
20969
|
+
covers: entry.covers,
|
|
20970
|
+
tickets: (entry.tc.story.tickets ?? []).map((t) => t.id),
|
|
20971
|
+
errorMessage: failingScenarioMessage(entry.tc),
|
|
20972
|
+
regressed: entry.regressed,
|
|
20973
|
+
reason: entry.regressed ? "regression" : "failing"
|
|
20974
|
+
}));
|
|
20975
|
+
return {
|
|
20976
|
+
total: testCases.length,
|
|
20977
|
+
failing: failing.length,
|
|
20978
|
+
regressions: items.filter((i) => i.regressed).length,
|
|
20979
|
+
needsCovers: items.filter((i) => i.covers.length === 0).length,
|
|
20980
|
+
items
|
|
20981
|
+
};
|
|
20982
|
+
}
|
|
20983
|
+
function renderTriage(report, format) {
|
|
20984
|
+
if (format === "json") return JSON.stringify(report, null, 2);
|
|
20985
|
+
if (report.items.length === 0) {
|
|
20986
|
+
return "Nothing to triage. No failing scenarios.";
|
|
20987
|
+
}
|
|
20988
|
+
const header = report.regressions > 0 ? `${report.items.length} items to triage (${report.regressions} regression${report.regressions === 1 ? "" : "s"})` : `${report.items.length} items to triage`;
|
|
20989
|
+
const lines = [header, ""];
|
|
20990
|
+
for (const item of report.items) {
|
|
20991
|
+
const tag = item.regressed ? "[regression] " : "";
|
|
20992
|
+
lines.push(`${item.rank}. ${tag}${item.scenario}`);
|
|
20993
|
+
lines.push(` ${item.location}`);
|
|
20994
|
+
if (item.errorMessage) {
|
|
20995
|
+
lines.push(` \u2192 ${item.errorMessage.split("\n")[0]}`);
|
|
20996
|
+
}
|
|
20997
|
+
if (item.covers.length > 0) {
|
|
20998
|
+
lines.push(` fix: ${item.covers.join(", ")}`);
|
|
20999
|
+
} else {
|
|
21000
|
+
lines.push(" fix: (no covers declared \u2014 add `covers` to route this to code)");
|
|
21001
|
+
}
|
|
21002
|
+
if (item.tickets.length > 0) {
|
|
21003
|
+
lines.push(` ticket: ${item.tickets.join(", ")}`);
|
|
21004
|
+
}
|
|
21005
|
+
lines.push("");
|
|
21006
|
+
}
|
|
21007
|
+
if (report.needsCovers > 0) {
|
|
21008
|
+
lines.push(`${report.needsCovers} failing scenario(s) have no covers and can't be routed to code automatically.`);
|
|
21009
|
+
}
|
|
21010
|
+
return lines.join("\n").trimEnd();
|
|
21011
|
+
}
|
|
21012
|
+
|
|
20605
21013
|
// src/review/conventions.ts
|
|
20606
21014
|
var CHANGE_TAG_PREFIX = "change:";
|
|
20607
21015
|
var AUDIENCE_TAG_PREFIX = "audience:";
|
|
@@ -21403,6 +21811,7 @@ var FORMAT_EXTENSIONS = {
|
|
|
21403
21811
|
"behavior-manifest-json": ".behavior-manifest.json",
|
|
21404
21812
|
markdown: ".md",
|
|
21405
21813
|
"release-manifest": ".release-manifest.md",
|
|
21814
|
+
"traceability-matrix": ".traceability-matrix.md",
|
|
21406
21815
|
html: ".html",
|
|
21407
21816
|
"cucumber-html": ".cucumber.html",
|
|
21408
21817
|
junit: ".junit.xml",
|
|
@@ -21829,6 +22238,10 @@ var ReportGenerator = class {
|
|
|
21829
22238
|
const formatter = new ReleaseManifestFormatter();
|
|
21830
22239
|
return formatter.format(run);
|
|
21831
22240
|
}
|
|
22241
|
+
case "traceability-matrix": {
|
|
22242
|
+
const formatter = new TraceabilityMatrixFormatter();
|
|
22243
|
+
return formatter.format(run);
|
|
22244
|
+
}
|
|
21832
22245
|
case "story-report-json": {
|
|
21833
22246
|
const formatter = new StoryReportJsonFormatter({
|
|
21834
22247
|
pretty: this.options.storyReportJson.pretty
|
|
@@ -21909,12 +22322,16 @@ function normalizePlaywrightResults(testResults, adapterOptions, canonicalizeOpt
|
|
|
21909
22322
|
STORY_REPORT_SCHEMA_VERSION,
|
|
21910
22323
|
ScenarioIndexJsonFormatter,
|
|
21911
22324
|
StoryReportJsonFormatter,
|
|
22325
|
+
TraceabilityMatrixFormatter,
|
|
21912
22326
|
adaptJestRun,
|
|
21913
22327
|
adaptPlaywrightRun,
|
|
21914
22328
|
adaptVitestRun,
|
|
21915
22329
|
assertValidRun,
|
|
22330
|
+
buildCheck,
|
|
22331
|
+
buildGoal,
|
|
21916
22332
|
buildHtmlDocEntry,
|
|
21917
22333
|
buildReview,
|
|
22334
|
+
buildTriage,
|
|
21918
22335
|
bundleAssets,
|
|
21919
22336
|
calculateFlakiness,
|
|
21920
22337
|
calculateStability,
|
|
@@ -21964,6 +22381,9 @@ function normalizePlaywrightResults(testResults, adapterOptions, canonicalizeOpt
|
|
|
21964
22381
|
readPackageVersion,
|
|
21965
22382
|
recordDeployment,
|
|
21966
22383
|
regenerateArtifacts,
|
|
22384
|
+
renderCheck,
|
|
22385
|
+
renderGoal,
|
|
22386
|
+
renderTriage,
|
|
21967
22387
|
resolveAttachment,
|
|
21968
22388
|
resolveAttachments,
|
|
21969
22389
|
resolveTheme,
|
|
@@ -21985,6 +22405,7 @@ function normalizePlaywrightResults(testResults, adapterOptions, canonicalizeOpt
|
|
|
21985
22405
|
toReleaseManifest,
|
|
21986
22406
|
toScenarioIndex,
|
|
21987
22407
|
toStoryReport,
|
|
22408
|
+
toTraceabilityMatrix,
|
|
21988
22409
|
tryGetActiveOtelContext,
|
|
21989
22410
|
updateHistory,
|
|
21990
22411
|
validateCanonicalRun
|