@arghajit/dummy 0.3.27 → 0.3.31
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/reporter/playwright-pulse-reporter.js +10 -3
- package/dist/types/index.d.ts +2 -0
- package/package.json +1 -1
- package/scripts/generate-email-report.mjs +18 -3
- package/scripts/generate-report.mjs +112 -35
- package/scripts/generate-static-report.mjs +106 -32
- package/scripts/merge-pulse-report.js +2 -1
- package/dist/index.d.ts +0 -5
- package/dist/index.js +0 -26
- package/dist/playwright-pulse-reporter.d.ts +0 -26
- package/dist/playwright-pulse-reporter.js +0 -304
- package/dist/reporter/lib/report-types.d.ts +0 -8
- package/dist/reporter/lib/report-types.js +0 -2
- package/dist/reporter/reporter/playwright-pulse-reporter.d.ts +0 -1
- package/dist/reporter/reporter/playwright-pulse-reporter.js +0 -398
- package/dist/reporter/types/index.d.ts +0 -52
- package/dist/reporter/types/index.js +0 -2
|
@@ -362,19 +362,24 @@ class PlaywrightPulseReporter {
|
|
|
362
362
|
attempts.sort((a, b) => a.retries - b.retries);
|
|
363
363
|
const firstAttempt = attempts[0];
|
|
364
364
|
const retryAttempts = attempts.slice(1);
|
|
365
|
-
if
|
|
365
|
+
// Only populate retryHistory if there were actual failures that triggered retries
|
|
366
|
+
// If all attempts passed, we don't need to show retry history
|
|
367
|
+
const hasActualRetries = retryAttempts.length > 0 && retryAttempts.some(attempt => attempt.status === 'failed' || attempt.status === 'flaky' || firstAttempt.status === 'failed' || firstAttempt.status === 'flaky');
|
|
368
|
+
if (hasActualRetries) {
|
|
366
369
|
firstAttempt.retryHistory = retryAttempts;
|
|
367
370
|
// Calculate final status and outcome from the last attempt if retries exist
|
|
368
371
|
const lastAttempt = attempts[attempts.length - 1];
|
|
369
372
|
firstAttempt.final_status = lastAttempt.status;
|
|
370
373
|
// If the last attempt was flaky, ensure outcome is set on the main result
|
|
371
|
-
if (lastAttempt.outcome === 'flaky') {
|
|
374
|
+
if (lastAttempt.outcome === 'flaky' || lastAttempt.status === 'flaky') {
|
|
372
375
|
firstAttempt.outcome = 'flaky';
|
|
376
|
+
firstAttempt.status = 'flaky';
|
|
373
377
|
}
|
|
374
378
|
}
|
|
375
379
|
else {
|
|
376
|
-
// If no retries, ensure final_status
|
|
380
|
+
// If no actual retries (all attempts passed), ensure final_status and retryHistory are removed
|
|
377
381
|
delete firstAttempt.final_status;
|
|
382
|
+
delete firstAttempt.retryHistory;
|
|
378
383
|
}
|
|
379
384
|
finalResults.push(firstAttempt);
|
|
380
385
|
}
|
|
@@ -438,6 +443,7 @@ class PlaywrightPulseReporter {
|
|
|
438
443
|
finalRunData.passed = finalResultsList.filter((r) => r.status === "passed").length;
|
|
439
444
|
finalRunData.failed = finalResultsList.filter((r) => r.status === "failed").length;
|
|
440
445
|
finalRunData.skipped = finalResultsList.filter((r) => r.status === "skipped").length;
|
|
446
|
+
finalRunData.flaky = finalResultsList.filter((r) => r.status === "flaky").length;
|
|
441
447
|
finalRunData.totalTests = finalResultsList.length;
|
|
442
448
|
const reviveDates = (key, value) => {
|
|
443
449
|
const isoDateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/;
|
|
@@ -498,6 +504,7 @@ class PlaywrightPulseReporter {
|
|
|
498
504
|
passed: finalResults.filter((r) => r.status === "passed").length,
|
|
499
505
|
failed: finalResults.filter((r) => r.status === "failed").length,
|
|
500
506
|
skipped: finalResults.filter((r) => r.status === "skipped").length,
|
|
507
|
+
flaky: finalResults.filter((r) => r.status === "flaky").length,
|
|
501
508
|
duration,
|
|
502
509
|
environment: environmentDetails,
|
|
503
510
|
};
|
package/dist/types/index.d.ts
CHANGED
|
@@ -70,6 +70,7 @@ export interface TestRun {
|
|
|
70
70
|
passed: number;
|
|
71
71
|
failed: number;
|
|
72
72
|
skipped: number;
|
|
73
|
+
flaky?: number;
|
|
73
74
|
duration: number;
|
|
74
75
|
environment?: EnvDetails | EnvDetails[];
|
|
75
76
|
}
|
|
@@ -78,6 +79,7 @@ export interface TrendDataPoint {
|
|
|
78
79
|
passed: number;
|
|
79
80
|
failed: number;
|
|
80
81
|
skipped: number;
|
|
82
|
+
flaky?: number;
|
|
81
83
|
}
|
|
82
84
|
export interface SummaryMetric {
|
|
83
85
|
label: string;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arghajit/dummy",
|
|
3
3
|
"author": "Arghajit Singha",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.31",
|
|
5
5
|
"description": "A Playwright reporter and dashboard for visualizing test results.",
|
|
6
6
|
"homepage": "https://arghajit47.github.io/playwright-pulse/",
|
|
7
7
|
"repository": {
|
|
@@ -160,6 +160,8 @@ function getStatusClass(status) {
|
|
|
160
160
|
return "status-failed";
|
|
161
161
|
case "skipped":
|
|
162
162
|
return "status-skipped";
|
|
163
|
+
case "flaky":
|
|
164
|
+
return "status-flaky";
|
|
163
165
|
default:
|
|
164
166
|
return "status-unknown";
|
|
165
167
|
}
|
|
@@ -172,6 +174,8 @@ function getStatusIcon(status) {
|
|
|
172
174
|
return "❌";
|
|
173
175
|
case "skipped":
|
|
174
176
|
return "⏭️";
|
|
177
|
+
case "flaky":
|
|
178
|
+
return "⚠";
|
|
175
179
|
default:
|
|
176
180
|
return "❓";
|
|
177
181
|
}
|
|
@@ -243,13 +247,16 @@ function generateMinifiedHTML(reportData) {
|
|
|
243
247
|
)}; font-size: 0.8em; font-weight: 600; padding: 3px 8px; border-radius: 4px; color: #fff; margin-left: 10px; white-space: nowrap;">${severity}</span>`;
|
|
244
248
|
|
|
245
249
|
// --- NEW: Retry Count Badge ---
|
|
246
|
-
const
|
|
250
|
+
const unsuccessfulRetries = (test.retryHistory || []).filter(attempt =>
|
|
251
|
+
attempt.status === 'failed' || attempt.status === 'timedout' || attempt.status === 'flaky'
|
|
252
|
+
);
|
|
253
|
+
const retryCountBadge = (unsuccessfulRetries.length > 0)
|
|
247
254
|
? `<span style="background-color: #f59e0b; border: 1px solid #d97706; font-size: 0.8em; font-weight: 700; padding: 4px 10px; border-radius: 50px; color: #fff; margin-left: 10px; white-space: nowrap; display: inline-flex; align-items: center; gap: 4px;">
|
|
248
255
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="vertical-align: middle;">
|
|
249
256
|
<path d="M1 4v6h6"/>
|
|
250
257
|
<path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/>
|
|
251
258
|
</svg>
|
|
252
|
-
Retry Count: ${
|
|
259
|
+
Retry Count: ${unsuccessfulRetries.length}
|
|
253
260
|
</span>`
|
|
254
261
|
: '';
|
|
255
262
|
|
|
@@ -310,6 +317,7 @@ function generateMinifiedHTML(reportData) {
|
|
|
310
317
|
--light-gray-color: #ecf0f1; /* Light Grey */
|
|
311
318
|
--medium-gray-color: #bdc3c7; /* Medium Grey */
|
|
312
319
|
--dark-gray-color: #7f8c8d; /* Dark Grey */
|
|
320
|
+
--flaky-color: #00ccd3; /* Cyan/Teal for Flaky */
|
|
313
321
|
--text-color: #34495e; /* Dark Grey/Blue for text */
|
|
314
322
|
--background-color: #f8f9fa;
|
|
315
323
|
--card-background-color: #ffffff;
|
|
@@ -400,6 +408,8 @@ function generateMinifiedHTML(reportData) {
|
|
|
400
408
|
.stat-card.failed .value { color: var(--danger-color); }
|
|
401
409
|
.stat-card.skipped { border-left-color: var(--warning-color); }
|
|
402
410
|
.stat-card.skipped .value { color: var(--warning-color); }
|
|
411
|
+
.stat-card.flaky { border-left-color: var(--flaky-color); }
|
|
412
|
+
.stat-card.flaky .value { color: var(--flaky-color); }
|
|
403
413
|
|
|
404
414
|
.section-title {
|
|
405
415
|
font-size: 1.5em;
|
|
@@ -494,6 +504,7 @@ function generateMinifiedHTML(reportData) {
|
|
|
494
504
|
.test-item.status-passed .test-status-label { background-color: var(--success-color); }
|
|
495
505
|
.test-item.status-failed .test-status-label { background-color: var(--danger-color); }
|
|
496
506
|
.test-item.status-skipped .test-status-label { background-color: var(--warning-color); }
|
|
507
|
+
.test-item.status-flaky .test-status-label { background-color: var(--flaky-color); }
|
|
497
508
|
.test-item.status-unknown .test-status-label { background-color: var(--dark-gray-color); }
|
|
498
509
|
|
|
499
510
|
.no-tests {
|
|
@@ -570,6 +581,10 @@ function generateMinifiedHTML(reportData) {
|
|
|
570
581
|
<h3>Skipped</h3>
|
|
571
582
|
<div class="value">${runSummary.skipped || 0}</div>
|
|
572
583
|
</div>
|
|
584
|
+
<div class="stat-card flaky">
|
|
585
|
+
<h3>Flaky</h3>
|
|
586
|
+
<div class="value">${runSummary.flaky || 0}</div>
|
|
587
|
+
</div>
|
|
573
588
|
</div>
|
|
574
589
|
</section>
|
|
575
590
|
|
|
@@ -599,7 +614,7 @@ function generateMinifiedHTML(reportData) {
|
|
|
599
614
|
<option value="passed">✅ Passed</option>
|
|
600
615
|
<option value="failed">❌ Failed</option>
|
|
601
616
|
<option value="skipped">⏭️ Skipped</option>
|
|
602
|
-
<option value="
|
|
617
|
+
<option value="flaky">⚠ Flaky</option>
|
|
603
618
|
</select>
|
|
604
619
|
<select id="filter-min-browser">
|
|
605
620
|
<option value="">All Browsers</option>
|
|
@@ -295,6 +295,12 @@ function generateTestTrendsChart(trendData) {
|
|
|
295
295
|
color: "var(--warning-color)",
|
|
296
296
|
marker: { symbol: "circle" },
|
|
297
297
|
},
|
|
298
|
+
{
|
|
299
|
+
name: "Flaky",
|
|
300
|
+
data: runs.map((r) => r.flaky || 0),
|
|
301
|
+
color: "#00ccd3",
|
|
302
|
+
marker: { symbol: "circle" },
|
|
303
|
+
},
|
|
298
304
|
];
|
|
299
305
|
const runsForTooltip = runs.map((r) => ({
|
|
300
306
|
runId: r.runId,
|
|
@@ -481,6 +487,9 @@ function generateTestHistoryChart(history) {
|
|
|
481
487
|
case "skipped":
|
|
482
488
|
color = "var(--warning-color)";
|
|
483
489
|
break;
|
|
490
|
+
case "flaky":
|
|
491
|
+
color = "var(--neutral-500)";
|
|
492
|
+
break;
|
|
484
493
|
default:
|
|
485
494
|
color = "var(--dark-gray-color)";
|
|
486
495
|
}
|
|
@@ -590,6 +599,9 @@ function generatePieChart(data, chartWidth = 300, chartHeight = 300) {
|
|
|
590
599
|
case "Failed":
|
|
591
600
|
color = "var(--danger-color)";
|
|
592
601
|
break;
|
|
602
|
+
case "Flaky":
|
|
603
|
+
color = "#00ccd3";
|
|
604
|
+
break;
|
|
593
605
|
case "Skipped":
|
|
594
606
|
color = "var(--warning-color)";
|
|
595
607
|
break;
|
|
@@ -828,7 +840,9 @@ function generateEnvironmentSection(environmentData) {
|
|
|
828
840
|
}
|
|
829
841
|
|
|
830
842
|
function generateEnvironmentDashboard(environment, hideHeader = false) {
|
|
831
|
-
const
|
|
843
|
+
const cpuModel = environment.cpu && environment.cpu.model ? environment.cpu.model : "N/A";
|
|
844
|
+
const cpuCores = environment.cpu && environment.cpu.cores ? environment.cpu.cores : "N/A";
|
|
845
|
+
const cpuInfo = `model: ${cpuModel}, cores: ${cpuCores}`;
|
|
832
846
|
const osInfo = environment.os || "N/A";
|
|
833
847
|
const nodeInfo = environment.node || "N/A";
|
|
834
848
|
const v8Info = environment.v8 || "N/A";
|
|
@@ -1140,7 +1154,7 @@ function generateEnvironmentDashboard(environment, hideHeader = false) {
|
|
|
1140
1154
|
</div>
|
|
1141
1155
|
<div class="env-item-content">
|
|
1142
1156
|
<p class="env-item-label">Working Dir</p>
|
|
1143
|
-
<div class="env-item-value" title="${
|
|
1157
|
+
<div class="env-item-value" title="${cwdInfo}">${cwdInfo.length > 30 ? "..." + cwdInfo.slice(-27) : cwdInfo}</div>
|
|
1144
1158
|
</div>
|
|
1145
1159
|
</div>
|
|
1146
1160
|
</div>
|
|
@@ -1164,11 +1178,11 @@ function generateWorkerDistributionChart(results) {
|
|
|
1164
1178
|
const workerId =
|
|
1165
1179
|
typeof test.workerId !== "undefined" ? test.workerId : "N/A";
|
|
1166
1180
|
if (!acc[workerId]) {
|
|
1167
|
-
acc[workerId] = { passed: 0, failed: 0, skipped: 0, tests: [] };
|
|
1181
|
+
acc[workerId] = { passed: 0, failed: 0, skipped: 0, flaky: 0, tests: [] };
|
|
1168
1182
|
}
|
|
1169
1183
|
|
|
1170
1184
|
const status = String(test.status).toLowerCase();
|
|
1171
|
-
if (status === "passed" || status === "failed" || status === "skipped") {
|
|
1185
|
+
if (status === "passed" || status === "failed" || status === "skipped" || status === "flaky") {
|
|
1172
1186
|
acc[workerId][status]++;
|
|
1173
1187
|
}
|
|
1174
1188
|
|
|
@@ -1213,12 +1227,14 @@ function generateWorkerDistributionChart(results) {
|
|
|
1213
1227
|
const passedData = workerIds.map((id) => workerData[id].passed);
|
|
1214
1228
|
const failedData = workerIds.map((id) => workerData[id].failed);
|
|
1215
1229
|
const skippedData = workerIds.map((id) => workerData[id].skipped);
|
|
1230
|
+
const flakyData = workerIds.map((id) => workerData[id].flaky);
|
|
1216
1231
|
|
|
1217
1232
|
const categoriesString = JSON.stringify(categories);
|
|
1218
1233
|
const fullDataString = JSON.stringify(fullWorkerData);
|
|
1219
1234
|
const seriesString = JSON.stringify([
|
|
1220
1235
|
{ name: "Passed", data: passedData, color: "var(--success-color)" },
|
|
1221
1236
|
{ name: "Failed", data: failedData, color: "var(--danger-color)" },
|
|
1237
|
+
{ name: "Flaky", data: flakyData, color: "#00ccd3" },
|
|
1222
1238
|
{ name: "Skipped", data: skippedData, color: "var(--warning-color)" },
|
|
1223
1239
|
]);
|
|
1224
1240
|
|
|
@@ -1311,6 +1327,7 @@ function generateWorkerDistributionChart(results) {
|
|
|
1311
1327
|
if (test.status === 'passed') color = 'var(--success-color)';
|
|
1312
1328
|
else if (test.status === 'failed') color = 'var(--danger-color)';
|
|
1313
1329
|
else if (test.status === 'skipped') color = 'var(--warning-color)';
|
|
1330
|
+
else if (test.status === 'flaky') color = '#00ccd3';
|
|
1314
1331
|
|
|
1315
1332
|
const escapedName = test.name.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
1316
1333
|
testListHtml += \`<li style="color: \${color};"><span style="color: \${color}">[\${test.status.toUpperCase()}]</span> \${escapedName}</li>\`;
|
|
@@ -1476,6 +1493,7 @@ function generateTestHistoryContent(trendData) {
|
|
|
1476
1493
|
<option value="">All Statuses</option>
|
|
1477
1494
|
<option value="passed">Passed</option>
|
|
1478
1495
|
<option value="failed">Failed</option>
|
|
1496
|
+
<option value="flaky">Flaky</option>
|
|
1479
1497
|
<option value="skipped">Skipped</option>
|
|
1480
1498
|
</select>
|
|
1481
1499
|
<button id="clear-history-filters" class="clear-filters-btn">Clear Filters</button>
|
|
@@ -1594,6 +1612,7 @@ function getSuitesData(results) {
|
|
|
1594
1612
|
browser: browser,
|
|
1595
1613
|
passed: 0,
|
|
1596
1614
|
failed: 0,
|
|
1615
|
+
flaky: 0,
|
|
1597
1616
|
skipped: 0,
|
|
1598
1617
|
count: 0,
|
|
1599
1618
|
statusOverall: "passed",
|
|
@@ -1601,12 +1620,15 @@ function getSuitesData(results) {
|
|
|
1601
1620
|
}
|
|
1602
1621
|
const suite = suitesMap.get(key);
|
|
1603
1622
|
suite.count++;
|
|
1604
|
-
|
|
1623
|
+
let currentStatus = String(test.status).toLowerCase();
|
|
1624
|
+
if (test.outcome === 'flaky') currentStatus = 'flaky';
|
|
1605
1625
|
if (currentStatus && suite[currentStatus] !== undefined) {
|
|
1606
1626
|
suite[currentStatus]++;
|
|
1607
1627
|
}
|
|
1608
1628
|
if (currentStatus === "failed") suite.statusOverall = "failed";
|
|
1609
|
-
else if (currentStatus === "
|
|
1629
|
+
else if (currentStatus === "flaky" && suite.statusOverall !== "failed")
|
|
1630
|
+
suite.statusOverall = "flaky";
|
|
1631
|
+
else if (currentStatus === "skipped" && suite.statusOverall !== "failed" && suite.statusOverall !== "flaky")
|
|
1610
1632
|
suite.statusOverall = "skipped";
|
|
1611
1633
|
});
|
|
1612
1634
|
return Array.from(suitesMap.values());
|
|
@@ -1656,6 +1678,10 @@ function generateSuitesWidget(suitesData) {
|
|
|
1656
1678
|
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 16 16"><path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM5.354 4.646a.5.5 0 1 0-.708.708L7.293 8l-2.647 2.646a.5.5 0 0 0 .708.708L8 8.707l2.646 2.647a.5.5 0 0 0 .708-.708L8.707 8l2.647-2.646a.5.5 0 0 0-.708-.708L8 7.293 5.354 4.646z"/></svg>
|
|
1657
1679
|
${suite.failed}
|
|
1658
1680
|
</span>
|
|
1681
|
+
<span class="stat-pill flaky" title="Flaky">
|
|
1682
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 16 16"><path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"/></svg>
|
|
1683
|
+
${suite.flaky || 0}
|
|
1684
|
+
</span>
|
|
1659
1685
|
<span class="stat-pill skipped" title="Skipped">
|
|
1660
1686
|
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 16 16"><path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"/></svg>
|
|
1661
1687
|
${suite.skipped}
|
|
@@ -2060,6 +2086,7 @@ function generateSeverityDistributionChart(results) {
|
|
|
2060
2086
|
const data = {
|
|
2061
2087
|
passed: [0, 0, 0, 0, 0],
|
|
2062
2088
|
failed: [0, 0, 0, 0, 0],
|
|
2089
|
+
flaky: [0, 0, 0, 0, 0],
|
|
2063
2090
|
skipped: [0, 0, 0, 0, 0],
|
|
2064
2091
|
};
|
|
2065
2092
|
|
|
@@ -2078,6 +2105,8 @@ function generateSeverityDistributionChart(results) {
|
|
|
2078
2105
|
status === "interrupted"
|
|
2079
2106
|
) {
|
|
2080
2107
|
data.failed[index]++;
|
|
2108
|
+
} else if (status === "flaky") {
|
|
2109
|
+
data.flaky[index]++;
|
|
2081
2110
|
} else {
|
|
2082
2111
|
data.skipped[index]++;
|
|
2083
2112
|
}
|
|
@@ -2091,6 +2120,7 @@ function generateSeverityDistributionChart(results) {
|
|
|
2091
2120
|
const seriesData = [
|
|
2092
2121
|
{ name: "Passed", data: data.passed, color: "var(--success-color)" },
|
|
2093
2122
|
{ name: "Failed", data: data.failed, color: "var(--danger-color)" },
|
|
2123
|
+
{ name: "Flaky", data: data.flaky, color: "#00ccd3" },
|
|
2094
2124
|
{ name: "Skipped", data: data.skipped, color: "var(--warning-color)" },
|
|
2095
2125
|
];
|
|
2096
2126
|
|
|
@@ -2204,21 +2234,27 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2204
2234
|
return p.replace(new RegExp(`^${DEFAULT_OUTPUT_DIR}[\\\\/]`), "");
|
|
2205
2235
|
};
|
|
2206
2236
|
|
|
2207
|
-
|
|
2208
|
-
const passPercentage = Math.round((runSummary.passed / totalTestsOr1) * 100);
|
|
2209
|
-
const failPercentage = Math.round((runSummary.failed / totalTestsOr1) * 100);
|
|
2210
|
-
const skipPercentage = Math.round(
|
|
2211
|
-
((runSummary.skipped || 0) / totalTestsOr1) * 100,
|
|
2212
|
-
);
|
|
2237
|
+
|
|
2213
2238
|
const avgTestDuration =
|
|
2214
2239
|
runSummary.totalTests > 0
|
|
2215
2240
|
? formatDuration(runSummary.duration / runSummary.totalTests)
|
|
2216
2241
|
: "0.0s";
|
|
2217
2242
|
|
|
2243
|
+
const flakyCount = (results || []).filter(r => r.outcome === 'flaky').length;
|
|
2244
|
+
|
|
2218
2245
|
// Calculate retry statistics
|
|
2246
|
+
let retriedTestsCount = 0;
|
|
2219
2247
|
const totalRetried = (results || []).reduce((acc, test) => {
|
|
2220
2248
|
if (test.retryHistory && test.retryHistory.length > 0) {
|
|
2221
|
-
|
|
2249
|
+
// Filter out any "passed" or "skipped" entries in the history
|
|
2250
|
+
// We only count attempts that actually failed or timed out, triggering a retry.
|
|
2251
|
+
const unsuccessfulRetries = test.retryHistory.filter(attempt =>
|
|
2252
|
+
attempt.status === 'failed' || attempt.status === 'timedout' || attempt.status === 'flaky'
|
|
2253
|
+
);
|
|
2254
|
+
if (unsuccessfulRetries.length > 0) {
|
|
2255
|
+
retriedTestsCount++;
|
|
2256
|
+
}
|
|
2257
|
+
return acc + unsuccessfulRetries.length;
|
|
2222
2258
|
}
|
|
2223
2259
|
return acc;
|
|
2224
2260
|
}, 0);
|
|
@@ -2227,19 +2263,30 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2227
2263
|
let calculatedPassed = 0;
|
|
2228
2264
|
let calculatedFailed = 0;
|
|
2229
2265
|
let calculatedSkipped = 0;
|
|
2266
|
+
let calculatedFlaky = 0;
|
|
2230
2267
|
let calculatedTotal = 0;
|
|
2231
2268
|
|
|
2232
2269
|
(results || []).forEach(test => {
|
|
2233
2270
|
calculatedTotal++;
|
|
2234
|
-
//
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2271
|
+
// New Logic: If outcome is 'flaky', it overrides everything.
|
|
2272
|
+
let statusToUse = test.status;
|
|
2273
|
+
if (test.outcome === 'flaky') {
|
|
2274
|
+
statusToUse = 'flaky';
|
|
2275
|
+
} else if (test.status === 'flaky') {
|
|
2276
|
+
// Just in case outcome wasn't set but status was (unlikely with new reporter)
|
|
2277
|
+
statusToUse = 'flaky';
|
|
2278
|
+
} else if (test.retryHistory && test.retryHistory.length > 0 && test.final_status) {
|
|
2279
|
+
statusToUse = test.final_status;
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
// Update test status in place for charts
|
|
2283
|
+
test.status = statusToUse;
|
|
2238
2284
|
|
|
2239
2285
|
const s = String(statusToUse).toLowerCase();
|
|
2240
2286
|
if (s === 'passed') calculatedPassed++;
|
|
2241
2287
|
else if (s === 'skipped') calculatedSkipped++;
|
|
2242
|
-
else
|
|
2288
|
+
else if (s === 'flaky') calculatedFlaky++;
|
|
2289
|
+
else calculatedFailed++; // failed, timedout, interrupted
|
|
2243
2290
|
});
|
|
2244
2291
|
|
|
2245
2292
|
// Override runSummary counts with our calculated ones if results exist
|
|
@@ -2247,9 +2294,18 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2247
2294
|
runSummary.passed = calculatedPassed;
|
|
2248
2295
|
runSummary.failed = calculatedFailed;
|
|
2249
2296
|
runSummary.skipped = calculatedSkipped;
|
|
2297
|
+
runSummary.flaky = calculatedFlaky;
|
|
2250
2298
|
runSummary.totalTests = calculatedTotal;
|
|
2251
2299
|
}
|
|
2252
2300
|
|
|
2301
|
+
const totalTestsOr1 = runSummary.totalTests || 1;
|
|
2302
|
+
const passPercentage = Math.round((runSummary.passed / totalTestsOr1) * 100);
|
|
2303
|
+
const failPercentage = Math.round((runSummary.failed / totalTestsOr1) * 100);
|
|
2304
|
+
const skipPercentage = Math.round(
|
|
2305
|
+
((runSummary.skipped || 0) / totalTestsOr1) * 100,
|
|
2306
|
+
);
|
|
2307
|
+
const flakyPercentage = Math.round(((runSummary.flaky || 0) / totalTestsOr1) * 100);
|
|
2308
|
+
|
|
2253
2309
|
|
|
2254
2310
|
// Calculate browser distribution
|
|
2255
2311
|
const browserStats = (results || []).reduce((acc, test) => {
|
|
@@ -2284,8 +2340,8 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2284
2340
|
const severityBadge = `<span class="severity-badge" data-severity="${severity.toLowerCase()}">${severity}</span>`;
|
|
2285
2341
|
|
|
2286
2342
|
// --- Retry Count Badge (only show if retries occurred) ---
|
|
2287
|
-
const retryCount = test.retryHistory ? test.retryHistory.length : 0;
|
|
2288
|
-
const retryBadge =
|
|
2343
|
+
const retryCount = (test.retryHistory && test.retryHistory.length > 0) ? test.retryHistory.length : 0;
|
|
2344
|
+
const retryBadge = (test.retryHistory && test.retryHistory.length > 0) ? `<span class="retry-badge">Retry Count: ${retryCount}</span>` : '';
|
|
2289
2345
|
const generateStepsHTML = (steps, depth = 0) => {
|
|
2290
2346
|
if (!steps || steps.length === 0)
|
|
2291
2347
|
return "<div class='no-steps'>No steps recorded for this test.</div>";
|
|
@@ -2381,6 +2437,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2381
2437
|
if(s === 'passed') colorVar = 'var(--success-color)';
|
|
2382
2438
|
else if(s === 'failed') colorVar = 'var(--danger-color)';
|
|
2383
2439
|
else if(s === 'skipped') colorVar = 'var(--warning-color)';
|
|
2440
|
+
else if(s === 'flaky') colorVar = '#00ccd3';
|
|
2384
2441
|
|
|
2385
2442
|
return `<span style="
|
|
2386
2443
|
display: inline-block;
|
|
@@ -2654,7 +2711,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2654
2711
|
? test.final_status
|
|
2655
2712
|
: test.status;
|
|
2656
2713
|
|
|
2657
|
-
const outcomeBadge = (test.outcome)
|
|
2714
|
+
const outcomeBadge = (test.outcome && test.outcome !== 'flaky')
|
|
2658
2715
|
? `<span class="outcome-badge ${test.outcome}">${test.outcome}</span>`
|
|
2659
2716
|
: '';
|
|
2660
2717
|
|
|
@@ -2747,7 +2804,8 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2747
2804
|
--success-color: #10b981; --success-dark: #059669; --success-light: #34d399;
|
|
2748
2805
|
--danger-color: #ef4444; --danger-dark: #dc2626; --danger-light: #f87171;
|
|
2749
2806
|
--warning-color: #f59e0b; --warning-dark: #d97706; --warning-light: #fbbf24;
|
|
2750
|
-
--info-color: #3b82f6;
|
|
2807
|
+
--info-color: #3b82f6;
|
|
2808
|
+
--flaky-color: #00ccd3;
|
|
2751
2809
|
--neutral-50: #fafafa; --neutral-100: #f5f5f5; --neutral-200: #e5e5e5; --neutral-300: #d4d4d4;
|
|
2752
2810
|
--neutral-400: #a3a3a3; --neutral-500: #737373; --neutral-600: #525252; --neutral-700: #404040;
|
|
2753
2811
|
--neutral-800: #262626; --neutral-900: #171717;
|
|
@@ -2970,12 +3028,17 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2970
3028
|
}
|
|
2971
3029
|
}
|
|
2972
3030
|
|
|
3031
|
+
|
|
3032
|
+
.stat-pill.flaky { color: #4b5563; }
|
|
3033
|
+
|
|
2973
3034
|
.dashboard-grid {
|
|
2974
3035
|
display: grid;
|
|
2975
3036
|
grid-template-columns: repeat(4, 1fr);
|
|
2976
3037
|
gap: 0;
|
|
2977
3038
|
margin: 0 0 40px 0;
|
|
2978
3039
|
}
|
|
3040
|
+
.stats-pill.failed { color: var(--danger-dark); }
|
|
3041
|
+
.stats-pill.flaky { color: #4b5563; }
|
|
2979
3042
|
.browser-breakdown {
|
|
2980
3043
|
display: flex;
|
|
2981
3044
|
flex-direction: column;
|
|
@@ -3420,12 +3483,19 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3420
3483
|
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.2);
|
|
3421
3484
|
}
|
|
3422
3485
|
.summary-card.status-failed .value { color: #ef4444; }
|
|
3486
|
+
.summary-card.status-flaky::before { background: #00ccd3; }
|
|
3423
3487
|
.summary-card.status-skipped { background: rgba(245, 158, 11, 0.02); }
|
|
3424
3488
|
.summary-card.status-skipped:hover {
|
|
3425
3489
|
background: rgba(245, 158, 11, 0.15);
|
|
3426
3490
|
box-shadow: 0 4px 12px rgba(245, 158, 11, 0.2);
|
|
3427
3491
|
}
|
|
3428
3492
|
.summary-card.status-skipped .value { color: #f59e0b; }
|
|
3493
|
+
.summary-card.flaky-status { background: rgba(0, 204, 211, 0.05); }
|
|
3494
|
+
.summary-card.flaky-status:hover {
|
|
3495
|
+
background: rgba(0, 204, 211, 0.15);
|
|
3496
|
+
box-shadow: 0 4px 12px rgba(0, 204, 211, 0.2);
|
|
3497
|
+
}
|
|
3498
|
+
.summary-card.flaky-status .value { color: #00ccd3; }
|
|
3429
3499
|
.summary-card:not([class*='status-']) .value { color: #0f172a; }
|
|
3430
3500
|
.dashboard-bottom-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); gap: 28px; align-items: start; }
|
|
3431
3501
|
.dashboard-column {
|
|
@@ -3525,6 +3595,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3525
3595
|
}
|
|
3526
3596
|
.suite-card.status-passed::before { background: var(--success-color); }
|
|
3527
3597
|
.suite-card.status-failed::before { background: var(--danger-color); }
|
|
3598
|
+
.suite-card.status-flaky::before { background: #00ccd3; }
|
|
3528
3599
|
.suite-card.status-skipped::before { background: var(--warning-color); }
|
|
3529
3600
|
|
|
3530
3601
|
/* Outcome Badge */
|
|
@@ -3540,8 +3611,8 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3540
3611
|
letter-spacing: 0.5px;
|
|
3541
3612
|
}
|
|
3542
3613
|
.outcome-badge.flaky {
|
|
3543
|
-
background-color: #
|
|
3544
|
-
color: #
|
|
3614
|
+
background-color: #00ccd3;
|
|
3615
|
+
color: #fff;
|
|
3545
3616
|
}
|
|
3546
3617
|
|
|
3547
3618
|
.suite-card-header {
|
|
@@ -3570,8 +3641,9 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3570
3641
|
}
|
|
3571
3642
|
.status-indicator-dot.status-passed { background-color: var(--success-color); box-shadow: 0 0 0 4px rgba(16, 185, 129, 0.15); }
|
|
3572
3643
|
.status-indicator-dot.status-failed { background-color: var(--danger-color); box-shadow: 0 0 0 4px rgba(239, 68, 68, 0.15); }
|
|
3644
|
+
.status-indicator-dot.status-flaky { background-color: #00ccd3; box-shadow: 0 0 0 4px rgba(0, 204, 211, 0.15); }
|
|
3573
3645
|
.status-indicator-dot.status-skipped { background-color: rgba(245, 158, 11, 0.1); color: var(--warning-dark); border: 1px solid rgba(245, 158, 11, 0.2); }
|
|
3574
|
-
.status-flaky { background-color: rgba(
|
|
3646
|
+
.status-flaky { background-color: rgba(0, 204, 211, 0.1); color: #00ccd3; border: 1px solid #00ccd3; }
|
|
3575
3647
|
|
|
3576
3648
|
.browser-tag {
|
|
3577
3649
|
font-size: 0.8em;
|
|
@@ -3623,6 +3695,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3623
3695
|
.stat-pill svg { width: 14px; height: 14px; }
|
|
3624
3696
|
.stat-pill.passed { color: var(--success-dark); }
|
|
3625
3697
|
.stat-pill.failed { color: var(--danger-dark); }
|
|
3698
|
+
.stat-pill.flaky { color: #00ccd3; }
|
|
3626
3699
|
.stat-pill.skipped { color: var(--warning-dark); }
|
|
3627
3700
|
.filters {
|
|
3628
3701
|
display: flex;
|
|
@@ -3787,7 +3860,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3787
3860
|
border-radius: 0;
|
|
3788
3861
|
font-size: 0.7em;
|
|
3789
3862
|
font-weight: 800;
|
|
3790
|
-
color:
|
|
3863
|
+
color: black;
|
|
3791
3864
|
text-transform: uppercase;
|
|
3792
3865
|
min-width: 100px;
|
|
3793
3866
|
text-align: center;
|
|
@@ -4496,14 +4569,16 @@ function generateHTML(reportData, trendData = null) {
|
|
|
4496
4569
|
<div class="summary-card status-skipped"><h3>Skipped</h3><div class="value">${
|
|
4497
4570
|
runSummary.skipped || 0
|
|
4498
4571
|
}</div><div class="trend-percentage">${skipPercentage}%</div></div>
|
|
4499
|
-
<div class="summary-card"><h3>
|
|
4500
|
-
<div class="
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4572
|
+
<div class="summary-card flaky-status"><h3>Flaky</h3><div class="value">${runSummary.flaky || 0}</div>
|
|
4573
|
+
<div class="trend-percentage">${flakyPercentage}%</div></div>
|
|
4574
|
+
<div class="summary-card"><h3>Run Duration</h3><div class="value">${formatDuration(
|
|
4575
|
+
runSummary.duration,
|
|
4576
|
+
)}</div><div class="trend-percentage">Avg. Test Duration ${avgTestDuration}</div></div>
|
|
4577
|
+
<div class="summary-card">
|
|
4578
|
+
<h3>Total Retry Count</h3>
|
|
4579
|
+
<div class="value">${totalRetried}</div>
|
|
4580
|
+
<div class="trend-percentage">Test Retried ${retriedTestsCount}</div>
|
|
4581
|
+
</div>
|
|
4507
4582
|
<div class="summary-card">
|
|
4508
4583
|
<h3>🌐 Browser Distribution <span style="font-size: 0.7em; color: var(--text-color-secondary); font-weight: 400;">(${browserBreakdown.length} total)</span></h3>
|
|
4509
4584
|
<div class="browser-breakdown" style="max-height: 200px; overflow-y: auto; padding-right: 4px;">
|
|
@@ -4533,6 +4608,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
4533
4608
|
[
|
|
4534
4609
|
{ label: "Passed", value: runSummary.passed },
|
|
4535
4610
|
{ label: "Failed", value: runSummary.failed },
|
|
4611
|
+
{ label: "Flaky", value: runSummary.flaky || 0 },
|
|
4536
4612
|
{ label: "Skipped", value: runSummary.skipped || 0 },
|
|
4537
4613
|
],
|
|
4538
4614
|
400,
|
|
@@ -4550,7 +4626,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
4550
4626
|
<div id="test-runs" class="tab-content">
|
|
4551
4627
|
<div class="filters">
|
|
4552
4628
|
<input type="text" id="filter-name" placeholder="Filter by test name/path..." style="border-color: black; border-style: outset;">
|
|
4553
|
-
<select id="filter-status"><option value="">All Statuses</option><option value="passed">Passed</option><option value="failed">Failed</option><option value="skipped">Skipped</option></select>
|
|
4629
|
+
<select id="filter-status"><option value="">All Statuses</option><option value="passed">Passed</option><option value="failed">Failed</option><option value="flaky">Flaky</option><option value="skipped">Skipped</option></select>
|
|
4554
4630
|
<select id="filter-browser"><option value="">All Browsers</option>${Array.from(
|
|
4555
4631
|
new Set(
|
|
4556
4632
|
(results || []).map((test) => test.browser || "unknown"),
|
|
@@ -5308,6 +5384,7 @@ async function main() {
|
|
|
5308
5384
|
passed: histRunReport.run.passed,
|
|
5309
5385
|
failed: histRunReport.run.failed,
|
|
5310
5386
|
skipped: histRunReport.run.skipped || 0,
|
|
5387
|
+
flaky: histRunReport.run.flaky || (histRunReport.results ? histRunReport.results.filter(r => r.status === 'flaky' || r.outcome === 'flaky').length : 0),
|
|
5311
5388
|
});
|
|
5312
5389
|
|
|
5313
5390
|
if (histRunReport.results && Array.isArray(histRunReport.results)) {
|