executable-stories-formatters 0.7.8 → 0.7.9

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/dist/index.cjs CHANGED
@@ -31,6 +31,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var src_exports = {};
32
32
  __export(src_exports, {
33
33
  AstroFormatter: () => AstroFormatter,
34
+ ConfluenceFormatter: () => ConfluenceFormatter,
34
35
  CucumberHtmlFormatter: () => CucumberHtmlFormatter,
35
36
  CucumberJsonFormatter: () => CucumberJsonFormatter,
36
37
  CucumberMessagesFormatter: () => CucumberMessagesFormatter,
@@ -62,7 +63,7 @@ __export(src_exports, {
62
63
  detectPerformanceTrend: () => detectPerformanceTrend,
63
64
  diffRuns: () => diffRuns,
64
65
  findGitDir: () => findGitDir,
65
- formatDuration: () => formatDuration2,
66
+ formatDuration: () => formatDuration3,
66
67
  generateRunComparison: () => generateRunComparison,
67
68
  generateRunId: () => generateRunId,
68
69
  generateTestCaseId: () => generateTestCaseId,
@@ -80,6 +81,8 @@ __export(src_exports, {
80
81
  normalizeVitestResults: () => normalizeVitestResults,
81
82
  parseEnvelopes: () => parseEnvelopes,
82
83
  parseNdjson: () => parseNdjson,
84
+ publishConfluencePage: () => publishConfluencePage,
85
+ publishJiraIssue: () => publishJiraIssue,
83
86
  readBranchName: () => readBranchName,
84
87
  readGitSha: () => readGitSha,
85
88
  readPackageVersion: () => readPackageVersion,
@@ -136,8 +139,8 @@ function generateRunId(startedAtMs, projectRoot) {
136
139
  const input = `${startedAtMs}::${projectRoot}`;
137
140
  return (0, import_node_crypto.createHash)("sha1").update(input).digest("hex").slice(0, 16);
138
141
  }
139
- function slugify(text) {
140
- return text.toLowerCase().replace(/[/\\]+/g, "-").replace(/[^\w\s-]/g, "").replace(/[\s_]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
142
+ function slugify(text2) {
143
+ return text2.toLowerCase().replace(/[/\\]+/g, "-").replace(/[^\w\s-]/g, "").replace(/[\s_]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
141
144
  }
142
145
  function generateFeatureId(uri) {
143
146
  const pathWithoutExt = uri.replace(/\.[^.]+$/, "");
@@ -13126,7 +13129,7 @@ function renderDocEntry(entry, deps) {
13126
13129
  // src/formatters/html/renderers/steps.ts
13127
13130
  var CONTINUATION_KEYWORDS = ["And", "But", "*"];
13128
13131
  function renderStep(step, stepResult, index, deps) {
13129
- const statusIcon = stepResult ? deps.getStatusIcon(stepResult.status) : "\u25CB";
13132
+ const statusIcon2 = stepResult ? deps.getStatusIcon(stepResult.status) : "\u25CB";
13130
13133
  const statusClass = stepResult ? `status-${stepResult.status}` : "";
13131
13134
  const duration = stepResult && stepResult.durationMs > 0 ? `${stepResult.durationMs}ms` : "";
13132
13135
  const keywordTrimmed = step.keyword.trim();
@@ -13135,7 +13138,7 @@ function renderStep(step, stepResult, index, deps) {
13135
13138
  const stepDocs = deps.renderDocs(step.docs, "step-docs");
13136
13139
  const textHtml = deps.highlightStepParams ? deps.highlightStepParams(step.text) : deps.escapeHtml(step.text);
13137
13140
  return `<div class="${stepClass}" data-keyword="${deps.escapeHtml(keywordTrimmed)}" data-text="${deps.escapeHtml(step.text)}">
13138
- <span class="step-status ${statusClass}">${statusIcon}</span>
13141
+ <span class="step-status ${statusClass}">${statusIcon2}</span>
13139
13142
  <span class="step-keyword">${deps.escapeHtml(step.keyword)}</span>
13140
13143
  <span class="step-text">${textHtml}</span>
13141
13144
  <span class="step-duration">${duration}</span>
@@ -13151,10 +13154,10 @@ function renderSteps(args, deps) {
13151
13154
 
13152
13155
  // src/formatters/html/renderers/step-params.ts
13153
13156
  var STEP_PARAM_PATTERN = /"[^"]*"|(?<![\w.\-])\d+(?:\.\d+)?(?![\w.\-])/g;
13154
- function highlightStepParams(text, deps) {
13155
- const matches = Array.from(text.matchAll(STEP_PARAM_PATTERN));
13157
+ function highlightStepParams(text2, deps) {
13158
+ const matches = Array.from(text2.matchAll(STEP_PARAM_PATTERN));
13156
13159
  if (matches.length === 0) {
13157
- return deps.escapeHtml(text);
13160
+ return deps.escapeHtml(text2);
13158
13161
  }
13159
13162
  let result = "";
13160
13163
  let lastIndex = 0;
@@ -13162,13 +13165,13 @@ function highlightStepParams(text, deps) {
13162
13165
  const matchStart = match.index;
13163
13166
  const matchEnd = matchStart + match[0].length;
13164
13167
  if (matchStart > lastIndex) {
13165
- result += deps.escapeHtml(text.slice(lastIndex, matchStart));
13168
+ result += deps.escapeHtml(text2.slice(lastIndex, matchStart));
13166
13169
  }
13167
13170
  result += `<span class="step-param">${deps.escapeHtml(match[0])}</span>`;
13168
13171
  lastIndex = matchEnd;
13169
13172
  }
13170
- if (lastIndex < text.length) {
13171
- result += deps.escapeHtml(text.slice(lastIndex));
13173
+ if (lastIndex < text2.length) {
13174
+ result += deps.escapeHtml(text2.slice(lastIndex));
13172
13175
  }
13173
13176
  return result;
13174
13177
  }
@@ -13191,7 +13194,7 @@ function renderTicket(ticket, template, escapeHtml3) {
13191
13194
  }
13192
13195
  function renderScenario(args, deps) {
13193
13196
  const { tc } = args;
13194
- const statusIcon = deps.getStatusIcon(tc.status);
13197
+ const statusIcon2 = deps.getStatusIcon(tc.status);
13195
13198
  const statusClass = `status-${tc.status}`;
13196
13199
  const duration = tc.durationMs > 0 ? `${(tc.durationMs / 1e3).toFixed(2)}s` : "";
13197
13200
  const tags = tc.tags.map((t) => `<span class="tag">${deps.escapeHtml(t)}</span>`).join("");
@@ -13260,7 +13263,7 @@ function renderScenario(args, deps) {
13260
13263
  <div class="scenario-header" role="button" tabindex="0" aria-expanded="${ariaExpanded}">
13261
13264
  <div class="scenario-info">
13262
13265
  <div class="scenario-title">
13263
- <span class="status-icon ${statusClass}">${statusIcon}</span>
13266
+ <span class="status-icon ${statusClass}">${statusIcon2}</span>
13264
13267
  <span class="scenario-name">${deps.escapeHtml(tc.story.scenario)}</span>
13265
13268
  </div>
13266
13269
  <div class="scenario-meta">${tags}${tickets}${sourceLink}${traceBadge}${metricBadges}</div>
@@ -13399,11 +13402,11 @@ function buildTooltip(span, escapeHtml3) {
13399
13402
  parts.push(`${key}=${formatted}`);
13400
13403
  }
13401
13404
  }
13402
- let text = parts.join("\n");
13403
- if (text.length > TOOLTIP_MAX_LENGTH) {
13404
- text = text.slice(0, TOOLTIP_MAX_LENGTH - 3) + "...";
13405
+ let text2 = parts.join("\n");
13406
+ if (text2.length > TOOLTIP_MAX_LENGTH) {
13407
+ text2 = text2.slice(0, TOOLTIP_MAX_LENGTH - 3) + "...";
13405
13408
  }
13406
- return escapeHtml3(text);
13409
+ return escapeHtml3(text2);
13407
13410
  }
13408
13411
  function renderTraceView(args, deps) {
13409
13412
  if (!args.spans || args.spans.length === 0) return "";
@@ -13626,11 +13629,11 @@ function renderToc(args, deps) {
13626
13629
  const featureName = suitePaths.length > 0 && suitePaths[0].length > 0 ? suitePaths[0][0] : file.split("/").pop()?.replace(/\.[^.]+$/, "") ?? file;
13627
13630
  const featureSlug = `feature-${slugify(file)}`;
13628
13631
  const scenarios = testCases.map((tc) => {
13629
- const statusIcon = deps.getStatusIcon(tc.status);
13632
+ const statusIcon2 = deps.getStatusIcon(tc.status);
13630
13633
  const statusClass = `status-${tc.status}`;
13631
13634
  const failedClass = tc.status === "failed" ? " toc-failed" : "";
13632
13635
  return `<a class="toc-scenario${failedClass}" href="#scenario-${tc.id}">
13633
- <span class="toc-status ${statusClass}">${statusIcon}</span>
13636
+ <span class="toc-status ${statusClass}">${statusIcon2}</span>
13634
13637
  ${deps.escapeHtml(tc.story.scenario)}
13635
13638
  </a>`;
13636
13639
  }).join("\n");
@@ -13688,7 +13691,7 @@ function createHtmlFormatter(options = {}) {
13688
13691
  escapeHtml,
13689
13692
  getStatusIcon,
13690
13693
  renderDocs,
13691
- highlightStepParams: (text) => highlightStepParams(text, { escapeHtml })
13694
+ highlightStepParams: (text2) => highlightStepParams(text2, { escapeHtml })
13692
13695
  };
13693
13696
  const scenarioDeps = {
13694
13697
  escapeHtml,
@@ -14623,7 +14626,7 @@ function deterministicId(kind, salt, ...parts) {
14623
14626
 
14624
14627
  // src/formatters/cucumber-messages/build-gherkin-document.ts
14625
14628
  function buildGherkinDocumentEnvelopes(uri, testCases, synthesized, salt) {
14626
- const { lineMap, featureName, featureTags, text } = synthesized;
14629
+ const { lineMap, featureName, featureTags, text: text2 } = synthesized;
14627
14630
  const featureTagNodes = featureTags.map((tag, i) => ({
14628
14631
  location: {
14629
14632
  line: lineMap.featureTagLine ?? 1,
@@ -14692,7 +14695,7 @@ function buildGherkinDocumentEnvelopes(uri, testCases, synthesized, salt) {
14692
14695
  sourceEnvelope: {
14693
14696
  source: {
14694
14697
  uri,
14695
- data: text,
14698
+ data: text2,
14696
14699
  mediaType: "text/x.cucumber.gherkin+plain"
14697
14700
  }
14698
14701
  },
@@ -14703,8 +14706,8 @@ function buildStepArguments(step, stepLine) {
14703
14706
  if (!step.docs || step.docs.length === 0) return {};
14704
14707
  const tableDocs = step.docs.filter((d) => d.kind === "table");
14705
14708
  if (tableDocs.length > 0) {
14706
- const table = tableDocs[0];
14707
- return { dataTable: buildDataTable(table, stepLine + 1) };
14709
+ const table2 = tableDocs[0];
14710
+ return { dataTable: buildDataTable(table2, stepLine + 1) };
14708
14711
  }
14709
14712
  for (const doc of step.docs) {
14710
14713
  const ds = docEntryToDocString(doc, stepLine + 1);
@@ -14775,21 +14778,21 @@ function docEntryToDocString(doc, line) {
14775
14778
  return void 0;
14776
14779
  }
14777
14780
  }
14778
- function buildDataTable(table, line) {
14781
+ function buildDataTable(table2, line) {
14779
14782
  const rows = [];
14780
14783
  rows.push({
14781
14784
  location: { line },
14782
- cells: table.columns.map((col) => ({
14785
+ cells: table2.columns.map((col) => ({
14783
14786
  location: { line },
14784
14787
  value: col
14785
14788
  })),
14786
14789
  id: ""
14787
14790
  });
14788
- for (let r = 0; r < table.rows.length; r++) {
14791
+ for (let r = 0; r < table2.rows.length; r++) {
14789
14792
  const rowLine = line + 1 + r;
14790
14793
  rows.push({
14791
14794
  location: { line: rowLine },
14792
- cells: table.rows[r].map((cell) => ({
14795
+ cells: table2.rows[r].map((cell) => ({
14793
14796
  location: { line: rowLine },
14794
14797
  value: cell
14795
14798
  })),
@@ -14880,12 +14883,12 @@ function docEntryToPickleDocString(doc) {
14880
14883
  return void 0;
14881
14884
  }
14882
14885
  }
14883
- function buildPickleTable(table) {
14886
+ function buildPickleTable(table2) {
14884
14887
  const rows = [];
14885
14888
  rows.push({
14886
- cells: table.columns.map((col) => ({ value: col }))
14889
+ cells: table2.columns.map((col) => ({ value: col }))
14887
14890
  });
14888
- for (const row of table.rows) {
14891
+ for (const row of table2.rows) {
14889
14892
  rows.push({
14890
14893
  cells: row.map((cell) => ({ value: cell }))
14891
14894
  });
@@ -16301,6 +16304,414 @@ ${body}`;
16301
16304
  }
16302
16305
  };
16303
16306
 
16307
+ // src/formatters/confluence.ts
16308
+ var ConfluenceFormatter = class {
16309
+ options;
16310
+ constructor(options = {}) {
16311
+ this.options = {
16312
+ title: options.title ?? "User Stories",
16313
+ includeStatusIcons: options.includeStatusIcons ?? true,
16314
+ includeMetadata: options.includeMetadata ?? true,
16315
+ includeSummaryTable: options.includeSummaryTable ?? true,
16316
+ includeErrors: options.includeErrors ?? true,
16317
+ scenarioHeadingLevel: options.scenarioHeadingLevel ?? 3,
16318
+ groupBy: options.groupBy ?? "file",
16319
+ sortScenarios: options.sortScenarios ?? "source",
16320
+ pretty: options.pretty ?? true,
16321
+ permalinkBaseUrl: options.permalinkBaseUrl,
16322
+ ticketUrlTemplate: options.ticketUrlTemplate
16323
+ };
16324
+ }
16325
+ /** Build the ADF document tree. Returns the JS object (not stringified). */
16326
+ formatToAdf(run) {
16327
+ const content = [];
16328
+ content.push(heading(1, [text(this.options.title)]));
16329
+ if (this.options.includeMetadata) {
16330
+ const metaTable = this.renderMetadataTable(run);
16331
+ if (metaTable) content.push(metaTable);
16332
+ }
16333
+ if (this.options.includeSummaryTable) {
16334
+ content.push(this.renderSummaryTable(run));
16335
+ }
16336
+ switch (this.options.groupBy) {
16337
+ case "none":
16338
+ this.renderFlat(content, run.testCases);
16339
+ break;
16340
+ case "suite":
16341
+ this.renderBySuite(content, run.testCases);
16342
+ break;
16343
+ case "file":
16344
+ default:
16345
+ this.renderByFile(content, run.testCases);
16346
+ break;
16347
+ }
16348
+ return { version: 1, type: "doc", content };
16349
+ }
16350
+ /** Format a test run as an ADF JSON string. */
16351
+ format(run) {
16352
+ const adf = this.formatToAdf(run);
16353
+ return this.options.pretty ? JSON.stringify(adf, null, 2) : JSON.stringify(adf);
16354
+ }
16355
+ // --------------------------------------------------------------------------
16356
+ // Metadata / summary tables
16357
+ // --------------------------------------------------------------------------
16358
+ renderMetadataTable(run) {
16359
+ const rows = [];
16360
+ rows.push(["Date", new Date(run.startedAtMs).toISOString()]);
16361
+ if (run.packageVersion) rows.push(["Version", run.packageVersion]);
16362
+ if (run.gitSha) {
16363
+ const shortSha = run.gitSha.length > 7 ? run.gitSha.slice(0, 7) : run.gitSha;
16364
+ rows.push(["Git SHA", shortSha]);
16365
+ }
16366
+ if (rows.length === 0) return null;
16367
+ return table([
16368
+ tableRow([tableHeader("Key"), tableHeader("Value")]),
16369
+ ...rows.map(([k, v]) => tableRow([tableCell(k), tableCell(v)]))
16370
+ ]);
16371
+ }
16372
+ renderSummaryTable(run) {
16373
+ const total = run.testCases.length;
16374
+ const steps = run.testCases.reduce(
16375
+ (acc, tc) => acc + tc.story.steps.length,
16376
+ 0
16377
+ );
16378
+ const passed = run.testCases.filter((tc) => tc.status === "passed").length;
16379
+ const failed = run.testCases.filter((tc) => tc.status === "failed").length;
16380
+ const skipped = run.testCases.filter((tc) => tc.status === "skipped").length;
16381
+ const pending = run.testCases.filter((tc) => tc.status === "pending").length;
16382
+ return table([
16383
+ tableRow([
16384
+ tableHeader("Scenarios"),
16385
+ tableHeader("Steps"),
16386
+ tableHeader("Passed"),
16387
+ tableHeader("Failed"),
16388
+ tableHeader("Skipped"),
16389
+ tableHeader("Pending"),
16390
+ tableHeader("Duration")
16391
+ ]),
16392
+ tableRow([
16393
+ tableCell(String(total)),
16394
+ tableCell(String(steps)),
16395
+ tableCell(String(passed)),
16396
+ tableCell(String(failed)),
16397
+ tableCell(String(skipped)),
16398
+ tableCell(String(pending)),
16399
+ tableCell(formatDuration2(run.durationMs))
16400
+ ])
16401
+ ]);
16402
+ }
16403
+ // --------------------------------------------------------------------------
16404
+ // Grouping
16405
+ // --------------------------------------------------------------------------
16406
+ renderByFile(content, testCases) {
16407
+ const byFile = groupBy7(testCases, (tc) => tc.sourceFile);
16408
+ for (const [file, fileCases] of byFile) {
16409
+ content.push(heading(2, [codeInline(file)]));
16410
+ this.renderSuiteGroups(content, fileCases, 3);
16411
+ }
16412
+ }
16413
+ renderBySuite(content, testCases) {
16414
+ this.renderSuiteGroups(content, testCases, 2);
16415
+ }
16416
+ renderFlat(content, testCases) {
16417
+ const sorted = this.sortCases(testCases);
16418
+ for (const tc of sorted) this.renderScenario(content, tc);
16419
+ }
16420
+ renderSuiteGroups(content, testCases, baseLevel) {
16421
+ const bySuite = groupBy7(testCases, (tc) => tc.titlePath.join(" - "));
16422
+ const entries = this.sortSuiteGroups([...bySuite.entries()]);
16423
+ for (const [suitePath, cases] of entries) {
16424
+ if (suitePath) {
16425
+ content.push(
16426
+ heading(clampHeadingLevel(baseLevel), [text(suitePath)])
16427
+ );
16428
+ }
16429
+ for (const tc of this.sortCases(cases)) {
16430
+ this.renderScenario(content, tc);
16431
+ }
16432
+ }
16433
+ }
16434
+ // --------------------------------------------------------------------------
16435
+ // Scenario
16436
+ // --------------------------------------------------------------------------
16437
+ renderScenario(content, tc) {
16438
+ const level = clampHeadingLevel(this.options.scenarioHeadingLevel);
16439
+ const headingNodes = [];
16440
+ if (this.options.includeStatusIcons) {
16441
+ headingNodes.push(text(`${statusIcon(tc.status)} `));
16442
+ }
16443
+ headingNodes.push(text(tc.story.scenario));
16444
+ content.push(heading(level, headingNodes));
16445
+ const metaChildren = [];
16446
+ if (tc.tags.length > 0) {
16447
+ metaChildren.push(text("Tags: ", strong()));
16448
+ tc.tags.forEach((t, i) => {
16449
+ if (i > 0) metaChildren.push(text(", "));
16450
+ metaChildren.push(codeInline(t));
16451
+ });
16452
+ }
16453
+ if (tc.story.tickets && tc.story.tickets.length > 0) {
16454
+ if (metaChildren.length > 0) metaChildren.push(text(" | "));
16455
+ metaChildren.push(text("Tickets: ", strong()));
16456
+ tc.story.tickets.forEach((ticket, i) => {
16457
+ if (i > 0) metaChildren.push(text(", "));
16458
+ const url = ticket.url ?? (this.options.ticketUrlTemplate ? this.options.ticketUrlTemplate.replace("{ticket}", ticket.id) : void 0);
16459
+ metaChildren.push(
16460
+ url ? link(ticket.id, url) : codeInline(ticket.id)
16461
+ );
16462
+ });
16463
+ }
16464
+ if (this.options.permalinkBaseUrl && tc.sourceFile !== "unknown" && tc.sourceFile) {
16465
+ if (metaChildren.length > 0) metaChildren.push(text(" | "));
16466
+ metaChildren.push(text("Source: ", strong()));
16467
+ const base = this.options.permalinkBaseUrl.replace(/\/$/, "");
16468
+ const url = `${base}/${tc.sourceFile}${tc.sourceLine > 0 ? `#L${tc.sourceLine}` : ""}`;
16469
+ metaChildren.push(link(tc.sourceFile, url));
16470
+ }
16471
+ if (metaChildren.length > 0) {
16472
+ content.push(paragraph(metaChildren));
16473
+ }
16474
+ if (tc.story.docs && tc.story.docs.length > 0) {
16475
+ for (const doc of tc.story.docs) this.renderDocEntry(content, doc);
16476
+ }
16477
+ if (tc.story.steps.length > 0) {
16478
+ content.push(this.renderStepsList(tc.story.steps));
16479
+ for (const step of tc.story.steps) {
16480
+ if (step.docs && step.docs.length > 0) {
16481
+ for (const doc of step.docs) this.renderDocEntry(content, doc);
16482
+ }
16483
+ }
16484
+ }
16485
+ if (tc.status === "failed" && tc.errorMessage && this.options.includeErrors) {
16486
+ const errorContent = (tc.errorMessage ?? "") + (tc.errorStack ? `
16487
+
16488
+ ${tc.errorStack}` : "");
16489
+ content.push(
16490
+ panel("warning", [paragraph([text("Failure", strong())])])
16491
+ );
16492
+ content.push(codeBlock(errorContent, "text"));
16493
+ }
16494
+ }
16495
+ renderStepsList(steps) {
16496
+ return {
16497
+ type: "bulletList",
16498
+ content: steps.map((step) => {
16499
+ const children = [text(`${step.keyword} `, strong()), text(step.text)];
16500
+ if (step.mode && step.mode !== "normal") {
16501
+ children.push(text(` (${step.mode})`, em()));
16502
+ }
16503
+ return {
16504
+ type: "listItem",
16505
+ content: [paragraph(children)]
16506
+ };
16507
+ })
16508
+ };
16509
+ }
16510
+ // --------------------------------------------------------------------------
16511
+ // Doc entries
16512
+ // --------------------------------------------------------------------------
16513
+ renderDocEntry(content, entry) {
16514
+ switch (entry.kind) {
16515
+ case "note":
16516
+ content.push(panel("info", [paragraph([text(entry.text)])]));
16517
+ break;
16518
+ case "tag": {
16519
+ const kids = [];
16520
+ entry.names.forEach((name, i) => {
16521
+ if (i > 0) kids.push(text(" "));
16522
+ kids.push(codeInline(name));
16523
+ });
16524
+ if (kids.length > 0) content.push(paragraph(kids));
16525
+ break;
16526
+ }
16527
+ case "kv": {
16528
+ const val = typeof entry.value === "string" ? entry.value : JSON.stringify(entry.value);
16529
+ content.push(
16530
+ paragraph([text(`${entry.label}: `, strong()), codeInline(val)])
16531
+ );
16532
+ break;
16533
+ }
16534
+ case "code":
16535
+ if (entry.label) {
16536
+ content.push(paragraph([text(entry.label, strong())]));
16537
+ }
16538
+ content.push(codeBlock(entry.content ?? "", entry.lang));
16539
+ break;
16540
+ case "table":
16541
+ if (entry.label) {
16542
+ content.push(paragraph([text(entry.label, strong())]));
16543
+ }
16544
+ content.push(
16545
+ table([
16546
+ tableRow(entry.columns.map((c) => tableHeader(c))),
16547
+ ...entry.rows.map(
16548
+ (row) => tableRow(row.map((cell) => tableCell(cell)))
16549
+ )
16550
+ ])
16551
+ );
16552
+ break;
16553
+ case "link":
16554
+ content.push(paragraph([link(entry.label, entry.url)]));
16555
+ break;
16556
+ case "section":
16557
+ if (entry.title) {
16558
+ content.push(paragraph([text(entry.title, strong())]));
16559
+ }
16560
+ if (entry.markdown) {
16561
+ for (const para of entry.markdown.split(/\n{2,}/)) {
16562
+ const trimmed = para.trim();
16563
+ if (trimmed) content.push(paragraph([text(trimmed)]));
16564
+ }
16565
+ }
16566
+ break;
16567
+ case "mermaid":
16568
+ if (entry.title) {
16569
+ content.push(paragraph([text(entry.title, strong())]));
16570
+ }
16571
+ content.push(codeBlock(entry.code ?? "", "mermaid"));
16572
+ break;
16573
+ case "screenshot":
16574
+ content.push(
16575
+ paragraph([
16576
+ text(entry.alt ?? "Screenshot", strong()),
16577
+ text(": "),
16578
+ link(entry.path, entry.path)
16579
+ ])
16580
+ );
16581
+ break;
16582
+ case "custom":
16583
+ content.push(paragraph([text(`[${entry.type}]`, strong())]));
16584
+ content.push(codeBlock(JSON.stringify(entry.data ?? null, null, 2), "json"));
16585
+ break;
16586
+ }
16587
+ if (entry.children && entry.children.length > 0) {
16588
+ for (const child of entry.children) {
16589
+ this.renderDocEntry(content, child);
16590
+ }
16591
+ }
16592
+ }
16593
+ // --------------------------------------------------------------------------
16594
+ // Sorting
16595
+ // --------------------------------------------------------------------------
16596
+ sortCases(cases) {
16597
+ if (this.options.sortScenarios === "alpha") {
16598
+ return [...cases].sort(
16599
+ (a, b) => a.story.scenario.localeCompare(b.story.scenario)
16600
+ );
16601
+ }
16602
+ if (this.options.sortScenarios === "source") {
16603
+ return [...cases].sort(
16604
+ (a, b) => (a.story.sourceOrder ?? 0) - (b.story.sourceOrder ?? 0)
16605
+ );
16606
+ }
16607
+ return cases;
16608
+ }
16609
+ sortSuiteGroups(entries) {
16610
+ if (this.options.sortScenarios === "alpha") {
16611
+ return entries.sort(([a], [b]) => a.localeCompare(b));
16612
+ }
16613
+ if (this.options.sortScenarios === "source") {
16614
+ return entries.sort(([, a], [, b]) => {
16615
+ const minA = Math.min(...a.map((s) => s.story.sourceOrder ?? Infinity));
16616
+ const minB = Math.min(...b.map((s) => s.story.sourceOrder ?? Infinity));
16617
+ return minA - minB;
16618
+ });
16619
+ }
16620
+ return entries;
16621
+ }
16622
+ };
16623
+ function text(value, mark) {
16624
+ const node = { type: "text", text: value };
16625
+ if (mark) {
16626
+ node.marks = Array.isArray(mark) ? mark : [mark];
16627
+ }
16628
+ return node;
16629
+ }
16630
+ function strong() {
16631
+ return { type: "strong" };
16632
+ }
16633
+ function em() {
16634
+ return { type: "em" };
16635
+ }
16636
+ function codeMark() {
16637
+ return { type: "code" };
16638
+ }
16639
+ function codeInline(value) {
16640
+ return text(value, codeMark());
16641
+ }
16642
+ function link(label, href) {
16643
+ return text(label, { type: "link", attrs: { href } });
16644
+ }
16645
+ function paragraph(content) {
16646
+ return { type: "paragraph", content };
16647
+ }
16648
+ function heading(level, content) {
16649
+ return {
16650
+ type: "heading",
16651
+ attrs: { level: clampHeadingLevel(level) },
16652
+ content
16653
+ };
16654
+ }
16655
+ function codeBlock(content, lang) {
16656
+ return {
16657
+ type: "codeBlock",
16658
+ attrs: lang ? { language: lang } : {},
16659
+ content: content ? [{ type: "text", text: content }] : []
16660
+ };
16661
+ }
16662
+ function panel(panelType, content) {
16663
+ return { type: "panel", attrs: { panelType }, content };
16664
+ }
16665
+ function table(rows) {
16666
+ return {
16667
+ type: "table",
16668
+ attrs: { isNumberColumnEnabled: false, layout: "default" },
16669
+ content: rows
16670
+ };
16671
+ }
16672
+ function tableRow(cells) {
16673
+ return { type: "tableRow", content: cells };
16674
+ }
16675
+ function tableHeader(value) {
16676
+ return { type: "tableHeader", content: [paragraph([text(value)])] };
16677
+ }
16678
+ function tableCell(value) {
16679
+ return { type: "tableCell", content: [paragraph([text(value)])] };
16680
+ }
16681
+ function clampHeadingLevel(level) {
16682
+ if (level < 1) return 1;
16683
+ if (level > 6) return 6;
16684
+ return level;
16685
+ }
16686
+ function statusIcon(status) {
16687
+ switch (status) {
16688
+ case "passed":
16689
+ return "\u2705";
16690
+ case "failed":
16691
+ return "\u274C";
16692
+ case "skipped":
16693
+ return "\u23E9";
16694
+ case "pending":
16695
+ return "\u{1F4DD}";
16696
+ default:
16697
+ return "\u26A0\uFE0F";
16698
+ }
16699
+ }
16700
+ function formatDuration2(ms) {
16701
+ if (ms < 1e3) return `${ms}ms`;
16702
+ return `${(ms / 1e3).toFixed(2)}s`;
16703
+ }
16704
+ function groupBy7(items, keyFn) {
16705
+ const map = /* @__PURE__ */ new Map();
16706
+ for (const item of items) {
16707
+ const key = keyFn(item);
16708
+ const existing = map.get(key);
16709
+ if (existing) existing.push(item);
16710
+ else map.set(key, [item]);
16711
+ }
16712
+ return map;
16713
+ }
16714
+
16304
16715
  // src/formatters/astro-assets.ts
16305
16716
  var fs4 = __toESM(require("fs"), 1);
16306
16717
  var path4 = __toESM(require("path"), 1);
@@ -16826,6 +17237,235 @@ ${result.errors.join("\n")}`);
16826
17237
  }
16827
17238
  }
16828
17239
 
17240
+ // src/publishers/confluence.ts
17241
+ function parseAdf(adf) {
17242
+ let parsed;
17243
+ try {
17244
+ parsed = JSON.parse(adf);
17245
+ } catch (err) {
17246
+ throw new Error(
17247
+ `ADF payload is not valid JSON: ${err.message}`
17248
+ );
17249
+ }
17250
+ if (!parsed || typeof parsed !== "object" || parsed.type !== "doc" || !Array.isArray(parsed.content)) {
17251
+ throw new Error(
17252
+ `ADF payload must be an object with { version, type: "doc", content: [...] }`
17253
+ );
17254
+ }
17255
+ return parsed;
17256
+ }
17257
+ function basicAuthHeader(auth) {
17258
+ const raw = `${auth.email}:${auth.token}`;
17259
+ const encoded = typeof Buffer !== "undefined" ? Buffer.from(raw, "utf8").toString("base64") : btoa(raw);
17260
+ return `Basic ${encoded}`;
17261
+ }
17262
+ async function parseErrorBody(response) {
17263
+ try {
17264
+ const body = await response.text();
17265
+ return body ? body.slice(0, 800) : "";
17266
+ } catch {
17267
+ return "";
17268
+ }
17269
+ }
17270
+ async function publishConfluencePage(args, deps) {
17271
+ parseAdf(args.adf);
17272
+ if (!args.pageId && !args.spaceId) {
17273
+ throw new Error(
17274
+ "publishConfluencePage requires either pageId (update) or spaceId (create)"
17275
+ );
17276
+ }
17277
+ if (!args.pageId && !args.title) {
17278
+ throw new Error("Creating a new page requires a title");
17279
+ }
17280
+ const base = args.baseUrl.replace(/\/$/, "");
17281
+ const fetchFn = deps.fetch ?? globalThis.fetch;
17282
+ if (!fetchFn) {
17283
+ throw new Error("No fetch implementation available (Node >= 22 expected)");
17284
+ }
17285
+ const headers = {
17286
+ Authorization: basicAuthHeader(deps.auth),
17287
+ Accept: "application/json",
17288
+ "Content-Type": "application/json"
17289
+ };
17290
+ if (args.pageId) {
17291
+ return updatePage(args, base, headers, fetchFn);
17292
+ }
17293
+ return createPage(args, base, headers, fetchFn);
17294
+ }
17295
+ async function updatePage(args, base, headers, fetchFn) {
17296
+ const getUrl = `${base}/api/v2/pages/${encodeURIComponent(args.pageId)}`;
17297
+ const getResp = await fetchFn(getUrl, { method: "GET", headers });
17298
+ if (!getResp.ok) {
17299
+ const body = await parseErrorBody(getResp);
17300
+ throw new Error(
17301
+ `GET ${getUrl} failed with ${getResp.status} ${getResp.statusText}${body ? `: ${body}` : ""}`
17302
+ );
17303
+ }
17304
+ const current = await getResp.json();
17305
+ const nextVersion = current.version.number + 1;
17306
+ const title = args.title ?? current.title;
17307
+ const putUrl = `${base}/api/v2/pages/${encodeURIComponent(args.pageId)}`;
17308
+ const putResp = await fetchFn(putUrl, {
17309
+ method: "PUT",
17310
+ headers,
17311
+ body: JSON.stringify({
17312
+ id: args.pageId,
17313
+ status: "current",
17314
+ title,
17315
+ body: {
17316
+ representation: "atlas_doc_format",
17317
+ value: args.adf
17318
+ },
17319
+ version: { number: nextVersion }
17320
+ })
17321
+ });
17322
+ if (!putResp.ok) {
17323
+ const body = await parseErrorBody(putResp);
17324
+ throw new Error(
17325
+ `PUT ${putUrl} failed with ${putResp.status} ${putResp.statusText}${body ? `: ${body}` : ""}`
17326
+ );
17327
+ }
17328
+ const updated = await putResp.json();
17329
+ return {
17330
+ id: updated.id,
17331
+ title: updated.title,
17332
+ version: updated.version.number,
17333
+ url: buildPageUrl(base, updated._links?.webui, updated.id),
17334
+ action: "updated"
17335
+ };
17336
+ }
17337
+ async function createPage(args, base, headers, fetchFn) {
17338
+ const body = {
17339
+ spaceId: args.spaceId,
17340
+ status: "current",
17341
+ title: args.title,
17342
+ body: {
17343
+ representation: "atlas_doc_format",
17344
+ value: args.adf
17345
+ }
17346
+ };
17347
+ if (args.parentId) body.parentId = args.parentId;
17348
+ const postUrl = `${base}/api/v2/pages`;
17349
+ const resp = await fetchFn(postUrl, {
17350
+ method: "POST",
17351
+ headers,
17352
+ body: JSON.stringify(body)
17353
+ });
17354
+ if (!resp.ok) {
17355
+ const errBody = await parseErrorBody(resp);
17356
+ throw new Error(
17357
+ `POST ${postUrl} failed with ${resp.status} ${resp.statusText}${errBody ? `: ${errBody}` : ""}`
17358
+ );
17359
+ }
17360
+ const created = await resp.json();
17361
+ return {
17362
+ id: created.id,
17363
+ title: created.title,
17364
+ version: created.version.number,
17365
+ url: buildPageUrl(base, created._links?.webui, created.id),
17366
+ action: "created"
17367
+ };
17368
+ }
17369
+ function buildPageUrl(base, webui, id) {
17370
+ if (webui) {
17371
+ return webui.startsWith("http") ? webui : `${base}${webui}`;
17372
+ }
17373
+ return `${base}/pages/${id}`;
17374
+ }
17375
+
17376
+ // src/publishers/jira.ts
17377
+ function parseAdf2(adf) {
17378
+ let parsed;
17379
+ try {
17380
+ parsed = JSON.parse(adf);
17381
+ } catch (err) {
17382
+ throw new Error(
17383
+ `ADF payload is not valid JSON: ${err.message}`
17384
+ );
17385
+ }
17386
+ if (!parsed || typeof parsed !== "object" || parsed.type !== "doc" || !Array.isArray(parsed.content)) {
17387
+ throw new Error(
17388
+ `ADF payload must be an object with { version, type: "doc", content: [...] }`
17389
+ );
17390
+ }
17391
+ return parsed;
17392
+ }
17393
+ function basicAuthHeader2(auth) {
17394
+ const raw = `${auth.email}:${auth.token}`;
17395
+ const encoded = typeof Buffer !== "undefined" ? Buffer.from(raw, "utf8").toString("base64") : btoa(raw);
17396
+ return `Basic ${encoded}`;
17397
+ }
17398
+ async function parseErrorBody2(resp) {
17399
+ try {
17400
+ const body = await resp.text();
17401
+ return body ? body.slice(0, 800) : "";
17402
+ } catch {
17403
+ return "";
17404
+ }
17405
+ }
17406
+ async function publishJiraIssue(args, deps) {
17407
+ const adfObject = parseAdf2(args.adf);
17408
+ if (!args.issueKey) {
17409
+ throw new Error("publishJiraIssue requires an issueKey, e.g. PROJ-123");
17410
+ }
17411
+ const base = args.baseUrl.replace(/\/$/, "");
17412
+ const fetchFn = deps.fetch ?? globalThis.fetch;
17413
+ if (!fetchFn) {
17414
+ throw new Error("No fetch implementation available (Node >= 22 expected)");
17415
+ }
17416
+ const headers = {
17417
+ Authorization: basicAuthHeader2(deps.auth),
17418
+ Accept: "application/json",
17419
+ "Content-Type": "application/json"
17420
+ };
17421
+ const mode = args.mode ?? "comment";
17422
+ if (mode === "description") {
17423
+ return updateDescription(args.issueKey, base, adfObject, headers, fetchFn);
17424
+ }
17425
+ return addComment(args.issueKey, base, adfObject, headers, fetchFn);
17426
+ }
17427
+ async function addComment(issueKey, base, adf, headers, fetchFn) {
17428
+ const url = `${base}/rest/api/3/issue/${encodeURIComponent(issueKey)}/comment`;
17429
+ const resp = await fetchFn(url, {
17430
+ method: "POST",
17431
+ headers,
17432
+ body: JSON.stringify({ body: adf })
17433
+ });
17434
+ if (!resp.ok) {
17435
+ const body = await parseErrorBody2(resp);
17436
+ throw new Error(
17437
+ `POST ${url} failed with ${resp.status} ${resp.statusText}${body ? `: ${body}` : ""}`
17438
+ );
17439
+ }
17440
+ const comment = await resp.json();
17441
+ const issueUrl = `${base}/browse/${encodeURIComponent(issueKey)}`;
17442
+ return {
17443
+ issueKey,
17444
+ action: "comment-added",
17445
+ url: `${issueUrl}?focusedCommentId=${encodeURIComponent(comment.id)}`,
17446
+ commentId: comment.id
17447
+ };
17448
+ }
17449
+ async function updateDescription(issueKey, base, adf, headers, fetchFn) {
17450
+ const url = `${base}/rest/api/3/issue/${encodeURIComponent(issueKey)}`;
17451
+ const resp = await fetchFn(url, {
17452
+ method: "PUT",
17453
+ headers,
17454
+ body: JSON.stringify({ fields: { description: adf } })
17455
+ });
17456
+ if (!resp.ok) {
17457
+ const body = await parseErrorBody2(resp);
17458
+ throw new Error(
17459
+ `PUT ${url} failed with ${resp.status} ${resp.statusText}${body ? `: ${body}` : ""}`
17460
+ );
17461
+ }
17462
+ return {
17463
+ issueKey,
17464
+ action: "description-updated",
17465
+ url: `${base}/browse/${encodeURIComponent(issueKey)}`
17466
+ };
17467
+ }
17468
+
16829
17469
  // src/converters/ndjson-parser.ts
16830
17470
  function parseNdjson(ndjson) {
16831
17471
  const lines = ndjson.trim().split("\n").filter(Boolean);
@@ -17126,10 +17766,10 @@ function pickleStepArgumentToDocs(ps) {
17126
17766
  const docs = [];
17127
17767
  const phase = "static";
17128
17768
  if (ps.argument.dataTable) {
17129
- const table = ps.argument.dataTable;
17130
- if (table.rows.length > 0) {
17131
- const columns = table.rows[0].cells.map((c) => c.value);
17132
- const rows = table.rows.slice(1).map((r) => r.cells.map((c) => c.value));
17769
+ const table2 = ps.argument.dataTable;
17770
+ if (table2.rows.length > 0) {
17771
+ const columns = table2.rows[0].cells.map((c) => c.value);
17772
+ const rows = table2.rows.slice(1).map((r) => r.cells.map((c) => c.value));
17133
17773
  docs.push({
17134
17774
  kind: "table",
17135
17775
  label: "",
@@ -17264,7 +17904,7 @@ function readBranchName(cwd = process.cwd()) {
17264
17904
  }
17265
17905
 
17266
17906
  // src/utils/duration.ts
17267
- function formatDuration2(ms) {
17907
+ function formatDuration3(ms) {
17268
17908
  if (ms < 1e3) {
17269
17909
  return `${Math.round(ms)} ms`;
17270
17910
  }
@@ -17452,16 +18092,16 @@ function resolveTraceUrl(template, traceId) {
17452
18092
  }
17453
18093
 
17454
18094
  // src/notifiers/ansi-strip.ts
17455
- function stripAnsi(text) {
17456
- return text.replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, "");
18095
+ function stripAnsi(text2) {
18096
+ return text2.replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, "");
17457
18097
  }
17458
18098
 
17459
18099
  // src/notifiers/slack.ts
17460
- function truncate(text, maxLen) {
17461
- if (text.length <= maxLen) return text;
17462
- return text.slice(0, maxLen - 3) + "...";
18100
+ function truncate(text2, maxLen) {
18101
+ if (text2.length <= maxLen) return text2;
18102
+ return text2.slice(0, maxLen - 3) + "...";
17463
18103
  }
17464
- function formatDuration3(ms) {
18104
+ function formatDuration4(ms) {
17465
18105
  const seconds = ms / 1e3;
17466
18106
  if (seconds < 60) return `${seconds.toFixed(1)}s`;
17467
18107
  const minutes = Math.floor(seconds / 60);
@@ -17488,7 +18128,7 @@ function buildSlackPayload(summary, maxFailedTests) {
17488
18128
  { type: "mrkdwn", text: `*Passed:* ${summary.passed}` },
17489
18129
  { type: "mrkdwn", text: `*Failed:* ${summary.failed}` },
17490
18130
  { type: "mrkdwn", text: `*Skipped:* ${summary.skipped}` },
17491
- { type: "mrkdwn", text: `*Duration:* ${formatDuration3(summary.durationMs)}` },
18131
+ { type: "mrkdwn", text: `*Duration:* ${formatDuration4(summary.durationMs)}` },
17492
18132
  { type: "mrkdwn", text: `*Status:* ${statusText}` }
17493
18133
  ]
17494
18134
  });
@@ -17503,9 +18143,9 @@ function buildSlackPayload(summary, maxFailedTests) {
17503
18143
  }
17504
18144
  return `*${name}*`;
17505
18145
  });
17506
- let text = lines.join("\n\n");
18146
+ let text2 = lines.join("\n\n");
17507
18147
  if (summary.failedTests.length > maxFailedTests) {
17508
- text += `
18148
+ text2 += `
17509
18149
 
17510
18150
  _...and ${summary.failedTests.length - maxFailedTests} more_`;
17511
18151
  }
@@ -17513,7 +18153,7 @@ _...and ${summary.failedTests.length - maxFailedTests} more_`;
17513
18153
  type: "section",
17514
18154
  text: {
17515
18155
  type: "mrkdwn",
17516
- text
18156
+ text: text2
17517
18157
  }
17518
18158
  });
17519
18159
  }
@@ -17590,11 +18230,11 @@ async function sendSlackNotification(args, deps) {
17590
18230
  }
17591
18231
 
17592
18232
  // src/notifiers/teams.ts
17593
- function truncate2(text, maxLen) {
17594
- if (text.length <= maxLen) return text;
17595
- return text.slice(0, maxLen - 3) + "...";
18233
+ function truncate2(text2, maxLen) {
18234
+ if (text2.length <= maxLen) return text2;
18235
+ return text2.slice(0, maxLen - 3) + "...";
17596
18236
  }
17597
- function formatDuration4(ms) {
18237
+ function formatDuration5(ms) {
17598
18238
  const seconds = ms / 1e3;
17599
18239
  if (seconds < 60) return `${seconds.toFixed(1)}s`;
17600
18240
  const minutes = Math.floor(seconds / 60);
@@ -17620,7 +18260,7 @@ function buildTeamsPayload(summary, maxFailedTests) {
17620
18260
  { title: "Passed", value: String(summary.passed) },
17621
18261
  { title: "Failed", value: String(summary.failed) },
17622
18262
  { title: "Skipped", value: String(summary.skipped) },
17623
- { title: "Duration", value: formatDuration4(summary.durationMs) }
18263
+ { title: "Duration", value: formatDuration5(summary.durationMs) }
17624
18264
  ]
17625
18265
  });
17626
18266
  if (summary.failedTests.length > 0) {
@@ -18274,7 +18914,8 @@ var FORMAT_EXTENSIONS = {
18274
18914
  "cucumber-html": ".cucumber.html",
18275
18915
  junit: ".junit.xml",
18276
18916
  "cucumber-json": ".cucumber.json",
18277
- "cucumber-messages": ".ndjson"
18917
+ "cucumber-messages": ".ndjson",
18918
+ confluence: ".adf.json"
18278
18919
  };
18279
18920
  var TEST_EXTENSIONS = [
18280
18921
  ".test.ts",
@@ -18442,6 +19083,19 @@ var ReportGenerator = class {
18442
19083
  includeSourceLinks: options.markdown?.includeSourceLinks ?? true,
18443
19084
  customRenderers: options.markdown?.customRenderers
18444
19085
  },
19086
+ confluence: {
19087
+ title: options.confluence?.title ?? "User Stories",
19088
+ includeStatusIcons: options.confluence?.includeStatusIcons ?? true,
19089
+ includeMetadata: options.confluence?.includeMetadata ?? true,
19090
+ includeSummaryTable: options.confluence?.includeSummaryTable ?? true,
19091
+ includeErrors: options.confluence?.includeErrors ?? true,
19092
+ scenarioHeadingLevel: options.confluence?.scenarioHeadingLevel ?? 3,
19093
+ groupBy: options.confluence?.groupBy ?? "file",
19094
+ sortScenarios: options.confluence?.sortScenarios ?? "source",
19095
+ pretty: options.confluence?.pretty ?? true,
19096
+ permalinkBaseUrl: options.confluence?.permalinkBaseUrl,
19097
+ ticketUrlTemplate: options.confluence?.ticketUrlTemplate
19098
+ },
18445
19099
  astro: {
18446
19100
  assetsDir: options.astro?.assetsDir ?? "public/stories/assets",
18447
19101
  assetsBaseUrl: options.astro?.assetsBaseUrl ?? "/stories/assets",
@@ -18617,6 +19271,22 @@ var ReportGenerator = class {
18617
19271
  });
18618
19272
  return formatter.format(run);
18619
19273
  }
19274
+ case "confluence": {
19275
+ const formatter = new ConfluenceFormatter({
19276
+ title: this.options.confluence.title,
19277
+ includeStatusIcons: this.options.confluence.includeStatusIcons,
19278
+ includeMetadata: this.options.confluence.includeMetadata,
19279
+ includeSummaryTable: this.options.confluence.includeSummaryTable,
19280
+ includeErrors: this.options.confluence.includeErrors,
19281
+ scenarioHeadingLevel: this.options.confluence.scenarioHeadingLevel,
19282
+ groupBy: this.options.confluence.groupBy,
19283
+ sortScenarios: this.options.confluence.sortScenarios,
19284
+ pretty: this.options.confluence.pretty,
19285
+ permalinkBaseUrl: this.options.confluence.permalinkBaseUrl,
19286
+ ticketUrlTemplate: this.options.confluence.ticketUrlTemplate
19287
+ });
19288
+ return formatter.format(run);
19289
+ }
18620
19290
  case "markdown": {
18621
19291
  const formatter = new MarkdownFormatter({
18622
19292
  title: this.options.markdown.title,
@@ -18676,6 +19346,7 @@ function normalizePlaywrightResults(testResults, adapterOptions, canonicalizeOpt
18676
19346
  // Annotate the CommonJS export names for ESM import in node:
18677
19347
  0 && (module.exports = {
18678
19348
  AstroFormatter,
19349
+ ConfluenceFormatter,
18679
19350
  CucumberHtmlFormatter,
18680
19351
  CucumberJsonFormatter,
18681
19352
  CucumberMessagesFormatter,
@@ -18725,6 +19396,8 @@ function normalizePlaywrightResults(testResults, adapterOptions, canonicalizeOpt
18725
19396
  normalizeVitestResults,
18726
19397
  parseEnvelopes,
18727
19398
  parseNdjson,
19399
+ publishConfluencePage,
19400
+ publishJiraIssue,
18728
19401
  readBranchName,
18729
19402
  readGitSha,
18730
19403
  readPackageVersion,