@arghajit/dummy 0.3.19 → 0.3.28
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/lib/data-reader.js +116 -0
- package/dist/lib/data.js +39 -0
- package/dist/lib/utils.js +1 -0
- package/dist/pulse.js +3 -6
- package/dist/reporter/playwright-pulse-reporter.js +65 -58
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/types/index.d.ts +6 -2
- package/package.json +1 -1
- package/scripts/generate-email-report.mjs +21 -9
- package/scripts/generate-report.mjs +606 -223
- package/scripts/generate-static-report.mjs +699 -275
- package/scripts/merge-pulse-report.js +2 -1
|
@@ -339,6 +339,12 @@ function generateTestTrendsChart(trendData) {
|
|
|
339
339
|
color: "var(--warning-color)",
|
|
340
340
|
marker: { symbol: "circle" },
|
|
341
341
|
},
|
|
342
|
+
{
|
|
343
|
+
name: "Flaky",
|
|
344
|
+
data: runs.map((r) => r.flaky || 0),
|
|
345
|
+
color: "var(--neutral-500)",
|
|
346
|
+
marker: { symbol: "circle" },
|
|
347
|
+
},
|
|
342
348
|
];
|
|
343
349
|
const runsForTooltip = runs.map((r) => ({
|
|
344
350
|
runId: r.runId,
|
|
@@ -542,6 +548,9 @@ function generateTestHistoryChart(history) {
|
|
|
542
548
|
case "skipped":
|
|
543
549
|
color = "var(--warning-color)";
|
|
544
550
|
break;
|
|
551
|
+
case "flaky":
|
|
552
|
+
color = "var(--neutral-500)";
|
|
553
|
+
break;
|
|
545
554
|
default:
|
|
546
555
|
color = "var(--dark-gray-color)";
|
|
547
556
|
}
|
|
@@ -658,6 +667,9 @@ function generatePieChart(data, chartWidth = 300, chartHeight = 300) {
|
|
|
658
667
|
case "Failed":
|
|
659
668
|
color = "var(--danger-color)";
|
|
660
669
|
break;
|
|
670
|
+
case "Flaky":
|
|
671
|
+
color = "var(--neutral-500)";
|
|
672
|
+
break;
|
|
661
673
|
case "Skipped":
|
|
662
674
|
color = "var(--warning-color)";
|
|
663
675
|
break;
|
|
@@ -904,7 +916,10 @@ function generateEnvironmentSection(environmentData) {
|
|
|
904
916
|
}
|
|
905
917
|
|
|
906
918
|
function generateEnvironmentDashboard(environment, hideHeader = false) {
|
|
907
|
-
const
|
|
919
|
+
const cpuModel = environment.cpu && environment.cpu.model ? environment.cpu.model : "N/A";
|
|
920
|
+
const cpuCores = environment.cpu && environment.cpu.cores ? environment.cpu.cores : "N/A";
|
|
921
|
+
const cpuInfo = `model: ${cpuModel}, cores: ${cpuCores}`;
|
|
922
|
+
const cwdInfo = environment.cwd || "N/A";
|
|
908
923
|
|
|
909
924
|
return `
|
|
910
925
|
<div class="env-modern-card${hideHeader ? " no-header" : ""}">
|
|
@@ -1294,7 +1309,7 @@ function generateEnvironmentDashboard(environment, hideHeader = false) {
|
|
|
1294
1309
|
</div>
|
|
1295
1310
|
<div class="env-item-content">
|
|
1296
1311
|
<p class="env-item-label">Working Dir</p>
|
|
1297
|
-
<div class="env-item-value" title="${
|
|
1312
|
+
<div class="env-item-value" title="${cwdInfo}">${cwdInfo.length > 30 ? "..." + cwdInfo.slice(-27) : cwdInfo}</div>
|
|
1298
1313
|
</div>
|
|
1299
1314
|
</div>
|
|
1300
1315
|
</div>
|
|
@@ -1323,11 +1338,11 @@ function generateWorkerDistributionChart(results) {
|
|
|
1323
1338
|
const workerId =
|
|
1324
1339
|
typeof test.workerId !== "undefined" ? test.workerId : "N/A";
|
|
1325
1340
|
if (!acc[workerId]) {
|
|
1326
|
-
acc[workerId] = { passed: 0, failed: 0, skipped: 0, tests: [] };
|
|
1341
|
+
acc[workerId] = { passed: 0, failed: 0, skipped: 0, flaky: 0, tests: [] };
|
|
1327
1342
|
}
|
|
1328
1343
|
|
|
1329
1344
|
const status = String(test.status).toLowerCase();
|
|
1330
|
-
if (status === "passed" || status === "failed" || status === "skipped") {
|
|
1345
|
+
if (status === "passed" || status === "failed" || status === "skipped" || status === "flaky") {
|
|
1331
1346
|
acc[workerId][status]++;
|
|
1332
1347
|
}
|
|
1333
1348
|
|
|
@@ -1372,6 +1387,7 @@ function generateWorkerDistributionChart(results) {
|
|
|
1372
1387
|
const passedData = workerIds.map((id) => workerData[id].passed);
|
|
1373
1388
|
const failedData = workerIds.map((id) => workerData[id].failed);
|
|
1374
1389
|
const skippedData = workerIds.map((id) => workerData[id].skipped);
|
|
1390
|
+
const flakyData = workerIds.map((id) => workerData[id].flaky);
|
|
1375
1391
|
|
|
1376
1392
|
const categoriesString = JSON.stringify(categories);
|
|
1377
1393
|
const fullDataString = JSON.stringify(fullWorkerData);
|
|
@@ -1379,6 +1395,7 @@ function generateWorkerDistributionChart(results) {
|
|
|
1379
1395
|
{ name: "Passed", data: passedData, color: "var(--success-color)" },
|
|
1380
1396
|
{ name: "Failed", data: failedData, color: "var(--danger-color)" },
|
|
1381
1397
|
{ name: "Skipped", data: skippedData, color: "var(--warning-color)" },
|
|
1398
|
+
{ name: "Flaky", data: flakyData, color: "var(--neutral-500)" },
|
|
1382
1399
|
]);
|
|
1383
1400
|
|
|
1384
1401
|
// The HTML now includes the chart container, the modal, and styles for the modal
|
|
@@ -1645,6 +1662,7 @@ function generateTestHistoryContent(trendData) {
|
|
|
1645
1662
|
<option value="">All Statuses</option>
|
|
1646
1663
|
<option value="passed">Passed</option>
|
|
1647
1664
|
<option value="failed">Failed</option>
|
|
1665
|
+
<option value="flaky">Flaky</option>
|
|
1648
1666
|
<option value="skipped">Skipped</option>
|
|
1649
1667
|
</select>
|
|
1650
1668
|
<button id="clear-history-filters" class="clear-filters-btn">Clear Filters</button>
|
|
@@ -1717,6 +1735,8 @@ function getStatusClass(status) {
|
|
|
1717
1735
|
return "status-failed";
|
|
1718
1736
|
case "skipped":
|
|
1719
1737
|
return "status-skipped";
|
|
1738
|
+
case "flaky":
|
|
1739
|
+
return "status-flaky";
|
|
1720
1740
|
default:
|
|
1721
1741
|
return "status-unknown";
|
|
1722
1742
|
}
|
|
@@ -1734,6 +1754,8 @@ function getStatusIcon(status) {
|
|
|
1734
1754
|
return "❌";
|
|
1735
1755
|
case "skipped":
|
|
1736
1756
|
return "⏭️";
|
|
1757
|
+
case "flaky":
|
|
1758
|
+
return "⚠️";
|
|
1737
1759
|
default:
|
|
1738
1760
|
return "❓";
|
|
1739
1761
|
}
|
|
@@ -1774,6 +1796,7 @@ function getSuitesData(results) {
|
|
|
1774
1796
|
browser: browser,
|
|
1775
1797
|
passed: 0,
|
|
1776
1798
|
failed: 0,
|
|
1799
|
+
flaky: 0,
|
|
1777
1800
|
skipped: 0,
|
|
1778
1801
|
count: 0,
|
|
1779
1802
|
statusOverall: "passed",
|
|
@@ -1781,12 +1804,15 @@ function getSuitesData(results) {
|
|
|
1781
1804
|
}
|
|
1782
1805
|
const suite = suitesMap.get(key);
|
|
1783
1806
|
suite.count++;
|
|
1784
|
-
|
|
1807
|
+
let currentStatus = String(test.status).toLowerCase();
|
|
1808
|
+
if (test.outcome === 'flaky') currentStatus = 'flaky';
|
|
1785
1809
|
if (currentStatus && suite[currentStatus] !== undefined) {
|
|
1786
1810
|
suite[currentStatus]++;
|
|
1787
1811
|
}
|
|
1788
1812
|
if (currentStatus === "failed") suite.statusOverall = "failed";
|
|
1789
|
-
else if (currentStatus === "
|
|
1813
|
+
else if (currentStatus === "flaky" && suite.statusOverall !== "failed")
|
|
1814
|
+
suite.statusOverall = "flaky";
|
|
1815
|
+
else if (currentStatus === "skipped" && suite.statusOverall !== "failed" && suite.statusOverall !== "flaky")
|
|
1790
1816
|
suite.statusOverall = "skipped";
|
|
1791
1817
|
});
|
|
1792
1818
|
return Array.from(suitesMap.values());
|
|
@@ -1832,8 +1858,8 @@ function generateSuitesWidget(suitesData) {
|
|
|
1832
1858
|
|
|
1833
1859
|
// Added inline styles for height consistency with Pie Chart (approx 450px) and scrolling
|
|
1834
1860
|
return `
|
|
1835
|
-
<div class="suites-widget
|
|
1836
|
-
<div class="suites-header"
|
|
1861
|
+
<div class="suites-widget fixed-height-widget">
|
|
1862
|
+
<div class="suites-header">
|
|
1837
1863
|
<h2>Test Suites</h2>
|
|
1838
1864
|
<span class="summary-badge">${
|
|
1839
1865
|
suitesData.length
|
|
@@ -1843,40 +1869,40 @@ function generateSuitesWidget(suitesData) {
|
|
|
1843
1869
|
)} tests</span>
|
|
1844
1870
|
</div>
|
|
1845
1871
|
|
|
1846
|
-
<div class="suites-grid-container"
|
|
1872
|
+
<div class="suites-grid-container">
|
|
1847
1873
|
<div class="suites-grid">
|
|
1848
1874
|
${suitesData
|
|
1849
1875
|
.map(
|
|
1850
1876
|
(suite) => `
|
|
1851
1877
|
<div class="suite-card status-${suite.statusOverall}">
|
|
1852
1878
|
<div class="suite-card-header">
|
|
1853
|
-
<h3 class="suite-name" title="${sanitizeHTML(
|
|
1854
|
-
|
|
1855
|
-
)} (${sanitizeHTML(suite.browser)})">${sanitizeHTML(suite.name)}</h3>
|
|
1879
|
+
<h3 class="suite-name" title="${sanitizeHTML(suite.name)} (${sanitizeHTML(suite.browser)})">${sanitizeHTML(suite.name)}</h3>
|
|
1880
|
+
<div class="status-indicator-dot status-${suite.statusOverall}" title="${suite.statusOverall.charAt(0).toUpperCase() + suite.statusOverall.slice(1)}"></div>
|
|
1856
1881
|
</div>
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1882
|
+
|
|
1883
|
+
<div class="browser-tag" title="🌐Browser: ${sanitizeHTML(suite.browser)}">
|
|
1884
|
+
<span style="font-size: 1.1em;">🌐</span> ${sanitizeHTML(suite.browser)}
|
|
1885
|
+
</div>
|
|
1886
|
+
|
|
1860
1887
|
<div class="suite-card-body">
|
|
1861
|
-
<span class="test-count">${suite.count}
|
|
1862
|
-
suite.count !== 1 ? "s" : ""
|
|
1863
|
-
}</span>
|
|
1888
|
+
<span class="test-count-label">${suite.count} Test${suite.count !== 1 ? "s" : ""}</span>
|
|
1864
1889
|
<div class="suite-stats">
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1890
|
+
<span class="stat-pill passed" title="Passed">
|
|
1891
|
+
<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 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z"/></svg>
|
|
1892
|
+
${suite.passed}
|
|
1893
|
+
</span>
|
|
1894
|
+
<span class="stat-pill failed" title="Failed">
|
|
1895
|
+
<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>
|
|
1896
|
+
${suite.failed}
|
|
1897
|
+
</span>
|
|
1898
|
+
<span class="stat-pill flaky" title="Flaky">
|
|
1899
|
+
<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>
|
|
1900
|
+
${suite.flaky || 0}
|
|
1901
|
+
</span>
|
|
1902
|
+
<span class="stat-pill skipped" title="Skipped">
|
|
1903
|
+
<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>
|
|
1904
|
+
${suite.skipped}
|
|
1905
|
+
</span>
|
|
1880
1906
|
</div>
|
|
1881
1907
|
</div>
|
|
1882
1908
|
</div>`,
|
|
@@ -2255,6 +2281,7 @@ function generateSeverityDistributionChart(results) {
|
|
|
2255
2281
|
const data = {
|
|
2256
2282
|
passed: [0, 0, 0, 0, 0],
|
|
2257
2283
|
failed: [0, 0, 0, 0, 0],
|
|
2284
|
+
flaky: [0, 0, 0, 0, 0],
|
|
2258
2285
|
skipped: [0, 0, 0, 0, 0],
|
|
2259
2286
|
};
|
|
2260
2287
|
|
|
@@ -2273,6 +2300,8 @@ function generateSeverityDistributionChart(results) {
|
|
|
2273
2300
|
status === "interrupted"
|
|
2274
2301
|
) {
|
|
2275
2302
|
data.failed[index]++;
|
|
2303
|
+
} else if (status === "flaky") {
|
|
2304
|
+
data.flaky[index]++;
|
|
2276
2305
|
} else {
|
|
2277
2306
|
data.skipped[index]++;
|
|
2278
2307
|
}
|
|
@@ -2286,6 +2315,7 @@ function generateSeverityDistributionChart(results) {
|
|
|
2286
2315
|
const seriesData = [
|
|
2287
2316
|
{ name: "Passed", data: data.passed, color: "var(--success-color)" },
|
|
2288
2317
|
{ name: "Failed", data: data.failed, color: "var(--danger-color)" },
|
|
2318
|
+
{ name: "Flaky", data: data.flaky, color: "var(--neutral-500)" },
|
|
2289
2319
|
{ name: "Skipped", data: data.skipped, color: "var(--warning-color)" },
|
|
2290
2320
|
];
|
|
2291
2321
|
|
|
@@ -2429,32 +2459,76 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2429
2459
|
duration: 0,
|
|
2430
2460
|
timestamp: new Date().toISOString(),
|
|
2431
2461
|
};
|
|
2432
|
-
|
|
2433
|
-
const passPercentage = Math.round((runSummary.passed / totalTestsOr1) * 100);
|
|
2434
|
-
const failPercentage = Math.round((runSummary.failed / totalTestsOr1) * 100);
|
|
2435
|
-
const skipPercentage = Math.round(
|
|
2436
|
-
((runSummary.skipped || 0) / totalTestsOr1) * 100,
|
|
2437
|
-
);
|
|
2462
|
+
|
|
2438
2463
|
const avgTestDuration =
|
|
2439
2464
|
runSummary.totalTests > 0
|
|
2440
2465
|
? formatDuration(runSummary.duration / runSummary.totalTests)
|
|
2441
2466
|
: "0.0s";
|
|
2442
2467
|
|
|
2468
|
+
const flakyCount = (results || []).filter(r => r.outcome === 'flaky').length;
|
|
2469
|
+
|
|
2443
2470
|
// Calculate retry statistics
|
|
2444
2471
|
const totalRetried = (results || []).reduce((acc, test) => {
|
|
2445
|
-
if (test.
|
|
2446
|
-
return acc +
|
|
2472
|
+
if (test.retryHistory && test.retryHistory.length > 0) {
|
|
2473
|
+
return acc + test.retryHistory.length;
|
|
2447
2474
|
}
|
|
2448
2475
|
return acc;
|
|
2449
2476
|
}, 0);
|
|
2450
2477
|
|
|
2478
|
+
// --- RECALCULATE KPI METRICS BASED ON FINAL_STATUS ---
|
|
2479
|
+
let calculatedPassed = 0;
|
|
2480
|
+
let calculatedFailed = 0;
|
|
2481
|
+
let calculatedSkipped = 0;
|
|
2482
|
+
let calculatedFlaky = 0;
|
|
2483
|
+
let calculatedTotal = 0;
|
|
2484
|
+
|
|
2485
|
+
(results || []).forEach(test => {
|
|
2486
|
+
calculatedTotal++;
|
|
2487
|
+
// New Logic: If outcome is 'flaky', it overrides everything.
|
|
2488
|
+
let statusToUse = test.status;
|
|
2489
|
+
if (test.outcome === 'flaky') {
|
|
2490
|
+
statusToUse = 'flaky';
|
|
2491
|
+
} else if (test.status === 'flaky') {
|
|
2492
|
+
// Just in case outcome wasn't set but status was (unlikely with new reporter)
|
|
2493
|
+
statusToUse = 'flaky';
|
|
2494
|
+
} else if (test.retryHistory && test.retryHistory.length > 0 && test.final_status) {
|
|
2495
|
+
statusToUse = test.final_status;
|
|
2496
|
+
}
|
|
2497
|
+
|
|
2498
|
+
// Update test status in place for charts
|
|
2499
|
+
test.status = statusToUse;
|
|
2500
|
+
|
|
2501
|
+
const s = String(statusToUse).toLowerCase();
|
|
2502
|
+
if (s === 'passed') calculatedPassed++;
|
|
2503
|
+
else if (s === 'skipped') calculatedSkipped++;
|
|
2504
|
+
else if (s === 'flaky') calculatedFlaky++;
|
|
2505
|
+
else calculatedFailed++; // failed, timedout, interrupted
|
|
2506
|
+
});
|
|
2507
|
+
|
|
2508
|
+
// Override runSummary counts with our calculated ones if results exist
|
|
2509
|
+
if (results && results.length > 0) {
|
|
2510
|
+
runSummary.passed = calculatedPassed;
|
|
2511
|
+
runSummary.failed = calculatedFailed;
|
|
2512
|
+
runSummary.skipped = calculatedSkipped;
|
|
2513
|
+
runSummary.flaky = calculatedFlaky;
|
|
2514
|
+
runSummary.totalTests = calculatedTotal;
|
|
2515
|
+
}
|
|
2516
|
+
|
|
2517
|
+
const totalTestsOr1 = runSummary.totalTests || 1;
|
|
2518
|
+
const passPercentage = Math.round((runSummary.passed / totalTestsOr1) * 100);
|
|
2519
|
+
const failPercentage = Math.round((runSummary.failed / totalTestsOr1) * 100);
|
|
2520
|
+
const skipPercentage = Math.round(
|
|
2521
|
+
((runSummary.skipped || 0) / totalTestsOr1) * 100,
|
|
2522
|
+
);
|
|
2523
|
+
const flakyPercentage = Math.round(((runSummary.flaky || 0) / totalTestsOr1) * 100);
|
|
2524
|
+
|
|
2525
|
+
|
|
2451
2526
|
// Calculate browser distribution
|
|
2452
2527
|
const browserStats = (results || []).reduce((acc, test) => {
|
|
2453
2528
|
let browserName = "unknown";
|
|
2454
2529
|
if (test.browser) {
|
|
2455
|
-
//
|
|
2456
|
-
|
|
2457
|
-
browserName = match ? match[1] : test.browser;
|
|
2530
|
+
// Use full browser name
|
|
2531
|
+
browserName = test.browser;
|
|
2458
2532
|
}
|
|
2459
2533
|
acc[browserName] = (acc[browserName] || 0) + 1;
|
|
2460
2534
|
return acc;
|
|
@@ -2495,6 +2569,10 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2495
2569
|
const severity = test.severity || "Medium";
|
|
2496
2570
|
const severityBadge = `<span class="severity-badge" data-severity="${severity.toLowerCase()}">${severity}</span>`;
|
|
2497
2571
|
|
|
2572
|
+
// --- Retry Count Badge (only show if retries occurred) ---
|
|
2573
|
+
const retryCount = test.retryHistory ? test.retryHistory.length : 0;
|
|
2574
|
+
const retryBadge = retryCount > 0 ? `<span class="retry-badge">Retry Count: ${retryCount}</span>` : '';
|
|
2575
|
+
|
|
2498
2576
|
// --- Step Generation ---
|
|
2499
2577
|
const generateStepsHTML = (steps, depth = 0) => {
|
|
2500
2578
|
if (!steps || steps.length === 0)
|
|
@@ -2546,7 +2624,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2546
2624
|
)}</div>`
|
|
2547
2625
|
: ""
|
|
2548
2626
|
}
|
|
2549
|
-
<button class="copy-error-btn" onclick="copyErrorToClipboard(this)" style="margin-top: 8px; padding: 6px 12px; background: rgba(248, 113, 113, 0.15); border: 2px solid var(--danger-color); border-radius: 6px; cursor: pointer; font-size: 12px; color: var(--danger-color); font-weight: 600; transition: all 0.2s;" onmouseover="this.style.background='rgba(248, 113, 113, 0.25)'" onmouseout="this.style.background='rgba(248, 113, 113, 0.15)'">Copy Error Prompt</button>
|
|
2627
|
+
<button class="copy-error-btn" onclick="copyErrorToClipboard(this)" style="margin-top: 8px; padding: 6px 12px; background: rgba(248, 113, 113, 0.15); border: 2px solid var(--danger-color); border-radius: 6px; cursor: pointer; font-size: 12px; color: var(--danger-color); font-weight: 600; transition: all 0.2s; align-self: flex-end; width: auto;" onmouseover="this.style.background='rgba(248, 113, 113, 0.25)'" onmouseout="this.style.background='rgba(248, 113, 113, 0.15)'">Copy Error Prompt</button>
|
|
2550
2628
|
</div>`
|
|
2551
2629
|
: ""
|
|
2552
2630
|
}
|
|
@@ -2564,43 +2642,35 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2564
2642
|
.join("");
|
|
2565
2643
|
};
|
|
2566
2644
|
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
<
|
|
2591
|
-
<span class="status-badge ${getStatusClass(test.status)}">${String(
|
|
2592
|
-
test.status,
|
|
2593
|
-
).toUpperCase()}</span>
|
|
2594
|
-
<span class="test-duration">${formatDuration(test.duration)}</span>
|
|
2595
|
-
</div>
|
|
2596
|
-
</div>
|
|
2597
|
-
<div class="test-case-content" style="display: none;">
|
|
2598
|
-
<p><strong>Full Path:</strong> ${sanitizeHTML(test.name)}</p>
|
|
2645
|
+
// Helper for Tab Badges
|
|
2646
|
+
const getSmallStatusBadge = (status) => {
|
|
2647
|
+
const s = String(status).toLowerCase();
|
|
2648
|
+
let colorVar = 'var(--text-tertiary)';
|
|
2649
|
+
if(s === 'passed') colorVar = 'var(--success-color)';
|
|
2650
|
+
else if(s === 'failed') colorVar = 'var(--danger-color)';
|
|
2651
|
+
else if(s === 'skipped') colorVar = 'var(--warning-color)';
|
|
2652
|
+
|
|
2653
|
+
return `<span style="
|
|
2654
|
+
display: inline-block;
|
|
2655
|
+
width: 8px;
|
|
2656
|
+
height: 8px;
|
|
2657
|
+
border-radius: 50%;
|
|
2658
|
+
background-color: ${colorVar};
|
|
2659
|
+
margin-left: 6px;
|
|
2660
|
+
vertical-align: middle;
|
|
2661
|
+
" title="${s}"></span>`;
|
|
2662
|
+
};
|
|
2663
|
+
|
|
2664
|
+
// Function to generate test content HTML (used for base run and retry tabs)
|
|
2665
|
+
const getTestContentHTML = (testData, runSuffix) => {
|
|
2666
|
+
const logId = `stdout-log-${test.id || testIndex}-${runSuffix}`;
|
|
2667
|
+
return `
|
|
2668
|
+
<p><strong>Full Path:</strong> ${sanitizeHTML(testData.name)}</p>
|
|
2599
2669
|
${
|
|
2600
|
-
|
|
2601
|
-
? `<div class="annotations-section">
|
|
2602
|
-
<h4 style="margin-top: 0; margin-bottom: 10px; font-size: 1.1em;">📌 Annotations</h4>
|
|
2603
|
-
${
|
|
2670
|
+
testData.annotations && testData.annotations.length > 0
|
|
2671
|
+
? `<div class="annotations-section" style="margin: 12px 0; padding: 12px; background-color: rgba(139, 92, 246, 0.1); border: 1px solid rgba(139, 92, 246, 0.3); border-left: 4px solid #8b5cf6; border-radius: 4px;">
|
|
2672
|
+
<h4 style="margin-top: 0; margin-bottom: 10px; color: #8b5cf6; font-size: 1.1em;">📌 Annotations</h4>
|
|
2673
|
+
${testData.annotations
|
|
2604
2674
|
.map((annotation, idx) => {
|
|
2605
2675
|
const isIssueOrBug =
|
|
2606
2676
|
annotation.type === "issue" ||
|
|
@@ -2608,176 +2678,312 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2608
2678
|
const descriptionText = annotation.description || "";
|
|
2609
2679
|
const typeLabel = sanitizeHTML(annotation.type);
|
|
2610
2680
|
const descriptionHtml =
|
|
2611
|
-
isIssueOrBug && descriptionText.match(/^[A-Z]
|
|
2681
|
+
isIssueOrBug && descriptionText.match(/^[A-Z]+-\\d+$/)
|
|
2612
2682
|
? `<a href="#" class="annotation-link" data-annotation="${sanitizeHTML(
|
|
2613
2683
|
descriptionText,
|
|
2614
|
-
)}" style="color:
|
|
2684
|
+
)}" style="color: #3b82f6; text-decoration: underline; cursor: pointer;">${sanitizeHTML(
|
|
2615
2685
|
descriptionText,
|
|
2616
2686
|
)}</a>`
|
|
2617
2687
|
: sanitizeHTML(descriptionText);
|
|
2618
2688
|
const locationText = annotation.location
|
|
2619
|
-
? `<div style="font-size: 0.85em; color:
|
|
2689
|
+
? `<div style="font-size: 0.85em; color: #6b7280; margin-top: 4px;">Location: ${sanitizeHTML(
|
|
2620
2690
|
annotation.location.file,
|
|
2621
2691
|
)}:${annotation.location.line}:${
|
|
2622
2692
|
annotation.location.column
|
|
2623
2693
|
}</div>`
|
|
2624
2694
|
: "";
|
|
2625
2695
|
return `<div style="margin-bottom: ${
|
|
2626
|
-
idx <
|
|
2627
|
-
};"
|
|
2696
|
+
idx < testData.annotations.length - 1 ? "10px" : "0"
|
|
2697
|
+
};">
|
|
2698
|
+
<strong style="color: #8b5cf6;">Type:</strong> <span style="background-color: rgba(139, 92, 246, 0.2); padding: 2px 8px; border-radius: 4px; font-size: 0.9em;">${typeLabel}</span>
|
|
2699
|
+
${
|
|
2628
2700
|
descriptionText
|
|
2629
|
-
? `<br><strong>Description:</strong> ${descriptionHtml}`
|
|
2701
|
+
? `<br><strong style="color: #8b5cf6;">Description:</strong> ${descriptionHtml}`
|
|
2630
2702
|
: ""
|
|
2631
|
-
}
|
|
2703
|
+
}
|
|
2704
|
+
${locationText}
|
|
2705
|
+
</div>`;
|
|
2632
2706
|
})
|
|
2633
2707
|
.join("")}
|
|
2634
2708
|
</div>`
|
|
2635
2709
|
: ""
|
|
2636
2710
|
}
|
|
2637
2711
|
<p><strong>Test run Worker ID:</strong> ${sanitizeHTML(
|
|
2638
|
-
|
|
2712
|
+
testData.workerId,
|
|
2639
2713
|
)} [<strong>Total No. of Workers:</strong> ${sanitizeHTML(
|
|
2640
|
-
|
|
2714
|
+
testData.totalWorkers,
|
|
2641
2715
|
)}]</p>
|
|
2642
2716
|
${
|
|
2643
|
-
|
|
2644
|
-
? `<div class="test-error-summary">${formatPlaywrightError(
|
|
2645
|
-
|
|
2646
|
-
)}
|
|
2717
|
+
testData.errorMessage
|
|
2718
|
+
? `<div class="test-error-summary"><div class="stack-trace">${formatPlaywrightError(
|
|
2719
|
+
testData.errorMessage,
|
|
2720
|
+
)}</div>
|
|
2721
|
+
<button
|
|
2722
|
+
class="copy-error-btn"
|
|
2723
|
+
onclick="copyErrorToClipboard(this)"
|
|
2724
|
+
style="
|
|
2725
|
+
margin-top: 8px;
|
|
2726
|
+
padding: 6px 12px;
|
|
2727
|
+
background: rgba(248, 113, 113, 0.15);
|
|
2728
|
+
border: 2px solid var(--danger-color);
|
|
2729
|
+
border-radius: 6px;
|
|
2730
|
+
cursor: pointer;
|
|
2731
|
+
font-size: 12px;
|
|
2732
|
+
color: var(--danger-color);
|
|
2733
|
+
font-weight: 600;
|
|
2734
|
+
transition: 0.2s;
|
|
2735
|
+
align-self: flex-end;
|
|
2736
|
+
width: auto;
|
|
2737
|
+
"
|
|
2738
|
+
onmouseover="this.style.background='#e0e0e0'"
|
|
2739
|
+
onmouseout="this.style.background='#f0f0f0'"
|
|
2740
|
+
>
|
|
2741
|
+
Copy Error Prompt
|
|
2742
|
+
</button>
|
|
2743
|
+
</div>`
|
|
2647
2744
|
: ""
|
|
2648
2745
|
}
|
|
2649
2746
|
${
|
|
2650
|
-
|
|
2747
|
+
testData.snippet
|
|
2651
2748
|
? `<div class="code-section"><h4>Error Snippet</h4><pre><code>${formatPlaywrightError(
|
|
2652
|
-
|
|
2749
|
+
testData.snippet,
|
|
2653
2750
|
)}</code></pre></div>`
|
|
2654
2751
|
: ""
|
|
2655
2752
|
}
|
|
2656
2753
|
<h4>Steps</h4>
|
|
2657
|
-
<div class="steps-list">${generateStepsHTML(
|
|
2658
|
-
|
|
2659
|
-
${(() => {
|
|
2660
|
-
if (!test.stdout || test.stdout.length === 0) return "";
|
|
2661
|
-
// FIXED: Now using 'testIndex' which is guaranteed to be defined
|
|
2662
|
-
const logId = `stdout-log-${test.id || testIndex}`;
|
|
2663
|
-
return `<div class="console-output-section"><h4>Console Output (stdout) <button class="copy-btn" onclick="copyLogContent('${logId}', this)">Copy</button></h4><div class="log-wrapper"><pre id="${logId}" class="console-log stdout-log" style="background-color: var(--bg-tertiary); color: #f3e8c3; padding: 1.25em; border-radius: 0.85em; line-height: 1.2; border: 1px solid var(--border-light);">${formatPlaywrightError(
|
|
2664
|
-
test.stdout.map((line) => sanitizeHTML(line)).join("\n"),
|
|
2665
|
-
)}</pre></div></div>`;
|
|
2666
|
-
})()}
|
|
2667
|
-
|
|
2668
|
-
${(() => {
|
|
2669
|
-
if (!test.stderr || test.stderr.length === 0) return "";
|
|
2670
|
-
// FIXED: Using 'testIndex'
|
|
2671
|
-
const logId = `stderr-log-${test.id || testIndex}`;
|
|
2672
|
-
return `<div class="console-output-section"><h4>Console Output (stderr)</h4><pre class="console-log stderr-log" style="background-color: var(--bg-tertiary); color: #f87171; padding: 1.25em; border-radius: 0.85em; line-height: 1.2; border: 1px solid var(--border-light);">${formatPlaywrightError(
|
|
2673
|
-
test.stderr.map((line) => sanitizeHTML(line)).join("\n"),
|
|
2674
|
-
)}</pre></div>`;
|
|
2675
|
-
})()}
|
|
2676
|
-
|
|
2754
|
+
<div class="steps-list">${generateStepsHTML(testData.steps)}</div>
|
|
2677
2755
|
${(() => {
|
|
2678
|
-
if (!
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
return createLazyMedia(
|
|
2692
|
-
base64ImageData,
|
|
2693
|
-
"image/png",
|
|
2694
|
-
"image",
|
|
2695
|
-
sIndex + 1,
|
|
2696
|
-
`screenshot-${testIndex}-${sIndex}.png`,
|
|
2697
|
-
);
|
|
2698
|
-
} catch (e) {
|
|
2699
|
-
return `<div class="attachment-item error">Error loading screenshot</div>`;
|
|
2700
|
-
}
|
|
2701
|
-
})
|
|
2702
|
-
.join("");
|
|
2703
|
-
return `<div class="attachments-section"><h4>Screenshots</h4><div class="attachments-grid">${screenshotsHTML}</div></div>`;
|
|
2756
|
+
if (!testData.stdout || testData.stdout.length === 0) return "";
|
|
2757
|
+
return `<div class="console-output-section">
|
|
2758
|
+
<h4>Console Output (stdout)
|
|
2759
|
+
<button class="copy-btn" onclick="copyLogContent('${logId}', this)">Copy</ button>
|
|
2760
|
+
</h4>
|
|
2761
|
+
<div class="log-wrapper">
|
|
2762
|
+
<pre id="${logId}" class="console-log stdout-log" style="background-color: #2d2d2d; color: wheat; padding: 1.25em; border-radius: 0.85em; line-height: 1.2;">${formatPlaywrightError(
|
|
2763
|
+
testData.stdout
|
|
2764
|
+
.map((line) => sanitizeHTML(line))
|
|
2765
|
+
.join("\\n"),
|
|
2766
|
+
)}</pre>
|
|
2767
|
+
</div>
|
|
2768
|
+
</div>`;
|
|
2704
2769
|
})()}
|
|
2705
|
-
|
|
2706
|
-
${(() => {
|
|
2707
|
-
if (!test.videoPath || test.videoPath.length === 0) return "";
|
|
2708
|
-
const videosHTML = test.videoPath
|
|
2709
|
-
.map((videoPath, vIndex) => {
|
|
2710
|
-
try {
|
|
2711
|
-
const videoFilePath = path.resolve(
|
|
2712
|
-
DEFAULT_OUTPUT_DIR,
|
|
2713
|
-
videoPath,
|
|
2714
|
-
);
|
|
2715
|
-
if (!fsExistsSync(videoFilePath))
|
|
2716
|
-
return `<div class="attachment-item error">Video not found</div>`;
|
|
2717
|
-
const videoBase64 =
|
|
2718
|
-
readFileSync(videoFilePath).toString("base64");
|
|
2719
|
-
const ext = path.extname(videoPath).slice(1).toLowerCase();
|
|
2720
|
-
const mime =
|
|
2721
|
-
{ mp4: "video/mp4", webm: "video/webm" }[ext] ||
|
|
2722
|
-
"video/mp4";
|
|
2723
|
-
// LAZY LOAD: Using helper with unique ID
|
|
2724
|
-
return createLazyMedia(
|
|
2725
|
-
videoBase64,
|
|
2726
|
-
mime,
|
|
2727
|
-
"video",
|
|
2728
|
-
vIndex + 1,
|
|
2729
|
-
`video-${testIndex}-${vIndex}.${ext}`,
|
|
2730
|
-
);
|
|
2731
|
-
} catch (e) {
|
|
2732
|
-
return `<div class="attachment-item error">Error loading video</div>`;
|
|
2733
|
-
}
|
|
2734
|
-
})
|
|
2735
|
-
.join("");
|
|
2736
|
-
return `<div class="attachments-section"><h4>Videos</h4><div class="attachments-grid">${videosHTML}</div></div>`;
|
|
2737
|
-
})()}
|
|
2738
|
-
|
|
2739
2770
|
${
|
|
2740
|
-
|
|
2741
|
-
? `<div class="
|
|
2742
|
-
|
|
2743
|
-
)}</
|
|
2744
|
-
test.tracePath,
|
|
2745
|
-
)}" target="_blank" download="${sanitizeHTML(
|
|
2746
|
-
path.basename(test.tracePath),
|
|
2747
|
-
)}" class="download-trace">Download Trace</a></div></div></div></div></div>`
|
|
2771
|
+
testData.stderr && testData.stderr.length > 0
|
|
2772
|
+
? `<div class="console-output-section"><h4>Console Output (stderr)</h4><pre class="console-log stderr-log" style="background-color: #2d2d2d; color: indianred; padding: 1.25em; border-radius: 0.85em; line-height: 1.2;">${formatPlaywrightError(
|
|
2773
|
+
testData.stderr.map((line) => sanitizeHTML(line)).join("\\n"),
|
|
2774
|
+
)}</pre></div>`
|
|
2748
2775
|
: ""
|
|
2749
2776
|
}
|
|
2750
2777
|
${
|
|
2751
|
-
|
|
2752
|
-
?
|
|
2778
|
+
testData.screenshots && testData.screenshots.length > 0
|
|
2779
|
+
? `
|
|
2780
|
+
<div class="attachments-section">
|
|
2781
|
+
<h4>Screenshots</h4>
|
|
2782
|
+
<div class="attachments-grid">
|
|
2783
|
+
${testData.screenshots
|
|
2753
2784
|
.map(
|
|
2754
|
-
(
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2785
|
+
(screenshot, screenshotIndex) => `
|
|
2786
|
+
<div class="attachment-item">
|
|
2787
|
+
<img src="${sanitizeHTML(screenshot)}" alt="Screenshot ${
|
|
2788
|
+
screenshotIndex + 1
|
|
2789
|
+
}">
|
|
2790
|
+
<div class="attachment-info">
|
|
2791
|
+
<div class="trace-actions">
|
|
2792
|
+
<a href="${sanitizeHTML(
|
|
2793
|
+
screenshot,
|
|
2794
|
+
)}" target="_blank" class="view-full">View Full Image</a>
|
|
2795
|
+
<a href="${sanitizeHTML(
|
|
2796
|
+
screenshot,
|
|
2797
|
+
)}" target="_blank" download="screenshot-${Date.now()}-${screenshotIndex}.png">Download</a>
|
|
2798
|
+
</div>
|
|
2799
|
+
</div>
|
|
2800
|
+
</div>
|
|
2801
|
+
`,
|
|
2770
2802
|
)
|
|
2803
|
+
.join("")}
|
|
2804
|
+
</div>
|
|
2805
|
+
</div>
|
|
2806
|
+
`
|
|
2807
|
+
: ""
|
|
2808
|
+
}
|
|
2809
|
+
${
|
|
2810
|
+
testData.videoPath && testData.videoPath.length > 0
|
|
2811
|
+
? `<div class="attachments-section"><h4>Videos</h4><div class="attachments-grid">${testData.videoPath
|
|
2812
|
+
.map((videoUrl, videoIndex) => {
|
|
2813
|
+
const fixedVideoUrl = sanitizeHTML(videoUrl);
|
|
2814
|
+
const fileExtension = String(fixedVideoUrl)
|
|
2815
|
+
.split(".")
|
|
2816
|
+
.pop()
|
|
2817
|
+
.toLowerCase();
|
|
2818
|
+
const mimeType =
|
|
2819
|
+
{
|
|
2820
|
+
mp4: "video/mp4",
|
|
2821
|
+
webm: "video/webm",
|
|
2822
|
+
ogg: "video/ogg",
|
|
2823
|
+
mov: "video/quicktime",
|
|
2824
|
+
avi: "video/x-msvideo",
|
|
2825
|
+
}[fileExtension] || "video/mp4";
|
|
2826
|
+
return `<div class="attachment-item video-item">
|
|
2827
|
+
<video controls width="100%" height="auto" title="Video ${
|
|
2828
|
+
videoIndex + 1
|
|
2829
|
+
}">
|
|
2830
|
+
<source src="${sanitizeHTML(
|
|
2831
|
+
fixedVideoUrl,
|
|
2832
|
+
)}" type="${mimeType}">
|
|
2833
|
+
Your browser does not support the video tag.
|
|
2834
|
+
</video>
|
|
2835
|
+
<div class="attachment-info">
|
|
2836
|
+
<div class="trace-actions">
|
|
2837
|
+
<a href="${sanitizeHTML(
|
|
2838
|
+
fixedVideoUrl,
|
|
2839
|
+
)}" target="_blank" download="video-${Date.now()}-${videoIndex}.${fileExtension}">Download</a>
|
|
2840
|
+
</div>
|
|
2841
|
+
</div>
|
|
2842
|
+
</div>`;
|
|
2843
|
+
})
|
|
2771
2844
|
.join("")}</div></div>`
|
|
2772
2845
|
: ""
|
|
2773
2846
|
}
|
|
2774
2847
|
${
|
|
2775
|
-
|
|
2776
|
-
?
|
|
2777
|
-
|
|
2778
|
-
|
|
2848
|
+
testData.tracePath
|
|
2849
|
+
? `
|
|
2850
|
+
<div class="attachments-section">
|
|
2851
|
+
<h4>Trace Files</h4>
|
|
2852
|
+
<div class="attachments-grid">
|
|
2853
|
+
<div class="attachment-item trace-item">
|
|
2854
|
+
<div class="trace-preview">
|
|
2855
|
+
<span class="trace-icon">📄</span>
|
|
2856
|
+
<span class="trace-name">${sanitizeHTML(
|
|
2857
|
+
path.basename(testData.tracePath),
|
|
2858
|
+
)}</span>
|
|
2859
|
+
</div>
|
|
2860
|
+
<div class="attachment-info">
|
|
2861
|
+
<div class="trace-actions">
|
|
2862
|
+
<a href="${sanitizeHTML(
|
|
2863
|
+
sanitizeHTML(testData.tracePath),
|
|
2864
|
+
)}" target="_blank" download="${sanitizeHTML(
|
|
2865
|
+
path.basename(testData.tracePath),
|
|
2866
|
+
)}" class="download-trace">Download Trace</a>
|
|
2867
|
+
</div>
|
|
2868
|
+
</div>
|
|
2869
|
+
</div>
|
|
2870
|
+
</div>
|
|
2871
|
+
</div>
|
|
2872
|
+
`
|
|
2779
2873
|
: ""
|
|
2780
2874
|
}
|
|
2875
|
+
${
|
|
2876
|
+
testData.attachments && testData.attachments.length > 0
|
|
2877
|
+
? `
|
|
2878
|
+
<div class="attachments-section">
|
|
2879
|
+
<h4>Other Attachments</h4>
|
|
2880
|
+
<div class="attachments-grid">
|
|
2881
|
+
${testData.attachments
|
|
2882
|
+
.map(
|
|
2883
|
+
(attachment) => `
|
|
2884
|
+
<div class="attachment-item generic-attachment">
|
|
2885
|
+
<div class="attachment-icon">${getAttachmentIcon(
|
|
2886
|
+
attachment.contentType,
|
|
2887
|
+
)}</div>
|
|
2888
|
+
<div class="attachment-caption">
|
|
2889
|
+
<span class="attachment-name" title="${sanitizeHTML(
|
|
2890
|
+
attachment.name,
|
|
2891
|
+
)}">${sanitizeHTML(attachment.name)}</span>
|
|
2892
|
+
<span class="attachment-type">${sanitizeHTML(
|
|
2893
|
+
attachment.contentType,
|
|
2894
|
+
)}</span>
|
|
2895
|
+
</div>
|
|
2896
|
+
<div class="attachment-info">
|
|
2897
|
+
<div class="trace-actions">
|
|
2898
|
+
<a href="${sanitizeHTML(
|
|
2899
|
+
sanitizeHTML(attachment.path),
|
|
2900
|
+
)}" target="_blank" class="view-full">View</a>
|
|
2901
|
+
<a href="${sanitizeHTML(
|
|
2902
|
+
sanitizeHTML(attachment.path),
|
|
2903
|
+
)}" target="_blank" download="${sanitizeHTML(
|
|
2904
|
+
attachment.name,
|
|
2905
|
+
)}" class="download-trace">Download</a>
|
|
2906
|
+
</div>
|
|
2907
|
+
</div>
|
|
2908
|
+
</div>
|
|
2909
|
+
`,
|
|
2910
|
+
)
|
|
2911
|
+
.join("")}
|
|
2912
|
+
</div>
|
|
2913
|
+
</div>
|
|
2914
|
+
`
|
|
2915
|
+
: ""
|
|
2916
|
+
}
|
|
2917
|
+
`;
|
|
2918
|
+
};
|
|
2919
|
+
|
|
2920
|
+
|
|
2921
|
+
// Determine header status: use final_status if retried, else normal status
|
|
2922
|
+
const headerStatus = (test.retryHistory && test.retryHistory.length > 0 && test.final_status)
|
|
2923
|
+
? test.final_status
|
|
2924
|
+
: test.status;
|
|
2925
|
+
|
|
2926
|
+
const outcomeBadge = (test.outcome && test.outcome !== 'flaky')
|
|
2927
|
+
? `<span class="outcome-badge ${test.outcome}">${test.outcome}</span>`
|
|
2928
|
+
: '';
|
|
2929
|
+
|
|
2930
|
+
return `
|
|
2931
|
+
<div class="test-case" data-status="${
|
|
2932
|
+
headerStatus
|
|
2933
|
+
}" data-browser="${sanitizeHTML(browser)}" data-tags="${(test.tags || [])
|
|
2934
|
+
.join(",")
|
|
2935
|
+
.toLowerCase()}">
|
|
2936
|
+
<div class="test-case-header" role="button" aria-expanded="false">
|
|
2937
|
+
<div class="test-case-summary">
|
|
2938
|
+
<span class="test-case-title" title="${sanitizeHTML(
|
|
2939
|
+
test.name,
|
|
2940
|
+
)}">${sanitizeHTML(testTitle)}</span>
|
|
2941
|
+
<span class="test-case-browser">(${sanitizeHTML(browser)})</span>
|
|
2942
|
+
</div>
|
|
2943
|
+
<div class="test-case-meta">
|
|
2944
|
+
${severityBadge}
|
|
2945
|
+
${retryBadge}
|
|
2946
|
+
${outcomeBadge}
|
|
2947
|
+
${
|
|
2948
|
+
test.tags && test.tags.length > 0
|
|
2949
|
+
? test.tags
|
|
2950
|
+
.map((t) => `<span class="tag">${sanitizeHTML(t)}</span>`)
|
|
2951
|
+
.join(" ")
|
|
2952
|
+
: ""
|
|
2953
|
+
}
|
|
2954
|
+
</div>
|
|
2955
|
+
<div class="test-case-status-duration">
|
|
2956
|
+
<span class="status-badge ${getStatusClass(headerStatus)}">${String(
|
|
2957
|
+
headerStatus,
|
|
2958
|
+
).toUpperCase()}</span>
|
|
2959
|
+
<span class="test-duration">${formatDuration(test.duration)}</span>
|
|
2960
|
+
</div>
|
|
2961
|
+
</div>
|
|
2962
|
+
<div class="test-case-content" style="display: none;">
|
|
2963
|
+
${test.retryHistory && test.retryHistory.length > 0 ? `
|
|
2964
|
+
<div class="retry-tabs-container">
|
|
2965
|
+
<div class="retry-tabs-header">
|
|
2966
|
+
<button class="retry-tab active" onclick="switchRetryTab(event, 'base-run-${test.id}')">
|
|
2967
|
+
Base Run ${getSmallStatusBadge(test.final_status || test.status)}
|
|
2968
|
+
</button>
|
|
2969
|
+
${test.retryHistory.map((retry, idx) => `
|
|
2970
|
+
<button class="retry-tab" onclick="switchRetryTab(event, 'retry-${idx + 1}-${test.id}')">
|
|
2971
|
+
Retry ${idx + 1} ${getSmallStatusBadge(retry.final_status || retry.status)}
|
|
2972
|
+
</button>
|
|
2973
|
+
`).join('')}
|
|
2974
|
+
</div>
|
|
2975
|
+
|
|
2976
|
+
<div id="base-run-${test.id}" class="retry-tab-content active">
|
|
2977
|
+
${getTestContentHTML(test, 'base')}
|
|
2978
|
+
</div>
|
|
2979
|
+
|
|
2980
|
+
${test.retryHistory.map((retry, idx) => `
|
|
2981
|
+
<div id="retry-${idx + 1}-${test.id}" class="retry-tab-content" style="display: none;">
|
|
2982
|
+
${getTestContentHTML(retry, `retry-${idx + 1}`)}
|
|
2983
|
+
</div>
|
|
2984
|
+
`).join('')}
|
|
2985
|
+
</div>
|
|
2986
|
+
` : getTestContentHTML(test, 'single')}
|
|
2781
2987
|
</div>
|
|
2782
2988
|
</div>`;
|
|
2783
2989
|
})
|
|
@@ -2819,6 +3025,8 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2819
3025
|
--light-gray-color: #262626; --medium-gray-color: #333333; --dark-gray-color: #a3a3a3;
|
|
2820
3026
|
--text-color: #f9fafb; --text-color-secondary: #e5e7eb; --border-color: #262626;
|
|
2821
3027
|
--card-background-color: #0d0d0d;
|
|
3028
|
+
--neutral-100: #171717; --neutral-200: #262626; --neutral-300: #404040;
|
|
3029
|
+
--bg-hover: #171717;
|
|
2822
3030
|
--font-family: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
2823
3031
|
--radius-sm: 8px; --radius-md: 12px; --radius-lg: 16px; --radius-xl: 20px; --radius-2xl: 24px;
|
|
2824
3032
|
--shadow-xs: 0 1px 2px 0 rgba(0, 0, 0, 0.8);
|
|
@@ -2910,7 +3118,6 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2910
3118
|
background: var(--gradient-card);
|
|
2911
3119
|
border-radius: 14px;
|
|
2912
3120
|
box-shadow: var(--shadow-md);
|
|
2913
|
-
border: 1px solid var(--border-light);
|
|
2914
3121
|
overflow: hidden;
|
|
2915
3122
|
}
|
|
2916
3123
|
.run-info-item {
|
|
@@ -3084,8 +3291,17 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3084
3291
|
color: #f9fafb;
|
|
3085
3292
|
text-transform: capitalize;
|
|
3086
3293
|
font-size: 1.05em;
|
|
3294
|
+
white-space: nowrap;
|
|
3295
|
+
overflow: hidden;
|
|
3296
|
+
text-overflow: ellipsis;
|
|
3297
|
+
flex: 1;
|
|
3298
|
+
min-width: 0;
|
|
3299
|
+
margin-right: 8px;
|
|
3087
3300
|
}
|
|
3088
3301
|
.browser-stats {
|
|
3302
|
+
color: #9ca3af;
|
|
3303
|
+
white-space: nowrap;
|
|
3304
|
+
flex-shrink: 0;
|
|
3089
3305
|
color: #9ca3af;
|
|
3090
3306
|
font-weight: 700;
|
|
3091
3307
|
font-size: 0.95em;
|
|
@@ -3139,17 +3355,9 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3139
3355
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
|
3140
3356
|
gap: 20px;
|
|
3141
3357
|
}
|
|
3358
|
+
/* Updated Suite Cards in Main Block */
|
|
3142
3359
|
.suite-card {
|
|
3143
|
-
|
|
3144
|
-
border: 1px solid var(--border-light);
|
|
3145
|
-
border-radius: 8px;
|
|
3146
|
-
padding: 20px;
|
|
3147
|
-
transition: all 0.2s ease;
|
|
3148
|
-
}
|
|
3149
|
-
.suite-card:hover {
|
|
3150
|
-
transform: translateY(-2px);
|
|
3151
|
-
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
|
3152
|
-
border-color: var(--primary-color);
|
|
3360
|
+
/* See line ~3455 for main definition */
|
|
3153
3361
|
}
|
|
3154
3362
|
.suite-name {
|
|
3155
3363
|
font-size: 1.1em;
|
|
@@ -3262,6 +3470,16 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3262
3470
|
.summary-card.status-skipped .value {
|
|
3263
3471
|
color: #f59e0b;
|
|
3264
3472
|
}
|
|
3473
|
+
.summary-card.flaky-status {
|
|
3474
|
+
background: rgba(156, 163, 175, 0.08);
|
|
3475
|
+
}
|
|
3476
|
+
.summary-card.flaky-status:hover {
|
|
3477
|
+
background: rgba(156, 163, 175, 0.15);
|
|
3478
|
+
box-shadow: 0 4px 12px rgba(156, 163, 175, 0.2);
|
|
3479
|
+
}
|
|
3480
|
+
.summary-card.flaky-status .value {
|
|
3481
|
+
color: #9ca3af;
|
|
3482
|
+
}
|
|
3265
3483
|
.summary-card:not([class*='status-']) .value {
|
|
3266
3484
|
color: #f9fafb;
|
|
3267
3485
|
}
|
|
@@ -3324,70 +3542,171 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3324
3542
|
.status-badge-small-tooltip.status-unknown {
|
|
3325
3543
|
background-color: #9ca3af;
|
|
3326
3544
|
}
|
|
3327
|
-
.suites-header {
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
.summary-badge {
|
|
3334
|
-
background-color: var(--border-light);
|
|
3335
|
-
color: var(--text-secondary);
|
|
3336
|
-
padding: 7px 14px;
|
|
3337
|
-
border-radius: 16px;
|
|
3338
|
-
font-size: 0.9em;
|
|
3545
|
+
.suites-header {
|
|
3546
|
+
flex-shrink: 0;
|
|
3547
|
+
display: flex;
|
|
3548
|
+
justify-content: space-between;
|
|
3549
|
+
align-items: center;
|
|
3550
|
+
margin-bottom: 20px;
|
|
3339
3551
|
}
|
|
3340
|
-
.
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3552
|
+
.summary-badge { background-color: var(--border-light); color: var(--text-secondary); padding: 7px 14px; border-radius: 16px; font-size: 0.9em; }
|
|
3553
|
+
.suites-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 20px; }
|
|
3554
|
+
.suites-widget {
|
|
3555
|
+
display: flex;
|
|
3556
|
+
flex-direction: column;
|
|
3344
3557
|
}
|
|
3345
|
-
.
|
|
3346
|
-
|
|
3347
|
-
border-left: 4px solid var(--border-light);
|
|
3348
|
-
padding: 24px;
|
|
3349
|
-
background-color: var(--bg-card);
|
|
3350
|
-
transition: all 0.15s ease;
|
|
3558
|
+
.fixed-height-widget {
|
|
3559
|
+
height: 450px;
|
|
3351
3560
|
}
|
|
3352
|
-
.
|
|
3353
|
-
|
|
3354
|
-
|
|
3561
|
+
.suites-grid-container {
|
|
3562
|
+
flex-grow: 1;
|
|
3563
|
+
overflow-y: auto;
|
|
3564
|
+
padding-right: 5px;
|
|
3355
3565
|
}
|
|
3356
|
-
|
|
3357
|
-
|
|
3566
|
+
|
|
3567
|
+
@media (max-width: 768px) {
|
|
3568
|
+
.fixed-height-widget {
|
|
3569
|
+
height: auto;
|
|
3570
|
+
max-height: 600px;
|
|
3571
|
+
}
|
|
3358
3572
|
}
|
|
3359
|
-
.suite-card
|
|
3360
|
-
background:
|
|
3573
|
+
.suite-card {
|
|
3574
|
+
background: var(--bg-card); /* Changed from #ffffff */
|
|
3575
|
+
border: 1px solid var(--border-medium); /* Changed from border-light for better contrast */
|
|
3576
|
+
border-radius: 16px;
|
|
3577
|
+
padding: 24px;
|
|
3578
|
+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3), 0 2px 4px -1px rgba(0, 0, 0, 0.2); /* Darker shadow */
|
|
3579
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
3580
|
+
display: flex;
|
|
3581
|
+
flex-direction: column;
|
|
3582
|
+
height: 100%;
|
|
3583
|
+
position: relative;
|
|
3584
|
+
overflow: hidden;
|
|
3361
3585
|
}
|
|
3362
|
-
.suite-card
|
|
3363
|
-
|
|
3586
|
+
.suite-card:hover {
|
|
3587
|
+
transform: translateY(-4px);
|
|
3588
|
+
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.5), 0 10px 10px -5px rgba(0, 0, 0, 0.4);
|
|
3589
|
+
background: var(--bg-card-hover);
|
|
3590
|
+
border-color: var(--primary-dark);
|
|
3364
3591
|
}
|
|
3365
|
-
.suite-card
|
|
3366
|
-
|
|
3592
|
+
.suite-card::before {
|
|
3593
|
+
content: '';
|
|
3594
|
+
position: absolute;
|
|
3595
|
+
top: 0;
|
|
3596
|
+
left: 0;
|
|
3597
|
+
width: 100%;
|
|
3598
|
+
height: 4px;
|
|
3599
|
+
background: var(--neutral-200);
|
|
3600
|
+
opacity: 0.8;
|
|
3601
|
+
transition: background 0.3s ease;
|
|
3602
|
+
}
|
|
3603
|
+
.suite-card.status-passed::before { background: var(--success-color); }
|
|
3604
|
+
.suite-card.status-failed::before { background: var(--danger-color); }
|
|
3605
|
+
.suite-card.status-flaky::before { background: var(--neutral-500); }
|
|
3606
|
+
.suite-card.status-skipped::before { background: var(--warning-color); }
|
|
3607
|
+
|
|
3608
|
+
.suite-card.status-skipped::before { background: var(--warning-color); }
|
|
3609
|
+
|
|
3610
|
+
/* Outcome Badge */
|
|
3611
|
+
.outcome-badge {
|
|
3612
|
+
background-color: var(--secondary-color);
|
|
3613
|
+
color: #000;
|
|
3614
|
+
padding: 2px 8px;
|
|
3615
|
+
border-radius: 4px;
|
|
3616
|
+
font-size: 0.75em;
|
|
3617
|
+
font-weight: 700;
|
|
3618
|
+
text-transform: uppercase;
|
|
3619
|
+
margin-right: 8px;
|
|
3620
|
+
letter-spacing: 0.5px;
|
|
3367
3621
|
}
|
|
3368
|
-
.
|
|
3369
|
-
|
|
3622
|
+
.outcome-badge.flaky {
|
|
3623
|
+
background-color: #eab308; /* Yellow-500 */
|
|
3624
|
+
color: #000;
|
|
3370
3625
|
}
|
|
3371
|
-
|
|
3372
|
-
|
|
3626
|
+
|
|
3627
|
+
.suite-card-header {
|
|
3628
|
+
display: flex;
|
|
3629
|
+
justify-content: space-between;
|
|
3630
|
+
align-items: flex-start;
|
|
3631
|
+
margin-bottom: 16px;
|
|
3373
3632
|
}
|
|
3374
|
-
.suite-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3633
|
+
.suite-name {
|
|
3634
|
+
font-size: 1.15em;
|
|
3635
|
+
font-weight: 700;
|
|
3636
|
+
color: var(--text-primary);
|
|
3637
|
+
line-height: 1.4;
|
|
3638
|
+
display: -webkit-box;
|
|
3639
|
+
-webkit-line-clamp: 2;
|
|
3640
|
+
-webkit-box-orient: vertical;
|
|
3641
|
+
overflow: hidden;
|
|
3642
|
+
margin-right: 12px;
|
|
3379
3643
|
}
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3644
|
+
.status-indicator-dot {
|
|
3645
|
+
width: 10px;
|
|
3646
|
+
height: 10px;
|
|
3647
|
+
border-radius: 50%;
|
|
3648
|
+
flex-shrink: 0;
|
|
3649
|
+
margin-top: 6px;
|
|
3386
3650
|
}
|
|
3651
|
+
.status-indicator-dot.status-passed { background-color: var(--success-color); box-shadow: 0 0 0 4px rgba(16, 185, 129, 0.15); }
|
|
3652
|
+
.status-indicator-dot.status-failed { background-color: var(--danger-color); box-shadow: 0 0 0 4px rgba(239, 68, 68, 0.15); }
|
|
3653
|
+
.status-indicator-dot.status-skipped { background-color: var(--warning-color); box-shadow: 0 0 0 4px rgba(245, 158, 11, 0.15); }
|
|
3654
|
+
|
|
3387
3655
|
.browser-tag {
|
|
3656
|
+
font-size: 0.8em;
|
|
3657
|
+
font-weight: 600;
|
|
3658
|
+
background: var(--bg-secondary);
|
|
3659
|
+
color: var(--text-secondary);
|
|
3660
|
+
padding: 4px 10px;
|
|
3661
|
+
border-radius: 20px;
|
|
3662
|
+
border: 1px solid var(--border-light);
|
|
3663
|
+
display: inline-flex;
|
|
3664
|
+
align-items: center;
|
|
3665
|
+
gap: 6px;
|
|
3666
|
+
margin-bottom: 20px;
|
|
3667
|
+
align-self: flex-start;
|
|
3668
|
+
box-shadow: none;
|
|
3669
|
+
text-shadow: none;
|
|
3670
|
+
}
|
|
3671
|
+
.browser-tag:hover {
|
|
3672
|
+
/* Remove hover effect from previous */
|
|
3673
|
+
}
|
|
3674
|
+
|
|
3675
|
+
.suite-card-body {
|
|
3676
|
+
margin-top: auto;
|
|
3677
|
+
}
|
|
3678
|
+
|
|
3679
|
+
.test-count-label {
|
|
3388
3680
|
font-size: 0.85em;
|
|
3389
3681
|
font-weight: 600;
|
|
3390
|
-
|
|
3682
|
+
color: var(--text-tertiary);
|
|
3683
|
+
text-transform: uppercase;
|
|
3684
|
+
letter-spacing: 0.05em;
|
|
3685
|
+
margin-bottom: 8px;
|
|
3686
|
+
display: block;
|
|
3687
|
+
}
|
|
3688
|
+
|
|
3689
|
+
.suite-stats {
|
|
3690
|
+
display: flex;
|
|
3691
|
+
gap: 8px;
|
|
3692
|
+
background: var(--bg-secondary);
|
|
3693
|
+
padding: 10px 14px;
|
|
3694
|
+
border-radius: 10px;
|
|
3695
|
+
justify-content: space-between;
|
|
3696
|
+
}
|
|
3697
|
+
|
|
3698
|
+
.stat-pill {
|
|
3699
|
+
display: flex;
|
|
3700
|
+
align-items: center;
|
|
3701
|
+
gap: 6px;
|
|
3702
|
+
font-size: 0.9em;
|
|
3703
|
+
font-weight: 600;
|
|
3704
|
+
}
|
|
3705
|
+
.stat-pill svg { width: 14px; height: 14px; }
|
|
3706
|
+
.stat-pill.passed { color: var(--success-dark); }
|
|
3707
|
+
.stat-pill.failed { color: var(--danger-dark); }
|
|
3708
|
+
.stat-pill.flaky { color: #4b5563; }
|
|
3709
|
+
.stat-pill.skipped { color: var(--warning-dark); }
|
|
3391
3710
|
color: #93c5fd;
|
|
3392
3711
|
padding: 6px 12px;
|
|
3393
3712
|
border-radius: var(--radius-sm);
|
|
@@ -3581,6 +3900,11 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3581
3900
|
.status-badge.status-skipped {
|
|
3582
3901
|
background: var(--warning-color);
|
|
3583
3902
|
}
|
|
3903
|
+
.status-badge.status-flaky {
|
|
3904
|
+
background-color: #C0C0C0;
|
|
3905
|
+
color: #000000;
|
|
3906
|
+
border: 1px solid #A0A0A0;
|
|
3907
|
+
}
|
|
3584
3908
|
.status-badge.status-unknown {
|
|
3585
3909
|
background: var(--dark-gray-color);
|
|
3586
3910
|
}
|
|
@@ -3636,6 +3960,65 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3636
3960
|
border-color: rgba(148, 163, 184, 0.25);
|
|
3637
3961
|
}
|
|
3638
3962
|
|
|
3963
|
+
/* --- RETRY COUNT BADGE --- */
|
|
3964
|
+
.retry-badge {
|
|
3965
|
+
display: inline-flex;
|
|
3966
|
+
align-items: center;
|
|
3967
|
+
padding: 5px 12px;
|
|
3968
|
+
border-radius: 12px;
|
|
3969
|
+
font-size: 0.75rem;
|
|
3970
|
+
font-weight: 600;
|
|
3971
|
+
background: rgba(147, 51, 234, 0.15);
|
|
3972
|
+
color: #a855f7;
|
|
3973
|
+
border: 1px solid rgba(147, 51, 234, 0.3);
|
|
3974
|
+
margin-left: 8px;
|
|
3975
|
+
}
|
|
3976
|
+
|
|
3977
|
+
/* --- RETRY TABS --- */
|
|
3978
|
+
.retry-tabs-container {
|
|
3979
|
+
margin-top: 16px;
|
|
3980
|
+
}
|
|
3981
|
+
|
|
3982
|
+
.retry-tabs-header {
|
|
3983
|
+
display: flex;
|
|
3984
|
+
gap: 8px;
|
|
3985
|
+
border-bottom: 2px solid var(--border-medium);
|
|
3986
|
+
margin-bottom: 20px;
|
|
3987
|
+
flex-wrap: wrap;
|
|
3988
|
+
}
|
|
3989
|
+
|
|
3990
|
+
.retry-tab {
|
|
3991
|
+
padding: 10px 20px;
|
|
3992
|
+
background: transparent;
|
|
3993
|
+
border: none;
|
|
3994
|
+
border-bottom: 3px solid transparent;
|
|
3995
|
+
cursor: pointer;
|
|
3996
|
+
font-size: 0.95rem;
|
|
3997
|
+
font-weight: 600;
|
|
3998
|
+
color: var(--text-color-secondary);
|
|
3999
|
+
transition: all 0.2s ease;
|
|
4000
|
+
}
|
|
4001
|
+
|
|
4002
|
+
.retry-tab:hover {
|
|
4003
|
+
color: var(--primary-color);
|
|
4004
|
+
background: rgba(147, 51, 234, 0.05);
|
|
4005
|
+
}
|
|
4006
|
+
|
|
4007
|
+
.retry-tab.active {
|
|
4008
|
+
color: #a855f7;
|
|
4009
|
+
border-bottom-color: #a855f7;
|
|
4010
|
+
background: rgba(147, 51, 234, 0.1);
|
|
4011
|
+
}
|
|
4012
|
+
|
|
4013
|
+
.retry-tab-content {
|
|
4014
|
+
animation: fadeIn 0.3s ease-in;
|
|
4015
|
+
}
|
|
4016
|
+
|
|
4017
|
+
@keyframes fadeIn {
|
|
4018
|
+
from { opacity: 0; }
|
|
4019
|
+
to { opacity: 1; }
|
|
4020
|
+
}
|
|
4021
|
+
|
|
3639
4022
|
.tag {
|
|
3640
4023
|
display: inline-flex;
|
|
3641
4024
|
align-items: center;
|
|
@@ -3685,7 +4068,9 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3685
4068
|
background-color: rgba(248,113,113,0.08);
|
|
3686
4069
|
border: 1px solid rgba(248,113,113,0.25);
|
|
3687
4070
|
border-left: 4px solid var(--danger-color);
|
|
3688
|
-
border-radius: 4px;
|
|
4071
|
+
border-radius: 4px;
|
|
4072
|
+
display: flex;
|
|
4073
|
+
flex-direction: column;
|
|
3689
4074
|
}
|
|
3690
4075
|
.test-error-summary h4 {
|
|
3691
4076
|
color: #ef4444;
|
|
@@ -4153,6 +4538,10 @@ function generateHTML(reportData, trendData = null) {
|
|
|
4153
4538
|
.status-badge-small.status-skipped {
|
|
4154
4539
|
background-color: #f59e0b;
|
|
4155
4540
|
}
|
|
4541
|
+
.status-badge-small.status-flaky {
|
|
4542
|
+
background-color: #C0C0C0;
|
|
4543
|
+
color: #000000;
|
|
4544
|
+
}
|
|
4156
4545
|
.status-badge-small.status-unknown {
|
|
4157
4546
|
background-color: var(--dark-gray-color);
|
|
4158
4547
|
}
|
|
@@ -5378,8 +5767,17 @@ function generateHTML(reportData, trendData = null) {
|
|
|
5378
5767
|
color: #f9fafb;
|
|
5379
5768
|
text-transform: capitalize;
|
|
5380
5769
|
font-size: 1.05em;
|
|
5770
|
+
white-space: nowrap;
|
|
5771
|
+
overflow: hidden;
|
|
5772
|
+
text-overflow: ellipsis;
|
|
5773
|
+
flex: 1;
|
|
5774
|
+
min-width: 0;
|
|
5775
|
+
margin-right: 8px;
|
|
5381
5776
|
}
|
|
5382
5777
|
.browser-stats {
|
|
5778
|
+
color: #9ca3af;
|
|
5779
|
+
white-space: nowrap;
|
|
5780
|
+
flex-shrink: 0;
|
|
5383
5781
|
color: #9ca3af;
|
|
5384
5782
|
font-weight: 700;
|
|
5385
5783
|
font-size: 0.95em;
|
|
@@ -5844,31 +6242,32 @@ function generateHTML(reportData, trendData = null) {
|
|
|
5844
6242
|
<div class="summary-card status-skipped"><h3>Skipped</h3><div class="value">${
|
|
5845
6243
|
runSummary.skipped || 0
|
|
5846
6244
|
}</div><div class="trend-percentage">${skipPercentage}%</div></div>
|
|
5847
|
-
<div class="summary-card"><h3>
|
|
6245
|
+
<div class="summary-card flaky-status"><h3>Flaky</h3><div class="value">${runSummary.flaky || 0}</div>
|
|
6246
|
+
<div class="trend-percentage">${flakyPercentage}%</div></div>
|
|
5848
6247
|
<div class="summary-card"><h3>Run Duration</h3><div class="value">${formatDuration(
|
|
5849
6248
|
runSummary.duration,
|
|
5850
6249
|
)}</div></div>
|
|
5851
6250
|
<div class="summary-card">
|
|
5852
|
-
<h3
|
|
6251
|
+
<h3>Total Retry Count</h3>
|
|
5853
6252
|
<div class="value">${totalRetried}</div>
|
|
5854
6253
|
</div>
|
|
5855
6254
|
<div class="summary-card">
|
|
5856
6255
|
<h3>🌐 Browser Distribution <span style="font-size: 0.7em; color: var(--text-color-secondary); font-weight: 400;">(${browserBreakdown.length} total)</span></h3>
|
|
5857
6256
|
<div class="browser-breakdown" style="max-height: 200px; overflow-y: auto; padding-right: 4px;">
|
|
5858
6257
|
${browserBreakdown
|
|
5859
|
-
.slice(0,
|
|
6258
|
+
.slice(0, 3)
|
|
5860
6259
|
.map(
|
|
5861
6260
|
(b) =>
|
|
5862
6261
|
`<div class="browser-item">
|
|
5863
|
-
<span class="browser-name">${sanitizeHTML(b.browser)}</span>
|
|
6262
|
+
<span class="browser-name" title="${sanitizeHTML(b.browser)}">${sanitizeHTML(b.browser)}</span>
|
|
5864
6263
|
<span class="browser-stats">${b.percentage}% (${b.count})</span>
|
|
5865
6264
|
</div>`,
|
|
5866
6265
|
)
|
|
5867
6266
|
.join("")}
|
|
5868
6267
|
${
|
|
5869
|
-
browserBreakdown.length >
|
|
6268
|
+
browserBreakdown.length > 3
|
|
5870
6269
|
? `<div class="browser-item" style="opacity: 0.6; font-style: italic; justify-content: center; border-top: 1px solid var(--border-light); margin-top: 8px; padding-top: 8px;">
|
|
5871
|
-
<span>+${browserBreakdown.length -
|
|
6270
|
+
<span>+${browserBreakdown.length - 3} more browsers</span>
|
|
5872
6271
|
</div>`
|
|
5873
6272
|
: ""
|
|
5874
6273
|
}
|
|
@@ -5881,6 +6280,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
5881
6280
|
[
|
|
5882
6281
|
{ label: "Passed", value: runSummary.passed },
|
|
5883
6282
|
{ label: "Failed", value: runSummary.failed },
|
|
6283
|
+
{ label: "Flaky", value: runSummary.flaky || 0 },
|
|
5884
6284
|
{ label: "Skipped", value: runSummary.skipped || 0 },
|
|
5885
6285
|
],
|
|
5886
6286
|
400,
|
|
@@ -5897,7 +6297,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
5897
6297
|
<div id="test-runs" class="tab-content">
|
|
5898
6298
|
<div class="filters" style="border-color: black; border-style: groove;">
|
|
5899
6299
|
<input type="text" id="filter-name" placeholder="Filter by test name/path..." style="border-color: black; border-style: outset;">
|
|
5900
|
-
<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>
|
|
6300
|
+
<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>
|
|
5901
6301
|
<select id="filter-browser"><option value="">All Browsers</option>${Array.from(
|
|
5902
6302
|
new Set(
|
|
5903
6303
|
(results || []).map((test) => test.browser || "unknown"),
|
|
@@ -5988,6 +6388,29 @@ function generateHTML(reportData, trendData = null) {
|
|
|
5988
6388
|
return (ms / 1000).toFixed(1) + "s";
|
|
5989
6389
|
}
|
|
5990
6390
|
}
|
|
6391
|
+
function switchRetryTab(event, tabId) {
|
|
6392
|
+
// Find container
|
|
6393
|
+
const container = event.target.closest('.retry-tabs-container');
|
|
6394
|
+
|
|
6395
|
+
// Update tab buttons
|
|
6396
|
+
const buttons = container.querySelectorAll('.retry-tab');
|
|
6397
|
+
buttons.forEach(btn => btn.classList.remove('active'));
|
|
6398
|
+
event.target.classList.add('active');
|
|
6399
|
+
|
|
6400
|
+
// Update content
|
|
6401
|
+
const contents = container.querySelectorAll('.retry-tab-content');
|
|
6402
|
+
contents.forEach(content => {
|
|
6403
|
+
content.style.display = 'none';
|
|
6404
|
+
content.classList.remove('active');
|
|
6405
|
+
});
|
|
6406
|
+
|
|
6407
|
+
const activeContent = container.querySelector('#' + tabId);
|
|
6408
|
+
if (activeContent) {
|
|
6409
|
+
activeContent.style.display = 'block';
|
|
6410
|
+
activeContent.classList.add('active');
|
|
6411
|
+
}
|
|
6412
|
+
}
|
|
6413
|
+
|
|
5991
6414
|
function copyLogContent(elementId, button) {
|
|
5992
6415
|
const logElement = document.getElementById(elementId);
|
|
5993
6416
|
if (!logElement) {
|
|
@@ -6814,6 +7237,7 @@ async function main() {
|
|
|
6814
7237
|
passed: histRunReport.run.passed,
|
|
6815
7238
|
failed: histRunReport.run.failed,
|
|
6816
7239
|
skipped: histRunReport.run.skipped || 0,
|
|
7240
|
+
flaky: histRunReport.run.flaky || (histRunReport.results ? histRunReport.results.filter(r => r.status === 'flaky' || r.outcome === 'flaky').length : 0),
|
|
6817
7241
|
});
|
|
6818
7242
|
|
|
6819
7243
|
if (histRunReport.results && Array.isArray(histRunReport.results)) {
|
|
@@ -6822,7 +7246,7 @@ async function main() {
|
|
|
6822
7246
|
(test) => ({
|
|
6823
7247
|
testName: test.name,
|
|
6824
7248
|
duration: test.duration,
|
|
6825
|
-
status: test.status,
|
|
7249
|
+
status: test.final_status || test.status,
|
|
6826
7250
|
timestamp: new Date(test.startTime),
|
|
6827
7251
|
}),
|
|
6828
7252
|
);
|