@arghajit/dummy 0.3.28 → 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/pulse.js +6 -3
- package/dist/reporter/playwright-pulse-reporter.js +6 -2
- package/package.json +1 -1
- package/scripts/generate-email-report.mjs +18 -3
- package/scripts/generate-report.mjs +40 -28
- package/scripts/generate-static-report.mjs +33 -22
- package/dist/index.d.ts +0 -5
- package/dist/index.js +0 -26
- package/dist/lib/data-reader.js +0 -116
- package/dist/lib/data.js +0 -39
- package/dist/lib/utils.js +0 -1
- 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
- package/dist/tsconfig.tsbuildinfo +0 -1
package/dist/pulse.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.pulse = void 0;
|
|
4
|
+
const test_1 = require("@playwright/test");
|
|
5
|
+
exports.pulse = {
|
|
3
6
|
/**
|
|
4
7
|
* Sets the severity level for the current test.
|
|
5
8
|
* * @param level - The severity level ('Minor' | 'Low' | 'Medium' | 'High' | 'Critical')
|
|
@@ -13,7 +16,7 @@ export const pulse = {
|
|
|
13
16
|
// Default to "Medium" if an invalid string is passed
|
|
14
17
|
const selectedLevel = validLevels.includes(level) ? level : "Medium";
|
|
15
18
|
// Add the annotation to Playwright's test info
|
|
16
|
-
test.info().annotations.push({
|
|
19
|
+
test_1.test.info().annotations.push({
|
|
17
20
|
type: "pulse_severity",
|
|
18
21
|
description: selectedLevel,
|
|
19
22
|
});
|
|
@@ -362,7 +362,10 @@ 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];
|
|
@@ -374,8 +377,9 @@ class PlaywrightPulseReporter {
|
|
|
374
377
|
}
|
|
375
378
|
}
|
|
376
379
|
else {
|
|
377
|
-
// If no retries, ensure final_status
|
|
380
|
+
// If no actual retries (all attempts passed), ensure final_status and retryHistory are removed
|
|
378
381
|
delete firstAttempt.final_status;
|
|
382
|
+
delete firstAttempt.retryHistory;
|
|
379
383
|
}
|
|
380
384
|
finalResults.push(firstAttempt);
|
|
381
385
|
}
|
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>
|
|
@@ -298,7 +298,7 @@ function generateTestTrendsChart(trendData) {
|
|
|
298
298
|
{
|
|
299
299
|
name: "Flaky",
|
|
300
300
|
data: runs.map((r) => r.flaky || 0),
|
|
301
|
-
color: "
|
|
301
|
+
color: "#00ccd3",
|
|
302
302
|
marker: { symbol: "circle" },
|
|
303
303
|
},
|
|
304
304
|
];
|
|
@@ -600,7 +600,7 @@ function generatePieChart(data, chartWidth = 300, chartHeight = 300) {
|
|
|
600
600
|
color = "var(--danger-color)";
|
|
601
601
|
break;
|
|
602
602
|
case "Flaky":
|
|
603
|
-
color = "
|
|
603
|
+
color = "#00ccd3";
|
|
604
604
|
break;
|
|
605
605
|
case "Skipped":
|
|
606
606
|
color = "var(--warning-color)";
|
|
@@ -1234,7 +1234,7 @@ function generateWorkerDistributionChart(results) {
|
|
|
1234
1234
|
const seriesString = JSON.stringify([
|
|
1235
1235
|
{ name: "Passed", data: passedData, color: "var(--success-color)" },
|
|
1236
1236
|
{ name: "Failed", data: failedData, color: "var(--danger-color)" },
|
|
1237
|
-
{ name: "Flaky", data: flakyData, color: "
|
|
1237
|
+
{ name: "Flaky", data: flakyData, color: "#00ccd3" },
|
|
1238
1238
|
{ name: "Skipped", data: skippedData, color: "var(--warning-color)" },
|
|
1239
1239
|
]);
|
|
1240
1240
|
|
|
@@ -1327,7 +1327,7 @@ function generateWorkerDistributionChart(results) {
|
|
|
1327
1327
|
if (test.status === 'passed') color = 'var(--success-color)';
|
|
1328
1328
|
else if (test.status === 'failed') color = 'var(--danger-color)';
|
|
1329
1329
|
else if (test.status === 'skipped') color = 'var(--warning-color)';
|
|
1330
|
-
else if (test.status === 'flaky') color = '
|
|
1330
|
+
else if (test.status === 'flaky') color = '#00ccd3';
|
|
1331
1331
|
|
|
1332
1332
|
const escapedName = test.name.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
1333
1333
|
testListHtml += \`<li style="color: \${color};"><span style="color: \${color}">[\${test.status.toUpperCase()}]</span> \${escapedName}</li>\`;
|
|
@@ -2120,7 +2120,7 @@ function generateSeverityDistributionChart(results) {
|
|
|
2120
2120
|
const seriesData = [
|
|
2121
2121
|
{ name: "Passed", data: data.passed, color: "var(--success-color)" },
|
|
2122
2122
|
{ name: "Failed", data: data.failed, color: "var(--danger-color)" },
|
|
2123
|
-
{ name: "Flaky", data: data.flaky, color: "
|
|
2123
|
+
{ name: "Flaky", data: data.flaky, color: "#00ccd3" },
|
|
2124
2124
|
{ name: "Skipped", data: data.skipped, color: "var(--warning-color)" },
|
|
2125
2125
|
];
|
|
2126
2126
|
|
|
@@ -2243,9 +2243,18 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2243
2243
|
const flakyCount = (results || []).filter(r => r.outcome === 'flaky').length;
|
|
2244
2244
|
|
|
2245
2245
|
// Calculate retry statistics
|
|
2246
|
+
let retriedTestsCount = 0;
|
|
2246
2247
|
const totalRetried = (results || []).reduce((acc, test) => {
|
|
2247
2248
|
if (test.retryHistory && test.retryHistory.length > 0) {
|
|
2248
|
-
|
|
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;
|
|
2249
2258
|
}
|
|
2250
2259
|
return acc;
|
|
2251
2260
|
}, 0);
|
|
@@ -2331,8 +2340,8 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2331
2340
|
const severityBadge = `<span class="severity-badge" data-severity="${severity.toLowerCase()}">${severity}</span>`;
|
|
2332
2341
|
|
|
2333
2342
|
// --- Retry Count Badge (only show if retries occurred) ---
|
|
2334
|
-
const retryCount = test.retryHistory ? test.retryHistory.length : 0;
|
|
2335
|
-
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>` : '';
|
|
2336
2345
|
const generateStepsHTML = (steps, depth = 0) => {
|
|
2337
2346
|
if (!steps || steps.length === 0)
|
|
2338
2347
|
return "<div class='no-steps'>No steps recorded for this test.</div>";
|
|
@@ -2428,6 +2437,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2428
2437
|
if(s === 'passed') colorVar = 'var(--success-color)';
|
|
2429
2438
|
else if(s === 'failed') colorVar = 'var(--danger-color)';
|
|
2430
2439
|
else if(s === 'skipped') colorVar = 'var(--warning-color)';
|
|
2440
|
+
else if(s === 'flaky') colorVar = '#00ccd3';
|
|
2431
2441
|
|
|
2432
2442
|
return `<span style="
|
|
2433
2443
|
display: inline-block;
|
|
@@ -2794,7 +2804,8 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2794
2804
|
--success-color: #10b981; --success-dark: #059669; --success-light: #34d399;
|
|
2795
2805
|
--danger-color: #ef4444; --danger-dark: #dc2626; --danger-light: #f87171;
|
|
2796
2806
|
--warning-color: #f59e0b; --warning-dark: #d97706; --warning-light: #fbbf24;
|
|
2797
|
-
--info-color: #3b82f6;
|
|
2807
|
+
--info-color: #3b82f6;
|
|
2808
|
+
--flaky-color: #00ccd3;
|
|
2798
2809
|
--neutral-50: #fafafa; --neutral-100: #f5f5f5; --neutral-200: #e5e5e5; --neutral-300: #d4d4d4;
|
|
2799
2810
|
--neutral-400: #a3a3a3; --neutral-500: #737373; --neutral-600: #525252; --neutral-700: #404040;
|
|
2800
2811
|
--neutral-800: #262626; --neutral-900: #171717;
|
|
@@ -3472,19 +3483,19 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3472
3483
|
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.2);
|
|
3473
3484
|
}
|
|
3474
3485
|
.summary-card.status-failed .value { color: #ef4444; }
|
|
3475
|
-
.summary-card.status-flaky::before { background:
|
|
3486
|
+
.summary-card.status-flaky::before { background: #00ccd3; }
|
|
3476
3487
|
.summary-card.status-skipped { background: rgba(245, 158, 11, 0.02); }
|
|
3477
3488
|
.summary-card.status-skipped:hover {
|
|
3478
3489
|
background: rgba(245, 158, 11, 0.15);
|
|
3479
3490
|
box-shadow: 0 4px 12px rgba(245, 158, 11, 0.2);
|
|
3480
3491
|
}
|
|
3481
3492
|
.summary-card.status-skipped .value { color: #f59e0b; }
|
|
3482
|
-
.summary-card.flaky-status { background: rgba(
|
|
3493
|
+
.summary-card.flaky-status { background: rgba(0, 204, 211, 0.05); }
|
|
3483
3494
|
.summary-card.flaky-status:hover {
|
|
3484
|
-
background: rgba(
|
|
3485
|
-
box-shadow: 0 4px 12px rgba(
|
|
3495
|
+
background: rgba(0, 204, 211, 0.15);
|
|
3496
|
+
box-shadow: 0 4px 12px rgba(0, 204, 211, 0.2);
|
|
3486
3497
|
}
|
|
3487
|
-
.summary-card.flaky-status .value { color: #
|
|
3498
|
+
.summary-card.flaky-status .value { color: #00ccd3; }
|
|
3488
3499
|
.summary-card:not([class*='status-']) .value { color: #0f172a; }
|
|
3489
3500
|
.dashboard-bottom-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); gap: 28px; align-items: start; }
|
|
3490
3501
|
.dashboard-column {
|
|
@@ -3584,7 +3595,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3584
3595
|
}
|
|
3585
3596
|
.suite-card.status-passed::before { background: var(--success-color); }
|
|
3586
3597
|
.suite-card.status-failed::before { background: var(--danger-color); }
|
|
3587
|
-
.suite-card.status-flaky::before { background:
|
|
3598
|
+
.suite-card.status-flaky::before { background: #00ccd3; }
|
|
3588
3599
|
.suite-card.status-skipped::before { background: var(--warning-color); }
|
|
3589
3600
|
|
|
3590
3601
|
/* Outcome Badge */
|
|
@@ -3600,8 +3611,8 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3600
3611
|
letter-spacing: 0.5px;
|
|
3601
3612
|
}
|
|
3602
3613
|
.outcome-badge.flaky {
|
|
3603
|
-
background-color: #
|
|
3604
|
-
color: #
|
|
3614
|
+
background-color: #00ccd3;
|
|
3615
|
+
color: #fff;
|
|
3605
3616
|
}
|
|
3606
3617
|
|
|
3607
3618
|
.suite-card-header {
|
|
@@ -3630,9 +3641,9 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3630
3641
|
}
|
|
3631
3642
|
.status-indicator-dot.status-passed { background-color: var(--success-color); box-shadow: 0 0 0 4px rgba(16, 185, 129, 0.15); }
|
|
3632
3643
|
.status-indicator-dot.status-failed { background-color: var(--danger-color); box-shadow: 0 0 0 4px rgba(239, 68, 68, 0.15); }
|
|
3633
|
-
.status-indicator-dot.status-flaky { background-color:
|
|
3644
|
+
.status-indicator-dot.status-flaky { background-color: #00ccd3; box-shadow: 0 0 0 4px rgba(0, 204, 211, 0.15); }
|
|
3634
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); }
|
|
3635
|
-
.status-flaky { background-color:
|
|
3646
|
+
.status-flaky { background-color: rgba(0, 204, 211, 0.1); color: #00ccd3; border: 1px solid #00ccd3; }
|
|
3636
3647
|
|
|
3637
3648
|
.browser-tag {
|
|
3638
3649
|
font-size: 0.8em;
|
|
@@ -3684,7 +3695,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3684
3695
|
.stat-pill svg { width: 14px; height: 14px; }
|
|
3685
3696
|
.stat-pill.passed { color: var(--success-dark); }
|
|
3686
3697
|
.stat-pill.failed { color: var(--danger-dark); }
|
|
3687
|
-
.stat-pill.flaky { color: #
|
|
3698
|
+
.stat-pill.flaky { color: #00ccd3; }
|
|
3688
3699
|
.stat-pill.skipped { color: var(--warning-dark); }
|
|
3689
3700
|
.filters {
|
|
3690
3701
|
display: flex;
|
|
@@ -3849,7 +3860,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3849
3860
|
border-radius: 0;
|
|
3850
3861
|
font-size: 0.7em;
|
|
3851
3862
|
font-weight: 800;
|
|
3852
|
-
color:
|
|
3863
|
+
color: black;
|
|
3853
3864
|
text-transform: uppercase;
|
|
3854
3865
|
min-width: 100px;
|
|
3855
3866
|
text-align: center;
|
|
@@ -4560,13 +4571,14 @@ function generateHTML(reportData, trendData = null) {
|
|
|
4560
4571
|
}</div><div class="trend-percentage">${skipPercentage}%</div></div>
|
|
4561
4572
|
<div class="summary-card flaky-status"><h3>Flaky</h3><div class="value">${runSummary.flaky || 0}</div>
|
|
4562
4573
|
<div class="trend-percentage">${flakyPercentage}%</div></div>
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
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>
|
|
4570
4582
|
<div class="summary-card">
|
|
4571
4583
|
<h3>🌐 Browser Distribution <span style="font-size: 0.7em; color: var(--text-color-secondary); font-weight: 400;">(${browserBreakdown.length} total)</span></h3>
|
|
4572
4584
|
<div class="browser-breakdown" style="max-height: 200px; overflow-y: auto; padding-right: 4px;">
|
|
@@ -342,7 +342,7 @@ function generateTestTrendsChart(trendData) {
|
|
|
342
342
|
{
|
|
343
343
|
name: "Flaky",
|
|
344
344
|
data: runs.map((r) => r.flaky || 0),
|
|
345
|
-
color: "
|
|
345
|
+
color: "#00ccd3",
|
|
346
346
|
marker: { symbol: "circle" },
|
|
347
347
|
},
|
|
348
348
|
];
|
|
@@ -668,7 +668,7 @@ function generatePieChart(data, chartWidth = 300, chartHeight = 300) {
|
|
|
668
668
|
color = "var(--danger-color)";
|
|
669
669
|
break;
|
|
670
670
|
case "Flaky":
|
|
671
|
-
color = "
|
|
671
|
+
color = "#00ccd3";
|
|
672
672
|
break;
|
|
673
673
|
case "Skipped":
|
|
674
674
|
color = "var(--warning-color)";
|
|
@@ -1395,7 +1395,7 @@ function generateWorkerDistributionChart(results) {
|
|
|
1395
1395
|
{ name: "Passed", data: passedData, color: "var(--success-color)" },
|
|
1396
1396
|
{ name: "Failed", data: failedData, color: "var(--danger-color)" },
|
|
1397
1397
|
{ name: "Skipped", data: skippedData, color: "var(--warning-color)" },
|
|
1398
|
-
{ name: "Flaky", data: flakyData, color: "
|
|
1398
|
+
{ name: "Flaky", data: flakyData, color: "#00ccd3" },
|
|
1399
1399
|
]);
|
|
1400
1400
|
|
|
1401
1401
|
// The HTML now includes the chart container, the modal, and styles for the modal
|
|
@@ -2315,7 +2315,7 @@ function generateSeverityDistributionChart(results) {
|
|
|
2315
2315
|
const seriesData = [
|
|
2316
2316
|
{ name: "Passed", data: data.passed, color: "var(--success-color)" },
|
|
2317
2317
|
{ name: "Failed", data: data.failed, color: "var(--danger-color)" },
|
|
2318
|
-
{ name: "Flaky", data: data.flaky, color: "
|
|
2318
|
+
{ name: "Flaky", data: data.flaky, color: "#00ccd3" },
|
|
2319
2319
|
{ name: "Skipped", data: data.skipped, color: "var(--warning-color)" },
|
|
2320
2320
|
];
|
|
2321
2321
|
|
|
@@ -2468,9 +2468,18 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2468
2468
|
const flakyCount = (results || []).filter(r => r.outcome === 'flaky').length;
|
|
2469
2469
|
|
|
2470
2470
|
// Calculate retry statistics
|
|
2471
|
+
let retriedTestsCount = 0;
|
|
2471
2472
|
const totalRetried = (results || []).reduce((acc, test) => {
|
|
2472
2473
|
if (test.retryHistory && test.retryHistory.length > 0) {
|
|
2473
|
-
|
|
2474
|
+
// Filter out any "passed" or "skipped" entries in the history
|
|
2475
|
+
// We only count attempts that actually failed or timed out, triggering a retry.
|
|
2476
|
+
const unsuccessfulRetries = test.retryHistory.filter(attempt =>
|
|
2477
|
+
attempt.status === 'failed' || attempt.status === 'timedout' || attempt.status === 'flaky'
|
|
2478
|
+
);
|
|
2479
|
+
if (unsuccessfulRetries.length > 0) {
|
|
2480
|
+
retriedTestsCount++;
|
|
2481
|
+
}
|
|
2482
|
+
return acc + unsuccessfulRetries.length;
|
|
2474
2483
|
}
|
|
2475
2484
|
return acc;
|
|
2476
2485
|
}, 0);
|
|
@@ -2570,8 +2579,8 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2570
2579
|
const severityBadge = `<span class="severity-badge" data-severity="${severity.toLowerCase()}">${severity}</span>`;
|
|
2571
2580
|
|
|
2572
2581
|
// --- Retry Count Badge (only show if retries occurred) ---
|
|
2573
|
-
const retryCount = test.retryHistory ? test.retryHistory.length : 0;
|
|
2574
|
-
const retryBadge =
|
|
2582
|
+
const retryCount = (test.retryHistory && test.retryHistory.length > 0) ? test.retryHistory.length : 0;
|
|
2583
|
+
const retryBadge = (test.retryHistory && test.retryHistory.length > 0) ? `<span class="retry-badge">Retry Count: ${retryCount}</span>` : '';
|
|
2575
2584
|
|
|
2576
2585
|
// --- Step Generation ---
|
|
2577
2586
|
const generateStepsHTML = (steps, depth = 0) => {
|
|
@@ -2649,6 +2658,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2649
2658
|
if(s === 'passed') colorVar = 'var(--success-color)';
|
|
2650
2659
|
else if(s === 'failed') colorVar = 'var(--danger-color)';
|
|
2651
2660
|
else if(s === 'skipped') colorVar = 'var(--warning-color)';
|
|
2661
|
+
else if(s === 'flaky') colorVar = '#00ccd3';
|
|
2652
2662
|
|
|
2653
2663
|
return `<span style="
|
|
2654
2664
|
display: inline-block;
|
|
@@ -3017,7 +3027,8 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3017
3027
|
--success-color: #34d399; --success-dark: #10b981; --success-light: #6ee7b7;
|
|
3018
3028
|
--danger-color: #f87171; --danger-dark: #ef4444; --danger-light: #fca5a5;
|
|
3019
3029
|
--warning-color: #fbbf24; --warning-dark: #f59e0b; --warning-light: #fcd34d;
|
|
3020
|
-
--info-color: #9ca3af;
|
|
3030
|
+
--info-color: #9ca3af;
|
|
3031
|
+
--flaky-color: #00ccd3;
|
|
3021
3032
|
--text-primary: #f9fafb; --text-secondary: #e5e7eb; --text-tertiary: #d1d5db;
|
|
3022
3033
|
--bg-primary: #000000; --bg-secondary: #0a0a0a; --bg-tertiary: #050505;
|
|
3023
3034
|
--bg-card: #0d0d0d; --bg-card-hover: #121212;
|
|
@@ -3471,14 +3482,14 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3471
3482
|
color: #f59e0b;
|
|
3472
3483
|
}
|
|
3473
3484
|
.summary-card.flaky-status {
|
|
3474
|
-
background: rgba(
|
|
3485
|
+
background: rgba(0, 204, 211, 0.05);
|
|
3475
3486
|
}
|
|
3476
3487
|
.summary-card.flaky-status:hover {
|
|
3477
|
-
background: rgba(
|
|
3478
|
-
box-shadow: 0 4px 12px rgba(
|
|
3488
|
+
background: rgba(0, 204, 211, 0.15);
|
|
3489
|
+
box-shadow: 0 4px 12px rgba(0, 204, 211, 0.2);
|
|
3479
3490
|
}
|
|
3480
3491
|
.summary-card.flaky-status .value {
|
|
3481
|
-
color: #
|
|
3492
|
+
color: #00ccd3;
|
|
3482
3493
|
}
|
|
3483
3494
|
.summary-card:not([class*='status-']) .value {
|
|
3484
3495
|
color: #f9fafb;
|
|
@@ -3602,7 +3613,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3602
3613
|
}
|
|
3603
3614
|
.suite-card.status-passed::before { background: var(--success-color); }
|
|
3604
3615
|
.suite-card.status-failed::before { background: var(--danger-color); }
|
|
3605
|
-
.suite-card.status-flaky::before { background:
|
|
3616
|
+
.suite-card.status-flaky::before { background: #00ccd3; }
|
|
3606
3617
|
.suite-card.status-skipped::before { background: var(--warning-color); }
|
|
3607
3618
|
|
|
3608
3619
|
.suite-card.status-skipped::before { background: var(--warning-color); }
|
|
@@ -3620,8 +3631,8 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3620
3631
|
letter-spacing: 0.5px;
|
|
3621
3632
|
}
|
|
3622
3633
|
.outcome-badge.flaky {
|
|
3623
|
-
background-color: #
|
|
3624
|
-
color: #
|
|
3634
|
+
background-color: #00ccd3;
|
|
3635
|
+
color: #fff;
|
|
3625
3636
|
}
|
|
3626
3637
|
|
|
3627
3638
|
.suite-card-header {
|
|
@@ -3705,7 +3716,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3705
3716
|
.stat-pill svg { width: 14px; height: 14px; }
|
|
3706
3717
|
.stat-pill.passed { color: var(--success-dark); }
|
|
3707
3718
|
.stat-pill.failed { color: var(--danger-dark); }
|
|
3708
|
-
.stat-pill.flaky { color: #
|
|
3719
|
+
.stat-pill.flaky { color: #00ccd3; }
|
|
3709
3720
|
.stat-pill.skipped { color: var(--warning-dark); }
|
|
3710
3721
|
color: #93c5fd;
|
|
3711
3722
|
padding: 6px 12px;
|
|
@@ -3901,9 +3912,8 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3901
3912
|
background: var(--warning-color);
|
|
3902
3913
|
}
|
|
3903
3914
|
.status-badge.status-flaky {
|
|
3904
|
-
background-color: #
|
|
3905
|
-
color: #
|
|
3906
|
-
border: 1px solid #A0A0A0;
|
|
3915
|
+
background-color: #00ccd3;
|
|
3916
|
+
color: #fff;
|
|
3907
3917
|
}
|
|
3908
3918
|
.status-badge.status-unknown {
|
|
3909
3919
|
background: var(--dark-gray-color);
|
|
@@ -4539,8 +4549,8 @@ function generateHTML(reportData, trendData = null) {
|
|
|
4539
4549
|
background-color: #f59e0b;
|
|
4540
4550
|
}
|
|
4541
4551
|
.status-badge-small.status-flaky {
|
|
4542
|
-
background-color: #
|
|
4543
|
-
color: #
|
|
4552
|
+
background-color: #00ccd3;
|
|
4553
|
+
color: #fff;
|
|
4544
4554
|
}
|
|
4545
4555
|
.status-badge-small.status-unknown {
|
|
4546
4556
|
background-color: var(--dark-gray-color);
|
|
@@ -6246,10 +6256,11 @@ function generateHTML(reportData, trendData = null) {
|
|
|
6246
6256
|
<div class="trend-percentage">${flakyPercentage}%</div></div>
|
|
6247
6257
|
<div class="summary-card"><h3>Run Duration</h3><div class="value">${formatDuration(
|
|
6248
6258
|
runSummary.duration,
|
|
6249
|
-
)}</div></div>
|
|
6259
|
+
)}</div><div class="trend-percentage">Avg. Test Duration ${avgTestDuration}</div></div>
|
|
6250
6260
|
<div class="summary-card">
|
|
6251
6261
|
<h3>Total Retry Count</h3>
|
|
6252
6262
|
<div class="value">${totalRetried}</div>
|
|
6263
|
+
<div class="trend-percentage">Test Retried ${retriedTestsCount}</div>
|
|
6253
6264
|
</div>
|
|
6254
6265
|
<div class="summary-card">
|
|
6255
6266
|
<h3>🌐 Browser Distribution <span style="font-size: 0.7em; color: var(--text-color-secondary); font-weight: 400;">(${browserBreakdown.length} total)</span></h3>
|
package/dist/index.d.ts
DELETED
package/dist/index.js
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
-
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
-
};
|
|
16
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.PlaywrightPulseReporter = void 0;
|
|
18
|
-
// src/reporter/index.ts
|
|
19
|
-
const playwright_pulse_reporter_1 = require("./playwright-pulse-reporter");
|
|
20
|
-
Object.defineProperty(exports, "PlaywrightPulseReporter", { enumerable: true, get: function () { return playwright_pulse_reporter_1.PlaywrightPulseReporter; } });
|
|
21
|
-
// Export the reporter class as the default export for CommonJS compatibility
|
|
22
|
-
// and also as a named export for potential ES module consumers.
|
|
23
|
-
exports.default = playwright_pulse_reporter_1.PlaywrightPulseReporter;
|
|
24
|
-
// You can also export other related types or utilities if needed
|
|
25
|
-
__exportStar(require("../types"), exports); // Re-export shared types if they are used by the reporter consumers
|
|
26
|
-
__exportStar(require("../lib/report-types"), exports);
|
package/dist/lib/data-reader.js
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
'use server';
|
|
2
|
-
import * as fs from 'fs/promises';
|
|
3
|
-
import * as path from 'path';
|
|
4
|
-
const reportFileName = 'playwright-pulse-report.json'; // Use a constant for the filename
|
|
5
|
-
const reportFilePath = path.resolve(process.cwd(), reportFileName); // Default path relative to cwd
|
|
6
|
-
let cachedReportData = null;
|
|
7
|
-
let lastReadTime = null;
|
|
8
|
-
const CACHE_DURATION = 5000; // Cache duration in milliseconds (e.g., 5 seconds)
|
|
9
|
-
export async function readReportDataInternal() {
|
|
10
|
-
const now = Date.now();
|
|
11
|
-
// Use cache if it's recent and not forced refresh
|
|
12
|
-
if (cachedReportData && lastReadTime && (now - lastReadTime < CACHE_DURATION)) {
|
|
13
|
-
// console.log("Returning cached report data.");
|
|
14
|
-
return cachedReportData;
|
|
15
|
-
}
|
|
16
|
-
// console.log(`Attempting to read report file from: ${reportFilePath}`);
|
|
17
|
-
try {
|
|
18
|
-
const fileContent = await fs.readFile(reportFilePath, 'utf-8');
|
|
19
|
-
let parsedData;
|
|
20
|
-
try {
|
|
21
|
-
parsedData = JSON.parse(fileContent);
|
|
22
|
-
}
|
|
23
|
-
catch (parseError) {
|
|
24
|
-
console.error(`Error parsing JSON from ${reportFilePath}:`, parseError);
|
|
25
|
-
throw new Error(`Invalid JSON in report file: ${parseError.message}`);
|
|
26
|
-
}
|
|
27
|
-
// --- Date Reviver ---
|
|
28
|
-
// Function to convert ISO date strings back to Date objects recursively
|
|
29
|
-
const reviveDates = (key, value) => {
|
|
30
|
-
// Matches ISO 8601 date format (YYYY-MM-DDTHH:mm:ss.sssZ)
|
|
31
|
-
const isoDateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/;
|
|
32
|
-
if (typeof value === 'string' && isoDateRegex.test(value)) {
|
|
33
|
-
const date = new Date(value);
|
|
34
|
-
// Check if the parsed date is valid before returning
|
|
35
|
-
if (!isNaN(date.getTime())) {
|
|
36
|
-
return date;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
return value;
|
|
40
|
-
};
|
|
41
|
-
// Re-parse with the date reviver
|
|
42
|
-
const reportData = JSON.parse(fileContent, reviveDates);
|
|
43
|
-
// Basic validation after parsing and date revival
|
|
44
|
-
if (!reportData || typeof reportData !== 'object') {
|
|
45
|
-
throw new Error('Report file is empty or not a valid object.');
|
|
46
|
-
}
|
|
47
|
-
if (!reportData.metadata || typeof reportData.metadata.generatedAt !== 'string') { // generatedAt should remain string from JSON
|
|
48
|
-
throw new Error('Invalid or missing metadata in report file.');
|
|
49
|
-
}
|
|
50
|
-
if (!Array.isArray(reportData.results)) {
|
|
51
|
-
throw new Error('Missing or invalid "results" array in report file.');
|
|
52
|
-
}
|
|
53
|
-
// Optional: Validate run data if present
|
|
54
|
-
if (reportData.run && typeof reportData.run !== 'object') {
|
|
55
|
-
throw new Error('Invalid "run" data in report file.');
|
|
56
|
-
}
|
|
57
|
-
if (reportData.run && !(reportData.run.timestamp instanceof Date)) {
|
|
58
|
-
console.warn('Warning: Run timestamp was not correctly revived to a Date object.');
|
|
59
|
-
// Potentially attempt fallback parsing or throw error
|
|
60
|
-
reportData.run.timestamp = new Date(reportData.run.timestamp); // Attempt fallback
|
|
61
|
-
if (isNaN(reportData.run.timestamp.getTime())) {
|
|
62
|
-
throw new Error('Invalid run timestamp format.');
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
// Validate dates within results and steps
|
|
66
|
-
reportData.results.forEach((result, index) => {
|
|
67
|
-
if (!(result.startTime instanceof Date) || !(result.endTime instanceof Date)) {
|
|
68
|
-
console.warn(`Warning: Invalid start/end time for result index ${index}. Attempting fallback parsing.`);
|
|
69
|
-
result.startTime = new Date(result.startTime);
|
|
70
|
-
result.endTime = new Date(result.endTime);
|
|
71
|
-
if (isNaN(result.startTime.getTime()) || isNaN(result.endTime.getTime())) {
|
|
72
|
-
throw new Error(`Invalid start/end time in result index ${index}.`);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
if (Array.isArray(result.steps)) {
|
|
76
|
-
result.steps.forEach((step, stepIndex) => {
|
|
77
|
-
if (!(step.startTime instanceof Date) || !(step.endTime instanceof Date)) {
|
|
78
|
-
console.warn(`Warning: Invalid start/end time for step index ${stepIndex} in result index ${index}. Attempting fallback parsing.`);
|
|
79
|
-
step.startTime = new Date(step.startTime);
|
|
80
|
-
step.endTime = new Date(step.endTime);
|
|
81
|
-
if (isNaN(step.startTime.getTime()) || isNaN(step.endTime.getTime())) {
|
|
82
|
-
throw new Error(`Invalid start/end time in step index ${stepIndex}, result index ${index}.`);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
else {
|
|
88
|
-
result.steps = []; // Initialize if steps array is missing
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
cachedReportData = reportData;
|
|
92
|
-
lastReadTime = now;
|
|
93
|
-
// console.log("Successfully read, parsed, and cached report file.");
|
|
94
|
-
return reportData;
|
|
95
|
-
}
|
|
96
|
-
catch (error) {
|
|
97
|
-
if (error.code === 'ENOENT') {
|
|
98
|
-
console.warn(`Report file not found at ${reportFilePath}. Returning empty data structure. Ensure Playwright tests ran with the reporter enabled and the file exists.`);
|
|
99
|
-
// Return a valid, empty structure
|
|
100
|
-
const defaultReport = {
|
|
101
|
-
run: null,
|
|
102
|
-
results: [],
|
|
103
|
-
metadata: { generatedAt: new Date().toISOString() }
|
|
104
|
-
};
|
|
105
|
-
cachedReportData = defaultReport; // Cache the default empty state
|
|
106
|
-
lastReadTime = now;
|
|
107
|
-
return defaultReport;
|
|
108
|
-
}
|
|
109
|
-
else {
|
|
110
|
-
// Log the specific error for debugging
|
|
111
|
-
console.error(`Error processing report file at ${reportFilePath}:`, error);
|
|
112
|
-
// Propagate a user-friendly error
|
|
113
|
-
throw new Error(`Failed to load report data: ${error.message}`);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|