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
package/dist/index.js
CHANGED
|
@@ -526,14 +526,14 @@ var CucumberJsonFormatter = class {
|
|
|
526
526
|
duration: 0
|
|
527
527
|
};
|
|
528
528
|
}
|
|
529
|
-
const
|
|
529
|
+
const statusMap2 = {
|
|
530
530
|
passed: "passed",
|
|
531
531
|
failed: "failed",
|
|
532
532
|
skipped: "skipped",
|
|
533
533
|
pending: "pending"
|
|
534
534
|
};
|
|
535
535
|
const stepResult = {
|
|
536
|
-
status:
|
|
536
|
+
status: statusMap2[result.status] ?? "undefined",
|
|
537
537
|
// Duration in nanoseconds (Cucumber uses nanoseconds)
|
|
538
538
|
duration: result.durationMs * 1e6
|
|
539
539
|
};
|
|
@@ -15717,6 +15717,132 @@ function escapePipe(value) {
|
|
|
15717
15717
|
return value.replace(/\|/g, "\\|");
|
|
15718
15718
|
}
|
|
15719
15719
|
|
|
15720
|
+
// src/formatters/traceability-matrix.ts
|
|
15721
|
+
var TraceabilityMatrixFormatter = class {
|
|
15722
|
+
format(run) {
|
|
15723
|
+
const matrix = toTraceabilityMatrix(run);
|
|
15724
|
+
const lines = [];
|
|
15725
|
+
lines.push("# Traceability Matrix");
|
|
15726
|
+
lines.push("");
|
|
15727
|
+
lines.push(`Generated: ${matrix.generatedAt}`);
|
|
15728
|
+
lines.push(`Run: ${matrix.run.startedAt} to ${matrix.run.finishedAt}`);
|
|
15729
|
+
if (matrix.run.branch) lines.push(`Branch: ${matrix.run.branch}`);
|
|
15730
|
+
if (matrix.run.gitSha) lines.push(`Commit: ${matrix.run.gitSha}`);
|
|
15731
|
+
lines.push("");
|
|
15732
|
+
lines.push("| Requirements | Verified | Failing | Scenarios | Untraced |");
|
|
15733
|
+
lines.push("| ---: | ---: | ---: | ---: | ---: |");
|
|
15734
|
+
lines.push(
|
|
15735
|
+
`| ${matrix.summary.requirements} | ${matrix.summary.requirementsVerified} | ${matrix.summary.requirementsFailing} | ${matrix.summary.scenarios} | ${matrix.summary.untracedScenarios} |`
|
|
15736
|
+
);
|
|
15737
|
+
lines.push("");
|
|
15738
|
+
for (const req of matrix.requirements) {
|
|
15739
|
+
const heading2 = req.url ? `[${req.ticket}](${req.url})` : req.ticket;
|
|
15740
|
+
lines.push(`## ${heading2}`);
|
|
15741
|
+
lines.push("");
|
|
15742
|
+
lines.push(`Status: ${renderRequirementStatus(req.status)}`);
|
|
15743
|
+
if (req.covers.length > 0) {
|
|
15744
|
+
lines.push(`Covers: ${req.covers.map((path12) => `\`${path12}\``).join(", ")}`);
|
|
15745
|
+
}
|
|
15746
|
+
lines.push("");
|
|
15747
|
+
lines.push("| Status | Scenario | Source | Covers |");
|
|
15748
|
+
lines.push("| --- | --- | --- | --- |");
|
|
15749
|
+
for (const scenario of req.scenarios) {
|
|
15750
|
+
const source = `${scenario.sourceFile}:${scenario.sourceLine}`;
|
|
15751
|
+
const covers = scenario.covers.length > 0 ? scenario.covers.map((path12) => `\`${path12}\``).join(", ") : "";
|
|
15752
|
+
lines.push(`| ${scenario.status} | ${escapePipe2(scenario.title)} | \`${source}\` | ${covers} |`);
|
|
15753
|
+
}
|
|
15754
|
+
lines.push("");
|
|
15755
|
+
}
|
|
15756
|
+
if (matrix.untraced.length > 0) {
|
|
15757
|
+
lines.push("## Untraced scenarios");
|
|
15758
|
+
lines.push("");
|
|
15759
|
+
lines.push("Behavior with no requirement link. Add a `ticket` to each so it appears against a requirement.");
|
|
15760
|
+
lines.push("");
|
|
15761
|
+
lines.push("| Status | Scenario | Source |");
|
|
15762
|
+
lines.push("| --- | --- | --- |");
|
|
15763
|
+
for (const scenario of matrix.untraced) {
|
|
15764
|
+
const source = `${scenario.sourceFile}:${scenario.sourceLine}`;
|
|
15765
|
+
lines.push(`| ${scenario.status} | ${escapePipe2(scenario.title)} | \`${source}\` |`);
|
|
15766
|
+
}
|
|
15767
|
+
lines.push("");
|
|
15768
|
+
}
|
|
15769
|
+
return lines.join("\n").trimEnd();
|
|
15770
|
+
}
|
|
15771
|
+
};
|
|
15772
|
+
function toTraceabilityMatrix(run) {
|
|
15773
|
+
const sorted = [...run.testCases].sort((a, b) => a.id.localeCompare(b.id));
|
|
15774
|
+
const byTicket = /* @__PURE__ */ new Map();
|
|
15775
|
+
const untraced = [];
|
|
15776
|
+
for (const tc of sorted) {
|
|
15777
|
+
const tickets = tc.story.tickets ?? [];
|
|
15778
|
+
if (tickets.length === 0) {
|
|
15779
|
+
untraced.push({
|
|
15780
|
+
id: tc.id,
|
|
15781
|
+
title: tc.story.scenario,
|
|
15782
|
+
status: tc.status,
|
|
15783
|
+
sourceFile: tc.sourceFile,
|
|
15784
|
+
sourceLine: tc.sourceLine
|
|
15785
|
+
});
|
|
15786
|
+
continue;
|
|
15787
|
+
}
|
|
15788
|
+
for (const ticket of tickets) {
|
|
15789
|
+
const entry = byTicket.get(ticket.id) ?? { url: ticket.url, cases: [] };
|
|
15790
|
+
if (!entry.url && ticket.url) entry.url = ticket.url;
|
|
15791
|
+
entry.cases.push(tc);
|
|
15792
|
+
byTicket.set(ticket.id, entry);
|
|
15793
|
+
}
|
|
15794
|
+
}
|
|
15795
|
+
const requirements = [...byTicket.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([ticket, entry]) => {
|
|
15796
|
+
const scenarios = entry.cases.map((tc) => ({
|
|
15797
|
+
id: tc.id,
|
|
15798
|
+
title: tc.story.scenario,
|
|
15799
|
+
status: tc.status,
|
|
15800
|
+
sourceFile: tc.sourceFile,
|
|
15801
|
+
sourceLine: tc.sourceLine,
|
|
15802
|
+
covers: tc.story.covers ?? []
|
|
15803
|
+
}));
|
|
15804
|
+
const covers = [...new Set(scenarios.flatMap((s) => s.covers))].sort();
|
|
15805
|
+
return { ticket, url: entry.url, status: requirementStatus(entry.cases), scenarios, covers };
|
|
15806
|
+
});
|
|
15807
|
+
return {
|
|
15808
|
+
schemaVersion: "1.0",
|
|
15809
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15810
|
+
run: {
|
|
15811
|
+
startedAt: new Date(run.startedAtMs).toISOString(),
|
|
15812
|
+
finishedAt: new Date(run.finishedAtMs).toISOString(),
|
|
15813
|
+
gitSha: run.gitSha,
|
|
15814
|
+
branch: run.ci?.branch
|
|
15815
|
+
},
|
|
15816
|
+
summary: {
|
|
15817
|
+
requirements: requirements.length,
|
|
15818
|
+
requirementsVerified: requirements.filter((r) => r.status === "verified").length,
|
|
15819
|
+
requirementsFailing: requirements.filter((r) => r.status === "failing").length,
|
|
15820
|
+
scenarios: run.testCases.length,
|
|
15821
|
+
untracedScenarios: untraced.length
|
|
15822
|
+
},
|
|
15823
|
+
requirements,
|
|
15824
|
+
untraced
|
|
15825
|
+
};
|
|
15826
|
+
}
|
|
15827
|
+
function requirementStatus(cases) {
|
|
15828
|
+
if (cases.some((tc) => tc.status === "failed")) return "failing";
|
|
15829
|
+
if (cases.some((tc) => tc.status === "passed")) return "verified";
|
|
15830
|
+
return "incomplete";
|
|
15831
|
+
}
|
|
15832
|
+
function renderRequirementStatus(status) {
|
|
15833
|
+
switch (status) {
|
|
15834
|
+
case "verified":
|
|
15835
|
+
return "verified (all scenarios passed)";
|
|
15836
|
+
case "failing":
|
|
15837
|
+
return "failing (a scenario failed)";
|
|
15838
|
+
default:
|
|
15839
|
+
return "incomplete (no scenario passed yet)";
|
|
15840
|
+
}
|
|
15841
|
+
}
|
|
15842
|
+
function escapePipe2(value) {
|
|
15843
|
+
return value.replace(/\|/g, "\\|");
|
|
15844
|
+
}
|
|
15845
|
+
|
|
15720
15846
|
// src/formatters/cucumber-messages/synthesize-feature.ts
|
|
15721
15847
|
function extractFeatureName(testCases, uri) {
|
|
15722
15848
|
for (const tc of testCases) {
|
|
@@ -20464,6 +20590,280 @@ function collectDocKinds(testCase) {
|
|
|
20464
20590
|
return [...kinds].sort();
|
|
20465
20591
|
}
|
|
20466
20592
|
|
|
20593
|
+
// src/scenario-failure.ts
|
|
20594
|
+
function failingScenarioMessage(tc) {
|
|
20595
|
+
const failingStep = tc.stepResults.find((s) => s.status === "failed" && s.errorMessage);
|
|
20596
|
+
return failingStep?.errorMessage ?? tc.errorMessage;
|
|
20597
|
+
}
|
|
20598
|
+
|
|
20599
|
+
// src/check.ts
|
|
20600
|
+
var ICON_PASS = "\u2713";
|
|
20601
|
+
var ICON_FAIL = "\u2717";
|
|
20602
|
+
var ICON_SKIP = "\u2298";
|
|
20603
|
+
var ICON_PENDING = "\u23F3";
|
|
20604
|
+
var ICON_WARN = "\u26A0";
|
|
20605
|
+
function buildCheck(args, _deps = {}) {
|
|
20606
|
+
const { testCases, baseline } = args;
|
|
20607
|
+
const summary = {
|
|
20608
|
+
total: testCases.length,
|
|
20609
|
+
passed: testCases.filter((tc) => tc.status === "passed").length,
|
|
20610
|
+
failed: testCases.filter((tc) => tc.status === "failed").length,
|
|
20611
|
+
skipped: testCases.filter((tc) => tc.status === "skipped").length,
|
|
20612
|
+
pending: testCases.filter((tc) => tc.status === "pending").length
|
|
20613
|
+
};
|
|
20614
|
+
let regressed = 0;
|
|
20615
|
+
let fixed = 0;
|
|
20616
|
+
if (baseline) {
|
|
20617
|
+
for (const tc of testCases) {
|
|
20618
|
+
const before = baseline.get(tc.id);
|
|
20619
|
+
if (before === "passed" && tc.status === "failed") regressed += 1;
|
|
20620
|
+
if (before === "failed" && tc.status === "passed") fixed += 1;
|
|
20621
|
+
}
|
|
20622
|
+
}
|
|
20623
|
+
const failures = testCases.filter((tc) => tc.status === "failed").map((tc) => toFailure(tc, baseline)).sort((a, b) => {
|
|
20624
|
+
if (a.regressed !== b.regressed) return a.regressed ? -1 : 1;
|
|
20625
|
+
return a.location.localeCompare(b.location);
|
|
20626
|
+
});
|
|
20627
|
+
return {
|
|
20628
|
+
summary,
|
|
20629
|
+
failures,
|
|
20630
|
+
regressed,
|
|
20631
|
+
fixed,
|
|
20632
|
+
comparedToBaseline: baseline !== void 0
|
|
20633
|
+
};
|
|
20634
|
+
}
|
|
20635
|
+
function toFailure(tc, baseline) {
|
|
20636
|
+
const failedIndexes = new Set(
|
|
20637
|
+
tc.stepResults.filter((s) => s.status === "failed").map((s) => s.index)
|
|
20638
|
+
);
|
|
20639
|
+
const steps = tc.story.steps.map((step, index) => ({
|
|
20640
|
+
keyword: step.keyword,
|
|
20641
|
+
text: step.text,
|
|
20642
|
+
failed: failedIndexes.has(index)
|
|
20643
|
+
}));
|
|
20644
|
+
return {
|
|
20645
|
+
id: tc.id,
|
|
20646
|
+
scenario: tc.story.scenario,
|
|
20647
|
+
location: `${tc.sourceFile}:${tc.sourceLine}`,
|
|
20648
|
+
steps,
|
|
20649
|
+
errorMessage: failingScenarioMessage(tc),
|
|
20650
|
+
covers: tc.story.covers ?? [],
|
|
20651
|
+
tickets: (tc.story.tickets ?? []).map((t) => t.id),
|
|
20652
|
+
regressed: baseline?.get(tc.id) === "passed"
|
|
20653
|
+
};
|
|
20654
|
+
}
|
|
20655
|
+
function renderCheck(report, format) {
|
|
20656
|
+
return format === "json" ? JSON.stringify(report, null, 2) : renderCheckText(report);
|
|
20657
|
+
}
|
|
20658
|
+
function renderCheckText(report) {
|
|
20659
|
+
const { summary, failures } = report;
|
|
20660
|
+
const headlineParts = [`${ICON_PASS} ${summary.passed} passed`];
|
|
20661
|
+
if (summary.failed > 0) headlineParts.push(`${ICON_FAIL} ${summary.failed} failed`);
|
|
20662
|
+
if (summary.skipped > 0) headlineParts.push(`${ICON_SKIP} ${summary.skipped} skipped`);
|
|
20663
|
+
if (summary.pending > 0) headlineParts.push(`${ICON_PENDING} ${summary.pending} pending`);
|
|
20664
|
+
const headline = `${headlineParts.join(" ")} (${summary.total} scenarios)`;
|
|
20665
|
+
if (failures.length === 0) {
|
|
20666
|
+
const lines2 = [headline];
|
|
20667
|
+
if (report.comparedToBaseline && report.fixed > 0) {
|
|
20668
|
+
lines2.push(`${ICON_PASS} ${report.fixed} fixed since baseline.`);
|
|
20669
|
+
}
|
|
20670
|
+
lines2.push("All scenarios green.");
|
|
20671
|
+
return lines2.join("\n");
|
|
20672
|
+
}
|
|
20673
|
+
const lines = [headline, ""];
|
|
20674
|
+
for (const f of failures) {
|
|
20675
|
+
lines.push(`${ICON_FAIL} ${f.scenario}${f.regressed ? " (regressed)" : ""}`);
|
|
20676
|
+
lines.push(` ${f.location}`);
|
|
20677
|
+
for (const step of f.steps) {
|
|
20678
|
+
const marker = step.failed ? ` ${ICON_FAIL} ` : " ";
|
|
20679
|
+
lines.push(`${marker}${step.keyword} ${step.text}`);
|
|
20680
|
+
}
|
|
20681
|
+
if (f.errorMessage) {
|
|
20682
|
+
const firstLine = f.errorMessage.split("\n")[0];
|
|
20683
|
+
lines.push(` \u2192 ${firstLine}`);
|
|
20684
|
+
}
|
|
20685
|
+
if (f.covers.length > 0) {
|
|
20686
|
+
lines.push(` covers: ${f.covers.join(", ")}`);
|
|
20687
|
+
}
|
|
20688
|
+
if (f.tickets.length > 0) {
|
|
20689
|
+
lines.push(` ticket: ${f.tickets.join(", ")}`);
|
|
20690
|
+
}
|
|
20691
|
+
lines.push("");
|
|
20692
|
+
}
|
|
20693
|
+
if (report.comparedToBaseline) {
|
|
20694
|
+
if (report.regressed > 0) {
|
|
20695
|
+
lines.push(`${ICON_WARN} ${report.regressed} regressed since baseline (was passing).`);
|
|
20696
|
+
}
|
|
20697
|
+
if (report.fixed > 0) {
|
|
20698
|
+
lines.push(`${ICON_PASS} ${report.fixed} fixed since baseline.`);
|
|
20699
|
+
}
|
|
20700
|
+
if (report.regressed === 0 && report.fixed === 0) {
|
|
20701
|
+
lines.push("No status changes vs. baseline.");
|
|
20702
|
+
}
|
|
20703
|
+
}
|
|
20704
|
+
return lines.join("\n").trimEnd();
|
|
20705
|
+
}
|
|
20706
|
+
|
|
20707
|
+
// src/goal.ts
|
|
20708
|
+
var ACTIVE = ["passed", "failed"];
|
|
20709
|
+
function buildGoal(args, _deps = {}) {
|
|
20710
|
+
const { run, baseline } = args;
|
|
20711
|
+
const cases = run.testCases;
|
|
20712
|
+
const selectors = [
|
|
20713
|
+
...args.requireTags.map((tag) => ({ label: `tag:${tag}`, match: (tc) => tc.tags.includes(tag) })),
|
|
20714
|
+
...args.requireTickets.map((id) => ({ label: `ticket:${id}`, match: (tc) => (tc.story.tickets ?? []).some((t) => t.id === id) })),
|
|
20715
|
+
...args.requireScenarios.map((sel) => ({ label: `scenario:${sel}`, match: (tc) => tc.id === sel || tc.story.scenario === sel }))
|
|
20716
|
+
];
|
|
20717
|
+
const requirements = selectors.length === 0 ? [evaluate("all scenarios", cases)] : selectors.map((s) => evaluate(s.label, cases.filter(s.match)));
|
|
20718
|
+
const regressions = [];
|
|
20719
|
+
if (baseline && args.enforceNoRegressions) {
|
|
20720
|
+
const before = statusMap(baseline);
|
|
20721
|
+
for (const tc of cases) {
|
|
20722
|
+
if (before.get(tc.id) === "passed" && tc.status === "failed") {
|
|
20723
|
+
regressions.push({ id: tc.id, title: tc.story.scenario });
|
|
20724
|
+
}
|
|
20725
|
+
}
|
|
20726
|
+
}
|
|
20727
|
+
const violations = [];
|
|
20728
|
+
if (baseline && args.enforceRatchet) {
|
|
20729
|
+
const current = new Map(cases.map((tc) => [tc.id, tc]));
|
|
20730
|
+
for (const base of baseline.testCases) {
|
|
20731
|
+
const now = current.get(base.id);
|
|
20732
|
+
if (!now) {
|
|
20733
|
+
violations.push({ id: base.id, title: base.story.scenario, kind: "removed", detail: "scenario no longer present" });
|
|
20734
|
+
continue;
|
|
20735
|
+
}
|
|
20736
|
+
if (ACTIVE.includes(base.status) && (now.status === "skipped" || now.status === "pending")) {
|
|
20737
|
+
violations.push({ id: base.id, title: base.story.scenario, kind: "disabled", detail: `${base.status} -> ${now.status}` });
|
|
20738
|
+
}
|
|
20739
|
+
const baseSteps = base.story.steps.length;
|
|
20740
|
+
const nowSteps = now.story.steps.length;
|
|
20741
|
+
if (nowSteps < baseSteps) {
|
|
20742
|
+
violations.push({ id: base.id, title: base.story.scenario, kind: "weakened", detail: `${baseSteps} steps -> ${nowSteps} steps` });
|
|
20743
|
+
}
|
|
20744
|
+
}
|
|
20745
|
+
}
|
|
20746
|
+
const met = requirements.every((r) => r.met) && regressions.length === 0 && violations.length === 0;
|
|
20747
|
+
return {
|
|
20748
|
+
met,
|
|
20749
|
+
requirements,
|
|
20750
|
+
regressions,
|
|
20751
|
+
regressionsEnforced: Boolean(baseline && args.enforceNoRegressions),
|
|
20752
|
+
ratchet: { enforced: Boolean(baseline && args.enforceRatchet), violations }
|
|
20753
|
+
};
|
|
20754
|
+
}
|
|
20755
|
+
function evaluate(selector, matched) {
|
|
20756
|
+
const passed = matched.filter((tc) => tc.status === "passed").length;
|
|
20757
|
+
const failing = matched.filter((tc) => tc.status !== "passed").map((tc) => tc.story.scenario);
|
|
20758
|
+
return {
|
|
20759
|
+
selector,
|
|
20760
|
+
matched: matched.length,
|
|
20761
|
+
passed,
|
|
20762
|
+
failing,
|
|
20763
|
+
met: matched.length > 0 && failing.length === 0
|
|
20764
|
+
};
|
|
20765
|
+
}
|
|
20766
|
+
function statusMap(run) {
|
|
20767
|
+
return new Map(run.testCases.map((tc) => [tc.id, tc.status]));
|
|
20768
|
+
}
|
|
20769
|
+
function renderGoal(report, format) {
|
|
20770
|
+
if (format === "json") return JSON.stringify(report, null, 2);
|
|
20771
|
+
const lines = [`GOAL: ${report.met ? "met" : "not met"}`];
|
|
20772
|
+
for (const req of report.requirements) {
|
|
20773
|
+
if (req.matched === 0) {
|
|
20774
|
+
lines.push(` ${req.selector}: no matching scenario (no proof)`);
|
|
20775
|
+
continue;
|
|
20776
|
+
}
|
|
20777
|
+
const tail = req.failing.length > 0 ? ` (${req.failing.length} failing)` : "";
|
|
20778
|
+
lines.push(` ${req.selector}: ${req.passed}/${req.matched} scenarios pass${tail}`);
|
|
20779
|
+
}
|
|
20780
|
+
if (report.regressionsEnforced) {
|
|
20781
|
+
if (report.regressions.length === 0) {
|
|
20782
|
+
lines.push(" regressions: 0");
|
|
20783
|
+
} else {
|
|
20784
|
+
lines.push(` regressions: ${report.regressions.length} (${report.regressions.map((r) => r.title).join(", ")})`);
|
|
20785
|
+
}
|
|
20786
|
+
}
|
|
20787
|
+
if (report.ratchet.enforced) {
|
|
20788
|
+
if (report.ratchet.violations.length === 0) {
|
|
20789
|
+
lines.push(" ratchet: clean (0 scenarios removed/weakened)");
|
|
20790
|
+
} else {
|
|
20791
|
+
lines.push(` ratchet: ${report.ratchet.violations.length} removed/weakened`);
|
|
20792
|
+
for (const v of report.ratchet.violations) {
|
|
20793
|
+
lines.push(` ${v.kind}: ${v.title} (${v.detail})`);
|
|
20794
|
+
}
|
|
20795
|
+
}
|
|
20796
|
+
}
|
|
20797
|
+
return lines.join("\n");
|
|
20798
|
+
}
|
|
20799
|
+
|
|
20800
|
+
// src/triage.ts
|
|
20801
|
+
function buildTriage(args, _deps = {}) {
|
|
20802
|
+
const { testCases, baseline } = args;
|
|
20803
|
+
const failing = testCases.filter((tc) => tc.status === "failed");
|
|
20804
|
+
const ranked = failing.map((tc) => {
|
|
20805
|
+
const regressed = baseline?.get(tc.id) === "passed";
|
|
20806
|
+
return {
|
|
20807
|
+
tc,
|
|
20808
|
+
regressed,
|
|
20809
|
+
covers: tc.story.covers ?? []
|
|
20810
|
+
};
|
|
20811
|
+
}).sort((a, b) => {
|
|
20812
|
+
if (a.regressed !== b.regressed) return a.regressed ? -1 : 1;
|
|
20813
|
+
const la = `${a.tc.sourceFile}:${a.tc.sourceLine}`;
|
|
20814
|
+
const lb = `${b.tc.sourceFile}:${b.tc.sourceLine}`;
|
|
20815
|
+
return la.localeCompare(lb);
|
|
20816
|
+
});
|
|
20817
|
+
const items = ranked.map((entry, index) => ({
|
|
20818
|
+
rank: index + 1,
|
|
20819
|
+
id: entry.tc.id,
|
|
20820
|
+
scenario: entry.tc.story.scenario,
|
|
20821
|
+
status: entry.tc.status,
|
|
20822
|
+
location: `${entry.tc.sourceFile}:${entry.tc.sourceLine}`,
|
|
20823
|
+
covers: entry.covers,
|
|
20824
|
+
tickets: (entry.tc.story.tickets ?? []).map((t) => t.id),
|
|
20825
|
+
errorMessage: failingScenarioMessage(entry.tc),
|
|
20826
|
+
regressed: entry.regressed,
|
|
20827
|
+
reason: entry.regressed ? "regression" : "failing"
|
|
20828
|
+
}));
|
|
20829
|
+
return {
|
|
20830
|
+
total: testCases.length,
|
|
20831
|
+
failing: failing.length,
|
|
20832
|
+
regressions: items.filter((i) => i.regressed).length,
|
|
20833
|
+
needsCovers: items.filter((i) => i.covers.length === 0).length,
|
|
20834
|
+
items
|
|
20835
|
+
};
|
|
20836
|
+
}
|
|
20837
|
+
function renderTriage(report, format) {
|
|
20838
|
+
if (format === "json") return JSON.stringify(report, null, 2);
|
|
20839
|
+
if (report.items.length === 0) {
|
|
20840
|
+
return "Nothing to triage. No failing scenarios.";
|
|
20841
|
+
}
|
|
20842
|
+
const header = report.regressions > 0 ? `${report.items.length} items to triage (${report.regressions} regression${report.regressions === 1 ? "" : "s"})` : `${report.items.length} items to triage`;
|
|
20843
|
+
const lines = [header, ""];
|
|
20844
|
+
for (const item of report.items) {
|
|
20845
|
+
const tag = item.regressed ? "[regression] " : "";
|
|
20846
|
+
lines.push(`${item.rank}. ${tag}${item.scenario}`);
|
|
20847
|
+
lines.push(` ${item.location}`);
|
|
20848
|
+
if (item.errorMessage) {
|
|
20849
|
+
lines.push(` \u2192 ${item.errorMessage.split("\n")[0]}`);
|
|
20850
|
+
}
|
|
20851
|
+
if (item.covers.length > 0) {
|
|
20852
|
+
lines.push(` fix: ${item.covers.join(", ")}`);
|
|
20853
|
+
} else {
|
|
20854
|
+
lines.push(" fix: (no covers declared \u2014 add `covers` to route this to code)");
|
|
20855
|
+
}
|
|
20856
|
+
if (item.tickets.length > 0) {
|
|
20857
|
+
lines.push(` ticket: ${item.tickets.join(", ")}`);
|
|
20858
|
+
}
|
|
20859
|
+
lines.push("");
|
|
20860
|
+
}
|
|
20861
|
+
if (report.needsCovers > 0) {
|
|
20862
|
+
lines.push(`${report.needsCovers} failing scenario(s) have no covers and can't be routed to code automatically.`);
|
|
20863
|
+
}
|
|
20864
|
+
return lines.join("\n").trimEnd();
|
|
20865
|
+
}
|
|
20866
|
+
|
|
20467
20867
|
// src/review/conventions.ts
|
|
20468
20868
|
var CHANGE_TAG_PREFIX = "change:";
|
|
20469
20869
|
var AUDIENCE_TAG_PREFIX = "audience:";
|
|
@@ -21265,6 +21665,7 @@ var FORMAT_EXTENSIONS = {
|
|
|
21265
21665
|
"behavior-manifest-json": ".behavior-manifest.json",
|
|
21266
21666
|
markdown: ".md",
|
|
21267
21667
|
"release-manifest": ".release-manifest.md",
|
|
21668
|
+
"traceability-matrix": ".traceability-matrix.md",
|
|
21268
21669
|
html: ".html",
|
|
21269
21670
|
"cucumber-html": ".cucumber.html",
|
|
21270
21671
|
junit: ".junit.xml",
|
|
@@ -21691,6 +22092,10 @@ var ReportGenerator = class {
|
|
|
21691
22092
|
const formatter = new ReleaseManifestFormatter();
|
|
21692
22093
|
return formatter.format(run);
|
|
21693
22094
|
}
|
|
22095
|
+
case "traceability-matrix": {
|
|
22096
|
+
const formatter = new TraceabilityMatrixFormatter();
|
|
22097
|
+
return formatter.format(run);
|
|
22098
|
+
}
|
|
21694
22099
|
case "story-report-json": {
|
|
21695
22100
|
const formatter = new StoryReportJsonFormatter({
|
|
21696
22101
|
pretty: this.options.storyReportJson.pretty
|
|
@@ -21770,12 +22175,16 @@ export {
|
|
|
21770
22175
|
STORY_REPORT_SCHEMA_VERSION,
|
|
21771
22176
|
ScenarioIndexJsonFormatter,
|
|
21772
22177
|
StoryReportJsonFormatter,
|
|
22178
|
+
TraceabilityMatrixFormatter,
|
|
21773
22179
|
adaptJestRun,
|
|
21774
22180
|
adaptPlaywrightRun,
|
|
21775
22181
|
adaptVitestRun,
|
|
21776
22182
|
assertValidRun,
|
|
22183
|
+
buildCheck,
|
|
22184
|
+
buildGoal,
|
|
21777
22185
|
buildHtmlDocEntry,
|
|
21778
22186
|
buildReview,
|
|
22187
|
+
buildTriage,
|
|
21779
22188
|
bundleAssets,
|
|
21780
22189
|
calculateFlakiness,
|
|
21781
22190
|
calculateStability,
|
|
@@ -21825,6 +22234,9 @@ export {
|
|
|
21825
22234
|
readPackageVersion,
|
|
21826
22235
|
recordDeployment,
|
|
21827
22236
|
regenerateArtifacts,
|
|
22237
|
+
renderCheck,
|
|
22238
|
+
renderGoal,
|
|
22239
|
+
renderTriage,
|
|
21828
22240
|
resolveAttachment,
|
|
21829
22241
|
resolveAttachments,
|
|
21830
22242
|
resolveTheme,
|
|
@@ -21846,6 +22258,7 @@ export {
|
|
|
21846
22258
|
toReleaseManifest,
|
|
21847
22259
|
toScenarioIndex,
|
|
21848
22260
|
toStoryReport,
|
|
22261
|
+
toTraceabilityMatrix,
|
|
21849
22262
|
tryGetActiveOtelContext,
|
|
21850
22263
|
updateHistory,
|
|
21851
22264
|
validateCanonicalRun
|