@arghajit/dummy 0.1.0-beta-25 → 0.1.0-beta-27
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/README.md +48 -53
- package/dist/reporter/playwright-pulse-reporter.js +5 -4
- package/dist/reporter/tsconfig.reporter.tsbuildinfo +1 -0
- package/dist/types/index.d.ts +2 -1
- package/package.json +1 -1
- package/scripts/generate-email-report.mjs +6 -6
- package/scripts/generate-report.mjs +322 -24
- package/scripts/generate-static-report.mjs +379 -33
|
@@ -141,7 +141,6 @@ export function ansiToHtml(text) {
|
|
|
141
141
|
}
|
|
142
142
|
return html;
|
|
143
143
|
}
|
|
144
|
-
|
|
145
144
|
function sanitizeHTML(str) {
|
|
146
145
|
if (str === null || str === undefined) return "";
|
|
147
146
|
return String(str).replace(
|
|
@@ -150,7 +149,6 @@ function sanitizeHTML(str) {
|
|
|
150
149
|
({ "&": "&", "<": "<", ">": ">", '"': '"', "'": "'" }[match] || match)
|
|
151
150
|
);
|
|
152
151
|
}
|
|
153
|
-
|
|
154
152
|
function capitalize(str) {
|
|
155
153
|
if (!str) return "";
|
|
156
154
|
return str[0].toUpperCase() + str.slice(1).toLowerCase();
|
|
@@ -1216,6 +1214,19 @@ function getSuitesData(results) {
|
|
|
1216
1214
|
});
|
|
1217
1215
|
return Array.from(suitesMap.values());
|
|
1218
1216
|
}
|
|
1217
|
+
function getAttachmentIcon(contentType) {
|
|
1218
|
+
if (!contentType) return "📎"; // Handle undefined/null
|
|
1219
|
+
|
|
1220
|
+
const normalizedType = contentType.toLowerCase();
|
|
1221
|
+
|
|
1222
|
+
if (normalizedType.includes("pdf")) return "📄";
|
|
1223
|
+
if (normalizedType.includes("json")) return "{ }";
|
|
1224
|
+
if (/html/.test(normalizedType)) return "🌐"; // Fixed: regex for any HTML type
|
|
1225
|
+
if (normalizedType.includes("xml")) return "<>";
|
|
1226
|
+
if (normalizedType.includes("csv")) return "📊";
|
|
1227
|
+
if (normalizedType.startsWith("text/")) return "📝";
|
|
1228
|
+
return "📎";
|
|
1229
|
+
}
|
|
1219
1230
|
function generateSuitesWidget(suitesData) {
|
|
1220
1231
|
if (!suitesData || suitesData.length === 0) {
|
|
1221
1232
|
return `<div class="suites-widget"><div class="suites-header"><h2>Test Suites</h2></div><div class="no-data">No suite data available.</div></div>`;
|
|
@@ -1272,6 +1283,260 @@ function generateSuitesWidget(suitesData) {
|
|
|
1272
1283
|
</div>
|
|
1273
1284
|
</div>`;
|
|
1274
1285
|
}
|
|
1286
|
+
function generateWorkerDistributionChart(results) {
|
|
1287
|
+
if (!results || results.length === 0) {
|
|
1288
|
+
return '<div class="no-data">No test results data available to display worker distribution.</div>';
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
// 1. Sort results by startTime to ensure chronological order
|
|
1292
|
+
const sortedResults = [...results].sort((a, b) => {
|
|
1293
|
+
const timeA = a.startTime ? new Date(a.startTime).getTime() : 0;
|
|
1294
|
+
const timeB = b.startTime ? new Date(b.startTime).getTime() : 0;
|
|
1295
|
+
return timeA - timeB;
|
|
1296
|
+
});
|
|
1297
|
+
|
|
1298
|
+
const workerData = sortedResults.reduce((acc, test) => {
|
|
1299
|
+
const workerId =
|
|
1300
|
+
typeof test.workerId !== "undefined" ? test.workerId : "N/A";
|
|
1301
|
+
if (!acc[workerId]) {
|
|
1302
|
+
acc[workerId] = { passed: 0, failed: 0, skipped: 0, tests: [] };
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
const status = String(test.status).toLowerCase();
|
|
1306
|
+
if (status === "passed" || status === "failed" || status === "skipped") {
|
|
1307
|
+
acc[workerId][status]++;
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
const testTitleParts = test.name.split(" > ");
|
|
1311
|
+
const testTitle =
|
|
1312
|
+
testTitleParts[testTitleParts.length - 1] || "Unnamed Test";
|
|
1313
|
+
// Store both name and status for each test
|
|
1314
|
+
acc[workerId].tests.push({ name: testTitle, status: status });
|
|
1315
|
+
|
|
1316
|
+
return acc;
|
|
1317
|
+
}, {});
|
|
1318
|
+
|
|
1319
|
+
const workerIds = Object.keys(workerData).sort((a, b) => {
|
|
1320
|
+
if (a === "N/A") return 1;
|
|
1321
|
+
if (b === "N/A") return -1;
|
|
1322
|
+
return parseInt(a, 10) - parseInt(b, 10);
|
|
1323
|
+
});
|
|
1324
|
+
|
|
1325
|
+
if (workerIds.length === 0) {
|
|
1326
|
+
return '<div class="no-data">Could not determine worker distribution from test data.</div>';
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
const chartId = `workerDistChart-${Date.now()}-${Math.random()
|
|
1330
|
+
.toString(36)
|
|
1331
|
+
.substring(2, 7)}`;
|
|
1332
|
+
const renderFunctionName = `renderWorkerDistChart_${chartId.replace(
|
|
1333
|
+
/-/g,
|
|
1334
|
+
"_"
|
|
1335
|
+
)}`;
|
|
1336
|
+
const modalJsNamespace = `modal_funcs_${chartId.replace(/-/g, "_")}`;
|
|
1337
|
+
|
|
1338
|
+
// The categories now just need the name for the axis labels
|
|
1339
|
+
const categories = workerIds.map((id) => `Worker ${id}`);
|
|
1340
|
+
|
|
1341
|
+
// We pass the full data separately to the script
|
|
1342
|
+
const fullWorkerData = workerIds.map((id) => ({
|
|
1343
|
+
id: id,
|
|
1344
|
+
name: `Worker ${id}`,
|
|
1345
|
+
tests: workerData[id].tests,
|
|
1346
|
+
}));
|
|
1347
|
+
|
|
1348
|
+
const passedData = workerIds.map((id) => workerData[id].passed);
|
|
1349
|
+
const failedData = workerIds.map((id) => workerData[id].failed);
|
|
1350
|
+
const skippedData = workerIds.map((id) => workerData[id].skipped);
|
|
1351
|
+
|
|
1352
|
+
const categoriesString = JSON.stringify(categories);
|
|
1353
|
+
const fullDataString = JSON.stringify(fullWorkerData);
|
|
1354
|
+
const seriesString = JSON.stringify([
|
|
1355
|
+
{ name: "Passed", data: passedData, color: "var(--success-color)" },
|
|
1356
|
+
{ name: "Failed", data: failedData, color: "var(--danger-color)" },
|
|
1357
|
+
{ name: "Skipped", data: skippedData, color: "var(--warning-color)" },
|
|
1358
|
+
]);
|
|
1359
|
+
|
|
1360
|
+
// The HTML now includes the chart container, the modal, and styles for the modal
|
|
1361
|
+
return `
|
|
1362
|
+
<style>
|
|
1363
|
+
.worker-modal-overlay {
|
|
1364
|
+
position: fixed; z-index: 1050; left: 0; top: 0; width: 100%; height: 100%;
|
|
1365
|
+
overflow: auto; background-color: rgba(0,0,0,0.6);
|
|
1366
|
+
display: none; align-items: center; justify-content: center;
|
|
1367
|
+
}
|
|
1368
|
+
.worker-modal-content {
|
|
1369
|
+
background-color: #3d4043;
|
|
1370
|
+
color: var(--card-background-color);
|
|
1371
|
+
margin: auto; padding: 20px; border: 1px solid var(--border-color, #888);
|
|
1372
|
+
width: 80%; max-width: 700px; border-radius: 8px;
|
|
1373
|
+
position: relative; box-shadow: 0 5px 15px rgba(0,0,0,0.5);
|
|
1374
|
+
}
|
|
1375
|
+
.worker-modal-close {
|
|
1376
|
+
position: absolute; top: 10px; right: 20px;
|
|
1377
|
+
font-size: 28px; font-weight: bold; cursor: pointer;
|
|
1378
|
+
line-height: 1;
|
|
1379
|
+
}
|
|
1380
|
+
.worker-modal-close:hover, .worker-modal-close:focus {
|
|
1381
|
+
color: var(--text-color, #000);
|
|
1382
|
+
}
|
|
1383
|
+
#worker-modal-body-${chartId} ul {
|
|
1384
|
+
list-style-type: none; padding-left: 0; margin-top: 15px; max-height: 45vh; overflow-y: auto;
|
|
1385
|
+
}
|
|
1386
|
+
#worker-modal-body-${chartId} li {
|
|
1387
|
+
padding: 8px 5px; border-bottom: 1px solid var(--border-color, #eee);
|
|
1388
|
+
font-size: 0.9em;
|
|
1389
|
+
}
|
|
1390
|
+
#worker-modal-body-${chartId} li:last-child {
|
|
1391
|
+
border-bottom: none;
|
|
1392
|
+
}
|
|
1393
|
+
#worker-modal-body-${chartId} li > span {
|
|
1394
|
+
display: inline-block;
|
|
1395
|
+
width: 70px;
|
|
1396
|
+
font-weight: bold;
|
|
1397
|
+
text-align: right;
|
|
1398
|
+
margin-right: 10px;
|
|
1399
|
+
}
|
|
1400
|
+
</style>
|
|
1401
|
+
|
|
1402
|
+
<div id="${chartId}" class="trend-chart-container lazy-load-chart" data-render-function-name="${renderFunctionName}" style="min-height: 350px;">
|
|
1403
|
+
<div class="no-data">Loading Worker Distribution Chart...</div>
|
|
1404
|
+
</div>
|
|
1405
|
+
|
|
1406
|
+
<div id="worker-modal-${chartId}" class="worker-modal-overlay">
|
|
1407
|
+
<div class="worker-modal-content">
|
|
1408
|
+
<span class="worker-modal-close">×</span>
|
|
1409
|
+
<h3 id="worker-modal-title-${chartId}" style="text-align: center; margin-top: 0; margin-bottom: 25px; font-size: 1.25em; font-weight: 600; color: #fff"></h3>
|
|
1410
|
+
<div id="worker-modal-body-${chartId}"></div>
|
|
1411
|
+
</div>
|
|
1412
|
+
</div>
|
|
1413
|
+
|
|
1414
|
+
<script>
|
|
1415
|
+
// Namespace for modal functions to avoid global scope pollution
|
|
1416
|
+
window.${modalJsNamespace} = {};
|
|
1417
|
+
|
|
1418
|
+
window.${renderFunctionName} = function() {
|
|
1419
|
+
const chartContainer = document.getElementById('${chartId}');
|
|
1420
|
+
if (!chartContainer) { console.error("Chart container ${chartId} not found."); return; }
|
|
1421
|
+
|
|
1422
|
+
// --- Modal Setup ---
|
|
1423
|
+
const modal = document.getElementById('worker-modal-${chartId}');
|
|
1424
|
+
const modalTitle = document.getElementById('worker-modal-title-${chartId}');
|
|
1425
|
+
const modalBody = document.getElementById('worker-modal-body-${chartId}');
|
|
1426
|
+
const closeModalBtn = modal.querySelector('.worker-modal-close');
|
|
1427
|
+
|
|
1428
|
+
window.${modalJsNamespace}.open = function(worker) {
|
|
1429
|
+
if (!worker) return;
|
|
1430
|
+
modalTitle.textContent = 'Test Details for ' + worker.name;
|
|
1431
|
+
|
|
1432
|
+
let testListHtml = '<ul>';
|
|
1433
|
+
if (worker.tests && worker.tests.length > 0) {
|
|
1434
|
+
worker.tests.forEach(test => {
|
|
1435
|
+
let color = 'inherit';
|
|
1436
|
+
if (test.status === 'passed') color = 'var(--success-color)';
|
|
1437
|
+
else if (test.status === 'failed') color = 'var(--danger-color)';
|
|
1438
|
+
else if (test.status === 'skipped') color = 'var(--warning-color)';
|
|
1439
|
+
|
|
1440
|
+
const escapedName = test.name.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
1441
|
+
testListHtml += \`<li style="color: \${color};"><span style="color: \${color}">[\${test.status.toUpperCase()}]</span> \${escapedName}</li>\`;
|
|
1442
|
+
});
|
|
1443
|
+
} else {
|
|
1444
|
+
testListHtml += '<li>No detailed test data available for this worker.</li>';
|
|
1445
|
+
}
|
|
1446
|
+
testListHtml += '</ul>';
|
|
1447
|
+
|
|
1448
|
+
modalBody.innerHTML = testListHtml;
|
|
1449
|
+
modal.style.display = 'flex';
|
|
1450
|
+
};
|
|
1451
|
+
|
|
1452
|
+
const closeModal = function() {
|
|
1453
|
+
modal.style.display = 'none';
|
|
1454
|
+
};
|
|
1455
|
+
|
|
1456
|
+
closeModalBtn.onclick = closeModal;
|
|
1457
|
+
modal.onclick = function(event) {
|
|
1458
|
+
// Close if clicked on the dark overlay background
|
|
1459
|
+
if (event.target == modal) {
|
|
1460
|
+
closeModal();
|
|
1461
|
+
}
|
|
1462
|
+
};
|
|
1463
|
+
|
|
1464
|
+
|
|
1465
|
+
// --- Highcharts Setup ---
|
|
1466
|
+
if (typeof Highcharts !== 'undefined') {
|
|
1467
|
+
try {
|
|
1468
|
+
chartContainer.innerHTML = '';
|
|
1469
|
+
const fullData = ${fullDataString};
|
|
1470
|
+
|
|
1471
|
+
const chartOptions = {
|
|
1472
|
+
chart: { type: 'bar', height: 350, backgroundColor: 'transparent' },
|
|
1473
|
+
title: { text: null },
|
|
1474
|
+
xAxis: {
|
|
1475
|
+
categories: ${categoriesString},
|
|
1476
|
+
title: { text: 'Worker ID' },
|
|
1477
|
+
labels: { style: { color: 'var(--text-color-secondary)' }}
|
|
1478
|
+
},
|
|
1479
|
+
yAxis: {
|
|
1480
|
+
min: 0,
|
|
1481
|
+
title: { text: 'Number of Tests' },
|
|
1482
|
+
labels: { style: { color: 'var(--text-color-secondary)' }},
|
|
1483
|
+
stackLabels: { enabled: true, style: { fontWeight: 'bold', color: 'var(--text-color)' } }
|
|
1484
|
+
},
|
|
1485
|
+
legend: { reversed: true, itemStyle: { fontSize: "12px", color: 'var(--text-color)' } },
|
|
1486
|
+
plotOptions: {
|
|
1487
|
+
series: {
|
|
1488
|
+
stacking: 'normal',
|
|
1489
|
+
cursor: 'pointer',
|
|
1490
|
+
point: {
|
|
1491
|
+
events: {
|
|
1492
|
+
click: function () {
|
|
1493
|
+
// 'this.x' is the index of the category
|
|
1494
|
+
const workerData = fullData[this.x];
|
|
1495
|
+
window.${modalJsNamespace}.open(workerData);
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
},
|
|
1501
|
+
tooltip: {
|
|
1502
|
+
shared: true,
|
|
1503
|
+
headerFormat: '<b>{point.key}</b> (Click for details)<br/>',
|
|
1504
|
+
pointFormat: '<span style="color:{series.color}">●</span> {series.name}: <b>{point.y}</b><br/>',
|
|
1505
|
+
footerFormat: 'Total: <b>{point.total}</b>'
|
|
1506
|
+
},
|
|
1507
|
+
series: ${seriesString},
|
|
1508
|
+
credits: { enabled: false }
|
|
1509
|
+
};
|
|
1510
|
+
Highcharts.chart('${chartId}', chartOptions);
|
|
1511
|
+
} catch (e) {
|
|
1512
|
+
console.error("Error rendering chart ${chartId}:", e);
|
|
1513
|
+
chartContainer.innerHTML = '<div class="no-data">Error rendering worker distribution chart.</div>';
|
|
1514
|
+
}
|
|
1515
|
+
} else {
|
|
1516
|
+
chartContainer.innerHTML = '<div class="no-data">Charting library not available for worker distribution.</div>';
|
|
1517
|
+
}
|
|
1518
|
+
};
|
|
1519
|
+
</script>
|
|
1520
|
+
`;
|
|
1521
|
+
}
|
|
1522
|
+
const infoTooltip = `
|
|
1523
|
+
<span class="info-tooltip" style="display: inline-block; margin-left: 8px;">
|
|
1524
|
+
<span class="info-icon"
|
|
1525
|
+
style="cursor: pointer; font-size: 1.25rem;"
|
|
1526
|
+
onclick="window.workerInfoPrompt()">ℹ️</span>
|
|
1527
|
+
</span>
|
|
1528
|
+
<script>
|
|
1529
|
+
window.workerInfoPrompt = function() {
|
|
1530
|
+
const message = 'Why is worker -1 special?\\n\\n' +
|
|
1531
|
+
'Playwright assigns skipped tests to worker -1 because:\\n' +
|
|
1532
|
+
'1. They don\\'t require browser execution\\n' +
|
|
1533
|
+
'2. This keeps real workers focused on actual tests\\n' +
|
|
1534
|
+
'3. Maintains clean reporting\\n\\n' +
|
|
1535
|
+
'This is an intentional optimization by Playwright.';
|
|
1536
|
+
alert(message);
|
|
1537
|
+
}
|
|
1538
|
+
</script>
|
|
1539
|
+
`;
|
|
1275
1540
|
function generateHTML(reportData, trendData = null) {
|
|
1276
1541
|
const { run, results } = reportData;
|
|
1277
1542
|
const suitesData = getSuitesData(reportData.results || []);
|
|
@@ -1322,13 +1587,13 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1322
1587
|
step.duration
|
|
1323
1588
|
)}</span></div><div class="step-details" style="display: none;">${
|
|
1324
1589
|
step.codeLocation
|
|
1325
|
-
? `<div class="step-info"><strong>Location:</strong> ${sanitizeHTML(
|
|
1590
|
+
? `<div class="step-info code-section"><strong>Location:</strong> ${sanitizeHTML(
|
|
1326
1591
|
step.codeLocation
|
|
1327
1592
|
)}</div>`
|
|
1328
1593
|
: ""
|
|
1329
1594
|
}${
|
|
1330
1595
|
step.errorMessage
|
|
1331
|
-
? `<div class="
|
|
1596
|
+
? `<div class="test-error-summary">${
|
|
1332
1597
|
step.stackTrace
|
|
1333
1598
|
? `<div class="stack-trace">${formatPlaywrightError(
|
|
1334
1599
|
step.stackTrace
|
|
@@ -1386,19 +1651,55 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1386
1651
|
test.errorMessage
|
|
1387
1652
|
? `<div class="test-error-summary">${formatPlaywrightError(
|
|
1388
1653
|
test.errorMessage
|
|
1389
|
-
)}<button
|
|
1654
|
+
)}<button
|
|
1655
|
+
class="copy-error-btn"
|
|
1656
|
+
onclick="copyErrorToClipboard(this)"
|
|
1657
|
+
style="
|
|
1658
|
+
margin-top: 8px;
|
|
1659
|
+
padding: 4px 8px;
|
|
1660
|
+
background: #f0f0f0;
|
|
1661
|
+
border: 2px solid #ccc;
|
|
1662
|
+
border-radius: 4px;
|
|
1663
|
+
cursor: pointer;
|
|
1664
|
+
font-size: 12px;
|
|
1665
|
+
border-color: #8B0000;
|
|
1666
|
+
color: #8B0000;
|
|
1667
|
+
"
|
|
1668
|
+
onmouseover="this.style.background='#e0e0e0'"
|
|
1669
|
+
onmouseout="this.style.background='#f0f0f0'"
|
|
1670
|
+
>
|
|
1671
|
+
Copy Error Prompt
|
|
1672
|
+
</button></div>`
|
|
1390
1673
|
: ""
|
|
1391
1674
|
}
|
|
1392
|
-
<h4>Steps</h4><div class="steps-list">${generateStepsHTML(
|
|
1393
|
-
test.steps
|
|
1394
|
-
)}</div>
|
|
1395
1675
|
${
|
|
1396
|
-
test.
|
|
1397
|
-
? `<div class="
|
|
1398
|
-
test.
|
|
1399
|
-
)}</pre></div>`
|
|
1676
|
+
test.snippet
|
|
1677
|
+
? `<div class="code-section"><h4>Error Snippet</h4><pre><code>${formatPlaywrightError(
|
|
1678
|
+
test.snippet
|
|
1679
|
+
)}</code></pre></div>`
|
|
1400
1680
|
: ""
|
|
1401
1681
|
}
|
|
1682
|
+
<h4>Steps</h4><div class="steps-list">${generateStepsHTML(
|
|
1683
|
+
test.steps
|
|
1684
|
+
)}</div>
|
|
1685
|
+
${(() => {
|
|
1686
|
+
if (!test.stdout || test.stdout.length === 0)
|
|
1687
|
+
return "";
|
|
1688
|
+
// Create a unique ID for the <pre> element to target it for copying
|
|
1689
|
+
const logId = `stdout-log-${test.id || testIndex}`;
|
|
1690
|
+
return `<div class="console-output-section">
|
|
1691
|
+
<h4>Console Output (stdout)
|
|
1692
|
+
<button class="copy-btn" onclick="copyLogContent('${logId}', this)">Copy Console</button>
|
|
1693
|
+
</h4>
|
|
1694
|
+
<div class="log-wrapper">
|
|
1695
|
+
<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(
|
|
1696
|
+
test.stdout
|
|
1697
|
+
.map((line) => sanitizeHTML(line))
|
|
1698
|
+
.join("\n")
|
|
1699
|
+
)}</pre>
|
|
1700
|
+
</div>
|
|
1701
|
+
</div>`;
|
|
1702
|
+
})()}
|
|
1402
1703
|
${
|
|
1403
1704
|
test.stderr && test.stderr.length > 0
|
|
1404
1705
|
? `<div class="console-output-section"><h4>Console Output (stderr)</h4><pre class="console-log stderr-log">${test.stderr
|
|
@@ -1505,6 +1806,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1505
1806
|
test.attachments.length === 0
|
|
1506
1807
|
)
|
|
1507
1808
|
return "";
|
|
1809
|
+
|
|
1508
1810
|
return `<div class="attachments-section"><h4>Other Attachments</h4><div class="attachments-grid">${test.attachments
|
|
1509
1811
|
.map((attachment) => {
|
|
1510
1812
|
try {
|
|
@@ -1512,27 +1814,50 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1512
1814
|
DEFAULT_OUTPUT_DIR,
|
|
1513
1815
|
attachment.path
|
|
1514
1816
|
);
|
|
1515
|
-
|
|
1817
|
+
|
|
1818
|
+
if (!fsExistsSync(attachmentPath)) {
|
|
1819
|
+
console.warn(
|
|
1820
|
+
`Attachment not found at: ${attachmentPath}`
|
|
1821
|
+
);
|
|
1516
1822
|
return `<div class="attachment-item error">Attachment not found: ${sanitizeHTML(
|
|
1517
1823
|
attachment.name
|
|
1518
1824
|
)}</div>`;
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1519
1827
|
const attachmentBase64 =
|
|
1520
1828
|
readFileSync(attachmentPath).toString(
|
|
1521
1829
|
"base64"
|
|
1522
1830
|
);
|
|
1523
1831
|
const attachmentDataUri = `data:${attachment.contentType};base64,${attachmentBase64}`;
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1832
|
+
|
|
1833
|
+
return `<div class="attachment-item generic-attachment">
|
|
1834
|
+
<div class="attachment-icon">${getAttachmentIcon(
|
|
1835
|
+
attachment.contentType
|
|
1836
|
+
)}</div>
|
|
1837
|
+
<div class="attachment-caption">
|
|
1838
|
+
<span class="attachment-name" title="${sanitizeHTML(
|
|
1839
|
+
attachment.name
|
|
1840
|
+
)}">${sanitizeHTML(
|
|
1529
1841
|
attachment.name
|
|
1530
|
-
)}</span
|
|
1531
|
-
|
|
1532
|
-
|
|
1842
|
+
)}</span>
|
|
1843
|
+
<span class="attachment-type">${sanitizeHTML(
|
|
1844
|
+
attachment.contentType
|
|
1845
|
+
)}</span>
|
|
1846
|
+
</div>
|
|
1847
|
+
<div class="attachment-info">
|
|
1848
|
+
<div class="trace-actions">
|
|
1849
|
+
<a href="${attachmentDataUri}" target="_blank" class="view-full">View</a>
|
|
1850
|
+
<a href="${attachmentDataUri}" download="${sanitizeHTML(
|
|
1533
1851
|
attachment.name
|
|
1534
|
-
)}">Download</a
|
|
1852
|
+
)}">Download</a>
|
|
1853
|
+
</div>
|
|
1854
|
+
</div>
|
|
1855
|
+
</div>`;
|
|
1535
1856
|
} catch (e) {
|
|
1857
|
+
console.error(
|
|
1858
|
+
`Failed to process attachment "${attachment.name}":`,
|
|
1859
|
+
e
|
|
1860
|
+
);
|
|
1536
1861
|
return `<div class="attachment-item error">Failed to load attachment: ${sanitizeHTML(
|
|
1537
1862
|
attachment.name
|
|
1538
1863
|
)}</div>`;
|
|
@@ -1559,8 +1884,8 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1559
1884
|
<head>
|
|
1560
1885
|
<meta charset="UTF-8">
|
|
1561
1886
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1562
|
-
<link rel="icon" type="image/png" href="https://i.postimg.cc/
|
|
1563
|
-
<link rel="apple-touch-icon" href="https://i.postimg.cc/
|
|
1887
|
+
<link rel="icon" type="image/png" href="https://i.postimg.cc/v817w4sg/logo.png">
|
|
1888
|
+
<link rel="apple-touch-icon" href="https://i.postimg.cc/v817w4sg/logo.png">
|
|
1564
1889
|
<script src="https://code.highcharts.com/highcharts.js" defer></script>
|
|
1565
1890
|
<title>Playwright Pulse Report</title>
|
|
1566
1891
|
<style>
|
|
@@ -1586,7 +1911,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1586
1911
|
.header { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; padding-bottom: 25px; border-bottom: 1px solid var(--border-color); margin-bottom: 25px; }
|
|
1587
1912
|
.header-title { display: flex; align-items: center; gap: 15px; }
|
|
1588
1913
|
.header h1 { margin: 0; font-size: 1.85em; font-weight: 600; color: var(--primary-color); }
|
|
1589
|
-
#report-logo { height: 40px; width:
|
|
1914
|
+
#report-logo { height: 40px; width: 55px; }
|
|
1590
1915
|
.run-info { font-size: 0.9em; text-align: right; color: var(--text-color-secondary); line-height:1.5;}
|
|
1591
1916
|
.run-info strong { color: var(--text-color); }
|
|
1592
1917
|
.tabs { display: flex; border-bottom: 2px solid var(--border-color); margin-bottom: 30px; overflow-x: auto; }
|
|
@@ -1665,8 +1990,8 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1665
1990
|
.step-duration { color: var(--dark-gray-color); font-size: 0.9em; }
|
|
1666
1991
|
.step-details { display: none; padding: 14px; margin-top: 8px; background: #fdfdfd; border-radius: 6px; font-size: 0.95em; border: 1px solid var(--light-gray-color); }
|
|
1667
1992
|
.step-info { margin-bottom: 8px; }
|
|
1668
|
-
.
|
|
1669
|
-
.
|
|
1993
|
+
.test-error-summary { color: var(--danger-color); margin-top: 12px; padding: 14px; background: rgba(244,67,54,0.05); border-radius: 4px; font-size: 0.95em; border-left: 3px solid var(--danger-color); }
|
|
1994
|
+
.test-error-summary pre.stack-trace { margin-top: 10px; padding: 12px; background-color: rgba(0,0,0,0.03); border-radius: 4px; font-size:0.9em; max-height: 280px; overflow-y: auto; white-space: pre-wrap; word-break: break-all; }
|
|
1670
1995
|
.step-hook { background-color: rgba(33,150,243,0.04); border-left: 3px solid var(--info-color) !important; }
|
|
1671
1996
|
.step-hook .step-title { font-style: italic; color: var(--info-color)}
|
|
1672
1997
|
.nested-steps { margin-top: 12px; }
|
|
@@ -1716,12 +2041,13 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1716
2041
|
.download-trace:hover { background: #cbd5e0; }
|
|
1717
2042
|
.filters button.clear-filters-btn { background-color: var(--medium-gray-color); color: var(--text-color); }
|
|
1718
2043
|
.filters button.clear-filters-btn:hover { background-color: var(--dark-gray-color); color: #fff; }
|
|
2044
|
+
.copy-btn {color: var(--primary-color); background: #fefefe; border-radius: 8px; cursor: pointer; border-color: var(--primary-color); font-size: 1em; margin-left: 93%; font-weight: 600;}
|
|
1719
2045
|
@media (max-width: 1200px) { .trend-charts-row { grid-template-columns: 1fr; } }
|
|
1720
2046
|
@media (max-width: 992px) { .dashboard-bottom-row { grid-template-columns: 1fr; } .pie-chart-wrapper div[id^="pieChart-"] { max-width: 350px; margin: 0 auto; } .filters input { min-width: 180px; } .filters select { min-width: 150px; } }
|
|
1721
2047
|
@media (max-width: 768px) { body { font-size: 15px; } .container { margin: 10px; padding: 20px; } .header { flex-direction: column; align-items: flex-start; gap: 15px; } .header h1 { font-size: 1.6em; } .run-info { text-align: left; font-size:0.9em; } .tabs { margin-bottom: 25px;} .tab-button { padding: 12px 20px; font-size: 1.05em;} .dashboard-grid { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 18px;} .summary-card .value {font-size: 2em;} .summary-card h3 {font-size: 0.95em;} .filters { flex-direction: column; padding: 18px; gap: 12px;} .filters input, .filters select, .filters button {width: 100%; box-sizing: border-box;} .test-case-header { flex-direction: column; align-items: flex-start; gap: 10px; padding: 14px; } .test-case-summary {gap: 10px;} .test-case-title {font-size: 1.05em;} .test-case-meta { flex-direction: row; flex-wrap: wrap; gap: 8px; margin-top: 8px;} .attachments-grid {grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 18px;} .test-history-grid {grid-template-columns: 1fr;} .pie-chart-wrapper {min-height: auto;} }
|
|
1722
|
-
@media (max-width: 480px) { body {font-size: 14px;} .container {padding: 15px;} .header h1 {font-size: 1.4em;} #report-logo { height: 35px; width:
|
|
2048
|
+
@media (max-width: 480px) { body {font-size: 14px;} .container {padding: 15px;} .header h1 {font-size: 1.4em;} #report-logo { height: 35px; width: 45px; } .tab-button {padding: 10px 15px; font-size: 1em;} .summary-card .value {font-size: 1.8em;} .attachments-grid {grid-template-columns: 1fr;} .step-item {padding-left: calc(var(--depth, 0) * 18px);} .test-case-content, .step-details {padding: 15px;} .trend-charts-row {gap: 20px;} .trend-chart {padding: 20px;} }
|
|
1723
2049
|
.trace-actions a { text-decoration: none; color: var(--primary-color); font-weight: 500; font-size: 0.9em; }
|
|
1724
|
-
.generic-attachment { text-align: center; padding: 1rem; justify-content: center;
|
|
2050
|
+
.generic-attachment { text-align: center; padding: 1rem; justify-content: center; }
|
|
1725
2051
|
.attachment-icon { font-size: 2.5rem; display: block; margin-bottom: 0.75rem; }
|
|
1726
2052
|
.attachment-caption { display: flex; flex-direction: column; align-items: center; justify-content: center; flex-grow: 1; }
|
|
1727
2053
|
.attachment-name { font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
|
|
@@ -1732,7 +2058,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1732
2058
|
<div class="container">
|
|
1733
2059
|
<header class="header">
|
|
1734
2060
|
<div class="header-title">
|
|
1735
|
-
<img id="report-logo" src="
|
|
2061
|
+
<img id="report-logo" src="https://i.postimg.cc/v817w4sg/logo.png" alt="Report Logo">
|
|
1736
2062
|
<h1>Playwright Pulse Report</h1>
|
|
1737
2063
|
</div>
|
|
1738
2064
|
<div class="run-info"><strong>Run Date:</strong> ${formatDate(
|
|
@@ -1823,6 +2149,12 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1823
2149
|
? generateDurationTrendChart(trendData)
|
|
1824
2150
|
: '<div class="no-data">Overall trend data not available for durations.</div>'
|
|
1825
2151
|
}
|
|
2152
|
+
</div>
|
|
2153
|
+
</div>
|
|
2154
|
+
<h2 class="tab-main-title">Test Distribution by Worker ${infoTooltip}</h2>
|
|
2155
|
+
<div class="trend-charts-row">
|
|
2156
|
+
<div class="trend-chart">
|
|
2157
|
+
${generateWorkerDistributionChart(results)}
|
|
1826
2158
|
</div>
|
|
1827
2159
|
</div>
|
|
1828
2160
|
<h2 class="tab-main-title">Individual Test History</h2>
|
|
@@ -1839,7 +2171,6 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1839
2171
|
</div>
|
|
1840
2172
|
<footer style="padding: 0.5rem; box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05); text-align: center; font-family: 'Segoe UI', system-ui, sans-serif;">
|
|
1841
2173
|
<div style="display: inline-flex; align-items: center; gap: 0.5rem; color: #333; font-size: 0.9rem; font-weight: 600; letter-spacing: 0.5px;">
|
|
1842
|
-
<img width="48" height="48" src="https://img.icons8.com/emoji/48/index-pointing-at-the-viewer-light-skin-tone-emoji.png" alt="index-pointing-at-the-viewer-light-skin-tone-emoji"/>
|
|
1843
2174
|
<span>Created by</span>
|
|
1844
2175
|
<a href="https://github.com/Arghajit47" target="_blank" rel="noopener noreferrer" style="color: #7737BF; font-weight: 700; font-style: italic; text-decoration: none; transition: all 0.2s ease;" onmouseover="this.style.color='#BF5C37'" onmouseout="this.style.color='#7737BF'">Arghajit Singha</a>
|
|
1845
2176
|
</div>
|
|
@@ -1854,6 +2185,21 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1854
2185
|
return (ms / 1000).toFixed(1) + "s";
|
|
1855
2186
|
}
|
|
1856
2187
|
}
|
|
2188
|
+
function copyLogContent(elementId, button) {
|
|
2189
|
+
const logElement = document.getElementById(elementId);
|
|
2190
|
+
if (!logElement) {
|
|
2191
|
+
console.error('Could not find log element with ID:', elementId);
|
|
2192
|
+
return;
|
|
2193
|
+
}
|
|
2194
|
+
navigator.clipboard.writeText(logElement.innerText).then(() => {
|
|
2195
|
+
button.textContent = 'Copied!';
|
|
2196
|
+
setTimeout(() => { button.textContent = 'Copy'; }, 2000);
|
|
2197
|
+
}).catch(err => {
|
|
2198
|
+
console.error('Failed to copy log content:', err);
|
|
2199
|
+
button.textContent = 'Failed';
|
|
2200
|
+
setTimeout(() => { button.textContent = 'Copy'; }, 2000);
|
|
2201
|
+
});
|
|
2202
|
+
}
|
|
1857
2203
|
function initializeReportInteractivity() {
|
|
1858
2204
|
const tabButtons = document.querySelectorAll('.tab-button');
|
|
1859
2205
|
const tabContents = document.querySelectorAll('.tab-content');
|
|
@@ -2007,9 +2353,9 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2007
2353
|
|
|
2008
2354
|
function copyErrorToClipboard(button) {
|
|
2009
2355
|
// 1. Find the main error container, which should always be present.
|
|
2010
|
-
const errorContainer = button.closest('.
|
|
2356
|
+
const errorContainer = button.closest('.test-error-summary');
|
|
2011
2357
|
if (!errorContainer) {
|
|
2012
|
-
console.error("Could not find '.
|
|
2358
|
+
console.error("Could not find '.test-error-summary' container. The report's HTML structure might have changed.");
|
|
2013
2359
|
return;
|
|
2014
2360
|
}
|
|
2015
2361
|
|