donobu 5.26.0 → 5.27.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/dist/cli/donobu-cli.js +203 -251
- package/dist/codegen/CodeGenerator.js +12 -16
- package/dist/esm/cli/donobu-cli.js +203 -251
- package/dist/esm/codegen/CodeGenerator.js +12 -16
- package/dist/esm/managers/DonobuFlowsManager.js +2 -1
- package/dist/esm/managers/TestsManager.js +2 -2
- package/dist/esm/models/CreateTest.d.ts +1 -1
- package/dist/esm/models/CreateTest.js +6 -0
- package/dist/esm/persistence/DonobuSqliteDb.js +102 -0
- package/dist/esm/persistence/TestConfigHash.d.ts +11 -0
- package/dist/esm/persistence/TestConfigHash.js +31 -0
- package/dist/esm/persistence/flows/FlowsPersistenceSqlite.d.ts +0 -9
- package/dist/esm/persistence/flows/FlowsPersistenceSqlite.js +4 -33
- package/dist/esm/persistence/normalizeFlowMetadata.d.ts +16 -0
- package/dist/esm/persistence/normalizeFlowMetadata.js +34 -0
- package/dist/esm/reporter/buildReport.d.ts +22 -0
- package/dist/esm/reporter/buildReport.js +106 -0
- package/dist/esm/reporter/html.d.ts +5 -9
- package/dist/esm/reporter/html.js +25 -101
- package/dist/esm/reporter/markdown.d.ts +33 -0
- package/dist/esm/reporter/markdown.js +62 -0
- package/dist/esm/reporter/merge.d.ts +33 -0
- package/dist/esm/reporter/merge.js +229 -0
- package/dist/esm/reporter/model.d.ts +101 -0
- package/dist/esm/reporter/model.js +27 -0
- package/dist/{cli/playwright-json-to-html.d.ts → esm/reporter/render.d.ts} +9 -14
- package/dist/esm/{cli/playwright-json-to-html.js → reporter/render.js} +52 -152
- package/dist/esm/reporter/renderMarkdown.d.ts +11 -0
- package/dist/esm/{cli/playwright-json-to-markdown.js → reporter/renderMarkdown.js} +31 -110
- package/dist/esm/reporter/renderSlack.d.ts +17 -0
- package/dist/esm/reporter/renderSlack.js +100 -0
- package/dist/esm/reporter/reportWalk.d.ts +28 -0
- package/dist/esm/reporter/reportWalk.js +61 -0
- package/dist/esm/reporter/slack.d.ts +93 -0
- package/dist/esm/reporter/slack.js +150 -0
- package/dist/esm/reporter/stateFile.d.ts +31 -0
- package/dist/esm/reporter/stateFile.js +70 -0
- package/dist/esm/tools/AssertPageTool.d.ts +2 -2
- package/dist/esm/utils/MiscUtils.d.ts +0 -13
- package/dist/esm/utils/MiscUtils.js +0 -21
- package/dist/esm/utils/displayName.d.ts +16 -0
- package/dist/esm/utils/displayName.js +28 -0
- package/dist/managers/DonobuFlowsManager.js +2 -1
- package/dist/managers/TestsManager.js +2 -2
- package/dist/models/CreateTest.d.ts +1 -1
- package/dist/models/CreateTest.js +6 -0
- package/dist/persistence/DonobuSqliteDb.js +102 -0
- package/dist/persistence/TestConfigHash.d.ts +11 -0
- package/dist/persistence/TestConfigHash.js +31 -0
- package/dist/persistence/flows/FlowsPersistenceSqlite.d.ts +0 -9
- package/dist/persistence/flows/FlowsPersistenceSqlite.js +4 -33
- package/dist/persistence/normalizeFlowMetadata.d.ts +16 -0
- package/dist/persistence/normalizeFlowMetadata.js +34 -0
- package/dist/reporter/buildReport.d.ts +22 -0
- package/dist/reporter/buildReport.js +106 -0
- package/dist/reporter/html.d.ts +5 -9
- package/dist/reporter/html.js +25 -101
- package/dist/reporter/markdown.d.ts +33 -0
- package/dist/reporter/markdown.js +62 -0
- package/dist/reporter/merge.d.ts +33 -0
- package/dist/reporter/merge.js +229 -0
- package/dist/reporter/model.d.ts +101 -0
- package/dist/reporter/model.js +27 -0
- package/dist/{esm/cli/playwright-json-to-html.d.ts → reporter/render.d.ts} +9 -14
- package/dist/{cli/playwright-json-to-html.js → reporter/render.js} +52 -152
- package/dist/reporter/renderMarkdown.d.ts +11 -0
- package/dist/{cli/playwright-json-to-markdown.js → reporter/renderMarkdown.js} +31 -110
- package/dist/reporter/renderSlack.d.ts +17 -0
- package/dist/reporter/renderSlack.js +100 -0
- package/dist/reporter/reportWalk.d.ts +28 -0
- package/dist/reporter/reportWalk.js +61 -0
- package/dist/reporter/slack.d.ts +93 -0
- package/dist/reporter/slack.js +150 -0
- package/dist/reporter/stateFile.d.ts +31 -0
- package/dist/reporter/stateFile.js +70 -0
- package/dist/tools/AssertPageTool.d.ts +2 -2
- package/dist/utils/MiscUtils.d.ts +0 -13
- package/dist/utils/MiscUtils.js +0 -21
- package/dist/utils/displayName.d.ts +16 -0
- package/dist/utils/displayName.js +28 -0
- package/package.json +11 -5
- package/dist/cli/playwright-json-to-markdown.d.ts +0 -43
- package/dist/cli/playwright-json-to-slack-json.d.ts +0 -3
- package/dist/cli/playwright-json-to-slack-json.js +0 -214
- package/dist/esm/cli/playwright-json-to-markdown.d.ts +0 -43
- package/dist/esm/cli/playwright-json-to-slack-json.d.ts +0 -3
- package/dist/esm/cli/playwright-json-to-slack-json.js +0 -214
|
@@ -1,24 +1,19 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
1
|
"use strict";
|
|
3
2
|
/**
|
|
4
|
-
* @fileoverview
|
|
3
|
+
* @fileoverview Donobu HTML report renderer.
|
|
5
4
|
*
|
|
6
|
-
*
|
|
7
|
-
* data) into a
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* @usage
|
|
11
|
-
* ```bash
|
|
12
|
-
* npm exec playwright-json-to-html report.json -o report.html
|
|
13
|
-
* npm exec playwright-json-to-html report.json --triage-dir ./donobu-triage/run-id/ -o report.html
|
|
14
|
-
* cat merged-report.json | npx playwright-json-to-html --triage-dir ./triage/ -o report.html
|
|
15
|
-
* ```
|
|
5
|
+
* Pure library that turns a `DonobuReport` (Playwright-JSON superset with
|
|
6
|
+
* optional triage data) into a self-contained HTML document. No filesystem
|
|
7
|
+
* writes, no CLI arg parsing, no environment variable reads — callers (the
|
|
8
|
+
* reporter and the auto-heal orchestrator) own I/O.
|
|
16
9
|
*/
|
|
17
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.stripAnsi = stripAnsi;
|
|
18
12
|
exports.loadTriageData = loadTriageData;
|
|
19
|
-
exports.
|
|
13
|
+
exports.renderHtml = renderHtml;
|
|
20
14
|
const fs_1 = require("fs");
|
|
21
15
|
const path_1 = require("path");
|
|
16
|
+
const reportWalk_1 = require("./reportWalk");
|
|
22
17
|
// ---------------------------------------------------------------------------
|
|
23
18
|
// Helpers
|
|
24
19
|
// ---------------------------------------------------------------------------
|
|
@@ -132,41 +127,6 @@ function normalizeTestFile(filePath) {
|
|
|
132
127
|
// normalize to "foo.test.ts".
|
|
133
128
|
return (0, path_1.basename)(filePath);
|
|
134
129
|
}
|
|
135
|
-
// ---------------------------------------------------------------------------
|
|
136
|
-
// CLI argument parsing
|
|
137
|
-
// ---------------------------------------------------------------------------
|
|
138
|
-
function parseArgs() {
|
|
139
|
-
const args = process.argv.slice(2);
|
|
140
|
-
let outputFile = null;
|
|
141
|
-
let triageDir = null;
|
|
142
|
-
let inputFile = null;
|
|
143
|
-
for (let i = 0; i < args.length; i++) {
|
|
144
|
-
if ((args[i] === '-o' || args[i] === '--output') && i + 1 < args.length) {
|
|
145
|
-
outputFile = args[i + 1];
|
|
146
|
-
i++;
|
|
147
|
-
}
|
|
148
|
-
else if (args[i] === '--triage-dir' && i + 1 < args.length) {
|
|
149
|
-
triageDir = args[i + 1];
|
|
150
|
-
i++;
|
|
151
|
-
}
|
|
152
|
-
else if (!args[i].startsWith('-')) {
|
|
153
|
-
inputFile = args[i];
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
const jsonData = inputFile
|
|
157
|
-
? JSON.parse((0, fs_1.readFileSync)(inputFile, 'utf8'))
|
|
158
|
-
: JSON.parse((0, fs_1.readFileSync)(0, 'utf8'));
|
|
159
|
-
// Default output to a sibling of the input JSON so asset relative paths work
|
|
160
|
-
if (!outputFile && inputFile) {
|
|
161
|
-
outputFile = (0, path_1.join)((0, path_1.dirname)((0, path_1.resolve)(inputFile)), 'index.html');
|
|
162
|
-
}
|
|
163
|
-
return {
|
|
164
|
-
jsonData,
|
|
165
|
-
outputFile,
|
|
166
|
-
triageDir: triageDir ? (0, path_1.resolve)(triageDir) : null,
|
|
167
|
-
inputFile,
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
130
|
function loadTriageData(triageDir) {
|
|
171
131
|
const plans = [];
|
|
172
132
|
const evidence = [];
|
|
@@ -287,22 +247,13 @@ function parseStderrSteps(stderrEntries) {
|
|
|
287
247
|
}
|
|
288
248
|
return steps;
|
|
289
249
|
}
|
|
290
|
-
// Recursively collect all specs from a suite and its nested sub-suites
|
|
291
|
-
function collectSpecs(suite) {
|
|
292
|
-
const specs = [...(suite.specs ?? [])];
|
|
293
|
-
for (const child of suite.suites ?? []) {
|
|
294
|
-
specs.push(...collectSpecs(child));
|
|
295
|
-
}
|
|
296
|
-
return specs;
|
|
297
|
-
}
|
|
298
250
|
function extractTests(jsonData) {
|
|
299
251
|
const tests = [];
|
|
300
252
|
for (const suite of jsonData.suites ?? []) {
|
|
301
|
-
for (const spec of collectSpecs(suite)) {
|
|
253
|
+
for (const spec of (0, reportWalk_1.collectSpecs)(suite)) {
|
|
302
254
|
for (const test of spec.tests ?? []) {
|
|
303
255
|
const annotations = test.annotations ?? [];
|
|
304
|
-
const
|
|
305
|
-
const isSelfHealed = hasHealAnnotation || test.donobuStatus === 'healed';
|
|
256
|
+
const isSelfHealed = (0, reportWalk_1.isSelfHealed)(test);
|
|
306
257
|
let status;
|
|
307
258
|
const lastResult = test.results?.at(-1);
|
|
308
259
|
if (test.status === 'skipped' ||
|
|
@@ -621,25 +572,40 @@ function renderNativeStep(ns) {
|
|
|
621
572
|
const locationStr = ns.location?.file
|
|
622
573
|
? esc(`${ns.location.file.replace(/.*[/\\]/, '')}:${ns.location.line}`)
|
|
623
574
|
: '';
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
575
|
+
const snippet = ns.location?.file
|
|
576
|
+
? readSourceSnippet(ns.location.file, ns.location.line)
|
|
577
|
+
: null;
|
|
578
|
+
const hasBody = snippet || (!ns.passed && ns.error?.message);
|
|
579
|
+
const renderHeader = (tag) => {
|
|
580
|
+
let header = `<${tag} class="filmstrip-header">`;
|
|
581
|
+
header += statusIcon;
|
|
582
|
+
header += `<span class="native-step-title">${esc(ns.title)}</span>`;
|
|
583
|
+
header += categoryBadge;
|
|
584
|
+
if (locationStr) {
|
|
585
|
+
header += `<span class="native-step-location">${locationStr}</span>`;
|
|
586
|
+
}
|
|
587
|
+
if (tag === 'summary') {
|
|
588
|
+
header +=
|
|
589
|
+
'<span class="native-step-chevron" aria-hidden="true">▸</span>';
|
|
590
|
+
}
|
|
591
|
+
header += `</${tag}>`;
|
|
592
|
+
return header;
|
|
593
|
+
};
|
|
594
|
+
if (!hasBody) {
|
|
595
|
+
return `<div class="filmstrip-step native-step">${renderHeader('div')}</div>`;
|
|
596
|
+
}
|
|
597
|
+
// Failing steps render expanded so the error is immediately visible;
|
|
598
|
+
// passing steps collapse so a test with many expects stays scannable.
|
|
599
|
+
const passClass = ns.passed ? 'native-step--passed' : 'native-step--failed';
|
|
600
|
+
let html = `<details class="filmstrip-step native-step ${passClass}"${ns.passed ? '' : ' open'}>`;
|
|
601
|
+
html += renderHeader('summary');
|
|
633
602
|
if (!ns.passed && ns.error?.message) {
|
|
634
603
|
html += `<pre class="native-step-error">${ansiToHtml(ns.error.message)}</pre>`;
|
|
635
604
|
}
|
|
636
|
-
if (
|
|
637
|
-
|
|
638
|
-
if (snippet) {
|
|
639
|
-
html += snippet;
|
|
640
|
-
}
|
|
605
|
+
if (snippet) {
|
|
606
|
+
html += snippet;
|
|
641
607
|
}
|
|
642
|
-
html += `</
|
|
608
|
+
html += `</details>`;
|
|
643
609
|
return html;
|
|
644
610
|
}
|
|
645
611
|
const AUDIT_CHECK_DEFS = [
|
|
@@ -1223,7 +1189,8 @@ function renderResultTimeline(results, outputDir) {
|
|
|
1223
1189
|
return html;
|
|
1224
1190
|
}
|
|
1225
1191
|
const LOGO_SVG = '<svg viewBox="0 0 245 238" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#logo-clip)"><path d="M8.75 123.921L21.3889 110.367H32.0833L40.8333 133.602L43.75 155.869L32.0833 158.774L19.4444 155.869L8.75 123.921Z" fill="#FF7F3A"/><path d="M237.225 125.857L224.586 112.303H213.891L205.141 135.539L202.225 157.806L213.891 160.71L226.53 157.806L237.225 125.857Z" fill="#FF7F3A"/><path d="M130.276 57.1198L55.415 78.4187L46.665 88.1V142.315V164.583L68.0539 206.212L93.3317 225.575L117.637 231.384C130.276 230.416 156.332 227.898 159.443 225.575C163.332 222.671 186.665 192.658 190.554 182.977C194.443 173.296 195.415 157.806 195.415 147.156C195.415 136.507 196.387 120.048 195.415 115.208C194.637 111.335 177.591 89.0682 169.165 78.4187L130.276 57.1198Z" fill="#FF7F3A"/><path d="M129.868 30.4824C131.882 28.9302 134.006 26.8043 135.359 24.6522C136.96 22.1107 137.166 19.9586 138.26 17.5486C138.487 17.0434 138.418 16.0226 139.353 16.8172C139.565 16.9961 141.99 20.9373 142.291 21.5214C144.854 26.5781 145.435 32.282 145.943 37.8597C168.385 44.6265 188.639 58.1076 200.824 78.3555C205.004 85.296 209.099 93.9466 210.108 101.971C224.772 94.6044 239.515 103.244 243.626 118.262C250.342 142.793 231.583 169.955 205.421 169.855C204.697 174.096 204.153 178.232 202.975 182.4C196.089 206.684 169.943 227.994 146.101 234.582C99.7856 247.374 45.9189 218.933 39.5938 169.782C13.2839 169.982-5.80773 142.093 1.61651 117.51C5.93895 103.192 20.5496 94.6201 34.5791 101.955C35.3453 101.903 36.3176 96.3197 36.6875 95.1358C41.9188 78.566 53.1846 63.2537 67.0819 52.8614C80.1232 43.1111 99.2202 37.8281 107.247 22.9421C111.099 15.7964 111.564 7.89294 110.333-.0157471C123.083 4.47268 130.724 17.1855 129.863 30.4719L129.868 30.4824ZM121.953 106.181C116.272 96.8144 103.95 89.0267 93.0272 87.4008C79.1774 85.3434 65.745 91.2788 58.8492 103.544C48.8727 121.288 56.6246 142.414 74.9606 150.349C50.8279 179.885 74.8179 211.972 107.157 218.949C130.492 223.985 160.295 216.781 173.69 195.828C183.032 181.216 180.908 163.446 169.98 150.349C195.931 140.352 197.643 103.692 172.971 90.8526C155.042 81.5232 133.636 89.8213 123.041 105.849L121.953 106.181ZM36.9517 154.538C35.9107 151.496 35.1075 148.334 34.4787 145.182C33.4219 139.889 32.328 133.38 32.1801 128.018C32.0057 121.793 35.266 115.552 26.954 113.774C15.5138 111.327 12.3803 123.834 13.2628 132.664C14.4306 144.345 24.3067 156.327 36.957 154.538H36.9517ZM217.876 113.658C216.338 113.968 212.423 116.331 212.195 118.036C212.053 119.12 212.734 120.425 212.761 121.666C212.882 127.481 212.407 133.658 211.53 139.41C210.8 144.193 209.553 149.255 208.063 153.864C207.921 155.969 213.178 154.69 214.436 154.401C222.732 152.512 229.21 144.277 231.055 136.253C233.237 126.734 231.261 110.937 217.871 113.663L217.876 113.658Z" fill="#1a1a1a"/><path d="M158.212 170.413C162.36 169.755 164.738 174.88 161.018 178.48C148.087 190.971 111.162 196.554 95.0185 189.509C91.848 188.125 91.1293 183.589 94.2998 181.958C96.5192 180.816 102.902 183.021 105.904 183.399C119.711 185.136 134.507 183.226 147.104 177.248C149.308 176.206 157.44 170.539 158.206 170.413H158.212Z" fill="#1a1a1a"/><path d="M150.305 114.72C163.975 111.91 165.682 135.542 153.835 137.152C140.884 138.915 139.943 116.846 150.305 114.72Z" fill="#1a1a1a"/><path d="M92.7645 114.721C103.808 112.437 108.141 129.933 99.2058 135.853C88.7326 142.788 79.628 123.75 90.0061 116.178C90.767 115.626 91.8503 114.91 92.7645 114.721Z" fill="#1a1a1a"/><path d="M132.355 145.198C137.211 144.109 140.059 148.608 137.713 152.643C136.217 155.222 126.283 159.526 124.841 156.653C123.715 152.88 128.74 146.008 132.355 145.198Z" fill="#1a1a1a"/><path d="M110.192 145.188C116.206 143.872 123.155 155.432 119.847 157.453C115.984 158.085 107.223 154.901 106.657 150.56C106.367 148.308 107.899 145.693 110.198 145.188H110.192Z" fill="#1a1a1a"/></g><defs><clipPath id="logo-clip"><rect width="245" height="237.65" fill="white"/></clipPath></defs></svg>';
|
|
1226
|
-
function
|
|
1192
|
+
function renderHtml(report, triage, outputDir) {
|
|
1193
|
+
const jsonData = report;
|
|
1227
1194
|
const tests = extractTests(jsonData);
|
|
1228
1195
|
const isMergedReport = !!jsonData.metadata?.donobuMergedReport;
|
|
1229
1196
|
// Match triage data to tests
|
|
@@ -1555,9 +1522,9 @@ body::before{content:'';position:fixed;top:-750px;left:50%;transform:translateX(
|
|
|
1555
1522
|
|
|
1556
1523
|
/* Steps */
|
|
1557
1524
|
.steps-section{margin:8px 0;border:1px solid var(--border-subtle);border-radius:var(--radius);overflow:hidden}
|
|
1558
|
-
.steps-section
|
|
1525
|
+
.steps-section>summary{font-size:12px;font-weight:600;color:var(--text-muted);padding:8px 12px;cursor:pointer;user-select:none;background:var(--surface-raised)}
|
|
1559
1526
|
.steps-section summary:hover{background:var(--surface)}
|
|
1560
|
-
.steps-section[open]
|
|
1527
|
+
.steps-section[open]>summary{border-bottom:1px solid var(--border-subtle)}
|
|
1561
1528
|
.steps-list{padding:4px 0}
|
|
1562
1529
|
.step-entry{display:flex;gap:8px;padding:4px 12px;font-size:12px;align-items:baseline}
|
|
1563
1530
|
.step-time{color:var(--text-dim);font-family:var(--mono);font-size:11px;flex-shrink:0;width:85px}
|
|
@@ -1632,18 +1599,23 @@ body::before{content:'';position:fixed;top:-750px;left:50%;transform:translateX(
|
|
|
1632
1599
|
.audit-a11y-more{font-size:10px;color:var(--text-dim);margin-top:2px}
|
|
1633
1600
|
|
|
1634
1601
|
/* Native Playwright steps (expect / test.step) inside the filmstrip */
|
|
1635
|
-
.native-step{
|
|
1602
|
+
details.native-step>summary{list-style:none;cursor:pointer}
|
|
1603
|
+
details.native-step>summary::-webkit-details-marker{display:none}
|
|
1636
1604
|
.native-step-title{font-size:12px;font-weight:500;color:var(--text);font-family:var(--mono);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
1637
1605
|
.native-step-badge{font-size:10px;font-weight:600;padding:1px 5px;border-radius:3px;white-space:nowrap;flex-shrink:0}
|
|
1638
1606
|
.native-step-badge--expect{background:rgba(99,102,241,.12);color:#818cf8}
|
|
1639
1607
|
.native-step-badge--test\.step{background:rgba(16,185,129,.10);color:#34d399}
|
|
1640
1608
|
.native-step-location{font-size:10px;color:var(--text-dim);font-family:var(--mono);margin-left:auto;flex-shrink:0;white-space:nowrap}
|
|
1609
|
+
.native-step-chevron{font-size:10px;color:var(--text-dim);flex-shrink:0;transition:transform .12s;display:inline-block;margin-left:4px}
|
|
1610
|
+
details.native-step[open]>summary .native-step-chevron{transform:rotate(90deg)}
|
|
1641
1611
|
.native-step-error{font-size:11px;font-family:var(--mono);padding:4px 0 2px 22px;margin:0;white-space:pre-wrap;word-break:break-word;color:var(--text-muted)}
|
|
1642
|
-
.native-step-snippet{font-size:11px;font-family:var(--mono);margin:4px 0 2px 22px;
|
|
1612
|
+
.native-step-snippet{font-size:11px;font-family:var(--mono);margin:4px 0 2px 22px;overflow:hidden}
|
|
1643
1613
|
.snippet-line{display:flex;padding:1px 8px;white-space:pre}
|
|
1644
1614
|
.snippet-line--target{background:rgba(239,68,68,.10)}
|
|
1645
1615
|
.snippet-linenum{color:var(--text-dim);min-width:40px;user-select:none}
|
|
1646
1616
|
.snippet-line--target .snippet-linenum{color:var(--red)}
|
|
1617
|
+
.native-step--passed .snippet-line--target{background:rgba(52,211,153,.10)}
|
|
1618
|
+
.native-step--passed .snippet-line--target .snippet-linenum{color:var(--green)}
|
|
1647
1619
|
.snippet-code{color:var(--text)}
|
|
1648
1620
|
|
|
1649
1621
|
/* Result timeline */
|
|
@@ -1871,76 +1843,4 @@ body::before{content:'';position:fixed;top:-750px;left:50%;transform:translateX(
|
|
|
1871
1843
|
</body>
|
|
1872
1844
|
</html>`;
|
|
1873
1845
|
}
|
|
1874
|
-
|
|
1875
|
-
// Main
|
|
1876
|
-
// ---------------------------------------------------------------------------
|
|
1877
|
-
// Only run CLI logic when this file is executed directly (not imported as a module).
|
|
1878
|
-
if (require.main === module) {
|
|
1879
|
-
try {
|
|
1880
|
-
const { jsonData, outputFile, triageDir, inputFile } = parseArgs();
|
|
1881
|
-
// Auto-discover triage dir from merged report metadata if not explicitly given
|
|
1882
|
-
let resolvedTriageDir = triageDir;
|
|
1883
|
-
// Prefer the explicit triageRunDir recorded during the merge.
|
|
1884
|
-
if (!resolvedTriageDir && jsonData.metadata?.triageRunDir) {
|
|
1885
|
-
const candidate = jsonData.metadata.triageRunDir;
|
|
1886
|
-
if ((0, fs_1.existsSync)(candidate)) {
|
|
1887
|
-
resolvedTriageDir = candidate;
|
|
1888
|
-
}
|
|
1889
|
-
}
|
|
1890
|
-
// Legacy fallback: derive triage dir from the initial report path (older
|
|
1891
|
-
// merged reports stored the initial-playwright-report.json copy inside the
|
|
1892
|
-
// triage run directory).
|
|
1893
|
-
if (!resolvedTriageDir && jsonData.metadata?.sources?.initial) {
|
|
1894
|
-
const initialPath = jsonData.metadata.sources.initial;
|
|
1895
|
-
// Try the absolute path first (works when running in the same env as CI)
|
|
1896
|
-
const candidate = (0, path_1.dirname)(initialPath);
|
|
1897
|
-
if ((0, fs_1.existsSync)(candidate) && (0, path_1.basename)(candidate) !== '.') {
|
|
1898
|
-
resolvedTriageDir = candidate;
|
|
1899
|
-
}
|
|
1900
|
-
else {
|
|
1901
|
-
// Fallback: extract the donobu-triage/... suffix and resolve relative
|
|
1902
|
-
// to the input file. This handles downloaded CI artifacts where the
|
|
1903
|
-
// absolute CI path no longer exists.
|
|
1904
|
-
const triageMatch = initialPath.match(/donobu-triage[/\\][^/\\]+[/\\]?.*$/);
|
|
1905
|
-
if (triageMatch && inputFile) {
|
|
1906
|
-
const localCandidate = (0, path_1.resolve)((0, path_1.dirname)((0, path_1.resolve)(inputFile)), (0, path_1.dirname)(triageMatch[0]));
|
|
1907
|
-
if ((0, fs_1.existsSync)(localCandidate)) {
|
|
1908
|
-
resolvedTriageDir = localCandidate;
|
|
1909
|
-
}
|
|
1910
|
-
}
|
|
1911
|
-
}
|
|
1912
|
-
}
|
|
1913
|
-
// Last resort: scan for donobu-triage dirs next to the input file
|
|
1914
|
-
if (!resolvedTriageDir && inputFile) {
|
|
1915
|
-
const triageRoot = (0, path_1.resolve)((0, path_1.dirname)((0, path_1.resolve)(inputFile)), 'donobu-triage');
|
|
1916
|
-
if ((0, fs_1.existsSync)(triageRoot)) {
|
|
1917
|
-
// Use the most recent triage run directory
|
|
1918
|
-
const subdirs = (0, fs_1.readdirSync)(triageRoot)
|
|
1919
|
-
.filter((d) => (0, fs_1.existsSync)((0, path_1.resolve)(triageRoot, d, '.')))
|
|
1920
|
-
.sort()
|
|
1921
|
-
.reverse();
|
|
1922
|
-
if (subdirs.length > 0) {
|
|
1923
|
-
resolvedTriageDir = (0, path_1.resolve)(triageRoot, subdirs[0]);
|
|
1924
|
-
}
|
|
1925
|
-
}
|
|
1926
|
-
}
|
|
1927
|
-
const triage = resolvedTriageDir
|
|
1928
|
-
? loadTriageData(resolvedTriageDir)
|
|
1929
|
-
: { plans: [], evidence: [] };
|
|
1930
|
-
const outputDir = outputFile ? (0, path_1.dirname)((0, path_1.resolve)(outputFile)) : null;
|
|
1931
|
-
const html = generateHtml(jsonData, triage, outputDir);
|
|
1932
|
-
if (outputFile) {
|
|
1933
|
-
(0, fs_1.writeFileSync)(outputFile, html, 'utf8');
|
|
1934
|
-
console.error(`Report written to ${outputFile}`);
|
|
1935
|
-
}
|
|
1936
|
-
else {
|
|
1937
|
-
console.log(html);
|
|
1938
|
-
}
|
|
1939
|
-
}
|
|
1940
|
-
catch (error) {
|
|
1941
|
-
console.error('Error processing JSON:', error.message);
|
|
1942
|
-
process.exit(1);
|
|
1943
|
-
}
|
|
1944
|
-
}
|
|
1945
|
-
// end of CLI guard
|
|
1946
|
-
//# sourceMappingURL=playwright-json-to-html.js.map
|
|
1846
|
+
//# sourceMappingURL=render.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Donobu Markdown report renderer.
|
|
3
|
+
*
|
|
4
|
+
* Pure library that turns a `DonobuReport` into a Markdown string suitable
|
|
5
|
+
* for GitHub Actions step summaries, PR comments, or any other
|
|
6
|
+
* Markdown-consuming surface. No filesystem writes, no CLI arg parsing, no
|
|
7
|
+
* environment variable reads — callers own I/O.
|
|
8
|
+
*/
|
|
9
|
+
import type { DonobuReport } from './model';
|
|
10
|
+
export declare function renderMarkdown(report: DonobuReport): string;
|
|
11
|
+
//# sourceMappingURL=renderMarkdown.d.ts.map
|
|
@@ -1,51 +1,16 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
1
|
"use strict";
|
|
3
2
|
/**
|
|
4
|
-
* @fileoverview
|
|
3
|
+
* @fileoverview Donobu Markdown report renderer.
|
|
5
4
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* @usage
|
|
11
|
-
* Read from file:
|
|
12
|
-
* ```bash
|
|
13
|
-
* npm exec playwright-json-to-markdown report.json
|
|
14
|
-
* ```
|
|
15
|
-
*
|
|
16
|
-
* Read from stdin (useful in pipelines):
|
|
17
|
-
* ```bash
|
|
18
|
-
* cat test-results/results.json | npx playwright-json-to-markdown
|
|
19
|
-
* npm exec playwright test --reporter=json | npx playwright-json-to-markdown
|
|
20
|
-
* ```
|
|
21
|
-
*
|
|
22
|
-
* @input
|
|
23
|
-
* Expects a Playwright JSON report with the following structure:
|
|
24
|
-
* - config: Test configuration including projects.
|
|
25
|
-
* - suites: Array of test suites containing specs and tests.
|
|
26
|
-
* - stats: Summary statistics (duration, expected, unexpected, flaky, skipped).
|
|
27
|
-
*
|
|
28
|
-
* @output
|
|
29
|
-
* Generates a Markdown report containing:
|
|
30
|
-
* - 📊 Summary table with test counts and duration.
|
|
31
|
-
* - Overall pass/fail status with appropriate emoji.
|
|
32
|
-
* - 📋 Detailed results organized by file and test suite.
|
|
33
|
-
* - ❌ Error details and locations for failed tests.
|
|
34
|
-
* - 🚀 List of configured projects.
|
|
35
|
-
*
|
|
36
|
-
* @features
|
|
37
|
-
* - Emoji indicators for test status (✅ passed, ❌ failed).
|
|
38
|
-
* - Human-readable duration formatting (ms, s, m s).
|
|
39
|
-
* - Hierarchical organization (file → spec → test).
|
|
40
|
-
* - Error message and location details for failures.
|
|
41
|
-
* - Summary statistics in tabular format.
|
|
5
|
+
* Pure library that turns a `DonobuReport` into a Markdown string suitable
|
|
6
|
+
* for GitHub Actions step summaries, PR comments, or any other
|
|
7
|
+
* Markdown-consuming surface. No filesystem writes, no CLI arg parsing, no
|
|
8
|
+
* environment variable reads — callers own I/O.
|
|
42
9
|
*/
|
|
43
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
// Function to format duration in a readable way
|
|
11
|
+
exports.renderMarkdown = renderMarkdown;
|
|
12
|
+
const render_1 = require("./render");
|
|
13
|
+
const reportWalk_1 = require("./reportWalk");
|
|
49
14
|
function formatDuration(ms) {
|
|
50
15
|
if (ms < 1000) {
|
|
51
16
|
return `${ms}ms`;
|
|
@@ -58,38 +23,16 @@ function formatDuration(ms) {
|
|
|
58
23
|
const remainingSeconds = seconds % 60;
|
|
59
24
|
return `${minutes}m ${remainingSeconds}s`;
|
|
60
25
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const args = process.argv.slice(2);
|
|
64
|
-
if (args.length > 0) {
|
|
65
|
-
return JSON.parse((0, fs_1.readFileSync)(args[0], 'utf8'));
|
|
66
|
-
}
|
|
67
|
-
else {
|
|
68
|
-
return JSON.parse((0, fs_1.readFileSync)(0, 'utf8')); // Read from stdin
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
// Recursively collect all specs from a suite and its nested sub-suites
|
|
72
|
-
function collectSpecs(suite) {
|
|
73
|
-
const specs = [...(suite.specs ?? [])];
|
|
74
|
-
for (const child of suite.suites ?? []) {
|
|
75
|
-
specs.push(...collectSpecs(child));
|
|
76
|
-
}
|
|
77
|
-
return specs;
|
|
78
|
-
}
|
|
79
|
-
// Process JSON and create markdown
|
|
80
|
-
function generateMarkdown(jsonData) {
|
|
81
|
-
const { suites } = jsonData;
|
|
82
|
-
// Create report header
|
|
26
|
+
function renderMarkdown(report) {
|
|
27
|
+
const suites = (report.suites ?? []);
|
|
83
28
|
let markdown = `# Playwright Test Report\n\n`;
|
|
84
|
-
if (
|
|
29
|
+
if (report.metadata?.donobuMergedReport) {
|
|
85
30
|
markdown += `> ⚙️ Auto-heal summary generated by Donobu (merged initial and retry runs).\n\n`;
|
|
86
31
|
}
|
|
87
|
-
//
|
|
32
|
+
// Per-file summary table
|
|
88
33
|
markdown += `## Summary\n\n`;
|
|
89
|
-
// Create file summary table with status counts
|
|
90
34
|
markdown += `| File | Passed | Self-Healed | Failed | Timed Out | Skipped | Interrupted | Duration |\n`;
|
|
91
35
|
markdown += `| - | - | - | - | - | - | - | - |\n`;
|
|
92
|
-
// Track totals for summary row
|
|
93
36
|
let totalPassed = 0;
|
|
94
37
|
let totalFailed = 0;
|
|
95
38
|
let totalTimedOut = 0;
|
|
@@ -98,27 +41,23 @@ function generateMarkdown(jsonData) {
|
|
|
98
41
|
let totalSelfHealed = 0;
|
|
99
42
|
let totalDuration = 0;
|
|
100
43
|
suites.forEach((suite) => {
|
|
101
|
-
// Count tests by status for this file
|
|
102
44
|
let passed = 0;
|
|
103
45
|
let failed = 0;
|
|
104
46
|
let timedOut = 0;
|
|
105
47
|
let skipped = 0;
|
|
106
48
|
let interrupted = 0;
|
|
107
49
|
let selfHealed = 0;
|
|
108
|
-
const allSpecs = collectSpecs(suite);
|
|
50
|
+
const allSpecs = (0, reportWalk_1.collectSpecs)(suite);
|
|
109
51
|
const fileDuration = allSpecs.reduce((total, spec) => total +
|
|
110
|
-
spec.tests.reduce((testTotal, test) => {
|
|
52
|
+
(spec.tests ?? []).reduce((testTotal, test) => {
|
|
111
53
|
const result = test.results?.at(-1);
|
|
112
|
-
const
|
|
113
|
-
const hasAnnotation = annotations.some((a) => a.type === 'self-healed');
|
|
114
|
-
const donobuHealed = test.donobuStatus === 'healed';
|
|
115
|
-
const isSelfHealed = hasAnnotation || donobuHealed;
|
|
54
|
+
const healed = (0, reportWalk_1.isSelfHealed)(test);
|
|
116
55
|
if (test.status === 'skipped' ||
|
|
117
56
|
(!result && test.status === undefined)) {
|
|
118
57
|
skipped++;
|
|
119
58
|
}
|
|
120
59
|
else if (result) {
|
|
121
|
-
if (
|
|
60
|
+
if (healed) {
|
|
122
61
|
selfHealed++;
|
|
123
62
|
}
|
|
124
63
|
else {
|
|
@@ -143,7 +82,6 @@ function generateMarkdown(jsonData) {
|
|
|
143
82
|
}
|
|
144
83
|
return testTotal + (result?.duration || 0);
|
|
145
84
|
}, 0), 0);
|
|
146
|
-
// Add to totals
|
|
147
85
|
totalPassed += passed;
|
|
148
86
|
totalFailed += failed;
|
|
149
87
|
totalTimedOut += timedOut;
|
|
@@ -153,35 +91,31 @@ function generateMarkdown(jsonData) {
|
|
|
153
91
|
totalDuration += fileDuration;
|
|
154
92
|
markdown += `| ${suite.file} | ${passed ? passed + ' ✅' : ''} | ${selfHealed ? selfHealed + ' ❤️🩹' : ''} | ${failed ? failed + ' ❌' : ''} | ${timedOut ? timedOut + ' ⏰' : ''} | ${skipped ? skipped + ' ⏭️' : ''} | ${interrupted ? interrupted + ' ⚡' : ''} | ${formatDuration(fileDuration)} |\n`;
|
|
155
93
|
});
|
|
156
|
-
// Add totals row
|
|
157
94
|
markdown += `| **TOTAL** | **${totalPassed + ' ✅'}** | **${totalSelfHealed + ' ❤️🩹'}** | **${totalFailed + ' ❌'}** | **${totalTimedOut + ' ⏰'}** | **${totalSkipped + ' ⏭️'}** | **${totalInterrupted + ' ⚡'}** | **${formatDuration(totalDuration)}** |\n`;
|
|
158
95
|
markdown += `\n`;
|
|
159
|
-
//
|
|
96
|
+
// Per-test details
|
|
160
97
|
suites.forEach((suite) => {
|
|
161
98
|
const fileName = suite.file;
|
|
162
99
|
markdown += `## ${fileName}\n\n`;
|
|
163
|
-
collectSpecs(suite).forEach((spec) => {
|
|
100
|
+
(0, reportWalk_1.collectSpecs)(suite).forEach((spec) => {
|
|
164
101
|
markdown += `### ${spec.title}\n\n`;
|
|
165
|
-
spec.tests.forEach((test) => {
|
|
102
|
+
(spec.tests ?? []).forEach((test) => {
|
|
166
103
|
const result = test.results?.at(-1);
|
|
167
|
-
if (test.status === 'skipped' ||
|
|
104
|
+
if (test.status === 'skipped' ||
|
|
105
|
+
(!result && test.status === undefined)) {
|
|
168
106
|
markdown += `**Status**: ⏭️ Skipped \n`;
|
|
169
107
|
markdown += `**Duration**: N/A \n`;
|
|
170
108
|
const objectiveAnnotation = test.annotations?.find((a) => a.type === 'objective');
|
|
171
109
|
if (objectiveAnnotation) {
|
|
172
|
-
const objective = (objectiveAnnotation.description || 'No objective provided').replace(/```/g, '\\`\\`\\`');
|
|
110
|
+
const objective = (objectiveAnnotation.description || 'No objective provided').replace(/```/g, '\\`\\`\\`');
|
|
173
111
|
markdown += `**Objective**:\n\`\`\`\n${objective}\n\`\`\`\n`;
|
|
174
112
|
}
|
|
175
113
|
markdown += `\n---\n\n`;
|
|
176
114
|
return;
|
|
177
115
|
}
|
|
178
|
-
const
|
|
179
|
-
const hasAnnotation = annotations.some((a) => a.type === 'self-healed');
|
|
180
|
-
const donobuHealed = test.donobuStatus === 'healed';
|
|
181
|
-
const isSelfHealed = hasAnnotation || donobuHealed;
|
|
182
|
-
// Determine status based on result status and self-healing annotation
|
|
116
|
+
const healed = (0, reportWalk_1.isSelfHealed)(test);
|
|
183
117
|
let status;
|
|
184
|
-
if (
|
|
118
|
+
if (healed) {
|
|
185
119
|
status = '❤️🩹 Healed';
|
|
186
120
|
}
|
|
187
121
|
else {
|
|
@@ -208,21 +142,19 @@ function generateMarkdown(jsonData) {
|
|
|
208
142
|
const duration = formatDuration(result.duration || 0);
|
|
209
143
|
markdown += `**Status**: ${status} \n`;
|
|
210
144
|
markdown += `**Duration**: ${duration} \n`;
|
|
211
|
-
if (
|
|
145
|
+
if (healed) {
|
|
212
146
|
markdown += `> ❤️🩹 This test was automatically healed by re-running with Donobu treatment plan directives.\n\n`;
|
|
213
147
|
}
|
|
214
148
|
const objectiveAnnotation = test.annotations?.find((a) => a.type === 'objective');
|
|
215
149
|
if (objectiveAnnotation) {
|
|
216
|
-
const objective = (objectiveAnnotation.description || 'No objective provided').replace(/```/g, '\\`\\`\\`');
|
|
150
|
+
const objective = (objectiveAnnotation.description || 'No objective provided').replace(/```/g, '\\`\\`\\`');
|
|
217
151
|
markdown += `**Objective**:\n\`\`\`\n${objective}\n\`\`\`\n`;
|
|
218
152
|
}
|
|
219
|
-
// Add error details if test failed
|
|
220
153
|
if (result.status === 'failed' && result.error) {
|
|
221
154
|
markdown += `\n<details>\n<summary>⚠️ Error Details</summary>\n\n`;
|
|
222
155
|
markdown += `\`\`\`\n${result.error.message || 'No error message available'}\n\`\`\`\n\n`;
|
|
223
|
-
// Include code snippet if available
|
|
224
156
|
if (result.error.snippet) {
|
|
225
|
-
markdown += `**Code Snippet**:\n\`\`\`\n${
|
|
157
|
+
markdown += `**Code Snippet**:\n\`\`\`\n${(0, render_1.stripAnsi)(result.error.snippet)}\n\`\`\`\n\n`;
|
|
226
158
|
}
|
|
227
159
|
markdown += `</details>\n\n`;
|
|
228
160
|
}
|
|
@@ -230,26 +162,15 @@ function generateMarkdown(jsonData) {
|
|
|
230
162
|
});
|
|
231
163
|
});
|
|
232
164
|
});
|
|
233
|
-
if (Array.isArray(
|
|
234
|
-
|
|
165
|
+
if (Array.isArray(report.metadata?.donobuHealedTests) &&
|
|
166
|
+
report.metadata.donobuHealedTests.length > 0) {
|
|
235
167
|
markdown += `### Auto-Healed Tests\n\n`;
|
|
236
|
-
|
|
168
|
+
report.metadata.donobuHealedTests.forEach((entry) => {
|
|
237
169
|
markdown += `- ❤️🩹 ${entry}\n`;
|
|
238
170
|
});
|
|
239
171
|
markdown += `\n`;
|
|
240
172
|
}
|
|
241
|
-
// Add timestamp footer
|
|
242
173
|
markdown += `_Report generated on ${new Date().toLocaleString()} by Donobu_\n`;
|
|
243
174
|
return markdown;
|
|
244
175
|
}
|
|
245
|
-
|
|
246
|
-
try {
|
|
247
|
-
const jsonData = readInput();
|
|
248
|
-
const markdown = generateMarkdown(jsonData);
|
|
249
|
-
console.log(markdown);
|
|
250
|
-
}
|
|
251
|
-
catch (error) {
|
|
252
|
-
console.error('Error processing JSON: ', error.message);
|
|
253
|
-
process.exit(1);
|
|
254
|
-
}
|
|
255
|
-
//# sourceMappingURL=playwright-json-to-markdown.js.map
|
|
176
|
+
//# sourceMappingURL=renderMarkdown.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Donobu Slack payload renderer.
|
|
3
|
+
*
|
|
4
|
+
* Pure library that turns a `DonobuReport` into a Slack Block Kit payload
|
|
5
|
+
* suitable for POSTing to an Incoming Webhook. No filesystem writes, no
|
|
6
|
+
* network calls, no env-var reads — callers own I/O.
|
|
7
|
+
*/
|
|
8
|
+
import type { DonobuReport } from './model';
|
|
9
|
+
export interface RenderSlackOptions {
|
|
10
|
+
/** Optional URL linking back to the full HTML report or CI job. */
|
|
11
|
+
reportUrl?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface SlackBlockPayload {
|
|
14
|
+
blocks: unknown[];
|
|
15
|
+
}
|
|
16
|
+
export declare function renderSlack(report: DonobuReport, options?: RenderSlackOptions): SlackBlockPayload;
|
|
17
|
+
//# sourceMappingURL=renderSlack.d.ts.map
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview Donobu Slack payload renderer.
|
|
4
|
+
*
|
|
5
|
+
* Pure library that turns a `DonobuReport` into a Slack Block Kit payload
|
|
6
|
+
* suitable for POSTing to an Incoming Webhook. No filesystem writes, no
|
|
7
|
+
* network calls, no env-var reads — callers own I/O.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.renderSlack = renderSlack;
|
|
11
|
+
const reportWalk_1 = require("./reportWalk");
|
|
12
|
+
function renderSlack(report, options = {}) {
|
|
13
|
+
const suites = (report.suites ?? []);
|
|
14
|
+
const blocks = [];
|
|
15
|
+
blocks.push({
|
|
16
|
+
type: 'header',
|
|
17
|
+
text: { type: 'plain_text', text: '🐵 Donobu Test Summary' },
|
|
18
|
+
});
|
|
19
|
+
let totalPassed = 0;
|
|
20
|
+
let totalFailed = 0;
|
|
21
|
+
let totalTimedOut = 0;
|
|
22
|
+
let totalSkipped = 0;
|
|
23
|
+
let totalInterrupted = 0;
|
|
24
|
+
let totalSelfHealed = 0;
|
|
25
|
+
suites.forEach((suite) => {
|
|
26
|
+
(0, reportWalk_1.collectSpecs)(suite).forEach((spec) => {
|
|
27
|
+
(spec.tests ?? []).forEach((test) => {
|
|
28
|
+
const result = test.results?.at(-1);
|
|
29
|
+
const healed = (0, reportWalk_1.isSelfHealed)(test);
|
|
30
|
+
if (test.status === 'skipped' ||
|
|
31
|
+
(!result && test.status === undefined)) {
|
|
32
|
+
totalSkipped++;
|
|
33
|
+
}
|
|
34
|
+
else if (result) {
|
|
35
|
+
if (healed) {
|
|
36
|
+
totalSelfHealed++;
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
switch (result.status) {
|
|
40
|
+
case 'passed':
|
|
41
|
+
totalPassed++;
|
|
42
|
+
break;
|
|
43
|
+
case 'failed':
|
|
44
|
+
totalFailed++;
|
|
45
|
+
break;
|
|
46
|
+
case 'timedOut':
|
|
47
|
+
totalTimedOut++;
|
|
48
|
+
break;
|
|
49
|
+
case 'skipped':
|
|
50
|
+
totalSkipped++;
|
|
51
|
+
break;
|
|
52
|
+
case 'interrupted':
|
|
53
|
+
totalInterrupted++;
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
const statusRows = [
|
|
62
|
+
{ name: 'Passed', emoji: '✅', count: totalPassed },
|
|
63
|
+
{ name: 'Self-Healed', emoji: '❤️🩹', count: totalSelfHealed },
|
|
64
|
+
{ name: 'Failed', emoji: '❌', count: totalFailed },
|
|
65
|
+
{ name: 'Timed Out', emoji: '⏰', count: totalTimedOut },
|
|
66
|
+
{ name: 'Skipped', emoji: '⏭️', count: totalSkipped },
|
|
67
|
+
{ name: 'Interrupted', emoji: '⚡', count: totalInterrupted },
|
|
68
|
+
];
|
|
69
|
+
statusRows.forEach((row) => {
|
|
70
|
+
blocks.push({
|
|
71
|
+
type: 'section',
|
|
72
|
+
fields: [
|
|
73
|
+
{ type: 'mrkdwn', text: `${row.emoji} ${row.name}` },
|
|
74
|
+
{ type: 'mrkdwn', text: `${row.count}` },
|
|
75
|
+
],
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
if (options.reportUrl) {
|
|
79
|
+
blocks.push({ type: 'divider' });
|
|
80
|
+
blocks.push({
|
|
81
|
+
type: 'section',
|
|
82
|
+
text: {
|
|
83
|
+
type: 'mrkdwn',
|
|
84
|
+
text: `📊 <${options.reportUrl}|View Full Report>`,
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
blocks.push({ type: 'divider' });
|
|
89
|
+
blocks.push({
|
|
90
|
+
type: 'context',
|
|
91
|
+
elements: [
|
|
92
|
+
{
|
|
93
|
+
type: 'mrkdwn',
|
|
94
|
+
text: `Report generated on ${new Date().toLocaleString()} by Donobu`,
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
});
|
|
98
|
+
return { blocks };
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=renderSlack.js.map
|