@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
|
@@ -295,6 +295,12 @@ function generateTestTrendsChart(trendData) {
|
|
|
295
295
|
color: "var(--warning-color)",
|
|
296
296
|
marker: { symbol: "circle" },
|
|
297
297
|
},
|
|
298
|
+
{
|
|
299
|
+
name: "Flaky",
|
|
300
|
+
data: runs.map((r) => r.flaky || 0),
|
|
301
|
+
color: "var(--neutral-500)",
|
|
302
|
+
marker: { symbol: "circle" },
|
|
303
|
+
},
|
|
298
304
|
];
|
|
299
305
|
const runsForTooltip = runs.map((r) => ({
|
|
300
306
|
runId: r.runId,
|
|
@@ -481,6 +487,9 @@ function generateTestHistoryChart(history) {
|
|
|
481
487
|
case "skipped":
|
|
482
488
|
color = "var(--warning-color)";
|
|
483
489
|
break;
|
|
490
|
+
case "flaky":
|
|
491
|
+
color = "var(--neutral-500)";
|
|
492
|
+
break;
|
|
484
493
|
default:
|
|
485
494
|
color = "var(--dark-gray-color)";
|
|
486
495
|
}
|
|
@@ -590,6 +599,9 @@ function generatePieChart(data, chartWidth = 300, chartHeight = 300) {
|
|
|
590
599
|
case "Failed":
|
|
591
600
|
color = "var(--danger-color)";
|
|
592
601
|
break;
|
|
602
|
+
case "Flaky":
|
|
603
|
+
color = "var(--neutral-500)";
|
|
604
|
+
break;
|
|
593
605
|
case "Skipped":
|
|
594
606
|
color = "var(--warning-color)";
|
|
595
607
|
break;
|
|
@@ -828,7 +840,9 @@ function generateEnvironmentSection(environmentData) {
|
|
|
828
840
|
}
|
|
829
841
|
|
|
830
842
|
function generateEnvironmentDashboard(environment, hideHeader = false) {
|
|
831
|
-
const
|
|
843
|
+
const cpuModel = environment.cpu && environment.cpu.model ? environment.cpu.model : "N/A";
|
|
844
|
+
const cpuCores = environment.cpu && environment.cpu.cores ? environment.cpu.cores : "N/A";
|
|
845
|
+
const cpuInfo = `model: ${cpuModel}, cores: ${cpuCores}`;
|
|
832
846
|
const osInfo = environment.os || "N/A";
|
|
833
847
|
const nodeInfo = environment.node || "N/A";
|
|
834
848
|
const v8Info = environment.v8 || "N/A";
|
|
@@ -1140,7 +1154,7 @@ function generateEnvironmentDashboard(environment, hideHeader = false) {
|
|
|
1140
1154
|
</div>
|
|
1141
1155
|
<div class="env-item-content">
|
|
1142
1156
|
<p class="env-item-label">Working Dir</p>
|
|
1143
|
-
<div class="env-item-value" title="${
|
|
1157
|
+
<div class="env-item-value" title="${cwdInfo}">${cwdInfo.length > 30 ? "..." + cwdInfo.slice(-27) : cwdInfo}</div>
|
|
1144
1158
|
</div>
|
|
1145
1159
|
</div>
|
|
1146
1160
|
</div>
|
|
@@ -1164,11 +1178,11 @@ function generateWorkerDistributionChart(results) {
|
|
|
1164
1178
|
const workerId =
|
|
1165
1179
|
typeof test.workerId !== "undefined" ? test.workerId : "N/A";
|
|
1166
1180
|
if (!acc[workerId]) {
|
|
1167
|
-
acc[workerId] = { passed: 0, failed: 0, skipped: 0, tests: [] };
|
|
1181
|
+
acc[workerId] = { passed: 0, failed: 0, skipped: 0, flaky: 0, tests: [] };
|
|
1168
1182
|
}
|
|
1169
1183
|
|
|
1170
1184
|
const status = String(test.status).toLowerCase();
|
|
1171
|
-
if (status === "passed" || status === "failed" || status === "skipped") {
|
|
1185
|
+
if (status === "passed" || status === "failed" || status === "skipped" || status === "flaky") {
|
|
1172
1186
|
acc[workerId][status]++;
|
|
1173
1187
|
}
|
|
1174
1188
|
|
|
@@ -1213,12 +1227,14 @@ function generateWorkerDistributionChart(results) {
|
|
|
1213
1227
|
const passedData = workerIds.map((id) => workerData[id].passed);
|
|
1214
1228
|
const failedData = workerIds.map((id) => workerData[id].failed);
|
|
1215
1229
|
const skippedData = workerIds.map((id) => workerData[id].skipped);
|
|
1230
|
+
const flakyData = workerIds.map((id) => workerData[id].flaky);
|
|
1216
1231
|
|
|
1217
1232
|
const categoriesString = JSON.stringify(categories);
|
|
1218
1233
|
const fullDataString = JSON.stringify(fullWorkerData);
|
|
1219
1234
|
const seriesString = JSON.stringify([
|
|
1220
1235
|
{ name: "Passed", data: passedData, color: "var(--success-color)" },
|
|
1221
1236
|
{ name: "Failed", data: failedData, color: "var(--danger-color)" },
|
|
1237
|
+
{ name: "Flaky", data: flakyData, color: "var(--neutral-500)" },
|
|
1222
1238
|
{ name: "Skipped", data: skippedData, color: "var(--warning-color)" },
|
|
1223
1239
|
]);
|
|
1224
1240
|
|
|
@@ -1311,6 +1327,7 @@ function generateWorkerDistributionChart(results) {
|
|
|
1311
1327
|
if (test.status === 'passed') color = 'var(--success-color)';
|
|
1312
1328
|
else if (test.status === 'failed') color = 'var(--danger-color)';
|
|
1313
1329
|
else if (test.status === 'skipped') color = 'var(--warning-color)';
|
|
1330
|
+
else if (test.status === 'flaky') color = 'var(--neutral-500)';
|
|
1314
1331
|
|
|
1315
1332
|
const escapedName = test.name.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
1316
1333
|
testListHtml += \`<li style="color: \${color};"><span style="color: \${color}">[\${test.status.toUpperCase()}]</span> \${escapedName}</li>\`;
|
|
@@ -1476,6 +1493,7 @@ function generateTestHistoryContent(trendData) {
|
|
|
1476
1493
|
<option value="">All Statuses</option>
|
|
1477
1494
|
<option value="passed">Passed</option>
|
|
1478
1495
|
<option value="failed">Failed</option>
|
|
1496
|
+
<option value="flaky">Flaky</option>
|
|
1479
1497
|
<option value="skipped">Skipped</option>
|
|
1480
1498
|
</select>
|
|
1481
1499
|
<button id="clear-history-filters" class="clear-filters-btn">Clear Filters</button>
|
|
@@ -1543,6 +1561,8 @@ function getStatusClass(status) {
|
|
|
1543
1561
|
return "status-failed";
|
|
1544
1562
|
case "skipped":
|
|
1545
1563
|
return "status-skipped";
|
|
1564
|
+
case "flaky":
|
|
1565
|
+
return "status-flaky";
|
|
1546
1566
|
default:
|
|
1547
1567
|
return "status-unknown";
|
|
1548
1568
|
}
|
|
@@ -1555,6 +1575,8 @@ function getStatusIcon(status) {
|
|
|
1555
1575
|
return "❌";
|
|
1556
1576
|
case "skipped":
|
|
1557
1577
|
return "⏭️";
|
|
1578
|
+
case "flaky":
|
|
1579
|
+
return "⚠️";
|
|
1558
1580
|
default:
|
|
1559
1581
|
return "❓";
|
|
1560
1582
|
}
|
|
@@ -1590,6 +1612,7 @@ function getSuitesData(results) {
|
|
|
1590
1612
|
browser: browser,
|
|
1591
1613
|
passed: 0,
|
|
1592
1614
|
failed: 0,
|
|
1615
|
+
flaky: 0,
|
|
1593
1616
|
skipped: 0,
|
|
1594
1617
|
count: 0,
|
|
1595
1618
|
statusOverall: "passed",
|
|
@@ -1597,12 +1620,15 @@ function getSuitesData(results) {
|
|
|
1597
1620
|
}
|
|
1598
1621
|
const suite = suitesMap.get(key);
|
|
1599
1622
|
suite.count++;
|
|
1600
|
-
|
|
1623
|
+
let currentStatus = String(test.status).toLowerCase();
|
|
1624
|
+
if (test.outcome === 'flaky') currentStatus = 'flaky';
|
|
1601
1625
|
if (currentStatus && suite[currentStatus] !== undefined) {
|
|
1602
1626
|
suite[currentStatus]++;
|
|
1603
1627
|
}
|
|
1604
1628
|
if (currentStatus === "failed") suite.statusOverall = "failed";
|
|
1605
|
-
else if (currentStatus === "
|
|
1629
|
+
else if (currentStatus === "flaky" && suite.statusOverall !== "failed")
|
|
1630
|
+
suite.statusOverall = "flaky";
|
|
1631
|
+
else if (currentStatus === "skipped" && suite.statusOverall !== "failed" && suite.statusOverall !== "flaky")
|
|
1606
1632
|
suite.statusOverall = "skipped";
|
|
1607
1633
|
});
|
|
1608
1634
|
return Array.from(suitesMap.values());
|
|
@@ -1613,10 +1639,10 @@ function generateSuitesWidget(suitesData) {
|
|
|
1613
1639
|
return `<div class="suites-widget" style="height: 450px;"><div class="suites-header"><h2>Test Suites</h2></div><div class="no-data">No suite data available.</div></div>`;
|
|
1614
1640
|
}
|
|
1615
1641
|
|
|
1616
|
-
//
|
|
1642
|
+
// Uses CSS classes for responsiveness instead of inline styles
|
|
1617
1643
|
return `
|
|
1618
|
-
<div class="suites-widget
|
|
1619
|
-
<div class="suites-header"
|
|
1644
|
+
<div class="suites-widget fixed-height-widget">
|
|
1645
|
+
<div class="suites-header">
|
|
1620
1646
|
<h2>Test Suites</h2>
|
|
1621
1647
|
<span class="summary-badge">${
|
|
1622
1648
|
suitesData.length
|
|
@@ -1626,40 +1652,40 @@ function generateSuitesWidget(suitesData) {
|
|
|
1626
1652
|
)} tests</span>
|
|
1627
1653
|
</div>
|
|
1628
1654
|
|
|
1629
|
-
<div class="suites-grid-container"
|
|
1655
|
+
<div class="suites-grid-container">
|
|
1630
1656
|
<div class="suites-grid">
|
|
1631
1657
|
${suitesData
|
|
1632
1658
|
.map(
|
|
1633
1659
|
(suite) => `
|
|
1634
1660
|
<div class="suite-card status-${suite.statusOverall}">
|
|
1635
1661
|
<div class="suite-card-header">
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
}</span>
|
|
1662
|
+
<h3 class="suite-name" title="${sanitizeHTML(suite.name)} (${sanitizeHTML(suite.browser)})">${sanitizeHTML(suite.name)}</h3>
|
|
1663
|
+
<div class="status-indicator-dot status-${suite.statusOverall}" title="${suite.statusOverall.charAt(0).toUpperCase() + suite.statusOverall.slice(1)}"></div>
|
|
1664
|
+
</div>
|
|
1665
|
+
|
|
1666
|
+
<div class="browser-tag" title="🌐Browser: ${sanitizeHTML(suite.browser)}">
|
|
1667
|
+
<span style="font-size: 1.1em;">🌐</span> ${sanitizeHTML(suite.browser)}
|
|
1668
|
+
</div>
|
|
1669
|
+
|
|
1670
|
+
<div class="suite-card-body">
|
|
1671
|
+
<span class="test-count-label">${suite.count} Test${suite.count !== 1 ? "s" : ""}</span>
|
|
1647
1672
|
<div class="suite-stats">
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1673
|
+
<span class="stat-pill passed" title="Passed">
|
|
1674
|
+
<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>
|
|
1675
|
+
${suite.passed}
|
|
1676
|
+
</span>
|
|
1677
|
+
<span class="stat-pill failed" title="Failed">
|
|
1678
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 16 16"><path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM5.354 4.646a.5.5 0 1 0-.708.708L7.293 8l-2.647 2.646a.5.5 0 0 0 .708.708L8 8.707l2.646 2.647a.5.5 0 0 0 .708-.708L8.707 8l2.647-2.646a.5.5 0 0 0-.708-.708L8 7.293 5.354 4.646z"/></svg>
|
|
1679
|
+
${suite.failed}
|
|
1680
|
+
</span>
|
|
1681
|
+
<span class="stat-pill flaky" title="Flaky">
|
|
1682
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 16 16"><path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"/></svg>
|
|
1683
|
+
${suite.flaky || 0}
|
|
1684
|
+
</span>
|
|
1685
|
+
<span class="stat-pill skipped" title="Skipped">
|
|
1686
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 16 16"><path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"/></svg>
|
|
1687
|
+
${suite.skipped}
|
|
1688
|
+
</span>
|
|
1663
1689
|
</div>
|
|
1664
1690
|
</div>
|
|
1665
1691
|
</div>`,
|
|
@@ -2060,6 +2086,7 @@ function generateSeverityDistributionChart(results) {
|
|
|
2060
2086
|
const data = {
|
|
2061
2087
|
passed: [0, 0, 0, 0, 0],
|
|
2062
2088
|
failed: [0, 0, 0, 0, 0],
|
|
2089
|
+
flaky: [0, 0, 0, 0, 0],
|
|
2063
2090
|
skipped: [0, 0, 0, 0, 0],
|
|
2064
2091
|
};
|
|
2065
2092
|
|
|
@@ -2078,6 +2105,8 @@ function generateSeverityDistributionChart(results) {
|
|
|
2078
2105
|
status === "interrupted"
|
|
2079
2106
|
) {
|
|
2080
2107
|
data.failed[index]++;
|
|
2108
|
+
} else if (status === "flaky") {
|
|
2109
|
+
data.flaky[index]++;
|
|
2081
2110
|
} else {
|
|
2082
2111
|
data.skipped[index]++;
|
|
2083
2112
|
}
|
|
@@ -2091,6 +2120,7 @@ function generateSeverityDistributionChart(results) {
|
|
|
2091
2120
|
const seriesData = [
|
|
2092
2121
|
{ name: "Passed", data: data.passed, color: "var(--success-color)" },
|
|
2093
2122
|
{ name: "Failed", data: data.failed, color: "var(--danger-color)" },
|
|
2123
|
+
{ name: "Flaky", data: data.flaky, color: "var(--neutral-500)" },
|
|
2094
2124
|
{ name: "Skipped", data: data.skipped, color: "var(--warning-color)" },
|
|
2095
2125
|
];
|
|
2096
2126
|
|
|
@@ -2204,32 +2234,76 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2204
2234
|
return p.replace(new RegExp(`^${DEFAULT_OUTPUT_DIR}[\\\\/]`), "");
|
|
2205
2235
|
};
|
|
2206
2236
|
|
|
2207
|
-
|
|
2208
|
-
const passPercentage = Math.round((runSummary.passed / totalTestsOr1) * 100);
|
|
2209
|
-
const failPercentage = Math.round((runSummary.failed / totalTestsOr1) * 100);
|
|
2210
|
-
const skipPercentage = Math.round(
|
|
2211
|
-
((runSummary.skipped || 0) / totalTestsOr1) * 100,
|
|
2212
|
-
);
|
|
2237
|
+
|
|
2213
2238
|
const avgTestDuration =
|
|
2214
2239
|
runSummary.totalTests > 0
|
|
2215
2240
|
? formatDuration(runSummary.duration / runSummary.totalTests)
|
|
2216
2241
|
: "0.0s";
|
|
2217
2242
|
|
|
2243
|
+
const flakyCount = (results || []).filter(r => r.outcome === 'flaky').length;
|
|
2244
|
+
|
|
2218
2245
|
// Calculate retry statistics
|
|
2219
2246
|
const totalRetried = (results || []).reduce((acc, test) => {
|
|
2220
|
-
if (test.
|
|
2221
|
-
return acc +
|
|
2247
|
+
if (test.retryHistory && test.retryHistory.length > 0) {
|
|
2248
|
+
return acc + test.retryHistory.length;
|
|
2222
2249
|
}
|
|
2223
2250
|
return acc;
|
|
2224
2251
|
}, 0);
|
|
2225
2252
|
|
|
2253
|
+
// --- RECALCULATE KPI METRICS BASED ON FINAL_STATUS ---
|
|
2254
|
+
let calculatedPassed = 0;
|
|
2255
|
+
let calculatedFailed = 0;
|
|
2256
|
+
let calculatedSkipped = 0;
|
|
2257
|
+
let calculatedFlaky = 0;
|
|
2258
|
+
let calculatedTotal = 0;
|
|
2259
|
+
|
|
2260
|
+
(results || []).forEach(test => {
|
|
2261
|
+
calculatedTotal++;
|
|
2262
|
+
// New Logic: If outcome is 'flaky', it overrides everything.
|
|
2263
|
+
let statusToUse = test.status;
|
|
2264
|
+
if (test.outcome === 'flaky') {
|
|
2265
|
+
statusToUse = 'flaky';
|
|
2266
|
+
} else if (test.status === 'flaky') {
|
|
2267
|
+
// Just in case outcome wasn't set but status was (unlikely with new reporter)
|
|
2268
|
+
statusToUse = 'flaky';
|
|
2269
|
+
} else if (test.retryHistory && test.retryHistory.length > 0 && test.final_status) {
|
|
2270
|
+
statusToUse = test.final_status;
|
|
2271
|
+
}
|
|
2272
|
+
|
|
2273
|
+
// Update test status in place for charts
|
|
2274
|
+
test.status = statusToUse;
|
|
2275
|
+
|
|
2276
|
+
const s = String(statusToUse).toLowerCase();
|
|
2277
|
+
if (s === 'passed') calculatedPassed++;
|
|
2278
|
+
else if (s === 'skipped') calculatedSkipped++;
|
|
2279
|
+
else if (s === 'flaky') calculatedFlaky++;
|
|
2280
|
+
else calculatedFailed++; // failed, timedout, interrupted
|
|
2281
|
+
});
|
|
2282
|
+
|
|
2283
|
+
// Override runSummary counts with our calculated ones if results exist
|
|
2284
|
+
if (results && results.length > 0) {
|
|
2285
|
+
runSummary.passed = calculatedPassed;
|
|
2286
|
+
runSummary.failed = calculatedFailed;
|
|
2287
|
+
runSummary.skipped = calculatedSkipped;
|
|
2288
|
+
runSummary.flaky = calculatedFlaky;
|
|
2289
|
+
runSummary.totalTests = calculatedTotal;
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2292
|
+
const totalTestsOr1 = runSummary.totalTests || 1;
|
|
2293
|
+
const passPercentage = Math.round((runSummary.passed / totalTestsOr1) * 100);
|
|
2294
|
+
const failPercentage = Math.round((runSummary.failed / totalTestsOr1) * 100);
|
|
2295
|
+
const skipPercentage = Math.round(
|
|
2296
|
+
((runSummary.skipped || 0) / totalTestsOr1) * 100,
|
|
2297
|
+
);
|
|
2298
|
+
const flakyPercentage = Math.round(((runSummary.flaky || 0) / totalTestsOr1) * 100);
|
|
2299
|
+
|
|
2300
|
+
|
|
2226
2301
|
// Calculate browser distribution
|
|
2227
2302
|
const browserStats = (results || []).reduce((acc, test) => {
|
|
2228
2303
|
let browserName = "unknown";
|
|
2229
2304
|
if (test.browser) {
|
|
2230
|
-
//
|
|
2231
|
-
|
|
2232
|
-
browserName = match ? match[1] : test.browser;
|
|
2305
|
+
// Use full browser name
|
|
2306
|
+
browserName = test.browser;
|
|
2233
2307
|
}
|
|
2234
2308
|
acc[browserName] = (acc[browserName] || 0) + 1;
|
|
2235
2309
|
return acc;
|
|
@@ -2255,6 +2329,10 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2255
2329
|
// --- Simplified Severity Badge ---
|
|
2256
2330
|
const severity = test.severity || "Medium";
|
|
2257
2331
|
const severityBadge = `<span class="severity-badge" data-severity="${severity.toLowerCase()}">${severity}</span>`;
|
|
2332
|
+
|
|
2333
|
+
// --- Retry Count Badge (only show if retries occurred) ---
|
|
2334
|
+
const retryCount = test.retryHistory ? test.retryHistory.length : 0;
|
|
2335
|
+
const retryBadge = retryCount > 0 ? `<span class="retry-badge">Retry Count: ${retryCount}</span>` : '';
|
|
2258
2336
|
const generateStepsHTML = (steps, depth = 0) => {
|
|
2259
2337
|
if (!steps || steps.length === 0)
|
|
2260
2338
|
return "<div class='no-steps'>No steps recorded for this test.</div>";
|
|
@@ -2310,7 +2388,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2310
2388
|
onclick="copyErrorToClipboard(this)"
|
|
2311
2389
|
style="
|
|
2312
2390
|
margin-top: 8px;
|
|
2313
|
-
padding:
|
|
2391
|
+
padding: 6px 12px;
|
|
2314
2392
|
background: #f0f0f0;
|
|
2315
2393
|
border: 2px solid #ccc;
|
|
2316
2394
|
border-radius: 4px;
|
|
@@ -2318,6 +2396,8 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2318
2396
|
font-size: 12px;
|
|
2319
2397
|
border-color: #8B0000;
|
|
2320
2398
|
color: #8B0000;
|
|
2399
|
+
align-self: flex-end;
|
|
2400
|
+
width: auto;
|
|
2321
2401
|
"
|
|
2322
2402
|
onmouseover="this.style.background='#e0e0e0'"
|
|
2323
2403
|
onmouseout="this.style.background='#f0f0f0'"
|
|
@@ -2341,43 +2421,35 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2341
2421
|
.join("");
|
|
2342
2422
|
};
|
|
2343
2423
|
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
<
|
|
2368
|
-
<span class="status-badge ${getStatusClass(test.status)}">${String(
|
|
2369
|
-
test.status,
|
|
2370
|
-
).toUpperCase()}</span>
|
|
2371
|
-
<span class="test-duration">${formatDuration(test.duration)}</span>
|
|
2372
|
-
</div>
|
|
2373
|
-
</div>
|
|
2374
|
-
<div class="test-case-content" style="display: none;">
|
|
2375
|
-
<p><strong>Full Path:</strong> ${sanitizeHTML(test.name)}</p>
|
|
2424
|
+
// Helper for Tab Badges
|
|
2425
|
+
const getSmallStatusBadge = (status) => {
|
|
2426
|
+
const s = String(status).toLowerCase();
|
|
2427
|
+
let colorVar = 'var(--text-tertiary)';
|
|
2428
|
+
if(s === 'passed') colorVar = 'var(--success-color)';
|
|
2429
|
+
else if(s === 'failed') colorVar = 'var(--danger-color)';
|
|
2430
|
+
else if(s === 'skipped') colorVar = 'var(--warning-color)';
|
|
2431
|
+
|
|
2432
|
+
return `<span style="
|
|
2433
|
+
display: inline-block;
|
|
2434
|
+
width: 8px;
|
|
2435
|
+
height: 8px;
|
|
2436
|
+
border-radius: 50%;
|
|
2437
|
+
background-color: ${colorVar};
|
|
2438
|
+
margin-left: 6px;
|
|
2439
|
+
vertical-align: middle;
|
|
2440
|
+
" title="${s}"></span>`;
|
|
2441
|
+
};
|
|
2442
|
+
|
|
2443
|
+
// Function to generate test content HTML (used for base run and retry tabs)
|
|
2444
|
+
const getTestContentHTML = (testData, runSuffix) => {
|
|
2445
|
+
const logId = `stdout-log-${test.id || index}-${runSuffix}`;
|
|
2446
|
+
return `
|
|
2447
|
+
<p><strong>Full Path:</strong> ${sanitizeHTML(testData.name)}</p>
|
|
2376
2448
|
${
|
|
2377
|
-
|
|
2449
|
+
testData.annotations && testData.annotations.length > 0
|
|
2378
2450
|
? `<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;">
|
|
2379
2451
|
<h4 style="margin-top: 0; margin-bottom: 10px; color: #8b5cf6; font-size: 1.1em;">📌 Annotations</h4>
|
|
2380
|
-
${
|
|
2452
|
+
${testData.annotations
|
|
2381
2453
|
.map((annotation, idx) => {
|
|
2382
2454
|
const isIssueOrBug =
|
|
2383
2455
|
annotation.type === "issue" ||
|
|
@@ -2385,7 +2457,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2385
2457
|
const descriptionText = annotation.description || "";
|
|
2386
2458
|
const typeLabel = sanitizeHTML(annotation.type);
|
|
2387
2459
|
const descriptionHtml =
|
|
2388
|
-
isIssueOrBug && descriptionText.match(/^[A-Z]
|
|
2460
|
+
isIssueOrBug && descriptionText.match(/^[A-Z]+-\\d+$/)
|
|
2389
2461
|
? `<a href="#" class="annotation-link" data-annotation="${sanitizeHTML(
|
|
2390
2462
|
descriptionText,
|
|
2391
2463
|
)}" style="color: #3b82f6; text-decoration: underline; cursor: pointer;">${sanitizeHTML(
|
|
@@ -2400,7 +2472,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2400
2472
|
}</div>`
|
|
2401
2473
|
: "";
|
|
2402
2474
|
return `<div style="margin-bottom: ${
|
|
2403
|
-
idx <
|
|
2475
|
+
idx < testData.annotations.length - 1 ? "10px" : "0"
|
|
2404
2476
|
};">
|
|
2405
2477
|
<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>
|
|
2406
2478
|
${
|
|
@@ -2416,21 +2488,21 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2416
2488
|
: ""
|
|
2417
2489
|
}
|
|
2418
2490
|
<p><strong>Test run Worker ID:</strong> ${sanitizeHTML(
|
|
2419
|
-
|
|
2491
|
+
testData.workerId,
|
|
2420
2492
|
)} [<strong>Total No. of Workers:</strong> ${sanitizeHTML(
|
|
2421
|
-
|
|
2493
|
+
testData.totalWorkers,
|
|
2422
2494
|
)}]</p>
|
|
2423
2495
|
${
|
|
2424
|
-
|
|
2425
|
-
? `<div class="test-error-summary">${formatPlaywrightError(
|
|
2426
|
-
|
|
2427
|
-
)}
|
|
2496
|
+
testData.errorMessage
|
|
2497
|
+
? `<div class="test-error-summary"><div class="stack-trace">${formatPlaywrightError(
|
|
2498
|
+
testData.errorMessage,
|
|
2499
|
+
)}</div>
|
|
2428
2500
|
<button
|
|
2429
2501
|
class="copy-error-btn"
|
|
2430
2502
|
onclick="copyErrorToClipboard(this)"
|
|
2431
2503
|
style="
|
|
2432
2504
|
margin-top: 8px;
|
|
2433
|
-
padding:
|
|
2505
|
+
padding: 6px 12px;
|
|
2434
2506
|
background: #f0f0f0;
|
|
2435
2507
|
border: 2px solid #ccc;
|
|
2436
2508
|
border-radius: 4px;
|
|
@@ -2438,6 +2510,8 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2438
2510
|
font-size: 12px;
|
|
2439
2511
|
border-color: #8B0000;
|
|
2440
2512
|
color: #8B0000;
|
|
2513
|
+
align-self: flex-end;
|
|
2514
|
+
width: auto;
|
|
2441
2515
|
"
|
|
2442
2516
|
onmouseover="this.style.background='#e0e0e0'"
|
|
2443
2517
|
onmouseout="this.style.background='#f0f0f0'"
|
|
@@ -2448,50 +2522,48 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2448
2522
|
: ""
|
|
2449
2523
|
}
|
|
2450
2524
|
${
|
|
2451
|
-
|
|
2525
|
+
testData.snippet
|
|
2452
2526
|
? `<div class="code-section"><h4>Error Snippet</h4><pre><code>${formatPlaywrightError(
|
|
2453
|
-
|
|
2527
|
+
testData.snippet,
|
|
2454
2528
|
)}</code></pre></div>`
|
|
2455
2529
|
: ""
|
|
2456
2530
|
}
|
|
2457
2531
|
<h4>Steps</h4>
|
|
2458
|
-
<div class="steps-list">${generateStepsHTML(
|
|
2532
|
+
<div class="steps-list">${generateStepsHTML(testData.steps)}</div>
|
|
2459
2533
|
${(() => {
|
|
2460
|
-
if (!
|
|
2461
|
-
// Create a unique ID for the <pre> element to target it for copying
|
|
2462
|
-
const logId = `stdout-log-${test.id || index}`;
|
|
2534
|
+
if (!testData.stdout || testData.stdout.length === 0) return "";
|
|
2463
2535
|
return `<div class="console-output-section">
|
|
2464
2536
|
<h4>Console Output (stdout)
|
|
2465
|
-
<button class="copy-btn" onclick="copyLogContent('${logId}', this)">Copy</button>
|
|
2537
|
+
<button class="copy-btn" onclick="copyLogContent('${logId}', this)">Copy</ button>
|
|
2466
2538
|
</h4>
|
|
2467
2539
|
<div class="log-wrapper">
|
|
2468
2540
|
<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(
|
|
2469
|
-
|
|
2541
|
+
testData.stdout
|
|
2470
2542
|
.map((line) => sanitizeHTML(line))
|
|
2471
|
-
.join("
|
|
2543
|
+
.join("\\n"),
|
|
2472
2544
|
)}</pre>
|
|
2473
2545
|
</div>
|
|
2474
2546
|
</div>`;
|
|
2475
2547
|
})()}
|
|
2476
2548
|
${
|
|
2477
|
-
|
|
2549
|
+
testData.stderr && testData.stderr.length > 0
|
|
2478
2550
|
? `<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(
|
|
2479
|
-
|
|
2551
|
+
testData.stderr.map((line) => sanitizeHTML(line)).join("\\n"),
|
|
2480
2552
|
)}</pre></div>`
|
|
2481
2553
|
: ""
|
|
2482
2554
|
}
|
|
2483
2555
|
${
|
|
2484
|
-
|
|
2556
|
+
testData.screenshots && testData.screenshots.length > 0
|
|
2485
2557
|
? `
|
|
2486
2558
|
<div class="attachments-section">
|
|
2487
2559
|
<h4>Screenshots</h4>
|
|
2488
2560
|
<div class="attachments-grid">
|
|
2489
|
-
${
|
|
2561
|
+
${testData.screenshots
|
|
2490
2562
|
.map(
|
|
2491
|
-
(screenshot,
|
|
2563
|
+
(screenshot, screenshotIndex) => `
|
|
2492
2564
|
<div class="attachment-item">
|
|
2493
2565
|
<img src="${fixPath(screenshot)}" alt="Screenshot ${
|
|
2494
|
-
|
|
2566
|
+
screenshotIndex + 1
|
|
2495
2567
|
}">
|
|
2496
2568
|
<div class="attachment-info">
|
|
2497
2569
|
<div class="trace-actions">
|
|
@@ -2500,7 +2572,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2500
2572
|
)}" target="_blank" class="view-full">View Full Image</a>
|
|
2501
2573
|
<a href="${fixPath(
|
|
2502
2574
|
screenshot,
|
|
2503
|
-
)}" target="_blank" download="screenshot-${Date.now()}-${
|
|
2575
|
+
)}" target="_blank" download="screenshot-${Date.now()}-${screenshotIndex}.png">Download</a>
|
|
2504
2576
|
</div>
|
|
2505
2577
|
</div>
|
|
2506
2578
|
</div>
|
|
@@ -2513,9 +2585,9 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2513
2585
|
: ""
|
|
2514
2586
|
}
|
|
2515
2587
|
${
|
|
2516
|
-
|
|
2517
|
-
? `<div class="attachments-section"><h4>Videos</h4><div class="attachments-grid">${
|
|
2518
|
-
.map((videoUrl,
|
|
2588
|
+
testData.videoPath && testData.videoPath.length > 0
|
|
2589
|
+
? `<div class="attachments-section"><h4>Videos</h4><div class="attachments-grid">${testData.videoPath
|
|
2590
|
+
.map((videoUrl, videoIndex) => {
|
|
2519
2591
|
const fixedVideoUrl = fixPath(videoUrl);
|
|
2520
2592
|
const fileExtension = String(fixedVideoUrl)
|
|
2521
2593
|
.split(".")
|
|
@@ -2531,7 +2603,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2531
2603
|
}[fileExtension] || "video/mp4";
|
|
2532
2604
|
return `<div class="attachment-item video-item">
|
|
2533
2605
|
<video controls width="100%" height="auto" title="Video ${
|
|
2534
|
-
|
|
2606
|
+
videoIndex + 1
|
|
2535
2607
|
}">
|
|
2536
2608
|
<source src="${sanitizeHTML(
|
|
2537
2609
|
fixedVideoUrl,
|
|
@@ -2542,7 +2614,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2542
2614
|
<div class="trace-actions">
|
|
2543
2615
|
<a href="${sanitizeHTML(
|
|
2544
2616
|
fixedVideoUrl,
|
|
2545
|
-
)}" target="_blank" download="video-${Date.now()}-${
|
|
2617
|
+
)}" target="_blank" download="video-${Date.now()}-${videoIndex}.${fileExtension}">Download</a>
|
|
2546
2618
|
</div>
|
|
2547
2619
|
</div>
|
|
2548
2620
|
</div>`;
|
|
@@ -2551,7 +2623,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2551
2623
|
: ""
|
|
2552
2624
|
}
|
|
2553
2625
|
${
|
|
2554
|
-
|
|
2626
|
+
testData.tracePath
|
|
2555
2627
|
? `
|
|
2556
2628
|
<div class="attachments-section">
|
|
2557
2629
|
<h4>Trace Files</h4>
|
|
@@ -2560,15 +2632,15 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2560
2632
|
<div class="trace-preview">
|
|
2561
2633
|
<span class="trace-icon">📄</span>
|
|
2562
2634
|
<span class="trace-name">${sanitizeHTML(
|
|
2563
|
-
path.basename(
|
|
2635
|
+
path.basename(testData.tracePath),
|
|
2564
2636
|
)}</span>
|
|
2565
2637
|
</div>
|
|
2566
2638
|
<div class="attachment-info">
|
|
2567
2639
|
<div class="trace-actions">
|
|
2568
2640
|
<a href="${sanitizeHTML(
|
|
2569
|
-
fixPath(
|
|
2641
|
+
fixPath(testData.tracePath),
|
|
2570
2642
|
)}" target="_blank" download="${sanitizeHTML(
|
|
2571
|
-
path.basename(
|
|
2643
|
+
path.basename(testData.tracePath),
|
|
2572
2644
|
)}" class="download-trace">Download Trace</a>
|
|
2573
2645
|
</div>
|
|
2574
2646
|
</div>
|
|
@@ -2579,12 +2651,12 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2579
2651
|
: ""
|
|
2580
2652
|
}
|
|
2581
2653
|
${
|
|
2582
|
-
|
|
2654
|
+
testData.attachments && testData.attachments.length > 0
|
|
2583
2655
|
? `
|
|
2584
2656
|
<div class="attachments-section">
|
|
2585
2657
|
<h4>Other Attachments</h4>
|
|
2586
2658
|
<div class="attachments-grid">
|
|
2587
|
-
${
|
|
2659
|
+
${testData.attachments
|
|
2588
2660
|
.map(
|
|
2589
2661
|
(attachment) => `
|
|
2590
2662
|
<div class="attachment-item generic-attachment">
|
|
@@ -2620,13 +2692,76 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2620
2692
|
`
|
|
2621
2693
|
: ""
|
|
2622
2694
|
}
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2695
|
+
|
|
2696
|
+
`;
|
|
2697
|
+
};
|
|
2698
|
+
|
|
2699
|
+
// Determine header status: use final_status if retried, else normal status
|
|
2700
|
+
const headerStatus = (test.retryHistory && test.retryHistory.length > 0 && test.final_status)
|
|
2701
|
+
? test.final_status
|
|
2702
|
+
: test.status;
|
|
2703
|
+
|
|
2704
|
+
const outcomeBadge = (test.outcome && test.outcome !== 'flaky')
|
|
2705
|
+
? `<span class="outcome-badge ${test.outcome}">${test.outcome}</span>`
|
|
2706
|
+
: '';
|
|
2707
|
+
|
|
2708
|
+
return `
|
|
2709
|
+
<div class="test-case" data-status="${
|
|
2710
|
+
headerStatus
|
|
2711
|
+
}" data-browser="${sanitizeHTML(browser)}" data-tags="${(test.tags || [])
|
|
2712
|
+
.join(",")
|
|
2713
|
+
.toLowerCase()}">
|
|
2714
|
+
<div class="test-case-header" role="button" aria-expanded="false">
|
|
2715
|
+
<div class="test-case-summary">
|
|
2716
|
+
<span class="test-case-title" title="${sanitizeHTML(
|
|
2717
|
+
test.name,
|
|
2718
|
+
)}">${sanitizeHTML(testTitle)}</span>
|
|
2719
|
+
<span class="test-case-browser">(${sanitizeHTML(browser)})</span>
|
|
2720
|
+
</div>
|
|
2721
|
+
<div class="test-case-meta">
|
|
2722
|
+
${severityBadge}
|
|
2723
|
+
${retryBadge}
|
|
2724
|
+
${outcomeBadge}
|
|
2725
|
+
${
|
|
2726
|
+
test.tags && test.tags.length > 0
|
|
2727
|
+
? test.tags
|
|
2728
|
+
.map((t) => `<span class="tag">${sanitizeHTML(t)}</span>`)
|
|
2729
|
+
.join(" ")
|
|
2730
|
+
: ""
|
|
2731
|
+
}
|
|
2732
|
+
</div>
|
|
2733
|
+
<div class="test-case-status-duration">
|
|
2734
|
+
<span class="status-badge ${getStatusClass(headerStatus)}">${String(
|
|
2735
|
+
headerStatus,
|
|
2736
|
+
).toUpperCase()}</span>
|
|
2737
|
+
<span class="test-duration">${formatDuration(test.duration)}</span>
|
|
2738
|
+
</div>
|
|
2739
|
+
</div>
|
|
2740
|
+
<div class="test-case-content" style="display: none;">
|
|
2741
|
+
${test.retryHistory && test.retryHistory.length > 0 ? `
|
|
2742
|
+
<div class="retry-tabs-container">
|
|
2743
|
+
<div class="retry-tabs-header">
|
|
2744
|
+
<button class="retry-tab active" onclick="switchRetryTab(event, 'base-run-${test.id}')">
|
|
2745
|
+
Base Run ${getSmallStatusBadge(test.final_status || test.status)}
|
|
2746
|
+
</button>
|
|
2747
|
+
${test.retryHistory.map((retry, idx) => `
|
|
2748
|
+
<button class="retry-tab" onclick="switchRetryTab(event, 'retry-${idx + 1}-${test.id}')">
|
|
2749
|
+
Retry ${idx + 1} ${getSmallStatusBadge(retry.final_status || retry.status)}
|
|
2750
|
+
</button>
|
|
2751
|
+
`).join('')}
|
|
2752
|
+
</div>
|
|
2753
|
+
|
|
2754
|
+
<div id="base-run-${test.id}" class="retry-tab-content active">
|
|
2755
|
+
${getTestContentHTML(test, 'base')}
|
|
2756
|
+
</div>
|
|
2757
|
+
|
|
2758
|
+
${test.retryHistory.map((retry, idx) => `
|
|
2759
|
+
<div id="retry-${idx + 1}-${test.id}" class="retry-tab-content" style="display: none;">
|
|
2760
|
+
${getTestContentHTML(retry, `retry-${idx + 1}`)}
|
|
2761
|
+
</div>
|
|
2762
|
+
`).join('')}
|
|
2763
|
+
</div>
|
|
2764
|
+
` : getTestContentHTML(test, 'single')}
|
|
2630
2765
|
</div>
|
|
2631
2766
|
</div>`;
|
|
2632
2767
|
})
|
|
@@ -2677,7 +2812,9 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2677
2812
|
--glow-primary: 0 0 20px rgba(99, 102, 241, 0.4), 0 0 40px rgba(99, 102, 241, 0.2);
|
|
2678
2813
|
--glow-success: 0 0 20px rgba(16, 185, 129, 0.4), 0 0 40px rgba(16, 185, 129, 0.2);
|
|
2679
2814
|
--glow-danger: 0 0 20px rgba(239, 68, 68, 0.4), 0 0 40px rgba(239, 68, 68, 0.2);
|
|
2680
|
-
|
|
2815
|
+
--bg-card: #ffffff; --bg-card-hover: #f8fafc;
|
|
2816
|
+
--gradient-card: linear-gradient(145deg, #ffffff 0%, #f9fafb 100%);
|
|
2817
|
+
--border-medium: #cbd5e1;
|
|
2681
2818
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
2682
2819
|
::selection { background: var(--primary-color); color: white; }
|
|
2683
2820
|
::-webkit-scrollbar { width: 0; height: 0; display: none; }
|
|
@@ -2749,11 +2886,11 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2749
2886
|
display: flex;
|
|
2750
2887
|
gap: 16px;
|
|
2751
2888
|
align-items: stretch;
|
|
2752
|
-
background:
|
|
2889
|
+
background: transparent;
|
|
2753
2890
|
border-radius: 12px;
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
overflow: hidden;
|
|
2891
|
+
padding: 0;
|
|
2892
|
+
box-shadow: var(--shadow-md); /* Inherited from base static style */
|
|
2893
|
+
overflow: hidden; /* Inherited */
|
|
2757
2894
|
}
|
|
2758
2895
|
.run-info-item {
|
|
2759
2896
|
display: flex;
|
|
@@ -2762,54 +2899,61 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2762
2899
|
padding: 16px 28px;
|
|
2763
2900
|
position: relative;
|
|
2764
2901
|
flex: 1;
|
|
2765
|
-
min-width:
|
|
2766
|
-
max-width: 100%;
|
|
2767
|
-
overflow-wrap: break-word;
|
|
2768
|
-
word-break: break-word;
|
|
2769
|
-
}
|
|
2770
|
-
.run-info-item:not(:last-child)::after {
|
|
2771
|
-
content: '';
|
|
2772
|
-
position: absolute;
|
|
2773
|
-
right: 0;
|
|
2774
|
-
top: 20%;
|
|
2775
|
-
bottom: 20%;
|
|
2776
|
-
width: 1px;
|
|
2777
|
-
background: linear-gradient(to bottom, transparent, #e2e8f0 20%, #e2e8f0 80%, transparent);
|
|
2902
|
+
min-width: fit-content;
|
|
2778
2903
|
}
|
|
2904
|
+
|
|
2779
2905
|
.run-info-item:first-child {
|
|
2780
|
-
background: linear-gradient(135deg,
|
|
2906
|
+
background: linear-gradient(135deg, rgba(251, 191, 36, 0.2) 0%, rgba(245, 158, 11, 0.15) 50%, rgba(217, 119, 6, 0.1) 100%);
|
|
2907
|
+
border: 1px solid rgba(251, 191, 36, 0.3);
|
|
2908
|
+
border-radius: var(--radius-md);
|
|
2909
|
+
box-shadow: 0 4px 16px rgba(251, 191, 36, 0.2), inset 0 1px 0 rgba(251, 191, 36, 0.25), 0 0 40px rgba(251, 191, 36, 0.08);
|
|
2910
|
+
}
|
|
2911
|
+
.run-info-item:first-child:hover {
|
|
2912
|
+
background: linear-gradient(135deg, rgba(251, 191, 36, 0.28) 0%, rgba(245, 158, 11, 0.22) 50%, rgba(217, 119, 6, 0.15) 100%);
|
|
2913
|
+
border-color: rgba(251, 191, 36, 0.4);
|
|
2914
|
+
box-shadow: 0 8px 24px rgba(251, 191, 36, 0.3), inset 0 1px 0 rgba(251, 191, 36, 0.35), 0 0 50px rgba(251, 191, 36, 0.15);
|
|
2781
2915
|
}
|
|
2782
2916
|
.run-info-item:last-child {
|
|
2783
|
-
background: linear-gradient(135deg,
|
|
2917
|
+
background: linear-gradient(135deg, rgba(139, 92, 246, 0.18) 0%, rgba(124, 58, 237, 0.12) 50%, rgba(109, 40, 217, 0.08) 100%);
|
|
2918
|
+
border: 1px solid rgba(139, 92, 246, 0.3);
|
|
2919
|
+
border-radius: var(--radius-md);
|
|
2920
|
+
box-shadow: 0 4px 16px rgba(139, 92, 246, 0.2), inset 0 1px 0 rgba(139, 92, 246, 0.25), 0 0 40px rgba(139, 92, 246, 0.08);
|
|
2921
|
+
}
|
|
2922
|
+
.run-info-item:last-child:hover {
|
|
2923
|
+
background: linear-gradient(135deg, rgba(139, 92, 246, 0.25) 0%, rgba(124, 58, 237, 0.18) 50%, rgba(109, 40, 217, 0.12) 100%);
|
|
2924
|
+
border-color: rgba(139, 92, 246, 0.4);
|
|
2925
|
+
box-shadow: 0 8px 24px rgba(139, 92, 246, 0.3), inset 0 1px 0 rgba(139, 92, 246, 0.35), 0 0 50px rgba(139, 92, 246, 0.15);
|
|
2784
2926
|
}
|
|
2785
2927
|
.run-info strong {
|
|
2786
2928
|
display: flex;
|
|
2787
2929
|
align-items: center;
|
|
2788
|
-
gap:
|
|
2930
|
+
gap: 8px;
|
|
2789
2931
|
font-size: 0.7em;
|
|
2790
2932
|
text-transform: uppercase;
|
|
2791
|
-
letter-spacing:
|
|
2792
|
-
color: #
|
|
2933
|
+
letter-spacing: 1.2px;
|
|
2934
|
+
color: #9ca3af;
|
|
2793
2935
|
margin: 0;
|
|
2794
2936
|
font-weight: 700;
|
|
2795
2937
|
}
|
|
2796
2938
|
.run-info strong::before {
|
|
2797
2939
|
content: '';
|
|
2798
|
-
width:
|
|
2799
|
-
height:
|
|
2940
|
+
width: 10px;
|
|
2941
|
+
height: 10px;
|
|
2800
2942
|
border-radius: 50%;
|
|
2801
2943
|
background: currentColor;
|
|
2802
|
-
opacity: 0.
|
|
2944
|
+
opacity: 0.7;
|
|
2945
|
+
box-shadow: 0 0 8px currentColor;
|
|
2803
2946
|
}
|
|
2804
2947
|
.run-info-item:first-child strong {
|
|
2805
|
-
color:
|
|
2948
|
+
color: var(--warning-light);
|
|
2806
2949
|
}
|
|
2807
2950
|
.run-info-item:last-child strong {
|
|
2808
|
-
color:
|
|
2951
|
+
color: var(--secondary-light);
|
|
2809
2952
|
}
|
|
2810
2953
|
.run-info span {
|
|
2954
|
+
font-size: 1.5em;
|
|
2811
2955
|
font-weight: 800;
|
|
2812
|
-
color: #0f172a;
|
|
2956
|
+
color: #0f172a; /* Adjusted for light theme consistency, static uses #f9fafb */
|
|
2813
2957
|
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
|
|
2814
2958
|
letter-spacing: -0.02em;
|
|
2815
2959
|
line-height: 1.2;
|
|
@@ -2873,12 +3017,17 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2873
3017
|
}
|
|
2874
3018
|
}
|
|
2875
3019
|
|
|
3020
|
+
|
|
3021
|
+
.stat-pill.flaky { color: #4b5563; }
|
|
3022
|
+
|
|
2876
3023
|
.dashboard-grid {
|
|
2877
3024
|
display: grid;
|
|
2878
3025
|
grid-template-columns: repeat(4, 1fr);
|
|
2879
3026
|
gap: 0;
|
|
2880
3027
|
margin: 0 0 40px 0;
|
|
2881
3028
|
}
|
|
3029
|
+
.stats-pill.failed { color: var(--danger-dark); }
|
|
3030
|
+
.stats-pill.flaky { color: #4b5563; }
|
|
2882
3031
|
.browser-breakdown {
|
|
2883
3032
|
display: flex;
|
|
2884
3033
|
flex-direction: column;
|
|
@@ -2919,9 +3068,17 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2919
3068
|
color: #0f172a;
|
|
2920
3069
|
text-transform: capitalize;
|
|
2921
3070
|
font-size: 1.05em;
|
|
3071
|
+
white-space: nowrap;
|
|
3072
|
+
overflow: hidden;
|
|
3073
|
+
text-overflow: ellipsis;
|
|
3074
|
+
flex: 1;
|
|
3075
|
+
min-width: 0;
|
|
3076
|
+
margin-right: 8px;
|
|
2922
3077
|
}
|
|
2923
3078
|
.browser-stats {
|
|
2924
3079
|
color: #64748b;
|
|
3080
|
+
white-space: nowrap;
|
|
3081
|
+
flex-shrink: 0;
|
|
2925
3082
|
font-weight: 700;
|
|
2926
3083
|
font-size: 0.95em;
|
|
2927
3084
|
}
|
|
@@ -2965,9 +3122,11 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2965
3122
|
align-items: flex-start;
|
|
2966
3123
|
}
|
|
2967
3124
|
.run-info {
|
|
3125
|
+
flex-direction: column;
|
|
3126
|
+
gap: 0;
|
|
2968
3127
|
width: 100%;
|
|
2969
|
-
|
|
2970
|
-
|
|
3128
|
+
border-radius: 14px;
|
|
3129
|
+
overflow: hidden;
|
|
2971
3130
|
}
|
|
2972
3131
|
.dashboard-grid {
|
|
2973
3132
|
grid-template-columns: repeat(2, 1fr);
|
|
@@ -2976,11 +3135,23 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2976
3135
|
.summary-card:nth-child(n+7) { border-bottom: none; }
|
|
2977
3136
|
.filters {
|
|
2978
3137
|
padding: 24px;
|
|
2979
|
-
flex-
|
|
3138
|
+
flex-wrap: wrap;
|
|
3139
|
+
gap: 12px;
|
|
3140
|
+
}
|
|
3141
|
+
.filters input {
|
|
3142
|
+
flex: 1 1 auto;
|
|
3143
|
+
min-width: 0;
|
|
3144
|
+
width: auto;
|
|
3145
|
+
}
|
|
3146
|
+
.filters select {
|
|
3147
|
+
flex: 0 0 auto;
|
|
3148
|
+
min-width: 0;
|
|
3149
|
+
width: auto;
|
|
3150
|
+
}
|
|
3151
|
+
.filters button {
|
|
3152
|
+
width: auto;
|
|
3153
|
+
flex: 0 0 auto;
|
|
2980
3154
|
}
|
|
2981
|
-
.filters input { min-width: 100%; }
|
|
2982
|
-
.filters select { min-width: 100%; }
|
|
2983
|
-
.filters button { width: 100%; }
|
|
2984
3155
|
.copy-btn {
|
|
2985
3156
|
font-size: 0.75em;
|
|
2986
3157
|
padding: 8px 16px;
|
|
@@ -3179,16 +3350,13 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3179
3350
|
display: none;
|
|
3180
3351
|
}
|
|
3181
3352
|
.run-info-item:not(:last-child) {
|
|
3182
|
-
border-bottom: 1px solid var(--
|
|
3353
|
+
border-bottom: 1px solid var(--border-medium);
|
|
3183
3354
|
}
|
|
3184
|
-
.run-info strong {
|
|
3185
|
-
font-size: 0.65em;
|
|
3355
|
+
.run-info strong {
|
|
3356
|
+
font-size: 0.65em;
|
|
3186
3357
|
}
|
|
3187
|
-
.run-info span {
|
|
3188
|
-
font-size: 1.1em;
|
|
3189
|
-
white-space: normal;
|
|
3190
|
-
word-break: break-word;
|
|
3191
|
-
overflow-wrap: break-word;
|
|
3358
|
+
.run-info span {
|
|
3359
|
+
font-size: 1.1em;
|
|
3192
3360
|
}
|
|
3193
3361
|
.tabs {
|
|
3194
3362
|
flex-wrap: wrap;
|
|
@@ -3304,12 +3472,19 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3304
3472
|
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.2);
|
|
3305
3473
|
}
|
|
3306
3474
|
.summary-card.status-failed .value { color: #ef4444; }
|
|
3475
|
+
.summary-card.status-flaky::before { background: var(--neutral-500); }
|
|
3307
3476
|
.summary-card.status-skipped { background: rgba(245, 158, 11, 0.02); }
|
|
3308
3477
|
.summary-card.status-skipped:hover {
|
|
3309
3478
|
background: rgba(245, 158, 11, 0.15);
|
|
3310
3479
|
box-shadow: 0 4px 12px rgba(245, 158, 11, 0.2);
|
|
3311
3480
|
}
|
|
3312
3481
|
.summary-card.status-skipped .value { color: #f59e0b; }
|
|
3482
|
+
.summary-card.flaky-status { background: rgba(156, 163, 175, 0.05); }
|
|
3483
|
+
.summary-card.flaky-status:hover {
|
|
3484
|
+
background: rgba(156, 163, 175, 0.15);
|
|
3485
|
+
box-shadow: 0 4px 12px rgba(156, 163, 175, 0.2);
|
|
3486
|
+
}
|
|
3487
|
+
.summary-card.flaky-status .value { color: #64748b; }
|
|
3313
3488
|
.summary-card:not([class*='status-']) .value { color: #0f172a; }
|
|
3314
3489
|
.dashboard-bottom-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); gap: 28px; align-items: start; }
|
|
3315
3490
|
.dashboard-column {
|
|
@@ -3350,59 +3525,167 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3350
3525
|
.status-badge-small-tooltip.status-failed { background-color: var(--danger-color); }
|
|
3351
3526
|
.status-badge-small-tooltip.status-skipped { background-color: var(--warning-color); }
|
|
3352
3527
|
.status-badge-small-tooltip.status-unknown { background-color: var(--dark-gray-color); }
|
|
3353
|
-
.suites-header {
|
|
3528
|
+
.suites-header {
|
|
3529
|
+
flex-shrink: 0;
|
|
3530
|
+
display: flex;
|
|
3531
|
+
justify-content: space-between;
|
|
3532
|
+
align-items: center;
|
|
3533
|
+
margin-bottom: 20px;
|
|
3534
|
+
}
|
|
3354
3535
|
.summary-badge { background-color: var(--light-gray-color); color: var(--text-color-secondary); padding: 7px 14px; border-radius: 16px; font-size: 0.9em; }
|
|
3355
3536
|
.suites-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 20px; }
|
|
3356
|
-
.
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
padding: 24px;
|
|
3360
|
-
background: cornsilk;
|
|
3361
|
-
transition: all 0.15s ease;
|
|
3362
|
-
border-radius: 10px;
|
|
3537
|
+
.suites-widget {
|
|
3538
|
+
display: flex;
|
|
3539
|
+
flex-direction: column;
|
|
3363
3540
|
}
|
|
3364
|
-
.
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3541
|
+
.fixed-height-widget {
|
|
3542
|
+
height: 450px;
|
|
3543
|
+
}
|
|
3544
|
+
.suites-grid-container {
|
|
3545
|
+
flex-grow: 1;
|
|
3546
|
+
overflow-y: auto;
|
|
3547
|
+
padding-right: 5px;
|
|
3548
|
+
}
|
|
3549
|
+
|
|
3550
|
+
@media (max-width: 768px) {
|
|
3551
|
+
.fixed-height-widget {
|
|
3552
|
+
height: auto;
|
|
3553
|
+
max-height: 600px;
|
|
3554
|
+
}
|
|
3555
|
+
}
|
|
3556
|
+
.suite-card {
|
|
3557
|
+
background: #ffffff;
|
|
3558
|
+
border: 1px solid var(--border-light);
|
|
3559
|
+
border-radius: 16px;
|
|
3560
|
+
padding: 24px;
|
|
3561
|
+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05), 0 2px 4px -1px rgba(0, 0, 0, 0.03);
|
|
3562
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
3563
|
+
display: flex;
|
|
3564
|
+
flex-direction: column;
|
|
3565
|
+
height: 100%;
|
|
3566
|
+
position: relative;
|
|
3567
|
+
overflow: hidden;
|
|
3568
|
+
}
|
|
3569
|
+
.suite-card:hover {
|
|
3570
|
+
transform: translateY(-4px);
|
|
3571
|
+
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
|
3572
|
+
border-color: var(--primary-light);
|
|
3573
|
+
}
|
|
3574
|
+
.suite-card::before {
|
|
3575
|
+
content: '';
|
|
3576
|
+
position: absolute;
|
|
3577
|
+
top: 0;
|
|
3578
|
+
left: 0;
|
|
3579
|
+
width: 100%;
|
|
3580
|
+
height: 4px;
|
|
3581
|
+
background: var(--neutral-200);
|
|
3582
|
+
opacity: 0.8;
|
|
3583
|
+
transition: background 0.3s ease;
|
|
3584
|
+
}
|
|
3585
|
+
.suite-card.status-passed::before { background: var(--success-color); }
|
|
3586
|
+
.suite-card.status-failed::before { background: var(--danger-color); }
|
|
3587
|
+
.suite-card.status-flaky::before { background: var(--neutral-500); }
|
|
3588
|
+
.suite-card.status-skipped::before { background: var(--warning-color); }
|
|
3589
|
+
|
|
3590
|
+
/* Outcome Badge */
|
|
3591
|
+
.outcome-badge {
|
|
3592
|
+
background-color: var(--secondary-color);
|
|
3593
|
+
color: #fff;
|
|
3594
|
+
padding: 2px 8px;
|
|
3595
|
+
border-radius: 4px;
|
|
3596
|
+
font-size: 0.75em;
|
|
3597
|
+
font-weight: 700;
|
|
3598
|
+
text-transform: uppercase;
|
|
3599
|
+
margin-right: 8px;
|
|
3600
|
+
letter-spacing: 0.5px;
|
|
3601
|
+
}
|
|
3602
|
+
.outcome-badge.flaky {
|
|
3603
|
+
background-color: #eab308; /* Yellow-500 */
|
|
3604
|
+
color: #000;
|
|
3605
|
+
}
|
|
3606
|
+
|
|
3607
|
+
.suite-card-header {
|
|
3608
|
+
display: flex;
|
|
3609
|
+
justify-content: space-between;
|
|
3610
|
+
align-items: flex-start;
|
|
3611
|
+
margin-bottom: 16px;
|
|
3612
|
+
}
|
|
3613
|
+
.suite-name {
|
|
3614
|
+
font-size: 1.15em;
|
|
3615
|
+
font-weight: 700;
|
|
3616
|
+
color: var(--text-primary);
|
|
3617
|
+
line-height: 1.4;
|
|
3618
|
+
display: -webkit-box;
|
|
3619
|
+
-webkit-line-clamp: 2;
|
|
3620
|
+
-webkit-box-orient: vertical;
|
|
3621
|
+
overflow: hidden;
|
|
3622
|
+
margin-right: 12px;
|
|
3623
|
+
}
|
|
3624
|
+
.status-indicator-dot {
|
|
3625
|
+
width: 10px;
|
|
3626
|
+
height: 10px;
|
|
3627
|
+
border-radius: 50%;
|
|
3628
|
+
flex-shrink: 0;
|
|
3629
|
+
margin-top: 6px;
|
|
3630
|
+
}
|
|
3631
|
+
.status-indicator-dot.status-passed { background-color: var(--success-color); box-shadow: 0 0 0 4px rgba(16, 185, 129, 0.15); }
|
|
3632
|
+
.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: var(--neutral-500); box-shadow: 0 0 0 4px rgba(107, 114, 128, 0.15); }
|
|
3634
|
+
.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: #C0C0C0; color: #000000; border: 1px solid #A0A0A0; }
|
|
3636
|
+
|
|
3376
3637
|
.browser-tag {
|
|
3638
|
+
font-size: 0.8em;
|
|
3639
|
+
font-weight: 600;
|
|
3640
|
+
background: var(--bg-secondary);
|
|
3641
|
+
color: var(--text-secondary);
|
|
3642
|
+
padding: 4px 10px;
|
|
3643
|
+
border-radius: 20px;
|
|
3644
|
+
border: 1px solid var(--border-light);
|
|
3645
|
+
display: inline-flex;
|
|
3646
|
+
align-items: center;
|
|
3647
|
+
gap: 6px;
|
|
3648
|
+
margin-bottom: 20px;
|
|
3649
|
+
align-self: flex-start;
|
|
3650
|
+
box-shadow: none;
|
|
3651
|
+
text-shadow: none;
|
|
3652
|
+
}
|
|
3653
|
+
|
|
3654
|
+
.suite-card-body {
|
|
3655
|
+
margin-top: auto;
|
|
3656
|
+
}
|
|
3657
|
+
|
|
3658
|
+
.test-count-label {
|
|
3377
3659
|
font-size: 0.85em;
|
|
3378
3660
|
font-weight: 600;
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
display:
|
|
3384
|
-
box-shadow: 0 2px 8px rgba(96, 165, 250, 0.15), inset 0 1px 0 rgba(96, 165, 250, 0.2);
|
|
3385
|
-
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
|
|
3386
|
-
letter-spacing: 0.3px;
|
|
3387
|
-
max-width: 200px;
|
|
3388
|
-
overflow: hidden;
|
|
3389
|
-
text-overflow: ellipsis;
|
|
3390
|
-
white-space: nowrap;
|
|
3391
|
-
vertical-align: middle;
|
|
3392
|
-
cursor: help;
|
|
3393
|
-
transition: all 0.2s ease;
|
|
3661
|
+
color: var(--text-tertiary);
|
|
3662
|
+
text-transform: uppercase;
|
|
3663
|
+
letter-spacing: 0.05em;
|
|
3664
|
+
margin-bottom: 8px;
|
|
3665
|
+
display: block;
|
|
3394
3666
|
}
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
.
|
|
3667
|
+
|
|
3668
|
+
.suite-stats {
|
|
3669
|
+
display: flex;
|
|
3670
|
+
gap: 8px;
|
|
3671
|
+
background: var(--bg-secondary);
|
|
3672
|
+
padding: 10px 14px;
|
|
3673
|
+
border-radius: 10px;
|
|
3674
|
+
justify-content: space-between;
|
|
3675
|
+
}
|
|
3676
|
+
|
|
3677
|
+
.stat-pill {
|
|
3678
|
+
display: flex;
|
|
3679
|
+
align-items: center;
|
|
3680
|
+
gap: 6px;
|
|
3681
|
+
font-size: 0.9em;
|
|
3682
|
+
font-weight: 600;
|
|
3683
|
+
}
|
|
3684
|
+
.stat-pill svg { width: 14px; height: 14px; }
|
|
3685
|
+
.stat-pill.passed { color: var(--success-dark); }
|
|
3686
|
+
.stat-pill.failed { color: var(--danger-dark); }
|
|
3687
|
+
.stat-pill.flaky { color: #4b5563; }
|
|
3688
|
+
.stat-pill.skipped { color: var(--warning-dark); }
|
|
3406
3689
|
.filters {
|
|
3407
3690
|
display: flex;
|
|
3408
3691
|
flex-wrap: wrap;
|
|
@@ -3434,6 +3717,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3434
3717
|
min-width: 180px;
|
|
3435
3718
|
background: white;
|
|
3436
3719
|
cursor: pointer;
|
|
3720
|
+
width: 100%;
|
|
3437
3721
|
}
|
|
3438
3722
|
.filters select:focus {
|
|
3439
3723
|
outline: none;
|
|
@@ -3627,6 +3911,65 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3627
3911
|
border-color: rgba(148, 163, 184, 0.25);
|
|
3628
3912
|
}
|
|
3629
3913
|
|
|
3914
|
+
/* --- RETRY COUNT BADGE --- */
|
|
3915
|
+
.retry-badge {
|
|
3916
|
+
display: inline-flex;
|
|
3917
|
+
align-items: center;
|
|
3918
|
+
padding: 5px 12px;
|
|
3919
|
+
border-radius: 12px;
|
|
3920
|
+
font-size: 0.75rem;
|
|
3921
|
+
font-weight: 600;
|
|
3922
|
+
background: rgba(147, 51, 234, 0.15);
|
|
3923
|
+
color: #a855f7;
|
|
3924
|
+
border: 1px solid rgba(147, 51, 234, 0.3);
|
|
3925
|
+
margin-left: 8px;
|
|
3926
|
+
}
|
|
3927
|
+
|
|
3928
|
+
/* --- RETRY TABS --- */
|
|
3929
|
+
.retry-tabs-container {
|
|
3930
|
+
margin-top: 16px;
|
|
3931
|
+
}
|
|
3932
|
+
|
|
3933
|
+
.retry-tabs-header {
|
|
3934
|
+
display: flex;
|
|
3935
|
+
gap: 8px;
|
|
3936
|
+
border-bottom: 2px solid var(--border-medium);
|
|
3937
|
+
margin-bottom: 20px;
|
|
3938
|
+
flex-wrap: wrap;
|
|
3939
|
+
}
|
|
3940
|
+
|
|
3941
|
+
.retry-tab {
|
|
3942
|
+
padding: 10px 20px;
|
|
3943
|
+
background: transparent;
|
|
3944
|
+
border: none;
|
|
3945
|
+
border-bottom: 3px solid transparent;
|
|
3946
|
+
cursor: pointer;
|
|
3947
|
+
font-size: 0.95rem;
|
|
3948
|
+
font-weight: 600;
|
|
3949
|
+
color: var(--text-color-secondary);
|
|
3950
|
+
transition: all 0.2s ease;
|
|
3951
|
+
}
|
|
3952
|
+
|
|
3953
|
+
.retry-tab:hover {
|
|
3954
|
+
color: var(--primary-color);
|
|
3955
|
+
background: rgba(147, 51, 234, 0.05);
|
|
3956
|
+
}
|
|
3957
|
+
|
|
3958
|
+
.retry-tab.active {
|
|
3959
|
+
color: #a855f7;
|
|
3960
|
+
border-bottom-color: #a855f7;
|
|
3961
|
+
background: rgba(147, 51, 234, 0.1);
|
|
3962
|
+
}
|
|
3963
|
+
|
|
3964
|
+
.retry-tab-content {
|
|
3965
|
+
animation: fadeIn 0.3s ease-in;
|
|
3966
|
+
}
|
|
3967
|
+
|
|
3968
|
+
@keyframes fadeIn {
|
|
3969
|
+
from { opacity: 0; }
|
|
3970
|
+
to { opacity: 1; }
|
|
3971
|
+
}
|
|
3972
|
+
|
|
3630
3973
|
.tag {
|
|
3631
3974
|
display: inline-flex;
|
|
3632
3975
|
align-items: center;
|
|
@@ -3657,7 +4000,16 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3657
4000
|
}
|
|
3658
4001
|
.test-case-content h4 { margin-top: 22px; margin-bottom: 14px; font-size: 1.15em; color: var(--primary-color); }
|
|
3659
4002
|
.test-case-content p { margin-bottom: 10px; font-size: 1em; }
|
|
3660
|
-
.test-error-summary {
|
|
4003
|
+
.test-error-summary {
|
|
4004
|
+
margin-bottom: 20px;
|
|
4005
|
+
padding: 14px;
|
|
4006
|
+
background-color: rgba(244,67,54,0.05);
|
|
4007
|
+
border: 1px solid rgba(244,67,54,0.2);
|
|
4008
|
+
border-left: 4px solid var(--danger-color);
|
|
4009
|
+
border-radius: 4px;
|
|
4010
|
+
display: flex;
|
|
4011
|
+
flex-direction: column;
|
|
4012
|
+
}
|
|
3661
4013
|
.test-error-summary h4 { color: var(--danger-color); margin-top:0;}
|
|
3662
4014
|
.test-error-summary pre { white-space: pre-wrap; word-break: break-all; color: var(--danger-color); font-size: 0.95em;}
|
|
3663
4015
|
.steps-list { margin: 18px 0; }
|
|
@@ -3810,6 +4162,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3810
4162
|
color: var(--text-color);
|
|
3811
4163
|
pointer-events: auto;
|
|
3812
4164
|
cursor: pointer;
|
|
4165
|
+
width: 100%;
|
|
3813
4166
|
}
|
|
3814
4167
|
.filters button.clear-filters-btn:active,
|
|
3815
4168
|
.filters button.clear-filters-btn:focus {
|
|
@@ -4205,31 +4558,32 @@ function generateHTML(reportData, trendData = null) {
|
|
|
4205
4558
|
<div class="summary-card status-skipped"><h3>Skipped</h3><div class="value">${
|
|
4206
4559
|
runSummary.skipped || 0
|
|
4207
4560
|
}</div><div class="trend-percentage">${skipPercentage}%</div></div>
|
|
4208
|
-
<div class="summary-card"><h3>
|
|
4561
|
+
<div class="summary-card flaky-status"><h3>Flaky</h3><div class="value">${runSummary.flaky || 0}</div>
|
|
4562
|
+
<div class="trend-percentage">${flakyPercentage}%</div></div>
|
|
4209
4563
|
<div class="summary-card"><h3>Run Duration</h3><div class="value">${formatDuration(
|
|
4210
4564
|
runSummary.duration,
|
|
4211
4565
|
)}</div></div>
|
|
4212
4566
|
<div class="summary-card">
|
|
4213
|
-
<h3
|
|
4567
|
+
<h3>Total Retry Count</h3>
|
|
4214
4568
|
<div class="value">${totalRetried}</div>
|
|
4215
4569
|
</div>
|
|
4216
4570
|
<div class="summary-card">
|
|
4217
4571
|
<h3>🌐 Browser Distribution <span style="font-size: 0.7em; color: var(--text-color-secondary); font-weight: 400;">(${browserBreakdown.length} total)</span></h3>
|
|
4218
4572
|
<div class="browser-breakdown" style="max-height: 200px; overflow-y: auto; padding-right: 4px;">
|
|
4219
4573
|
${browserBreakdown
|
|
4220
|
-
.slice(0,
|
|
4574
|
+
.slice(0, 3)
|
|
4221
4575
|
.map(
|
|
4222
4576
|
(b) =>
|
|
4223
4577
|
`<div class="browser-item">
|
|
4224
|
-
<span class="browser-name">${sanitizeHTML(b.browser)}</span>
|
|
4578
|
+
<span class="browser-name" title="${sanitizeHTML(b.browser)}">${sanitizeHTML(b.browser)}</span>
|
|
4225
4579
|
<span class="browser-stats">${b.percentage}% (${b.count})</span>
|
|
4226
4580
|
</div>`,
|
|
4227
4581
|
)
|
|
4228
4582
|
.join("")}
|
|
4229
4583
|
${
|
|
4230
|
-
browserBreakdown.length >
|
|
4584
|
+
browserBreakdown.length > 3
|
|
4231
4585
|
? `<div class="browser-item" style="opacity: 0.6; font-style: italic; justify-content: center; border-top: 1px solid #e2e8f0; margin-top: 8px; padding-top: 8px;">
|
|
4232
|
-
<span>+${browserBreakdown.length -
|
|
4586
|
+
<span>+${browserBreakdown.length - 3} more browsers</span>
|
|
4233
4587
|
</div>`
|
|
4234
4588
|
: ""
|
|
4235
4589
|
}
|
|
@@ -4242,6 +4596,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
4242
4596
|
[
|
|
4243
4597
|
{ label: "Passed", value: runSummary.passed },
|
|
4244
4598
|
{ label: "Failed", value: runSummary.failed },
|
|
4599
|
+
{ label: "Flaky", value: runSummary.flaky || 0 },
|
|
4245
4600
|
{ label: "Skipped", value: runSummary.skipped || 0 },
|
|
4246
4601
|
],
|
|
4247
4602
|
400,
|
|
@@ -4259,7 +4614,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
4259
4614
|
<div id="test-runs" class="tab-content">
|
|
4260
4615
|
<div class="filters">
|
|
4261
4616
|
<input type="text" id="filter-name" placeholder="Filter by test name/path..." style="border-color: black; border-style: outset;">
|
|
4262
|
-
<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>
|
|
4617
|
+
<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>
|
|
4263
4618
|
<select id="filter-browser"><option value="">All Browsers</option>${Array.from(
|
|
4264
4619
|
new Set(
|
|
4265
4620
|
(results || []).map((test) => test.browser || "unknown"),
|
|
@@ -4356,6 +4711,33 @@ function generateHTML(reportData, trendData = null) {
|
|
|
4356
4711
|
});
|
|
4357
4712
|
}
|
|
4358
4713
|
|
|
4714
|
+
// --- Retry Tab Switching Function ---
|
|
4715
|
+
function switchRetryTab(event, tabId) {
|
|
4716
|
+
const tabButton = event.currentTarget;
|
|
4717
|
+
const tabsContainer = tabButton.closest('.retry-tabs-container');
|
|
4718
|
+
|
|
4719
|
+
// Hide all tab contents in this container
|
|
4720
|
+
const allTabContents = tabsContainer.querySelectorAll('.retry-tab-content');
|
|
4721
|
+
allTabContents.forEach(content => {
|
|
4722
|
+
content.style.display = 'none';
|
|
4723
|
+
content.classList.remove('active');
|
|
4724
|
+
});
|
|
4725
|
+
|
|
4726
|
+
// Remove active class from all tabs
|
|
4727
|
+
const allTabs = tabsContainer.querySelectorAll('.retry-tab');
|
|
4728
|
+
allTabs.forEach(tab => tab.classList.remove('active'));
|
|
4729
|
+
|
|
4730
|
+
// Show selected tab content
|
|
4731
|
+
const selectedContent = document.getElementById(tabId);
|
|
4732
|
+
if (selectedContent) {
|
|
4733
|
+
selectedContent.style.display = 'block';
|
|
4734
|
+
selectedContent.classList.add('active');
|
|
4735
|
+
}
|
|
4736
|
+
|
|
4737
|
+
// Add active class to clicked tab
|
|
4738
|
+
tabButton.classList.add('active');
|
|
4739
|
+
}
|
|
4740
|
+
|
|
4359
4741
|
// --- AI Failure Analyzer Functions ---
|
|
4360
4742
|
function getAIFix(button) {
|
|
4361
4743
|
const failureItem = button.closest('.compact-failure-item');
|
|
@@ -4990,6 +5372,7 @@ async function main() {
|
|
|
4990
5372
|
passed: histRunReport.run.passed,
|
|
4991
5373
|
failed: histRunReport.run.failed,
|
|
4992
5374
|
skipped: histRunReport.run.skipped || 0,
|
|
5375
|
+
flaky: histRunReport.run.flaky || (histRunReport.results ? histRunReport.results.filter(r => r.status === 'flaky' || r.outcome === 'flaky').length : 0),
|
|
4993
5376
|
});
|
|
4994
5377
|
|
|
4995
5378
|
if (histRunReport.results && Array.isArray(histRunReport.results)) {
|
|
@@ -4998,7 +5381,7 @@ async function main() {
|
|
|
4998
5381
|
(test) => ({
|
|
4999
5382
|
testName: test.name,
|
|
5000
5383
|
duration: test.duration,
|
|
5001
|
-
status: test.status,
|
|
5384
|
+
status: test.final_status || test.status,
|
|
5002
5385
|
timestamp: new Date(test.startTime),
|
|
5003
5386
|
}),
|
|
5004
5387
|
);
|