executable-stories-formatters 0.7.14 → 0.8.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 +19 -0
- package/dist/adapters.d.cts +1 -1
- package/dist/adapters.d.ts +1 -1
- package/dist/cli.js +706 -62
- package/dist/cli.js.map +1 -1
- package/dist/{index-fqrm5-Xr.d.cts → index-BiAYcEiz.d.cts} +1 -1
- package/dist/{index-fqrm5-Xr.d.ts → index-BiAYcEiz.d.ts} +1 -1
- package/dist/index.cjs +677 -42
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +275 -14
- package/dist/index.d.ts +275 -14
- package/dist/index.js +671 -42
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/schemas/story-report-v1.json +410 -0
package/dist/cli.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { parseArgs } from "util";
|
|
5
5
|
import * as fs8 from "fs";
|
|
6
|
-
import * as
|
|
6
|
+
import * as path9 from "path";
|
|
7
7
|
|
|
8
8
|
// src/validation/schema-validator.ts
|
|
9
9
|
import Ajv from "ajv/dist/2020.js";
|
|
@@ -492,17 +492,17 @@ function validateRawRun(data) {
|
|
|
492
492
|
return { valid: true, errors: [] };
|
|
493
493
|
}
|
|
494
494
|
const errors = (validate.errors ?? []).map((err) => {
|
|
495
|
-
const
|
|
495
|
+
const path10 = err.instancePath || "/";
|
|
496
496
|
const message = err.message ?? "unknown error";
|
|
497
497
|
if (err.keyword === "additionalProperties") {
|
|
498
498
|
const extra = err.params.additionalProperty;
|
|
499
|
-
return `${
|
|
499
|
+
return `${path10}: ${message} \u2014 '${extra}'`;
|
|
500
500
|
}
|
|
501
501
|
if (err.keyword === "enum") {
|
|
502
502
|
const allowed = err.params.allowedValues;
|
|
503
|
-
return `${
|
|
503
|
+
return `${path10}: ${message} \u2014 allowed: ${JSON.stringify(allowed)}`;
|
|
504
504
|
}
|
|
505
|
-
return `${
|
|
505
|
+
return `${path10}: ${message}`;
|
|
506
506
|
});
|
|
507
507
|
return { valid: false, errors };
|
|
508
508
|
}
|
|
@@ -976,7 +976,7 @@ ${result.errors.join("\n")}`);
|
|
|
976
976
|
|
|
977
977
|
// src/index.ts
|
|
978
978
|
import "fs";
|
|
979
|
-
import * as
|
|
979
|
+
import * as path7 from "path";
|
|
980
980
|
import * as fsPromises from "fs/promises";
|
|
981
981
|
|
|
982
982
|
// src/converters/acl/lines.ts
|
|
@@ -1355,9 +1355,298 @@ ${doc.markdown}`,
|
|
|
1355
1355
|
}
|
|
1356
1356
|
};
|
|
1357
1357
|
|
|
1358
|
+
// src/converters/story-report.ts
|
|
1359
|
+
import { posix as path2 } from "path";
|
|
1360
|
+
|
|
1361
|
+
// src/types/story-report.ts
|
|
1362
|
+
var STORY_REPORT_SCHEMA_VERSION = "1.0";
|
|
1363
|
+
|
|
1364
|
+
// src/converters/story-report.ts
|
|
1365
|
+
function reportSlug(text2) {
|
|
1366
|
+
return text2.toLowerCase().replace(/[/\\.]+/g, "-").replace(/[^\w\s-]/g, "").replace(/[\s_]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
|
|
1367
|
+
}
|
|
1368
|
+
function toRelativeSourceFile(sourceFile, projectRoot) {
|
|
1369
|
+
if (!sourceFile) return sourceFile;
|
|
1370
|
+
const normalized = sourceFile.split(path2.sep).join("/");
|
|
1371
|
+
const root = projectRoot.split(path2.sep).join("/").replace(/\/$/, "");
|
|
1372
|
+
if (root && normalized.startsWith(root + "/")) return normalized.slice(root.length + 1);
|
|
1373
|
+
return normalized;
|
|
1374
|
+
}
|
|
1375
|
+
function fileBasenameTitle(sourceFile) {
|
|
1376
|
+
const base = sourceFile.split("/").pop() ?? sourceFile;
|
|
1377
|
+
return base.replace(/\.(story\.)?(test|spec)\.[tj]sx?$/, "").replace(/\.[tj]sx?$/, "");
|
|
1378
|
+
}
|
|
1379
|
+
function emptySummary() {
|
|
1380
|
+
return { total: 0, passed: 0, failed: 0, skipped: 0, pending: 0, durationMs: 0 };
|
|
1381
|
+
}
|
|
1382
|
+
function addToSummary(summary, status, durationMs) {
|
|
1383
|
+
summary.total += 1;
|
|
1384
|
+
summary[status] += 1;
|
|
1385
|
+
summary.durationMs += durationMs;
|
|
1386
|
+
}
|
|
1387
|
+
function isKeyword(value) {
|
|
1388
|
+
return value === "Given" || value === "When" || value === "Then" || value === "And" || value === "But";
|
|
1389
|
+
}
|
|
1390
|
+
function copyDocEntries(entries) {
|
|
1391
|
+
if (!entries || entries.length === 0) return [];
|
|
1392
|
+
return entries.map(copyDocEntry);
|
|
1393
|
+
}
|
|
1394
|
+
function copyDocEntry(entry) {
|
|
1395
|
+
const children = entry.children ? { children: copyDocEntries(entry.children) } : {};
|
|
1396
|
+
switch (entry.kind) {
|
|
1397
|
+
case "note":
|
|
1398
|
+
return { kind: "note", text: entry.text, phase: entry.phase, ...children };
|
|
1399
|
+
case "tag":
|
|
1400
|
+
return { kind: "tag", names: [...entry.names], phase: entry.phase, ...children };
|
|
1401
|
+
case "kv":
|
|
1402
|
+
return { kind: "kv", label: entry.label, value: entry.value, phase: entry.phase, ...children };
|
|
1403
|
+
case "code":
|
|
1404
|
+
return {
|
|
1405
|
+
kind: "code",
|
|
1406
|
+
label: entry.label,
|
|
1407
|
+
content: entry.content,
|
|
1408
|
+
...entry.lang ? { lang: entry.lang } : {},
|
|
1409
|
+
phase: entry.phase,
|
|
1410
|
+
...children
|
|
1411
|
+
};
|
|
1412
|
+
case "table":
|
|
1413
|
+
return {
|
|
1414
|
+
kind: "table",
|
|
1415
|
+
label: entry.label,
|
|
1416
|
+
columns: [...entry.columns],
|
|
1417
|
+
rows: entry.rows.map((r) => [...r]),
|
|
1418
|
+
phase: entry.phase,
|
|
1419
|
+
...children
|
|
1420
|
+
};
|
|
1421
|
+
case "link":
|
|
1422
|
+
return { kind: "link", label: entry.label, url: entry.url, phase: entry.phase, ...children };
|
|
1423
|
+
case "section":
|
|
1424
|
+
return { kind: "section", title: entry.title, markdown: entry.markdown, phase: entry.phase, ...children };
|
|
1425
|
+
case "mermaid":
|
|
1426
|
+
return {
|
|
1427
|
+
kind: "mermaid",
|
|
1428
|
+
code: entry.code,
|
|
1429
|
+
...entry.title ? { title: entry.title } : {},
|
|
1430
|
+
phase: entry.phase,
|
|
1431
|
+
...children
|
|
1432
|
+
};
|
|
1433
|
+
case "screenshot":
|
|
1434
|
+
return {
|
|
1435
|
+
kind: "screenshot",
|
|
1436
|
+
path: entry.path,
|
|
1437
|
+
...entry.alt ? { alt: entry.alt } : {},
|
|
1438
|
+
phase: entry.phase,
|
|
1439
|
+
...children
|
|
1440
|
+
};
|
|
1441
|
+
case "custom":
|
|
1442
|
+
return {
|
|
1443
|
+
kind: "custom",
|
|
1444
|
+
type: entry.type,
|
|
1445
|
+
data: entry.data,
|
|
1446
|
+
phase: entry.phase,
|
|
1447
|
+
...children
|
|
1448
|
+
};
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
function buildStep(args) {
|
|
1452
|
+
const step = {
|
|
1453
|
+
id: `${args.scenarioId}--step-${args.index}`,
|
|
1454
|
+
index: args.index,
|
|
1455
|
+
keyword: args.keyword,
|
|
1456
|
+
text: args.text,
|
|
1457
|
+
status: args.status,
|
|
1458
|
+
durationMs: args.durationMs,
|
|
1459
|
+
docEntries: args.docEntries
|
|
1460
|
+
};
|
|
1461
|
+
if (args.errorMessage !== void 0) step.errorMessage = args.errorMessage;
|
|
1462
|
+
if (args.mode !== void 0) step.mode = args.mode;
|
|
1463
|
+
return step;
|
|
1464
|
+
}
|
|
1465
|
+
function buildSteps(scenarioId, tc) {
|
|
1466
|
+
const declared = tc.story.steps ?? [];
|
|
1467
|
+
const results = tc.stepResults;
|
|
1468
|
+
const max = Math.max(declared.length, results.length);
|
|
1469
|
+
const steps = [];
|
|
1470
|
+
for (let i = 0; i < max; i++) {
|
|
1471
|
+
const decl = declared[i];
|
|
1472
|
+
const res = results[i];
|
|
1473
|
+
const keywordSource = decl?.keyword;
|
|
1474
|
+
const keyword = keywordSource && isKeyword(keywordSource) ? keywordSource : "Given";
|
|
1475
|
+
const text2 = decl?.text ?? "";
|
|
1476
|
+
const status = res?.status ?? "pending";
|
|
1477
|
+
const durationMs = res?.durationMs ?? decl?.durationMs ?? 0;
|
|
1478
|
+
const docEntries = copyDocEntries(decl?.docs);
|
|
1479
|
+
steps.push(buildStep({
|
|
1480
|
+
scenarioId,
|
|
1481
|
+
index: i,
|
|
1482
|
+
keyword,
|
|
1483
|
+
text: text2,
|
|
1484
|
+
status,
|
|
1485
|
+
durationMs,
|
|
1486
|
+
...res?.errorMessage !== void 0 ? { errorMessage: res.errorMessage } : {},
|
|
1487
|
+
...decl?.mode !== void 0 ? { mode: decl.mode } : {},
|
|
1488
|
+
docEntries
|
|
1489
|
+
}));
|
|
1490
|
+
}
|
|
1491
|
+
return steps;
|
|
1492
|
+
}
|
|
1493
|
+
function buildAttachments(tc) {
|
|
1494
|
+
return tc.attachments.map((a) => ({
|
|
1495
|
+
name: a.name,
|
|
1496
|
+
mediaType: a.mediaType,
|
|
1497
|
+
body: a.body,
|
|
1498
|
+
contentEncoding: a.contentEncoding
|
|
1499
|
+
}));
|
|
1500
|
+
}
|
|
1501
|
+
function buildScenario(tc, featureId) {
|
|
1502
|
+
const titleRaw = tc.story.scenario?.trim() || "(untitled scenario)";
|
|
1503
|
+
const id = `${featureId}--${reportSlug(titleRaw) || `case-${tc.id}`}`;
|
|
1504
|
+
const steps = buildSteps(id, tc);
|
|
1505
|
+
const scenario = {
|
|
1506
|
+
id,
|
|
1507
|
+
title: titleRaw,
|
|
1508
|
+
status: tc.status,
|
|
1509
|
+
durationMs: tc.durationMs,
|
|
1510
|
+
tags: [...tc.tags],
|
|
1511
|
+
retry: tc.retry,
|
|
1512
|
+
retries: tc.retries,
|
|
1513
|
+
docEntries: copyDocEntries(tc.story.docs),
|
|
1514
|
+
steps,
|
|
1515
|
+
attachments: buildAttachments(tc)
|
|
1516
|
+
};
|
|
1517
|
+
if (tc.sourceLine && tc.sourceLine > 0) scenario.sourceLine = tc.sourceLine;
|
|
1518
|
+
if (tc.errorMessage !== void 0) scenario.errorMessage = tc.errorMessage;
|
|
1519
|
+
if (tc.errorStack !== void 0) scenario.errorStack = tc.errorStack;
|
|
1520
|
+
const tickets = tc.story.tickets;
|
|
1521
|
+
if (tickets && tickets.length > 0) {
|
|
1522
|
+
scenario.tickets = tickets.map((t) => t.url ? { id: t.id, url: t.url } : { id: t.id });
|
|
1523
|
+
}
|
|
1524
|
+
return scenario;
|
|
1525
|
+
}
|
|
1526
|
+
function deriveFeatureTitle(group, relSourceFile) {
|
|
1527
|
+
for (const tc of group) {
|
|
1528
|
+
const head = tc.titlePath?.[0];
|
|
1529
|
+
if (head && head.trim()) return head.trim();
|
|
1530
|
+
}
|
|
1531
|
+
return fileBasenameTitle(relSourceFile);
|
|
1532
|
+
}
|
|
1533
|
+
function compareScenarios(a, b) {
|
|
1534
|
+
const aLine = a.sourceLine ?? Number.POSITIVE_INFINITY;
|
|
1535
|
+
const bLine = b.sourceLine ?? Number.POSITIVE_INFINITY;
|
|
1536
|
+
if (aLine !== bLine) return aLine - bLine;
|
|
1537
|
+
return a.title.localeCompare(b.title);
|
|
1538
|
+
}
|
|
1539
|
+
function buildFeature(relSourceFile, group) {
|
|
1540
|
+
const id = `feature-${reportSlug(relSourceFile.replace(/\.[^.]+$/, "")) || "untitled"}`;
|
|
1541
|
+
const title = deriveFeatureTitle(group, relSourceFile);
|
|
1542
|
+
const summary = emptySummary();
|
|
1543
|
+
const scenarios = [];
|
|
1544
|
+
for (const tc of group) {
|
|
1545
|
+
const scenario = buildScenario(tc, id);
|
|
1546
|
+
scenarios.push(scenario);
|
|
1547
|
+
addToSummary(summary, scenario.status, scenario.durationMs);
|
|
1548
|
+
}
|
|
1549
|
+
scenarios.sort(compareScenarios);
|
|
1550
|
+
return { id, title, sourceFile: relSourceFile, summary, scenarios };
|
|
1551
|
+
}
|
|
1552
|
+
function ensureUniqueFeatureIds(features) {
|
|
1553
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1554
|
+
for (const f of features) {
|
|
1555
|
+
const count = seen.get(f.id) ?? 0;
|
|
1556
|
+
if (count > 0) f.id = `${f.id}-${count + 1}`;
|
|
1557
|
+
seen.set(f.id, count + 1);
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
function ensureUniqueScenarioIds(feature) {
|
|
1561
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1562
|
+
for (const s of feature.scenarios) {
|
|
1563
|
+
const count = seen.get(s.id) ?? 0;
|
|
1564
|
+
if (count > 0) {
|
|
1565
|
+
const newId = `${s.id}-${count + 1}`;
|
|
1566
|
+
for (const step of s.steps) {
|
|
1567
|
+
step.id = step.id.replace(s.id, newId);
|
|
1568
|
+
}
|
|
1569
|
+
s.id = newId;
|
|
1570
|
+
}
|
|
1571
|
+
seen.set(s.id, count + 1);
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
function toStoryReport(run) {
|
|
1575
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1576
|
+
for (const tc of run.testCases) {
|
|
1577
|
+
const rel = toRelativeSourceFile(tc.sourceFile, run.projectRoot);
|
|
1578
|
+
const existing = groups.get(rel);
|
|
1579
|
+
if (existing) existing.push(tc);
|
|
1580
|
+
else groups.set(rel, [tc]);
|
|
1581
|
+
}
|
|
1582
|
+
const features = [];
|
|
1583
|
+
for (const [rel, group] of groups) {
|
|
1584
|
+
features.push(buildFeature(rel, group));
|
|
1585
|
+
}
|
|
1586
|
+
features.sort((a, b) => a.title.localeCompare(b.title));
|
|
1587
|
+
ensureUniqueFeatureIds(features);
|
|
1588
|
+
for (const f of features) ensureUniqueScenarioIds(f);
|
|
1589
|
+
const summary = emptySummary();
|
|
1590
|
+
for (const f of features) {
|
|
1591
|
+
summary.total += f.summary.total;
|
|
1592
|
+
summary.passed += f.summary.passed;
|
|
1593
|
+
summary.failed += f.summary.failed;
|
|
1594
|
+
summary.skipped += f.summary.skipped;
|
|
1595
|
+
summary.pending += f.summary.pending;
|
|
1596
|
+
summary.durationMs += f.summary.durationMs;
|
|
1597
|
+
}
|
|
1598
|
+
const report = {
|
|
1599
|
+
schemaVersion: STORY_REPORT_SCHEMA_VERSION,
|
|
1600
|
+
runId: run.runId,
|
|
1601
|
+
startedAtMs: run.startedAtMs,
|
|
1602
|
+
finishedAtMs: run.finishedAtMs,
|
|
1603
|
+
durationMs: run.durationMs,
|
|
1604
|
+
projectRoot: run.projectRoot,
|
|
1605
|
+
summary,
|
|
1606
|
+
features
|
|
1607
|
+
};
|
|
1608
|
+
if (run.packageVersion) report.packageVersion = run.packageVersion;
|
|
1609
|
+
if (run.gitSha) report.gitSha = run.gitSha;
|
|
1610
|
+
if (run.ci) {
|
|
1611
|
+
const ci = { name: run.ci.name };
|
|
1612
|
+
if (run.ci.url) ci.url = run.ci.url;
|
|
1613
|
+
if (run.ci.buildNumber) ci.buildNumber = run.ci.buildNumber;
|
|
1614
|
+
if (run.ci.branch) ci.branch = run.ci.branch;
|
|
1615
|
+
if (run.ci.commitSha) ci.commitSha = run.ci.commitSha;
|
|
1616
|
+
if (run.ci.prNumber) ci.prNumber = run.ci.prNumber;
|
|
1617
|
+
report.ci = ci;
|
|
1618
|
+
}
|
|
1619
|
+
if (run.coverage) {
|
|
1620
|
+
const cov = {};
|
|
1621
|
+
if (run.coverage.linesPct !== void 0) cov.linesPct = run.coverage.linesPct;
|
|
1622
|
+
if (run.coverage.branchesPct !== void 0) cov.branchesPct = run.coverage.branchesPct;
|
|
1623
|
+
if (run.coverage.functionsPct !== void 0) cov.functionsPct = run.coverage.functionsPct;
|
|
1624
|
+
if (run.coverage.statementsPct !== void 0) cov.statementsPct = run.coverage.statementsPct;
|
|
1625
|
+
report.coverage = cov;
|
|
1626
|
+
}
|
|
1627
|
+
return report;
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
// src/formatters/story-report-json.ts
|
|
1631
|
+
var StoryReportJsonFormatter = class {
|
|
1632
|
+
options;
|
|
1633
|
+
constructor(options = {}) {
|
|
1634
|
+
this.options = {
|
|
1635
|
+
pretty: options.pretty ?? true
|
|
1636
|
+
};
|
|
1637
|
+
}
|
|
1638
|
+
toReport(run) {
|
|
1639
|
+
return toStoryReport(run);
|
|
1640
|
+
}
|
|
1641
|
+
format(run) {
|
|
1642
|
+
const report = toStoryReport(run);
|
|
1643
|
+
return this.options.pretty ? JSON.stringify(report, null, 2) : JSON.stringify(report);
|
|
1644
|
+
}
|
|
1645
|
+
};
|
|
1646
|
+
|
|
1358
1647
|
// src/formatters/html/renderers/index.ts
|
|
1359
1648
|
import * as fs2 from "fs";
|
|
1360
|
-
import * as
|
|
1649
|
+
import * as path3 from "path";
|
|
1361
1650
|
|
|
1362
1651
|
// src/formatters/html/template.ts
|
|
1363
1652
|
var JS_THEME = `
|
|
@@ -1708,11 +1997,53 @@ function initKeyboardShortcuts() {
|
|
|
1708
1997
|
});
|
|
1709
1998
|
}
|
|
1710
1999
|
|
|
1711
|
-
// Collapse/expand functionality
|
|
2000
|
+
// Collapse/expand functionality (persisted in localStorage)
|
|
2001
|
+
var COLLAPSE_KEY = 'es-collapsed-ids';
|
|
2002
|
+
|
|
2003
|
+
function loadCollapseState() {
|
|
2004
|
+
try {
|
|
2005
|
+
var raw = localStorage.getItem(COLLAPSE_KEY);
|
|
2006
|
+
return raw ? new Set(JSON.parse(raw)) : new Set();
|
|
2007
|
+
} catch (e) {
|
|
2008
|
+
return new Set();
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
|
|
2012
|
+
function saveCollapseState(set) {
|
|
2013
|
+
try {
|
|
2014
|
+
localStorage.setItem(COLLAPSE_KEY, JSON.stringify(Array.from(set)));
|
|
2015
|
+
} catch (e) { /* quota or disabled */ }
|
|
2016
|
+
}
|
|
2017
|
+
|
|
2018
|
+
function persistCollapse(container) {
|
|
2019
|
+
if (!container || !container.id) return;
|
|
2020
|
+
var state = loadCollapseState();
|
|
2021
|
+
if (container.classList.contains('collapsed')) {
|
|
2022
|
+
state.add(container.id);
|
|
2023
|
+
} else {
|
|
2024
|
+
state.delete(container.id);
|
|
2025
|
+
}
|
|
2026
|
+
saveCollapseState(state);
|
|
2027
|
+
}
|
|
2028
|
+
|
|
1712
2029
|
function toggleCollapse(header, container) {
|
|
1713
2030
|
container?.classList.toggle('collapsed');
|
|
1714
2031
|
const isCollapsed = container?.classList.contains('collapsed');
|
|
1715
2032
|
header.setAttribute('aria-expanded', !isCollapsed);
|
|
2033
|
+
persistCollapse(container);
|
|
2034
|
+
}
|
|
2035
|
+
|
|
2036
|
+
function restoreCollapseState() {
|
|
2037
|
+
var state = loadCollapseState();
|
|
2038
|
+
if (state.size === 0) return;
|
|
2039
|
+
state.forEach(function(id) {
|
|
2040
|
+
var el = document.getElementById(id);
|
|
2041
|
+
if (!el) return;
|
|
2042
|
+
if (!el.classList.contains('feature') && !el.classList.contains('scenario')) return;
|
|
2043
|
+
el.classList.add('collapsed');
|
|
2044
|
+
var header = el.querySelector('.feature-header, .scenario-header');
|
|
2045
|
+
if (header) header.setAttribute('aria-expanded', 'false');
|
|
2046
|
+
});
|
|
1716
2047
|
}
|
|
1717
2048
|
|
|
1718
2049
|
function initCollapse() {
|
|
@@ -1759,14 +2090,20 @@ function expandAll() {
|
|
|
1759
2090
|
const header = el.querySelector('.feature-header, .scenario-header, .trace-view-header');
|
|
1760
2091
|
header?.setAttribute('aria-expanded', 'true');
|
|
1761
2092
|
});
|
|
2093
|
+
saveCollapseState(new Set());
|
|
1762
2094
|
}
|
|
1763
2095
|
|
|
1764
2096
|
function collapseAll() {
|
|
2097
|
+
var ids = new Set();
|
|
1765
2098
|
document.querySelectorAll('.feature, .scenario, .trace-view').forEach(el => {
|
|
1766
2099
|
el.classList.add('collapsed');
|
|
1767
2100
|
const header = el.querySelector('.feature-header, .scenario-header, .trace-view-header');
|
|
1768
2101
|
header?.setAttribute('aria-expanded', 'false');
|
|
2102
|
+
if (el.id && (el.classList.contains('feature') || el.classList.contains('scenario'))) {
|
|
2103
|
+
ids.add(el.id);
|
|
2104
|
+
}
|
|
1769
2105
|
});
|
|
2106
|
+
saveCollapseState(ids);
|
|
1770
2107
|
}
|
|
1771
2108
|
|
|
1772
2109
|
// Detail level toggle
|
|
@@ -1893,6 +2230,68 @@ function copyScenarioAsMarkdown(scenarioId) {
|
|
|
1893
2230
|
});
|
|
1894
2231
|
}
|
|
1895
2232
|
|
|
2233
|
+
// Copy scenario as Claude-ready prompt (failure investigation context)
|
|
2234
|
+
function copyScenarioAsPrompt(scenarioId) {
|
|
2235
|
+
var scenario = document.getElementById(scenarioId);
|
|
2236
|
+
if (!scenario) return;
|
|
2237
|
+
|
|
2238
|
+
var feature = scenario.closest('.feature');
|
|
2239
|
+
var featureTitle = feature ? (feature.querySelector('.feature-title') || {}).textContent || '' : '';
|
|
2240
|
+
var title = (scenario.querySelector('.scenario-name') || {}).textContent || '';
|
|
2241
|
+
var statusEl = scenario.querySelector('.status-icon');
|
|
2242
|
+
var status = statusEl && statusEl.classList.contains('status-passed') ? 'passed' :
|
|
2243
|
+
statusEl && statusEl.classList.contains('status-failed') ? 'failed' :
|
|
2244
|
+
statusEl && statusEl.classList.contains('status-skipped') ? 'skipped' : 'pending';
|
|
2245
|
+
var sourceLink = scenario.querySelector('.source-link');
|
|
2246
|
+
var source = sourceLink ? sourceLink.textContent || '' : '';
|
|
2247
|
+
var tags = Array.from(scenario.querySelectorAll('.scenario-meta .tag')).map(function(t) { return t.textContent.trim(); });
|
|
2248
|
+
var steps = scenario.querySelectorAll('.step, .step.continuation');
|
|
2249
|
+
|
|
2250
|
+
var lines = [];
|
|
2251
|
+
lines.push('I need help investigating a failing executable-story scenario.');
|
|
2252
|
+
lines.push('');
|
|
2253
|
+
if (featureTitle.trim()) lines.push('Feature: ' + featureTitle.trim());
|
|
2254
|
+
lines.push('Scenario: ' + title.trim());
|
|
2255
|
+
lines.push('Status: ' + status);
|
|
2256
|
+
if (source.trim()) lines.push('Source: ' + source.trim());
|
|
2257
|
+
if (tags.length > 0) lines.push('Tags: ' + tags.join(', '));
|
|
2258
|
+
lines.push('');
|
|
2259
|
+
lines.push('Steps:');
|
|
2260
|
+
steps.forEach(function(step) {
|
|
2261
|
+
var keyword = step.getAttribute('data-keyword') || '';
|
|
2262
|
+
var text = step.getAttribute('data-text') || '';
|
|
2263
|
+
var stepStatusEl = step.querySelector('.step-status');
|
|
2264
|
+
var marker = ' ';
|
|
2265
|
+
if (stepStatusEl) {
|
|
2266
|
+
if (stepStatusEl.classList.contains('status-failed')) marker = 'x ';
|
|
2267
|
+
else if (stepStatusEl.classList.contains('status-passed')) marker = '+ ';
|
|
2268
|
+
else if (stepStatusEl.classList.contains('status-skipped')) marker = '- ';
|
|
2269
|
+
}
|
|
2270
|
+
lines.push(marker + keyword + ' ' + text);
|
|
2271
|
+
});
|
|
2272
|
+
|
|
2273
|
+
var errorBox = scenario.querySelector('.error-message');
|
|
2274
|
+
if (errorBox) {
|
|
2275
|
+
lines.push('');
|
|
2276
|
+
lines.push('Error:');
|
|
2277
|
+
lines.push((errorBox.textContent || '').trim());
|
|
2278
|
+
}
|
|
2279
|
+
var stackBox = scenario.querySelector('.error-stack');
|
|
2280
|
+
if (stackBox) {
|
|
2281
|
+
lines.push('');
|
|
2282
|
+
lines.push('Stack:');
|
|
2283
|
+
lines.push((stackBox.textContent || '').trim());
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
lines.push('');
|
|
2287
|
+
lines.push('Please read the source file, identify the root cause, and propose a fix.');
|
|
2288
|
+
|
|
2289
|
+
var text = lines.join('\\n');
|
|
2290
|
+
navigator.clipboard.writeText(text).then(function() {
|
|
2291
|
+
showCopyToast(scenario);
|
|
2292
|
+
});
|
|
2293
|
+
}
|
|
2294
|
+
|
|
1896
2295
|
// Hash scroll on load
|
|
1897
2296
|
function initHashScroll() {
|
|
1898
2297
|
if (!location.hash) return;
|
|
@@ -2062,6 +2461,7 @@ function generateScript(options) {
|
|
|
2062
2461
|
initCalls.push("initStatusFilter();");
|
|
2063
2462
|
initCalls.push("initKeyboardShortcuts();");
|
|
2064
2463
|
initCalls.push("initCollapse();");
|
|
2464
|
+
initCalls.push("restoreCollapseState();");
|
|
2065
2465
|
initCalls.push("initDetailLevel();");
|
|
2066
2466
|
initCalls.push("applyAllFilters();");
|
|
2067
2467
|
initCalls.push("initHashScroll();");
|
|
@@ -2166,6 +2566,80 @@ function escapeHtml(str) {
|
|
|
2166
2566
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
2167
2567
|
}
|
|
2168
2568
|
|
|
2569
|
+
// src/theme/tokens.ts
|
|
2570
|
+
var ES_THEME_TOKENS_CSS = `
|
|
2571
|
+
:root,
|
|
2572
|
+
[data-theme="light"] {
|
|
2573
|
+
--es-color-bg: #ffffff;
|
|
2574
|
+
--es-color-fg: #111827;
|
|
2575
|
+
--es-color-muted: #6b7280;
|
|
2576
|
+
--es-color-border: #e5e7eb;
|
|
2577
|
+
--es-color-surface: #f9fafb;
|
|
2578
|
+
--es-color-link: #2563eb;
|
|
2579
|
+
--es-color-passed: #16a34a;
|
|
2580
|
+
--es-color-failed: #dc2626;
|
|
2581
|
+
--es-color-skipped: #9ca3af;
|
|
2582
|
+
--es-color-pending: #d97706;
|
|
2583
|
+
--es-color-passed-bg: #f0fdf4;
|
|
2584
|
+
--es-color-failed-bg: #fef2f2;
|
|
2585
|
+
--es-color-skipped-bg: #f3f4f6;
|
|
2586
|
+
--es-color-pending-bg: #fffbeb;
|
|
2587
|
+
--es-font-body: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
|
|
2588
|
+
--es-font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
|
|
2589
|
+
--es-size-base: 1rem;
|
|
2590
|
+
--es-size-sm: 0.875rem;
|
|
2591
|
+
--es-size-xs: 0.75rem;
|
|
2592
|
+
--es-size-h1: 1.875rem;
|
|
2593
|
+
--es-size-h2: 1.5rem;
|
|
2594
|
+
--es-size-h3: 1.25rem;
|
|
2595
|
+
--es-space-1: 0.25rem;
|
|
2596
|
+
--es-space-2: 0.5rem;
|
|
2597
|
+
--es-space-3: 0.75rem;
|
|
2598
|
+
--es-space-4: 1rem;
|
|
2599
|
+
--es-space-6: 1.5rem;
|
|
2600
|
+
--es-space-8: 2rem;
|
|
2601
|
+
--es-radius: 0.5rem;
|
|
2602
|
+
--es-line: 1.6;
|
|
2603
|
+
--es-measure: 72ch;
|
|
2604
|
+
}
|
|
2605
|
+
|
|
2606
|
+
@media (prefers-color-scheme: dark) {
|
|
2607
|
+
:root {
|
|
2608
|
+
--es-color-bg: #0b0f17;
|
|
2609
|
+
--es-color-fg: #e5e7eb;
|
|
2610
|
+
--es-color-muted: #9ca3af;
|
|
2611
|
+
--es-color-border: #1f2937;
|
|
2612
|
+
--es-color-surface: #111827;
|
|
2613
|
+
--es-color-link: #60a5fa;
|
|
2614
|
+
--es-color-passed: #4ade80;
|
|
2615
|
+
--es-color-failed: #f87171;
|
|
2616
|
+
--es-color-skipped: #6b7280;
|
|
2617
|
+
--es-color-pending: #fbbf24;
|
|
2618
|
+
--es-color-passed-bg: rgba(74, 222, 128, 0.08);
|
|
2619
|
+
--es-color-failed-bg: rgba(248, 113, 113, 0.08);
|
|
2620
|
+
--es-color-skipped-bg: rgba(107, 114, 128, 0.08);
|
|
2621
|
+
--es-color-pending-bg: rgba(251, 191, 36, 0.08);
|
|
2622
|
+
}
|
|
2623
|
+
}
|
|
2624
|
+
|
|
2625
|
+
[data-theme="dark"] {
|
|
2626
|
+
--es-color-bg: #0b0f17;
|
|
2627
|
+
--es-color-fg: #e5e7eb;
|
|
2628
|
+
--es-color-muted: #9ca3af;
|
|
2629
|
+
--es-color-border: #1f2937;
|
|
2630
|
+
--es-color-surface: #111827;
|
|
2631
|
+
--es-color-link: #60a5fa;
|
|
2632
|
+
--es-color-passed: #4ade80;
|
|
2633
|
+
--es-color-failed: #f87171;
|
|
2634
|
+
--es-color-skipped: #6b7280;
|
|
2635
|
+
--es-color-pending: #fbbf24;
|
|
2636
|
+
--es-color-passed-bg: rgba(74, 222, 128, 0.08);
|
|
2637
|
+
--es-color-failed-bg: rgba(248, 113, 113, 0.08);
|
|
2638
|
+
--es-color-skipped-bg: rgba(107, 114, 128, 0.08);
|
|
2639
|
+
--es-color-pending-bg: rgba(251, 191, 36, 0.08);
|
|
2640
|
+
}
|
|
2641
|
+
`.trim();
|
|
2642
|
+
|
|
2169
2643
|
// src/formatters/html/styles.ts
|
|
2170
2644
|
var CSS_STYLES = `
|
|
2171
2645
|
/* ============================================================================
|
|
@@ -2173,6 +2647,14 @@ var CSS_STYLES = `
|
|
|
2173
2647
|
============================================================================ */
|
|
2174
2648
|
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500&display=swap');
|
|
2175
2649
|
|
|
2650
|
+
/* ============================================================================
|
|
2651
|
+
executable-stories canonical tokens (--es-*).
|
|
2652
|
+
Shared with executable-stories-react. Override on :root or any ancestor of
|
|
2653
|
+
the report to re-color both the standalone HTML and the React component.
|
|
2654
|
+
============================================================================ */
|
|
2655
|
+
${ES_THEME_TOKENS_CSS}
|
|
2656
|
+
|
|
2657
|
+
|
|
2176
2658
|
/* ============================================================================
|
|
2177
2659
|
CSS Custom Properties - Light Mode (Default)
|
|
2178
2660
|
Cucumber-branded shadcn/ui base theme
|
|
@@ -4061,6 +4543,33 @@ body {
|
|
|
4061
4543
|
color: var(--primary);
|
|
4062
4544
|
}
|
|
4063
4545
|
|
|
4546
|
+
.copy-prompt-btn {
|
|
4547
|
+
display: inline-flex;
|
|
4548
|
+
align-items: center;
|
|
4549
|
+
justify-content: center;
|
|
4550
|
+
width: 1.5rem;
|
|
4551
|
+
height: 1.5rem;
|
|
4552
|
+
border: none;
|
|
4553
|
+
background: none;
|
|
4554
|
+
color: var(--muted-foreground);
|
|
4555
|
+
cursor: pointer;
|
|
4556
|
+
opacity: 0.6;
|
|
4557
|
+
transition: opacity 0.15s ease, transform 0.15s ease;
|
|
4558
|
+
font-size: 0.95rem;
|
|
4559
|
+
padding: 0;
|
|
4560
|
+
flex-shrink: 0;
|
|
4561
|
+
}
|
|
4562
|
+
|
|
4563
|
+
.scenario-header:hover .copy-prompt-btn,
|
|
4564
|
+
.copy-prompt-btn:focus-visible {
|
|
4565
|
+
opacity: 1;
|
|
4566
|
+
}
|
|
4567
|
+
|
|
4568
|
+
.copy-prompt-btn:hover {
|
|
4569
|
+
color: var(--primary);
|
|
4570
|
+
transform: scale(1.15);
|
|
4571
|
+
}
|
|
4572
|
+
|
|
4064
4573
|
/* ============================================================================
|
|
4065
4574
|
Keyboard Navigation
|
|
4066
4575
|
============================================================================ */
|
|
@@ -4298,6 +4807,82 @@ a.toc-title:hover {
|
|
|
4298
4807
|
outline-offset: 2px;
|
|
4299
4808
|
}
|
|
4300
4809
|
|
|
4810
|
+
/* ============================================================================
|
|
4811
|
+
Mobile responsive refinements
|
|
4812
|
+
============================================================================ */
|
|
4813
|
+
@media (max-width: 640px) {
|
|
4814
|
+
.container {
|
|
4815
|
+
padding: 0.875rem;
|
|
4816
|
+
}
|
|
4817
|
+
|
|
4818
|
+
.header {
|
|
4819
|
+
flex-direction: column;
|
|
4820
|
+
align-items: stretch;
|
|
4821
|
+
gap: 0.75rem;
|
|
4822
|
+
}
|
|
4823
|
+
|
|
4824
|
+
.header-actions {
|
|
4825
|
+
flex-wrap: wrap;
|
|
4826
|
+
gap: 0.5rem;
|
|
4827
|
+
}
|
|
4828
|
+
|
|
4829
|
+
.search-input {
|
|
4830
|
+
width: 100%;
|
|
4831
|
+
flex: 1 1 100%;
|
|
4832
|
+
min-width: 0;
|
|
4833
|
+
}
|
|
4834
|
+
|
|
4835
|
+
.header h1 {
|
|
4836
|
+
font-size: 1.25rem;
|
|
4837
|
+
}
|
|
4838
|
+
|
|
4839
|
+
.scenario-header,
|
|
4840
|
+
.feature-header {
|
|
4841
|
+
flex-wrap: wrap;
|
|
4842
|
+
gap: 0.5rem;
|
|
4843
|
+
}
|
|
4844
|
+
|
|
4845
|
+
.scenario-meta {
|
|
4846
|
+
flex-wrap: wrap;
|
|
4847
|
+
}
|
|
4848
|
+
|
|
4849
|
+
.scenario-actions {
|
|
4850
|
+
flex-wrap: wrap;
|
|
4851
|
+
}
|
|
4852
|
+
|
|
4853
|
+
/* Always-visible action buttons on touch (no hover) */
|
|
4854
|
+
.copy-scenario-btn,
|
|
4855
|
+
.copy-prompt-btn,
|
|
4856
|
+
.permalink-anchor {
|
|
4857
|
+
opacity: 1 !important;
|
|
4858
|
+
}
|
|
4859
|
+
|
|
4860
|
+
.summary-card {
|
|
4861
|
+
padding: 0.75rem 0.875rem;
|
|
4862
|
+
}
|
|
4863
|
+
|
|
4864
|
+
.summary-card .value {
|
|
4865
|
+
font-size: 1.5rem;
|
|
4866
|
+
}
|
|
4867
|
+
|
|
4868
|
+
.tag-bar {
|
|
4869
|
+
overflow-x: auto;
|
|
4870
|
+
-webkit-overflow-scrolling: touch;
|
|
4871
|
+
}
|
|
4872
|
+
|
|
4873
|
+
.shortcuts-overlay {
|
|
4874
|
+
padding: 1rem;
|
|
4875
|
+
}
|
|
4876
|
+
}
|
|
4877
|
+
|
|
4878
|
+
@media (hover: none) and (pointer: coarse) {
|
|
4879
|
+
.copy-scenario-btn,
|
|
4880
|
+
.copy-prompt-btn,
|
|
4881
|
+
.permalink-anchor {
|
|
4882
|
+
opacity: 1;
|
|
4883
|
+
}
|
|
4884
|
+
}
|
|
4885
|
+
|
|
4301
4886
|
`;
|
|
4302
4887
|
|
|
4303
4888
|
// src/formatters/html/themes/default.ts
|
|
@@ -14016,6 +14601,7 @@ function renderScenario(args, deps) {
|
|
|
14016
14601
|
</div>
|
|
14017
14602
|
<div class="scenario-actions">
|
|
14018
14603
|
<button class="copy-scenario-btn" onclick="copyScenarioAsMarkdown('scenario-${tc.id}')" aria-label="Copy scenario as markdown" title="Copy as Markdown"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg></button>
|
|
14604
|
+
${tc.status === "failed" ? `<button class="copy-prompt-btn" onclick="copyScenarioAsPrompt('scenario-${tc.id}')" aria-label="Copy as Claude prompt" title="Copy as AI prompt">\u2728</button>` : ""}
|
|
14019
14605
|
<button class="permalink-anchor" onclick="copyPermalink('scenario-${tc.id}')" aria-label="Copy link to scenario" title="Copy link">#</button>
|
|
14020
14606
|
<span class="scenario-duration">${duration}</span>
|
|
14021
14607
|
</div>
|
|
@@ -14415,7 +15001,7 @@ var SCREENSHOT_MIME_BY_EXT = {
|
|
|
14415
15001
|
};
|
|
14416
15002
|
function readScreenshotAsDataUri(filePath) {
|
|
14417
15003
|
try {
|
|
14418
|
-
const ext =
|
|
15004
|
+
const ext = path3.extname(filePath).slice(1).toLowerCase();
|
|
14419
15005
|
const mime = SCREENSHOT_MIME_BY_EXT[ext];
|
|
14420
15006
|
if (!mime) return void 0;
|
|
14421
15007
|
if (!fs2.existsSync(filePath)) return void 0;
|
|
@@ -15892,8 +16478,8 @@ function extractDocAttachments(step) {
|
|
|
15892
16478
|
}
|
|
15893
16479
|
return attachments;
|
|
15894
16480
|
}
|
|
15895
|
-
function guessMediaType(
|
|
15896
|
-
const lower =
|
|
16481
|
+
function guessMediaType(path10) {
|
|
16482
|
+
const lower = path10.toLowerCase();
|
|
15897
16483
|
if (lower.endsWith(".png")) return "image/png";
|
|
15898
16484
|
if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg";
|
|
15899
16485
|
if (lower.endsWith(".gif")) return "image/gif";
|
|
@@ -16968,7 +17554,7 @@ function selectTestCases(args, deps) {
|
|
|
16968
17554
|
|
|
16969
17555
|
// src/bundler/bundle-assets.ts
|
|
16970
17556
|
import * as fs4 from "fs";
|
|
16971
|
-
import * as
|
|
17557
|
+
import * as path5 from "path";
|
|
16972
17558
|
|
|
16973
17559
|
// src/bundler/scan-html-assets.ts
|
|
16974
17560
|
function scanHtmlAssets(html) {
|
|
@@ -16999,7 +17585,7 @@ function isLocalAssetRef(ref) {
|
|
|
16999
17585
|
|
|
17000
17586
|
// src/bundler/copy-asset.ts
|
|
17001
17587
|
import * as fs3 from "fs";
|
|
17002
|
-
import * as
|
|
17588
|
+
import * as path4 from "path";
|
|
17003
17589
|
import * as crypto from "crypto";
|
|
17004
17590
|
function copyAsset(sourcePath, assetsDir) {
|
|
17005
17591
|
if (!fs3.existsSync(assetsDir)) {
|
|
@@ -17007,10 +17593,10 @@ function copyAsset(sourcePath, assetsDir) {
|
|
|
17007
17593
|
}
|
|
17008
17594
|
const content = fs3.readFileSync(sourcePath);
|
|
17009
17595
|
const hash = crypto.createHash("sha256").update(content).digest("hex").slice(0, 8);
|
|
17010
|
-
const ext =
|
|
17011
|
-
const baseName = sanitize(
|
|
17596
|
+
const ext = path4.extname(sourcePath);
|
|
17597
|
+
const baseName = sanitize(path4.basename(sourcePath, ext));
|
|
17012
17598
|
const destName = `${baseName}-${hash}${ext}`;
|
|
17013
|
-
const destPath =
|
|
17599
|
+
const destPath = path4.join(assetsDir, destName);
|
|
17014
17600
|
if (!fs3.existsSync(destPath)) {
|
|
17015
17601
|
fs3.copyFileSync(sourcePath, destPath);
|
|
17016
17602
|
}
|
|
@@ -17022,14 +17608,14 @@ function sanitize(name) {
|
|
|
17022
17608
|
|
|
17023
17609
|
// src/bundler/bundle-assets.ts
|
|
17024
17610
|
function bundleAssets(htmlPath, options = {}) {
|
|
17025
|
-
const htmlDir =
|
|
17026
|
-
const assetsDir =
|
|
17611
|
+
const htmlDir = path5.dirname(htmlPath);
|
|
17612
|
+
const assetsDir = path5.join(htmlDir, "assets");
|
|
17027
17613
|
let html = fs4.readFileSync(htmlPath, "utf8");
|
|
17028
17614
|
const refs = scanHtmlAssets(html);
|
|
17029
17615
|
let copiedCount = 0;
|
|
17030
17616
|
const missing = [];
|
|
17031
17617
|
for (const ref of refs) {
|
|
17032
|
-
const absolutePath =
|
|
17618
|
+
const absolutePath = path5.resolve(htmlDir, ref);
|
|
17033
17619
|
if (!fs4.existsSync(absolutePath)) {
|
|
17034
17620
|
missing.push(ref);
|
|
17035
17621
|
continue;
|
|
@@ -17527,14 +18113,14 @@ function groupBy7(items, keyFn) {
|
|
|
17527
18113
|
|
|
17528
18114
|
// src/formatters/astro-assets.ts
|
|
17529
18115
|
import * as fs5 from "fs";
|
|
17530
|
-
import * as
|
|
18116
|
+
import * as path6 from "path";
|
|
17531
18117
|
var SKIP_PREFIXES = ["http://", "https://", "data:", "#"];
|
|
17532
18118
|
function isLocalPath(src) {
|
|
17533
18119
|
const trimmed = src.trim();
|
|
17534
18120
|
if (SKIP_PREFIXES.some((prefix) => trimmed.startsWith(prefix))) {
|
|
17535
18121
|
return false;
|
|
17536
18122
|
}
|
|
17537
|
-
return !
|
|
18123
|
+
return !path6.posix.isAbsolute(trimmed) && !path6.win32.isAbsolute(trimmed);
|
|
17538
18124
|
}
|
|
17539
18125
|
function stripCodeContent(markdown) {
|
|
17540
18126
|
let result = markdown.replace(/^[ \t]*(`{3,}|~{3,})[^\n]*\n[\s\S]*?^[ \t]*\1\s*$/gm, "");
|
|
@@ -17628,7 +18214,7 @@ function copyMarkdownAssets(options) {
|
|
|
17628
18214
|
const pathMap = /* @__PURE__ */ new Map();
|
|
17629
18215
|
const missing = [];
|
|
17630
18216
|
for (const ref of refs) {
|
|
17631
|
-
const absPath =
|
|
18217
|
+
const absPath = path6.resolve(markdownDir, ref);
|
|
17632
18218
|
if (!fs5.existsSync(absPath)) {
|
|
17633
18219
|
if (!allowMissing) {
|
|
17634
18220
|
throw new Error(`Asset not found: ${absPath}`);
|
|
@@ -18905,7 +19491,8 @@ var FORMAT_EXTENSIONS = {
|
|
|
18905
19491
|
junit: ".junit.xml",
|
|
18906
19492
|
"cucumber-json": ".cucumber.json",
|
|
18907
19493
|
"cucumber-messages": ".ndjson",
|
|
18908
|
-
confluence: ".adf.json"
|
|
19494
|
+
confluence: ".adf.json",
|
|
19495
|
+
"story-report-json": ".story-report.json"
|
|
18909
19496
|
};
|
|
18910
19497
|
var TEST_EXTENSIONS = [
|
|
18911
19498
|
".test.ts",
|
|
@@ -18932,11 +19519,11 @@ function computeOutputPath(sourceFile, format, mode, colocatedStyle, baseOutputD
|
|
|
18932
19519
|
const ext = FORMAT_EXTENSIONS[format];
|
|
18933
19520
|
const effectiveName = outputName + (outputNameSuffix ?? "");
|
|
18934
19521
|
if (mode === "aggregated") {
|
|
18935
|
-
return toPosix(
|
|
19522
|
+
return toPosix(path7.join(baseOutputDir, `${effectiveName}${ext}`));
|
|
18936
19523
|
}
|
|
18937
19524
|
const normalizedSource = toPosix(sourceFile);
|
|
18938
|
-
const dirOfSource =
|
|
18939
|
-
let baseName =
|
|
19525
|
+
const dirOfSource = path7.posix.dirname(normalizedSource);
|
|
19526
|
+
let baseName = path7.posix.basename(normalizedSource);
|
|
18940
19527
|
for (const testExt of TEST_EXTENSIONS) {
|
|
18941
19528
|
if (baseName.endsWith(testExt)) {
|
|
18942
19529
|
baseName = baseName.slice(0, -testExt.length);
|
|
@@ -18945,9 +19532,9 @@ function computeOutputPath(sourceFile, format, mode, colocatedStyle, baseOutputD
|
|
|
18945
19532
|
}
|
|
18946
19533
|
const fileName = `${baseName}.${effectiveName}${ext}`;
|
|
18947
19534
|
if (colocatedStyle === "adjacent") {
|
|
18948
|
-
return toPosix(
|
|
19535
|
+
return toPosix(path7.posix.join(dirOfSource, fileName));
|
|
18949
19536
|
}
|
|
18950
|
-
return toPosix(
|
|
19537
|
+
return toPosix(path7.posix.join(baseOutputDir, dirOfSource, fileName));
|
|
18951
19538
|
}
|
|
18952
19539
|
function groupTestCasesByOutput(testCases, format, options, logger, outputNameSuffix) {
|
|
18953
19540
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -19016,7 +19603,7 @@ var ReportGenerator = class {
|
|
|
19016
19603
|
exclude: options.exclude ?? [],
|
|
19017
19604
|
includeTags: options.includeTags ?? [],
|
|
19018
19605
|
excludeTags: options.excludeTags ?? [],
|
|
19019
|
-
formats: options.formats ?? ["
|
|
19606
|
+
formats: options.formats ?? ["html"],
|
|
19020
19607
|
outputDir: options.outputDir ?? "reports",
|
|
19021
19608
|
outputName: options.outputName ?? "index",
|
|
19022
19609
|
outputNameTimestamp: options.outputNameTimestamp ?? false,
|
|
@@ -19030,6 +19617,9 @@ var ReportGenerator = class {
|
|
|
19030
19617
|
cucumberJson: {
|
|
19031
19618
|
pretty: options.cucumberJson?.pretty ?? false
|
|
19032
19619
|
},
|
|
19620
|
+
storyReportJson: {
|
|
19621
|
+
pretty: options.storyReportJson?.pretty ?? true
|
|
19622
|
+
},
|
|
19033
19623
|
cucumberMessages: {
|
|
19034
19624
|
uriStrategy: options.cucumberMessages?.uriStrategy ?? "sourceFile",
|
|
19035
19625
|
includeSynthetics: options.cucumberMessages?.includeSynthetics ?? true,
|
|
@@ -19145,8 +19735,8 @@ var ReportGenerator = class {
|
|
|
19145
19735
|
if (astroPaths) {
|
|
19146
19736
|
for (const mdPath of astroPaths) {
|
|
19147
19737
|
const content = await fsPromises.readFile(mdPath, "utf8");
|
|
19148
|
-
const mdDir =
|
|
19149
|
-
const assetsDir =
|
|
19738
|
+
const mdDir = path7.dirname(mdPath);
|
|
19739
|
+
const assetsDir = path7.resolve(this.options.astro.assetsDir);
|
|
19150
19740
|
const result = copyMarkdownAssets({
|
|
19151
19741
|
markdown: content,
|
|
19152
19742
|
markdownDir: mdDir,
|
|
@@ -19177,9 +19767,9 @@ var ReportGenerator = class {
|
|
|
19177
19767
|
if (groups.size === 0 && this.options.output.mode === "aggregated") {
|
|
19178
19768
|
const ext = FORMAT_EXTENSIONS[format];
|
|
19179
19769
|
const effectiveName = this.options.outputName + (outputNameSuffix ?? "");
|
|
19180
|
-
const outputPath = toPosix(
|
|
19770
|
+
const outputPath = toPosix(path7.join(this.options.outputDir, `${effectiveName}${ext}`));
|
|
19181
19771
|
const content = await this.formatContent(run, format);
|
|
19182
|
-
const dir =
|
|
19772
|
+
const dir = path7.dirname(outputPath);
|
|
19183
19773
|
await fsPromises.mkdir(dir, { recursive: true });
|
|
19184
19774
|
await this.deps.writeFile(outputPath, content);
|
|
19185
19775
|
return [outputPath];
|
|
@@ -19191,7 +19781,7 @@ var ReportGenerator = class {
|
|
|
19191
19781
|
testCases
|
|
19192
19782
|
};
|
|
19193
19783
|
const content = await this.formatContent(groupRun, format);
|
|
19194
|
-
const dir =
|
|
19784
|
+
const dir = path7.dirname(outputPath);
|
|
19195
19785
|
await fsPromises.mkdir(dir, { recursive: true });
|
|
19196
19786
|
await this.deps.writeFile(outputPath, content);
|
|
19197
19787
|
writtenPaths.push(outputPath);
|
|
@@ -19298,6 +19888,12 @@ var ReportGenerator = class {
|
|
|
19298
19888
|
});
|
|
19299
19889
|
return formatter.format(run);
|
|
19300
19890
|
}
|
|
19891
|
+
case "story-report-json": {
|
|
19892
|
+
const formatter = new StoryReportJsonFormatter({
|
|
19893
|
+
pretty: this.options.storyReportJson.pretty
|
|
19894
|
+
});
|
|
19895
|
+
return formatter.format(run);
|
|
19896
|
+
}
|
|
19301
19897
|
default:
|
|
19302
19898
|
throw new Error(`Unknown format: ${format}`);
|
|
19303
19899
|
}
|
|
@@ -19311,7 +19907,7 @@ async function generateRunComparison(args) {
|
|
|
19311
19907
|
await fsPromises.mkdir(outputDir, { recursive: true });
|
|
19312
19908
|
for (const format of args.formats) {
|
|
19313
19909
|
const ext = format === "html" ? ".html" : ".md";
|
|
19314
|
-
const outputPath = toPosix(
|
|
19910
|
+
const outputPath = toPosix(path7.join(outputDir, `${outputName}${ext}`));
|
|
19315
19911
|
const content = format === "html" ? new RunDiffHtmlFormatter({ title: args.title }).format(diff) : new RunDiffMarkdownFormatter({ title: args.title }).format(diff);
|
|
19316
19912
|
await fsPromises.writeFile(outputPath, content, "utf8");
|
|
19317
19913
|
files.push(outputPath);
|
|
@@ -19321,9 +19917,9 @@ async function generateRunComparison(args) {
|
|
|
19321
19917
|
|
|
19322
19918
|
// src/init-astro.ts
|
|
19323
19919
|
import * as fs7 from "fs";
|
|
19324
|
-
import * as
|
|
19920
|
+
import * as path8 from "path";
|
|
19325
19921
|
import { fileURLToPath } from "url";
|
|
19326
|
-
var __dirname =
|
|
19922
|
+
var __dirname = path8.dirname(fileURLToPath(import.meta.url));
|
|
19327
19923
|
function initAstro(options = {}) {
|
|
19328
19924
|
const targetDir = options.targetDir ?? "./story-docs";
|
|
19329
19925
|
const force = options.force ?? false;
|
|
@@ -19335,7 +19931,7 @@ function initAstro(options = {}) {
|
|
|
19335
19931
|
);
|
|
19336
19932
|
}
|
|
19337
19933
|
}
|
|
19338
|
-
const templateDir =
|
|
19934
|
+
const templateDir = path8.resolve(__dirname, "..", "templates", "astro-starlight");
|
|
19339
19935
|
if (!fs7.existsSync(templateDir)) {
|
|
19340
19936
|
throw new Error(
|
|
19341
19937
|
`Template directory not found at ${templateDir}. Ensure the package is installed correctly.`
|
|
@@ -19348,8 +19944,8 @@ function copyDirRecursive(src, dest) {
|
|
|
19348
19944
|
fs7.mkdirSync(dest, { recursive: true });
|
|
19349
19945
|
const entries = fs7.readdirSync(src, { withFileTypes: true });
|
|
19350
19946
|
for (const entry of entries) {
|
|
19351
|
-
const srcPath =
|
|
19352
|
-
const destPath =
|
|
19947
|
+
const srcPath = path8.join(src, entry.name);
|
|
19948
|
+
const destPath = path8.join(dest, entry.name);
|
|
19353
19949
|
if (entry.isDirectory()) {
|
|
19354
19950
|
copyDirRecursive(srcPath, destPath);
|
|
19355
19951
|
} else {
|
|
@@ -19380,6 +19976,7 @@ var EXIT_SCHEMA_VALIDATION = 1;
|
|
|
19380
19976
|
var EXIT_CANONICAL_VALIDATION = 2;
|
|
19381
19977
|
var EXIT_GENERATION = 3;
|
|
19382
19978
|
var EXIT_USAGE = 4;
|
|
19979
|
+
var EXIT_COMPARE_GATE = 5;
|
|
19383
19980
|
var HELP_TEXT = `
|
|
19384
19981
|
executable-stories \u2014 Generate reports from test results JSON.
|
|
19385
19982
|
|
|
@@ -19404,7 +20001,7 @@ SUBCOMMANDS
|
|
|
19404
20001
|
publish-jira Publish an ADF JSON file to a Jira issue (as comment or description)
|
|
19405
20002
|
|
|
19406
20003
|
OPTIONS
|
|
19407
|
-
--format <formats> Comma-separated formats: html, markdown, junit, cucumber-json, cucumber-messages, cucumber-html, astro, confluence, or custom names from config (default: html)
|
|
20004
|
+
--format <formats> Comma-separated formats: html, markdown, junit, cucumber-json, cucumber-messages, cucumber-html, astro, confluence, story-report-json, or custom names from config (default: html)
|
|
19408
20005
|
astro Themed Markdown (for Astro docs sites with matching CSS)
|
|
19409
20006
|
confluence Atlassian Document Format (ADF) JSON for Confluence / Jira
|
|
19410
20007
|
html Custom HTML report (accessible, dark mode, mermaid)
|
|
@@ -19413,6 +20010,7 @@ OPTIONS
|
|
|
19413
20010
|
junit JUnit XML
|
|
19414
20011
|
cucumber-json Cucumber JSON
|
|
19415
20012
|
cucumber-messages Raw NDJSON (Cucumber Messages)
|
|
20013
|
+
story-report-json StoryReport v1 JSON (consumed by executable-stories-react and other UI renderers)
|
|
19416
20014
|
--config <path> Path to executable-stories.config.js (default: ./executable-stories.config.js)
|
|
19417
20015
|
--input-type <type> Input type: raw, canonical, or ndjson (default: raw)
|
|
19418
20016
|
--output-dir <dir> Output directory (default: reports)
|
|
@@ -19443,6 +20041,9 @@ OPTIONS
|
|
|
19443
20041
|
--baseline-dir <dir> Directory to scan when --baseline auto is used
|
|
19444
20042
|
--pr-summary Print a PR-friendly markdown summary after compare
|
|
19445
20043
|
--pr-summary-file <path> Write the PR-friendly markdown summary to a file
|
|
20044
|
+
--fail-on-regression Exit non-zero when any regression is detected in compare
|
|
20045
|
+
--fail-on-added-failures Exit non-zero when newly added scenarios are failing
|
|
20046
|
+
--max-regressions <n> Exit non-zero when regressions exceed threshold
|
|
19446
20047
|
--emit-canonical <path> Write canonical JSON to given path
|
|
19447
20048
|
--help Show this help message
|
|
19448
20049
|
|
|
@@ -19506,6 +20107,7 @@ EXIT CODES
|
|
|
19506
20107
|
2 Canonical validation failure
|
|
19507
20108
|
3 Formatter/generation failure
|
|
19508
20109
|
4 Bad arguments / usage error
|
|
20110
|
+
5 Compare gate failed
|
|
19509
20111
|
`.trim();
|
|
19510
20112
|
async function parseCliArgs(argv) {
|
|
19511
20113
|
const args = argv.slice(2);
|
|
@@ -19607,6 +20209,9 @@ async function parseCliArgs(argv) {
|
|
|
19607
20209
|
"allow-missing-assets": { type: "boolean", default: false },
|
|
19608
20210
|
"pr-summary": { type: "boolean", default: false },
|
|
19609
20211
|
"pr-summary-file": { type: "string" },
|
|
20212
|
+
"fail-on-regression": { type: "boolean", default: false },
|
|
20213
|
+
"fail-on-added-failures": { type: "boolean", default: false },
|
|
20214
|
+
"max-regressions": { type: "string" },
|
|
19610
20215
|
"config": { type: "string" },
|
|
19611
20216
|
help: { type: "boolean", default: false }
|
|
19612
20217
|
},
|
|
@@ -19647,7 +20252,7 @@ async function parseCliArgs(argv) {
|
|
|
19647
20252
|
}
|
|
19648
20253
|
const pluginConfig = await loadConfig(values["config"]);
|
|
19649
20254
|
const customFormatterNames = new Set(Object.keys(pluginConfig.formatters ?? {}));
|
|
19650
|
-
const builtInFormats = /* @__PURE__ */ new Set(["astro", "confluence", "html", "markdown", "junit", "cucumber-json", "cucumber-messages", "cucumber-html"]);
|
|
20255
|
+
const builtInFormats = /* @__PURE__ */ new Set(["astro", "confluence", "html", "markdown", "junit", "cucumber-json", "cucumber-messages", "cucumber-html", "story-report-json"]);
|
|
19651
20256
|
const formatStr = values.format;
|
|
19652
20257
|
const allRequestedFormats = formatStr.split(",").map((f) => f.trim());
|
|
19653
20258
|
const builtInRequested = allRequestedFormats.filter((f) => builtInFormats.has(f));
|
|
@@ -19655,7 +20260,7 @@ async function parseCliArgs(argv) {
|
|
|
19655
20260
|
const unknownFormats = allRequestedFormats.filter((f) => !builtInFormats.has(f) && !customFormatterNames.has(f));
|
|
19656
20261
|
if (unknownFormats.length > 0) {
|
|
19657
20262
|
const knownCustom = customFormatterNames.size > 0 ? `, ${[...customFormatterNames].join(", ")}` : "";
|
|
19658
|
-
console.error(`Error: Unknown format(s): ${unknownFormats.join(", ")}. Valid built-in: astro, confluence, html, markdown, junit, cucumber-json, cucumber-messages, cucumber-html${knownCustom}.`);
|
|
20263
|
+
console.error(`Error: Unknown format(s): ${unknownFormats.join(", ")}. Valid built-in: astro, confluence, html, markdown, junit, cucumber-json, cucumber-messages, cucumber-html, story-report-json${knownCustom}.`);
|
|
19659
20264
|
process.exit(EXIT_USAGE);
|
|
19660
20265
|
}
|
|
19661
20266
|
const formats = builtInRequested;
|
|
@@ -19714,6 +20319,12 @@ async function parseCliArgs(argv) {
|
|
|
19714
20319
|
console.error(`Error: --max-history-runs must be a positive integer, got "${maxHistoryRunsStr}".`);
|
|
19715
20320
|
process.exit(EXIT_USAGE);
|
|
19716
20321
|
}
|
|
20322
|
+
const maxRegressionsStr = values["max-regressions"];
|
|
20323
|
+
const maxRegressions = maxRegressionsStr !== void 0 ? parseInt(maxRegressionsStr, 10) : void 0;
|
|
20324
|
+
if (maxRegressionsStr !== void 0 && (isNaN(maxRegressions) || maxRegressions < 0)) {
|
|
20325
|
+
console.error(`Error: --max-regressions must be a non-negative integer, got "${maxRegressionsStr}".`);
|
|
20326
|
+
process.exit(EXIT_USAGE);
|
|
20327
|
+
}
|
|
19717
20328
|
const sortTestCasesRaw = values["sort-test-cases"];
|
|
19718
20329
|
const validSortModes = /* @__PURE__ */ new Set(["id", "source", "none"]);
|
|
19719
20330
|
if (!validSortModes.has(sortTestCasesRaw)) {
|
|
@@ -19774,6 +20385,9 @@ async function parseCliArgs(argv) {
|
|
|
19774
20385
|
allowMissingAssets: values["allow-missing-assets"],
|
|
19775
20386
|
prSummary: values["pr-summary"],
|
|
19776
20387
|
prSummaryFile: values["pr-summary-file"],
|
|
20388
|
+
failOnRegression: values["fail-on-regression"],
|
|
20389
|
+
failOnAddedFailures: values["fail-on-added-failures"],
|
|
20390
|
+
maxRegressions,
|
|
19777
20391
|
config: values["config"]
|
|
19778
20392
|
};
|
|
19779
20393
|
return { args: cliArgs, pluginConfig, customRequested };
|
|
@@ -19782,7 +20396,7 @@ async function readInput(args) {
|
|
|
19782
20396
|
if (args.stdin) {
|
|
19783
20397
|
return readStdin();
|
|
19784
20398
|
}
|
|
19785
|
-
const filePath =
|
|
20399
|
+
const filePath = path9.resolve(args.inputFile);
|
|
19786
20400
|
if (!fs8.existsSync(filePath)) {
|
|
19787
20401
|
console.error(`Error: File not found: ${filePath}`);
|
|
19788
20402
|
process.exit(EXIT_USAGE);
|
|
@@ -19790,7 +20404,7 @@ async function readInput(args) {
|
|
|
19790
20404
|
return fs8.readFileSync(filePath, "utf8");
|
|
19791
20405
|
}
|
|
19792
20406
|
function readFileInput(filePath) {
|
|
19793
|
-
const resolved =
|
|
20407
|
+
const resolved = path9.resolve(filePath);
|
|
19794
20408
|
if (!fs8.existsSync(resolved)) {
|
|
19795
20409
|
console.error(`Error: File not found: ${resolved}`);
|
|
19796
20410
|
process.exit(EXIT_USAGE);
|
|
@@ -19928,14 +20542,14 @@ function tryNormalizeRunFromText(text2, args) {
|
|
|
19928
20542
|
}
|
|
19929
20543
|
}
|
|
19930
20544
|
function listBaselineCandidates(currentFile, args) {
|
|
19931
|
-
const baselineDir =
|
|
19932
|
-
const currentResolved =
|
|
20545
|
+
const baselineDir = path9.resolve(args.baselineDir ?? path9.dirname(currentFile));
|
|
20546
|
+
const currentResolved = path9.resolve(currentFile);
|
|
19933
20547
|
if (!fs8.existsSync(baselineDir)) {
|
|
19934
20548
|
console.error(`Error: baseline directory not found: ${baselineDir}`);
|
|
19935
20549
|
process.exit(EXIT_USAGE);
|
|
19936
20550
|
}
|
|
19937
20551
|
const entries = fs8.readdirSync(baselineDir, { withFileTypes: true });
|
|
19938
|
-
return entries.filter((entry) => entry.isFile()).map((entry) =>
|
|
20552
|
+
return entries.filter((entry) => entry.isFile()).map((entry) => path9.join(baselineDir, entry.name)).filter((candidate) => path9.resolve(candidate) !== currentResolved).filter(
|
|
19939
20553
|
(candidate) => args.inputType === "ndjson" ? candidate.endsWith(".ndjson") : candidate.endsWith(".json")
|
|
19940
20554
|
);
|
|
19941
20555
|
}
|
|
@@ -19950,7 +20564,7 @@ function resolveBaselineAuto(currentFile, currentRun, args) {
|
|
|
19950
20564
|
}
|
|
19951
20565
|
if (comparable.length === 0) {
|
|
19952
20566
|
console.error(
|
|
19953
|
-
`Error: no compatible baseline files found in ${
|
|
20567
|
+
`Error: no compatible baseline files found in ${path9.resolve(args.baselineDir ?? path9.dirname(currentFile))}.`
|
|
19954
20568
|
);
|
|
19955
20569
|
process.exit(EXIT_USAGE);
|
|
19956
20570
|
}
|
|
@@ -19973,6 +20587,13 @@ async function main() {
|
|
|
19973
20587
|
try {
|
|
19974
20588
|
const result = await generateCompareReports(baseline, current, baselineFile, args);
|
|
19975
20589
|
printCompareResult(result, args, startMs);
|
|
20590
|
+
const gateFailures = evaluateCompareGate(result, args);
|
|
20591
|
+
if (gateFailures.length > 0) {
|
|
20592
|
+
for (const failure of gateFailures) {
|
|
20593
|
+
console.error(`Compare gate failed: ${failure}`);
|
|
20594
|
+
}
|
|
20595
|
+
process.exit(EXIT_COMPARE_GATE);
|
|
20596
|
+
}
|
|
19976
20597
|
process.exit(EXIT_SUCCESS);
|
|
19977
20598
|
} catch (err) {
|
|
19978
20599
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -20039,8 +20660,8 @@ async function main() {
|
|
|
20039
20660
|
process.exit(EXIT_SCHEMA_VALIDATION);
|
|
20040
20661
|
}
|
|
20041
20662
|
if (args.emitCanonical) {
|
|
20042
|
-
const outPath =
|
|
20043
|
-
fs8.mkdirSync(
|
|
20663
|
+
const outPath = path9.resolve(args.emitCanonical);
|
|
20664
|
+
fs8.mkdirSync(path9.dirname(outPath), { recursive: true });
|
|
20044
20665
|
fs8.writeFileSync(outPath, JSON.stringify(run, null, 2), "utf8");
|
|
20045
20666
|
}
|
|
20046
20667
|
try {
|
|
@@ -20098,8 +20719,8 @@ ${msg}`);
|
|
|
20098
20719
|
}
|
|
20099
20720
|
const run = data;
|
|
20100
20721
|
if (args.emitCanonical) {
|
|
20101
|
-
const outPath =
|
|
20102
|
-
fs8.mkdirSync(
|
|
20722
|
+
const outPath = path9.resolve(args.emitCanonical);
|
|
20723
|
+
fs8.mkdirSync(path9.dirname(outPath), { recursive: true });
|
|
20103
20724
|
fs8.writeFileSync(outPath, JSON.stringify(run, null, 2), "utf8");
|
|
20104
20725
|
}
|
|
20105
20726
|
try {
|
|
@@ -20156,8 +20777,8 @@ ${msg}`);
|
|
|
20156
20777
|
process.exit(EXIT_CANONICAL_VALIDATION);
|
|
20157
20778
|
}
|
|
20158
20779
|
if (args.emitCanonical) {
|
|
20159
|
-
const outPath =
|
|
20160
|
-
fs8.mkdirSync(
|
|
20780
|
+
const outPath = path9.resolve(args.emitCanonical);
|
|
20781
|
+
fs8.mkdirSync(path9.dirname(outPath), { recursive: true });
|
|
20161
20782
|
fs8.writeFileSync(outPath, JSON.stringify(canonical, null, 2), "utf8");
|
|
20162
20783
|
}
|
|
20163
20784
|
try {
|
|
@@ -20183,7 +20804,7 @@ function runCustomFormatters(run, customRequested, formatters, args) {
|
|
|
20183
20804
|
const ext = formatter.fileExtension ?? formatName;
|
|
20184
20805
|
const baseName = args.outputName ?? "report";
|
|
20185
20806
|
const filename = args.outputNameTimestamp ? `${baseName}-${Math.floor(run.startedAtMs / 1e3)}.${ext}` : `${baseName}.${ext}`;
|
|
20186
|
-
const filepath =
|
|
20807
|
+
const filepath = path9.join(outputDir, filename);
|
|
20187
20808
|
fs8.mkdirSync(outputDir, { recursive: true });
|
|
20188
20809
|
fs8.writeFileSync(filepath, content, "utf8");
|
|
20189
20810
|
console.log(`Generated: ${filepath}`);
|
|
@@ -20235,7 +20856,7 @@ async function dispatchNotifications(run, args) {
|
|
|
20235
20856
|
}
|
|
20236
20857
|
function runHistoryPipeline(run, args) {
|
|
20237
20858
|
if (!args.historyFile) return;
|
|
20238
|
-
const historyPath =
|
|
20859
|
+
const historyPath = path9.resolve(args.historyFile);
|
|
20239
20860
|
const store = loadHistory(
|
|
20240
20861
|
{ filePath: historyPath },
|
|
20241
20862
|
{
|
|
@@ -20254,7 +20875,7 @@ function runHistoryPipeline(run, args) {
|
|
|
20254
20875
|
run,
|
|
20255
20876
|
maxRuns: args.maxHistoryRuns
|
|
20256
20877
|
});
|
|
20257
|
-
const dir =
|
|
20878
|
+
const dir = path9.dirname(historyPath);
|
|
20258
20879
|
fs8.mkdirSync(dir, { recursive: true });
|
|
20259
20880
|
saveHistory(
|
|
20260
20881
|
{ filePath: historyPath, store: updated },
|
|
@@ -20331,6 +20952,9 @@ async function generateCompareReports(baseline, current, baselineFile, args) {
|
|
|
20331
20952
|
return {
|
|
20332
20953
|
files: result.files,
|
|
20333
20954
|
baselineFile,
|
|
20955
|
+
addedFailures: result.diff.scenarios.filter(
|
|
20956
|
+
(scenario) => scenario.kind === "added" && scenario.current?.status === "failed"
|
|
20957
|
+
).length,
|
|
20334
20958
|
summary: result.diff.summary,
|
|
20335
20959
|
prSummary: args.prSummary || args.prSummaryFile ? createPrCommentSummary(result.diff) : void 0
|
|
20336
20960
|
};
|
|
@@ -20356,8 +20980,8 @@ function printResult(result, args, startMs, droppedMissingStory = 0) {
|
|
|
20356
20980
|
function printCompareResult(result, args, startMs) {
|
|
20357
20981
|
const durationMs = Date.now() - startMs;
|
|
20358
20982
|
if (result.prSummary && args.prSummaryFile) {
|
|
20359
|
-
const outputPath =
|
|
20360
|
-
fs8.mkdirSync(
|
|
20983
|
+
const outputPath = path9.resolve(args.prSummaryFile);
|
|
20984
|
+
fs8.mkdirSync(path9.dirname(outputPath), { recursive: true });
|
|
20361
20985
|
fs8.writeFileSync(outputPath, result.prSummary, "utf8");
|
|
20362
20986
|
}
|
|
20363
20987
|
if (args.jsonSummary) {
|
|
@@ -20366,6 +20990,7 @@ function printCompareResult(result, args, startMs) {
|
|
|
20366
20990
|
{
|
|
20367
20991
|
files: result.files,
|
|
20368
20992
|
baselineFile: result.baselineFile,
|
|
20993
|
+
addedFailures: result.addedFailures,
|
|
20369
20994
|
diff: result.summary,
|
|
20370
20995
|
prSummary: result.prSummary,
|
|
20371
20996
|
durationMs
|
|
@@ -20385,6 +21010,25 @@ function printCompareResult(result, args, startMs) {
|
|
|
20385
21010
|
console.log(result.prSummary);
|
|
20386
21011
|
}
|
|
20387
21012
|
}
|
|
21013
|
+
function evaluateCompareGate(result, args) {
|
|
21014
|
+
const failures = [];
|
|
21015
|
+
if (args.failOnRegression && result.summary.regressed > 0) {
|
|
21016
|
+
failures.push(
|
|
21017
|
+
`regressions detected (${result.summary.regressed}) with --fail-on-regression`
|
|
21018
|
+
);
|
|
21019
|
+
}
|
|
21020
|
+
if (args.failOnAddedFailures && result.addedFailures > 0) {
|
|
21021
|
+
failures.push(
|
|
21022
|
+
`new failing scenarios detected (${result.addedFailures}) with --fail-on-added-failures`
|
|
21023
|
+
);
|
|
21024
|
+
}
|
|
21025
|
+
if (args.maxRegressions !== void 0 && result.summary.regressed > args.maxRegressions) {
|
|
21026
|
+
failures.push(
|
|
21027
|
+
`regressions ${result.summary.regressed} exceed --max-regressions ${args.maxRegressions}`
|
|
21028
|
+
);
|
|
21029
|
+
}
|
|
21030
|
+
return failures;
|
|
21031
|
+
}
|
|
20388
21032
|
async function runPublishConfluence(rawArgs) {
|
|
20389
21033
|
const { values, positionals } = parseArgs({
|
|
20390
21034
|
args: rawArgs,
|
|
@@ -20457,7 +21101,7 @@ Generate an API token at https://id.atlassian.com/manage-profile/security/api-to
|
|
|
20457
21101
|
console.error("Error: --title is required when creating a new page");
|
|
20458
21102
|
process.exit(EXIT_USAGE);
|
|
20459
21103
|
}
|
|
20460
|
-
const adf = fs8.readFileSync(
|
|
21104
|
+
const adf = fs8.readFileSync(path9.resolve(inputFile), "utf8");
|
|
20461
21105
|
if (dryRun) {
|
|
20462
21106
|
console.log(
|
|
20463
21107
|
JSON.stringify(
|
|
@@ -20563,7 +21207,7 @@ Generate an API token at https://id.atlassian.com/manage-profile/security/api-to
|
|
|
20563
21207
|
process.exit(EXIT_USAGE);
|
|
20564
21208
|
}
|
|
20565
21209
|
const mode = modeRaw;
|
|
20566
|
-
const adf = fs8.readFileSync(
|
|
21210
|
+
const adf = fs8.readFileSync(path9.resolve(inputFile), "utf8");
|
|
20567
21211
|
if (dryRun) {
|
|
20568
21212
|
console.log(
|
|
20569
21213
|
JSON.stringify(
|