@arghajit/dummy 0.3.28 → 0.3.33
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 +10 -2
- package/dist/utils/compression-utils.d.ts +19 -0
- package/dist/utils/compression-utils.js +108 -0
- package/package.json +2 -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
|
});
|
|
@@ -42,6 +42,7 @@ const path = __importStar(require("path"));
|
|
|
42
42
|
const crypto_1 = require("crypto");
|
|
43
43
|
const ua_parser_js_1 = __importDefault(require("ua-parser-js"));
|
|
44
44
|
const os = __importStar(require("os"));
|
|
45
|
+
const compression_utils_1 = require("../utils/compression-utils");
|
|
45
46
|
const convertStatus = (status, testCase) => {
|
|
46
47
|
if ((testCase === null || testCase === void 0 ? void 0 : testCase.expectedStatus) === "failed") {
|
|
47
48
|
return "failed";
|
|
@@ -325,7 +326,10 @@ class PlaywrightPulseReporter {
|
|
|
325
326
|
const relativeDestPath = path.join(ATTACHMENTS_SUBDIR, testSubfolder, uniqueFileName);
|
|
326
327
|
const absoluteDestPath = path.join(this.outputDir, relativeDestPath);
|
|
327
328
|
await this._ensureDirExists(path.dirname(absoluteDestPath));
|
|
329
|
+
// Copy file first
|
|
328
330
|
await fs.copyFile(attachment.path, absoluteDestPath);
|
|
331
|
+
// Compress in-place (preserves path/name)
|
|
332
|
+
await (0, compression_utils_1.compressAttachment)(absoluteDestPath, attachment.contentType);
|
|
329
333
|
if (attachment.contentType.startsWith("image/")) {
|
|
330
334
|
(_m = pulseResult.screenshots) === null || _m === void 0 ? void 0 : _m.push(relativeDestPath);
|
|
331
335
|
}
|
|
@@ -362,7 +366,10 @@ class PlaywrightPulseReporter {
|
|
|
362
366
|
attempts.sort((a, b) => a.retries - b.retries);
|
|
363
367
|
const firstAttempt = attempts[0];
|
|
364
368
|
const retryAttempts = attempts.slice(1);
|
|
365
|
-
if
|
|
369
|
+
// Only populate retryHistory if there were actual failures that triggered retries
|
|
370
|
+
// If all attempts passed, we don't need to show retry history
|
|
371
|
+
const hasActualRetries = retryAttempts.length > 0 && retryAttempts.some(attempt => attempt.status === 'failed' || attempt.status === 'flaky' || firstAttempt.status === 'failed' || firstAttempt.status === 'flaky');
|
|
372
|
+
if (hasActualRetries) {
|
|
366
373
|
firstAttempt.retryHistory = retryAttempts;
|
|
367
374
|
// Calculate final status and outcome from the last attempt if retries exist
|
|
368
375
|
const lastAttempt = attempts[attempts.length - 1];
|
|
@@ -374,8 +381,9 @@ class PlaywrightPulseReporter {
|
|
|
374
381
|
}
|
|
375
382
|
}
|
|
376
383
|
else {
|
|
377
|
-
// If no retries, ensure final_status
|
|
384
|
+
// If no actual retries (all attempts passed), ensure final_status and retryHistory are removed
|
|
378
385
|
delete firstAttempt.final_status;
|
|
386
|
+
delete firstAttempt.retryHistory;
|
|
379
387
|
}
|
|
380
388
|
finalResults.push(firstAttempt);
|
|
381
389
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compression utilities for images
|
|
3
|
+
* Uses sharp for image compression (works cross-platform with no external dependencies)
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Compress an image file in-place
|
|
7
|
+
* @param filePath - Absolute path to the image file
|
|
8
|
+
* @param options - Compression options
|
|
9
|
+
*/
|
|
10
|
+
export declare function compressImage(filePath: string, options?: {
|
|
11
|
+
quality?: number;
|
|
12
|
+
}): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Compress an attachment file (auto-detects type)
|
|
15
|
+
* Note: Only compresses images. Videos are already compressed by Playwright.
|
|
16
|
+
* @param filePath - Absolute path to the file
|
|
17
|
+
* @param contentType - MIME content type
|
|
18
|
+
*/
|
|
19
|
+
export declare function compressAttachment(filePath: string, contentType: string): Promise<void>;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/utils/compression-utils.ts
|
|
3
|
+
/**
|
|
4
|
+
* Compression utilities for images
|
|
5
|
+
* Uses sharp for image compression (works cross-platform with no external dependencies)
|
|
6
|
+
*/
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
+
}) : function(o, v) {
|
|
21
|
+
o["default"] = v;
|
|
22
|
+
});
|
|
23
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
24
|
+
var ownKeys = function(o) {
|
|
25
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
+
var ar = [];
|
|
27
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
+
return ar;
|
|
29
|
+
};
|
|
30
|
+
return ownKeys(o);
|
|
31
|
+
};
|
|
32
|
+
return function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
40
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
+
exports.compressImage = compressImage;
|
|
42
|
+
exports.compressAttachment = compressAttachment;
|
|
43
|
+
const fs = __importStar(require("fs/promises"));
|
|
44
|
+
const path = __importStar(require("path"));
|
|
45
|
+
/**
|
|
46
|
+
* Compress an image file in-place
|
|
47
|
+
* @param filePath - Absolute path to the image file
|
|
48
|
+
* @param options - Compression options
|
|
49
|
+
*/
|
|
50
|
+
async function compressImage(filePath, options = {}) {
|
|
51
|
+
try {
|
|
52
|
+
const sharp = require('sharp');
|
|
53
|
+
const quality = options.quality || 75;
|
|
54
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
55
|
+
// Read original file
|
|
56
|
+
const imageBuffer = await fs.readFile(filePath);
|
|
57
|
+
let compressedBuffer;
|
|
58
|
+
if (ext === '.png') {
|
|
59
|
+
// Compress PNG
|
|
60
|
+
compressedBuffer = await sharp(imageBuffer)
|
|
61
|
+
.png({ quality, compressionLevel: 9 })
|
|
62
|
+
.toBuffer();
|
|
63
|
+
}
|
|
64
|
+
else if (ext === '.jpg' || ext === '.jpeg') {
|
|
65
|
+
// Compress JPEG
|
|
66
|
+
compressedBuffer = await sharp(imageBuffer)
|
|
67
|
+
.jpeg({ quality, mozjpeg: true })
|
|
68
|
+
.toBuffer();
|
|
69
|
+
}
|
|
70
|
+
else if (ext === '.webp') {
|
|
71
|
+
// Compress WebP
|
|
72
|
+
compressedBuffer = await sharp(imageBuffer)
|
|
73
|
+
.webp({ quality })
|
|
74
|
+
.toBuffer();
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
// Unsupported format, skip compression
|
|
78
|
+
console.log(`Compression skipped for unsupported format: ${ext}`);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
// Only overwrite if compression actually reduced size
|
|
82
|
+
if (compressedBuffer.length < imageBuffer.length) {
|
|
83
|
+
await fs.writeFile(filePath, compressedBuffer);
|
|
84
|
+
const savedBytes = imageBuffer.length - compressedBuffer.length;
|
|
85
|
+
const savedPercent = ((savedBytes / imageBuffer.length) * 100).toFixed(1);
|
|
86
|
+
console.log(`Compressed ${path.basename(filePath)}: ${savedPercent}% smaller`);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
console.log(`Skipped ${path.basename(filePath)}: compression didn't reduce size`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
console.warn(`Failed to compress image ${filePath}:`, error.message);
|
|
94
|
+
// File remains unchanged
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Compress an attachment file (auto-detects type)
|
|
99
|
+
* Note: Only compresses images. Videos are already compressed by Playwright.
|
|
100
|
+
* @param filePath - Absolute path to the file
|
|
101
|
+
* @param contentType - MIME content type
|
|
102
|
+
*/
|
|
103
|
+
async function compressAttachment(filePath, contentType) {
|
|
104
|
+
if (contentType.startsWith('image/')) {
|
|
105
|
+
await compressImage(filePath, { quality: 75 });
|
|
106
|
+
}
|
|
107
|
+
// Videos are skipped - already compressed by Playwright as WebM
|
|
108
|
+
}
|
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.33",
|
|
5
5
|
"description": "A Playwright reporter and dashboard for visualizing test results.",
|
|
6
6
|
"homepage": "https://arghajit47.github.io/playwright-pulse/",
|
|
7
7
|
"repository": {
|
|
@@ -73,6 +73,7 @@
|
|
|
73
73
|
"node-fetch": "^3.3.2",
|
|
74
74
|
"nodemailer": "^7.0.3",
|
|
75
75
|
"patch-package": "^8.0.0",
|
|
76
|
+
"sharp": "^0.33.5",
|
|
76
77
|
"ua-parser-js": "^1.0.41",
|
|
77
78
|
"zod": "^3.24.2"
|
|
78
79
|
},
|
|
@@ -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>
|