@arghajit/dummy 0.1.0-beta-27 → 0.1.0-beta-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/package.json +1 -1
- package/scripts/generate-report.mjs +704 -309
- package/scripts/generate-static-report.mjs +615 -444
|
@@ -615,9 +615,8 @@ function generatePieChart(data, chartWidth = 300, chartHeight = 300) {
|
|
|
615
615
|
chart: {
|
|
616
616
|
type: 'pie',
|
|
617
617
|
width: ${chartWidth},
|
|
618
|
-
height: ${
|
|
619
|
-
|
|
620
|
-
}, // Adjusted height to make space for legend if chartHeight is for the whole wrapper
|
|
618
|
+
height: ${chartHeight - 40
|
|
619
|
+
}, // Adjusted height to make space for legend if chartHeight is for the whole wrapper
|
|
621
620
|
backgroundColor: 'transparent',
|
|
622
621
|
plotShadow: false,
|
|
623
622
|
spacingBottom: 40 // Ensure space for legend
|
|
@@ -669,9 +668,8 @@ function generatePieChart(data, chartWidth = 300, chartHeight = 300) {
|
|
|
669
668
|
return `
|
|
670
669
|
<div class="pie-chart-wrapper" style="align-items: center; max-height: 450px">
|
|
671
670
|
<div style="display: flex; align-items: start; width: 100%;"><h3>Test Distribution</h3></div>
|
|
672
|
-
<div id="${chartId}" style="width: ${chartWidth}px; height: ${
|
|
673
|
-
|
|
674
|
-
}px;"></div>
|
|
671
|
+
<div id="${chartId}" style="width: ${chartWidth}px; height: ${chartHeight - 40
|
|
672
|
+
}px;"></div>
|
|
675
673
|
<script>
|
|
676
674
|
document.addEventListener('DOMContentLoaded', function() {
|
|
677
675
|
if (typeof Highcharts !== 'undefined') {
|
|
@@ -899,15 +897,14 @@ function generateEnvironmentDashboard(environment, dashboardHeight = 600) {
|
|
|
899
897
|
<span class="env-detail-value">
|
|
900
898
|
<div class="env-cpu-cores">
|
|
901
899
|
${Array.from(
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
).join("")}
|
|
900
|
+
{ length: Math.max(0, environment.cpu.cores || 0) },
|
|
901
|
+
(_, i) =>
|
|
902
|
+
`<div class="env-core-indicator ${i >=
|
|
903
|
+
(environment.cpu.cores >= 8 ? 8 : environment.cpu.cores)
|
|
904
|
+
? "inactive"
|
|
905
|
+
: ""
|
|
906
|
+
}" title="Core ${i + 1}"></div>`
|
|
907
|
+
).join("")}
|
|
911
908
|
<span>${environment.cpu.cores || "N/A"} cores</span>
|
|
912
909
|
</div>
|
|
913
910
|
</span>
|
|
@@ -927,23 +924,20 @@ function generateEnvironmentDashboard(environment, dashboardHeight = 600) {
|
|
|
927
924
|
<div class="env-card-content">
|
|
928
925
|
<div class="env-detail-row">
|
|
929
926
|
<span class="env-detail-label">OS Type</span>
|
|
930
|
-
<span class="env-detail-value">${
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
}</span>
|
|
927
|
+
<span class="env-detail-value">${environment.os.split(" ")[0] === "darwin"
|
|
928
|
+
? "darwin (macOS)"
|
|
929
|
+
: environment.os.split(" ")[0] || "Unknown"
|
|
930
|
+
}</span>
|
|
935
931
|
</div>
|
|
936
932
|
<div class="env-detail-row">
|
|
937
933
|
<span class="env-detail-label">OS Version</span>
|
|
938
|
-
<span class="env-detail-value">${
|
|
939
|
-
|
|
940
|
-
}</span>
|
|
934
|
+
<span class="env-detail-value">${environment.os.split(" ")[1] || "N/A"
|
|
935
|
+
}</span>
|
|
941
936
|
</div>
|
|
942
937
|
<div class="env-detail-row">
|
|
943
938
|
<span class="env-detail-label">Hostname</span>
|
|
944
|
-
<span class="env-detail-value" title="${environment.host}">${
|
|
945
|
-
|
|
946
|
-
}</span>
|
|
939
|
+
<span class="env-detail-value" title="${environment.host}">${environment.host
|
|
940
|
+
}</span>
|
|
947
941
|
</div>
|
|
948
942
|
</div>
|
|
949
943
|
</div>
|
|
@@ -964,11 +958,10 @@ function generateEnvironmentDashboard(environment, dashboardHeight = 600) {
|
|
|
964
958
|
</div>
|
|
965
959
|
<div class="env-detail-row">
|
|
966
960
|
<span class="env-detail-label">Working Dir</span>
|
|
967
|
-
<span class="env-detail-value" title="${environment.cwd}">${
|
|
968
|
-
environment.cwd.length > 25
|
|
961
|
+
<span class="env-detail-value" title="${environment.cwd}">${environment.cwd.length > 25
|
|
969
962
|
? "..." + environment.cwd.slice(-22)
|
|
970
963
|
: environment.cwd
|
|
971
|
-
|
|
964
|
+
}</span>
|
|
972
965
|
</div>
|
|
973
966
|
</div>
|
|
974
967
|
</div>
|
|
@@ -982,33 +975,30 @@ function generateEnvironmentDashboard(environment, dashboardHeight = 600) {
|
|
|
982
975
|
<div class="env-detail-row">
|
|
983
976
|
<span class="env-detail-label">Platform Arch</span>
|
|
984
977
|
<span class="env-detail-value">
|
|
985
|
-
<span class="env-chip ${
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
: "x86/Other"
|
|
999
|
-
}
|
|
978
|
+
<span class="env-chip ${environment.os.includes("darwin") &&
|
|
979
|
+
environment.cpu.model.toLowerCase().includes("apple")
|
|
980
|
+
? "env-chip-success"
|
|
981
|
+
: "env-chip-warning"
|
|
982
|
+
}">
|
|
983
|
+
${environment.os.includes("darwin") &&
|
|
984
|
+
environment.cpu.model.toLowerCase().includes("apple")
|
|
985
|
+
? "Apple Silicon"
|
|
986
|
+
: environment.cpu.model.toLowerCase().includes("arm") ||
|
|
987
|
+
environment.cpu.model.toLowerCase().includes("aarch64")
|
|
988
|
+
? "ARM-based"
|
|
989
|
+
: "x86/Other"
|
|
990
|
+
}
|
|
1000
991
|
</span>
|
|
1001
992
|
</span>
|
|
1002
993
|
</div>
|
|
1003
994
|
<div class="env-detail-row">
|
|
1004
995
|
<span class="env-detail-label">Memory per Core</span>
|
|
1005
|
-
<span class="env-detail-value">${
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
}</span>
|
|
996
|
+
<span class="env-detail-value">${environment.cpu.cores > 0
|
|
997
|
+
? (
|
|
998
|
+
parseFloat(environment.memory) / environment.cpu.cores
|
|
999
|
+
).toFixed(2) + " GB"
|
|
1000
|
+
: "N/A"
|
|
1001
|
+
}</span>
|
|
1012
1002
|
</div>
|
|
1013
1003
|
<div class="env-detail-row">
|
|
1014
1004
|
<span class="env-detail-label">Run Context</span>
|
|
@@ -1340,19 +1330,19 @@ function generateTestHistoryContent(trendData) {
|
|
|
1340
1330
|
|
|
1341
1331
|
<div class="test-history-grid">
|
|
1342
1332
|
${testHistory
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1333
|
+
.map((test) => {
|
|
1334
|
+
const latestRun =
|
|
1335
|
+
test.history.length > 0
|
|
1336
|
+
? test.history[test.history.length - 1]
|
|
1337
|
+
: { status: "unknown" };
|
|
1338
|
+
return `
|
|
1349
1339
|
<div class="test-history-card" data-test-name="${sanitizeHTML(
|
|
1350
|
-
|
|
1351
|
-
|
|
1340
|
+
test.testTitle.toLowerCase()
|
|
1341
|
+
)}" data-latest-status="${latestRun.status}">
|
|
1352
1342
|
<div class="test-history-header">
|
|
1353
1343
|
<p title="${sanitizeHTML(test.testTitle)}">${capitalize(
|
|
1354
|
-
|
|
1355
|
-
|
|
1344
|
+
sanitizeHTML(test.testTitle)
|
|
1345
|
+
)}</p>
|
|
1356
1346
|
<span class="status-badge ${getStatusClass(latestRun.status)}">
|
|
1357
1347
|
${String(latestRun.status).toUpperCase()}
|
|
1358
1348
|
</span>
|
|
@@ -1367,27 +1357,27 @@ function generateTestHistoryContent(trendData) {
|
|
|
1367
1357
|
<thead><tr><th>Run</th><th>Status</th><th>Duration</th><th>Date</th></tr></thead>
|
|
1368
1358
|
<tbody>
|
|
1369
1359
|
${test.history
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1360
|
+
.slice()
|
|
1361
|
+
.reverse()
|
|
1362
|
+
.map(
|
|
1363
|
+
(run) => `
|
|
1374
1364
|
<tr>
|
|
1375
1365
|
<td>${run.runId}</td>
|
|
1376
1366
|
<td><span class="status-badge-small ${getStatusClass(
|
|
1377
|
-
|
|
1378
|
-
|
|
1367
|
+
run.status
|
|
1368
|
+
)}">${String(run.status).toUpperCase()}</span></td>
|
|
1379
1369
|
<td>${formatDuration(run.duration)}</td>
|
|
1380
1370
|
<td>${formatDate(run.timestamp)}</td>
|
|
1381
1371
|
</tr>`
|
|
1382
|
-
|
|
1383
|
-
|
|
1372
|
+
)
|
|
1373
|
+
.join("")}
|
|
1384
1374
|
</tbody>
|
|
1385
1375
|
</table>
|
|
1386
1376
|
</div>
|
|
1387
1377
|
</details>
|
|
1388
1378
|
</div>`;
|
|
1389
|
-
|
|
1390
|
-
|
|
1379
|
+
})
|
|
1380
|
+
.join("")}
|
|
1391
1381
|
</div>
|
|
1392
1382
|
</div>
|
|
1393
1383
|
`;
|
|
@@ -1472,12 +1462,11 @@ function generateSuitesWidget(suitesData) {
|
|
|
1472
1462
|
<div class="suites-widget">
|
|
1473
1463
|
<div class="suites-header">
|
|
1474
1464
|
<h2>Test Suites</h2>
|
|
1475
|
-
<span class="summary-badge">${
|
|
1476
|
-
suitesData.length
|
|
1465
|
+
<span class="summary-badge">${suitesData.length
|
|
1477
1466
|
} suites • ${suitesData.reduce(
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1467
|
+
(sum, suite) => sum + suite.count,
|
|
1468
|
+
0
|
|
1469
|
+
)} tests</span>
|
|
1481
1470
|
</div>
|
|
1482
1471
|
<div class="suites-grid">
|
|
1483
1472
|
${suitesData
|
|
@@ -1490,28 +1479,24 @@ function generateSuitesWidget(suitesData) {
|
|
|
1490
1479
|
)} (${sanitizeHTML(suite.browser)})">${sanitizeHTML(suite.name)}</h3>
|
|
1491
1480
|
</div>
|
|
1492
1481
|
<div>🖥️ <span class="browser-tag">${sanitizeHTML(
|
|
1493
|
-
|
|
1494
|
-
|
|
1482
|
+
suite.browser
|
|
1483
|
+
)}</span></div>
|
|
1495
1484
|
<div class="suite-card-body">
|
|
1496
|
-
<span class="test-count">${suite.count} test${
|
|
1497
|
-
|
|
1498
|
-
}</span>
|
|
1485
|
+
<span class="test-count">${suite.count} test${suite.count !== 1 ? "s" : ""
|
|
1486
|
+
}</span>
|
|
1499
1487
|
<div class="suite-stats">
|
|
1500
|
-
${
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
${
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
}
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
? `<span class="stat-skipped" title="Skipped"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" class="bi bi-exclamation-triangle-fill" 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> ${suite.skipped}</span>`
|
|
1513
|
-
: ""
|
|
1514
|
-
}
|
|
1488
|
+
${suite.passed > 0
|
|
1489
|
+
? `<span class="stat-passed" title="Passed"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" class="bi bi-check-circle-fill" 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> ${suite.passed}</span>`
|
|
1490
|
+
: ""
|
|
1491
|
+
}
|
|
1492
|
+
${suite.failed > 0
|
|
1493
|
+
? `<span class="stat-failed" title="Failed"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" class="bi bi-x-circle-fill" 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> ${suite.failed}</span>`
|
|
1494
|
+
: ""
|
|
1495
|
+
}
|
|
1496
|
+
${suite.skipped > 0
|
|
1497
|
+
? `<span class="stat-skipped" title="Skipped"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" class="bi bi-exclamation-triangle-fill" 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> ${suite.skipped}</span>`
|
|
1498
|
+
: ""
|
|
1499
|
+
}
|
|
1515
1500
|
</div>
|
|
1516
1501
|
</div>
|
|
1517
1502
|
</div>`
|
|
@@ -1533,6 +1518,91 @@ function getAttachmentIcon(contentType) {
|
|
|
1533
1518
|
if (normalizedType.startsWith("text/")) return "📝";
|
|
1534
1519
|
return "📎";
|
|
1535
1520
|
}
|
|
1521
|
+
function generateAIFailureAnalyzerTab(results) {
|
|
1522
|
+
const failedTests = (results || []).filter(test => test.status === 'failed');
|
|
1523
|
+
|
|
1524
|
+
if (failedTests.length === 0) {
|
|
1525
|
+
return `
|
|
1526
|
+
<h2 class="tab-main-title">AI Failure Analysis</h2>
|
|
1527
|
+
<div class="no-data">Congratulations! No failed tests in this run.</div>
|
|
1528
|
+
`;
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
// btoa is not available in Node.js environment, so we define a simple polyfill for it.
|
|
1532
|
+
const btoa = (str) => Buffer.from(str).toString('base64');
|
|
1533
|
+
|
|
1534
|
+
return `
|
|
1535
|
+
<h2 class="tab-main-title">AI Failure Analysis</h2>
|
|
1536
|
+
<div class="ai-analyzer-stats">
|
|
1537
|
+
<div class="stat-item">
|
|
1538
|
+
<span class="stat-number">${failedTests.length}</span>
|
|
1539
|
+
<span class="stat-label">Failed Tests</span>
|
|
1540
|
+
</div>
|
|
1541
|
+
<div class="stat-item">
|
|
1542
|
+
<span class="stat-number">${new Set(failedTests.map(t => t.browser)).size}</span>
|
|
1543
|
+
<span class="stat-label">Browsers</span>
|
|
1544
|
+
</div>
|
|
1545
|
+
<div class="stat-item">
|
|
1546
|
+
<span class="stat-number">${(Math.round(failedTests.reduce((sum, test) => sum + (test.duration || 0), 0) / 1000))}s</span>
|
|
1547
|
+
<span class="stat-label">Total Duration</span>
|
|
1548
|
+
</div>
|
|
1549
|
+
</div>
|
|
1550
|
+
<p class="ai-analyzer-description">
|
|
1551
|
+
Analyze failed tests using AI to get suggestions and potential fixes. Click the AI Fix button for specific failed test.
|
|
1552
|
+
</p>
|
|
1553
|
+
|
|
1554
|
+
<div class="compact-failure-list">
|
|
1555
|
+
${failedTests.map(test => {
|
|
1556
|
+
const testTitle = test.name.split(" > ").pop() || "Unnamed Test";
|
|
1557
|
+
const testJson = btoa(JSON.stringify(test)); // Base64 encode the test object
|
|
1558
|
+
const truncatedError = (test.errorMessage || "No error message").slice(0, 150) +
|
|
1559
|
+
(test.errorMessage && test.errorMessage.length > 150 ? "..." : "");
|
|
1560
|
+
|
|
1561
|
+
return `
|
|
1562
|
+
<div class="compact-failure-item">
|
|
1563
|
+
<div class="failure-header">
|
|
1564
|
+
<div class="failure-main-info">
|
|
1565
|
+
<h3 class="failure-title" title="${sanitizeHTML(test.name)}">${sanitizeHTML(testTitle)}</h3>
|
|
1566
|
+
<div class="failure-meta">
|
|
1567
|
+
<span class="browser-indicator">${sanitizeHTML(test.browser || 'unknown')}</span>
|
|
1568
|
+
<span class="duration-indicator">${formatDuration(test.duration)}</span>
|
|
1569
|
+
</div>
|
|
1570
|
+
</div>
|
|
1571
|
+
<button class="compact-ai-btn" onclick="getAIFix(this)" data-test-json="${testJson}">
|
|
1572
|
+
<span class="ai-text">AI Fix</span>
|
|
1573
|
+
</button>
|
|
1574
|
+
</div>
|
|
1575
|
+
<div class="failure-error-preview">
|
|
1576
|
+
<div class="error-snippet">${formatPlaywrightError(truncatedError)}</div>
|
|
1577
|
+
<button class="expand-error-btn" onclick="toggleErrorDetails(this)">
|
|
1578
|
+
<span class="expand-text">Show Full Error</span>
|
|
1579
|
+
<span class="expand-icon">▼</span>
|
|
1580
|
+
</button>
|
|
1581
|
+
</div>
|
|
1582
|
+
<div class="full-error-details" style="display: none;">
|
|
1583
|
+
<div class="full-error-content">
|
|
1584
|
+
${formatPlaywrightError(test.errorMessage || "No detailed error message available")}
|
|
1585
|
+
</div>
|
|
1586
|
+
</div>
|
|
1587
|
+
</div>
|
|
1588
|
+
`
|
|
1589
|
+
}).join('')}
|
|
1590
|
+
</div>
|
|
1591
|
+
|
|
1592
|
+
<!-- AI Fix Modal -->
|
|
1593
|
+
<div id="ai-fix-modal" class="ai-modal-overlay" onclick="closeAiModal()">
|
|
1594
|
+
<div class="ai-modal-content" onclick="event.stopPropagation()">
|
|
1595
|
+
<div class="ai-modal-header">
|
|
1596
|
+
<h3 id="ai-fix-modal-title">AI Analysis</h3>
|
|
1597
|
+
<span class="ai-modal-close" onclick="closeAiModal()">×</span>
|
|
1598
|
+
</div>
|
|
1599
|
+
<div class="ai-modal-body" id="ai-fix-modal-content">
|
|
1600
|
+
<!-- Content will be injected by JavaScript -->
|
|
1601
|
+
</div>
|
|
1602
|
+
</div>
|
|
1603
|
+
</div>
|
|
1604
|
+
`;
|
|
1605
|
+
}
|
|
1536
1606
|
function generateHTML(reportData, trendData = null) {
|
|
1537
1607
|
const { run, results } = reportData;
|
|
1538
1608
|
const suitesData = getSuitesData(reportData.results || []);
|
|
@@ -1544,6 +1614,13 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1544
1614
|
duration: 0,
|
|
1545
1615
|
timestamp: new Date().toISOString(),
|
|
1546
1616
|
};
|
|
1617
|
+
|
|
1618
|
+
const fixPath = (p) => {
|
|
1619
|
+
if (!p) return "";
|
|
1620
|
+
// This regex handles both forward slashes and backslashes
|
|
1621
|
+
return p.replace(new RegExp(`^${DEFAULT_OUTPUT_DIR}[\\\\/]`), '');
|
|
1622
|
+
};
|
|
1623
|
+
|
|
1547
1624
|
const totalTestsOr1 = runSummary.totalTests || 1;
|
|
1548
1625
|
const passPercentage = Math.round((runSummary.passed / totalTestsOr1) * 100);
|
|
1549
1626
|
const failPercentage = Math.round((runSummary.failed / totalTestsOr1) * 100);
|
|
@@ -1586,23 +1663,20 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1586
1663
|
)}</span>
|
|
1587
1664
|
</div>
|
|
1588
1665
|
<div class="step-details" style="display: none;">
|
|
1589
|
-
${
|
|
1590
|
-
step.codeLocation
|
|
1666
|
+
${step.codeLocation
|
|
1591
1667
|
? `<div class="step-info code-section"><strong>Location:</strong> ${sanitizeHTML(
|
|
1592
|
-
|
|
1593
|
-
|
|
1668
|
+
step.codeLocation
|
|
1669
|
+
)}</div>`
|
|
1594
1670
|
: ""
|
|
1595
|
-
|
|
1596
|
-
${
|
|
1597
|
-
step.errorMessage
|
|
1671
|
+
}
|
|
1672
|
+
${step.errorMessage
|
|
1598
1673
|
? `<div class="test-error-summary">
|
|
1599
|
-
${
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
}
|
|
1674
|
+
${step.stackTrace
|
|
1675
|
+
? `<div class="stack-trace">${formatPlaywrightError(
|
|
1676
|
+
step.stackTrace
|
|
1677
|
+
)}</div>`
|
|
1678
|
+
: ""
|
|
1679
|
+
}
|
|
1606
1680
|
<button
|
|
1607
1681
|
class="copy-error-btn"
|
|
1608
1682
|
onclick="copyErrorToClipboard(this)"
|
|
@@ -1624,15 +1698,14 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1624
1698
|
</button>
|
|
1625
1699
|
</div>`
|
|
1626
1700
|
: ""
|
|
1627
|
-
|
|
1628
|
-
${
|
|
1629
|
-
hasNestedSteps
|
|
1701
|
+
}
|
|
1702
|
+
${hasNestedSteps
|
|
1630
1703
|
? `<div class="nested-steps">${generateStepsHTML(
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1704
|
+
step.steps,
|
|
1705
|
+
depth + 1
|
|
1706
|
+
)}</div>`
|
|
1634
1707
|
: ""
|
|
1635
|
-
|
|
1708
|
+
}
|
|
1636
1709
|
</div>
|
|
1637
1710
|
</div>`;
|
|
1638
1711
|
})
|
|
@@ -1640,29 +1713,27 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1640
1713
|
};
|
|
1641
1714
|
|
|
1642
1715
|
return `
|
|
1643
|
-
<div class="test-case" data-status="${
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
.toLowerCase()}">
|
|
1716
|
+
<div class="test-case" data-status="${test.status
|
|
1717
|
+
}" data-browser="${sanitizeHTML(browser)}" data-tags="${(test.tags || [])
|
|
1718
|
+
.join(",")
|
|
1719
|
+
.toLowerCase()}">
|
|
1648
1720
|
<div class="test-case-header" role="button" aria-expanded="false">
|
|
1649
1721
|
<div class="test-case-summary">
|
|
1650
1722
|
<span class="status-badge ${getStatusClass(test.status)}">${String(
|
|
1651
|
-
|
|
1652
|
-
|
|
1723
|
+
test.status
|
|
1724
|
+
).toUpperCase()}</span>
|
|
1653
1725
|
<span class="test-case-title" title="${sanitizeHTML(
|
|
1654
1726
|
test.name
|
|
1655
1727
|
)}">${sanitizeHTML(testTitle)}</span>
|
|
1656
1728
|
<span class="test-case-browser">(${sanitizeHTML(browser)})</span>
|
|
1657
1729
|
</div>
|
|
1658
1730
|
<div class="test-case-meta">
|
|
1659
|
-
${
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
}
|
|
1731
|
+
${test.tags && test.tags.length > 0
|
|
1732
|
+
? test.tags
|
|
1733
|
+
.map((t) => `<span class="tag">${sanitizeHTML(t)}</span>`)
|
|
1734
|
+
.join(" ")
|
|
1735
|
+
: ""
|
|
1736
|
+
}
|
|
1666
1737
|
<span class="test-duration">${formatDuration(test.duration)}</span>
|
|
1667
1738
|
</div>
|
|
1668
1739
|
</div>
|
|
@@ -1671,13 +1742,12 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1671
1742
|
<p><strong>Test run Worker ID:</strong> ${sanitizeHTML(
|
|
1672
1743
|
test.workerId
|
|
1673
1744
|
)} [<strong>Total No. of Workers:</strong> ${sanitizeHTML(
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
${
|
|
1677
|
-
test
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
)}
|
|
1745
|
+
test.totalWorkers
|
|
1746
|
+
)}]</p>
|
|
1747
|
+
${test.errorMessage
|
|
1748
|
+
? `<div class="test-error-summary">${formatPlaywrightError(
|
|
1749
|
+
test.errorMessage
|
|
1750
|
+
)}
|
|
1681
1751
|
<button
|
|
1682
1752
|
class="copy-error-btn"
|
|
1683
1753
|
onclick="copyErrorToClipboard(this)"
|
|
@@ -1698,21 +1768,20 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1698
1768
|
Copy Error Prompt
|
|
1699
1769
|
</button>
|
|
1700
1770
|
</div>`
|
|
1701
|
-
|
|
1771
|
+
: ""
|
|
1702
1772
|
}
|
|
1703
|
-
${
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
: ""
|
|
1773
|
+
${test.snippet
|
|
1774
|
+
? `<div class="code-section"><h4>Error Snippet</h4><pre><code>${formatPlaywrightError(
|
|
1775
|
+
test.snippet
|
|
1776
|
+
)}</code></pre></div>`
|
|
1777
|
+
: ""
|
|
1709
1778
|
}
|
|
1710
1779
|
<h4>Steps</h4>
|
|
1711
1780
|
<div class="steps-list">${generateStepsHTML(test.steps)}</div>
|
|
1712
1781
|
${(() => {
|
|
1713
1782
|
if (!test.stdout || test.stdout.length === 0) return "";
|
|
1714
1783
|
// Create a unique ID for the <pre> element to target it for copying
|
|
1715
|
-
const logId = `stdout-log-${test.id ||
|
|
1784
|
+
const logId = `stdout-log-${test.id || index}`;
|
|
1716
1785
|
return `<div class="console-output-section">
|
|
1717
1786
|
<h4>Console Output (stdout)
|
|
1718
1787
|
<button class="copy-btn" onclick="copyLogContent('${logId}', this)">Copy Console</button>
|
|
@@ -1724,79 +1793,75 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1724
1793
|
</div>
|
|
1725
1794
|
</div>`;
|
|
1726
1795
|
})()}
|
|
1727
|
-
${
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
: ""
|
|
1796
|
+
${test.stderr && test.stderr.length > 0
|
|
1797
|
+
? `<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(
|
|
1798
|
+
test.stderr.map((line) => sanitizeHTML(line)).join("\n")
|
|
1799
|
+
)}</pre></div>`
|
|
1800
|
+
: ""
|
|
1733
1801
|
}
|
|
1734
|
-
${
|
|
1735
|
-
|
|
1736
|
-
? `
|
|
1802
|
+
${test.screenshots && test.screenshots.length > 0
|
|
1803
|
+
? `
|
|
1737
1804
|
<div class="attachments-section">
|
|
1738
1805
|
<h4>Screenshots</h4>
|
|
1739
1806
|
<div class="attachments-grid">
|
|
1740
1807
|
${test.screenshots
|
|
1741
|
-
|
|
1742
|
-
|
|
1808
|
+
.map(
|
|
1809
|
+
(screenshot, index) => `
|
|
1743
1810
|
<div class="attachment-item">
|
|
1744
|
-
<img src="${screenshot}" alt="Screenshot ${index + 1}">
|
|
1811
|
+
<img src="${fixPath(screenshot)}" alt="Screenshot ${index + 1}">
|
|
1745
1812
|
<div class="attachment-info">
|
|
1746
1813
|
<div class="trace-actions">
|
|
1747
|
-
<a href="${screenshot}" target="_blank" class="view-full">View Full Image</a>
|
|
1748
|
-
<a href="${screenshot}" target="_blank" download="screenshot-${Date.now()}-${index}.png">Download</a>
|
|
1814
|
+
<a href="${fixPath(screenshot)}" target="_blank" class="view-full">View Full Image</a>
|
|
1815
|
+
<a href="${fixPath(screenshot)}" target="_blank" download="screenshot-${Date.now()}-${index}.png">Download</a>
|
|
1749
1816
|
</div>
|
|
1750
1817
|
</div>
|
|
1751
1818
|
</div>
|
|
1752
1819
|
`
|
|
1753
|
-
|
|
1754
|
-
|
|
1820
|
+
)
|
|
1821
|
+
.join("")}
|
|
1755
1822
|
</div>
|
|
1756
1823
|
</div>
|
|
1757
1824
|
`
|
|
1758
|
-
|
|
1825
|
+
: ""
|
|
1759
1826
|
}
|
|
1760
|
-
${
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
<video controls width="100%" height="auto" title="Video ${
|
|
1778
|
-
|
|
1779
|
-
}">
|
|
1827
|
+
${test.videoPath && test.videoPath.length > 0
|
|
1828
|
+
? `<div class="attachments-section"><h4>Videos</h4><div class="attachments-grid">${test.videoPath
|
|
1829
|
+
.map((videoUrl, index) => {
|
|
1830
|
+
const fixedVideoUrl = fixPath(videoUrl);
|
|
1831
|
+
const fileExtension = String(fixedVideoUrl)
|
|
1832
|
+
.split(".")
|
|
1833
|
+
.pop()
|
|
1834
|
+
.toLowerCase();
|
|
1835
|
+
const mimeType =
|
|
1836
|
+
{
|
|
1837
|
+
mp4: "video/mp4",
|
|
1838
|
+
webm: "video/webm",
|
|
1839
|
+
ogg: "video/ogg",
|
|
1840
|
+
mov: "video/quicktime",
|
|
1841
|
+
avi: "video/x-msvideo",
|
|
1842
|
+
}[fileExtension] || "video/mp4";
|
|
1843
|
+
return `<div class="attachment-item video-item">
|
|
1844
|
+
<video controls width="100%" height="auto" title="Video ${index + 1
|
|
1845
|
+
}">
|
|
1780
1846
|
<source src="${sanitizeHTML(
|
|
1781
|
-
|
|
1782
|
-
|
|
1847
|
+
fixedVideoUrl
|
|
1848
|
+
)}" type="${mimeType}">
|
|
1783
1849
|
Your browser does not support the video tag.
|
|
1784
1850
|
</video>
|
|
1785
1851
|
<div class="attachment-info">
|
|
1786
1852
|
<div class="trace-actions">
|
|
1787
1853
|
<a href="${sanitizeHTML(
|
|
1788
|
-
|
|
1789
|
-
|
|
1854
|
+
fixedVideoUrl
|
|
1855
|
+
)}" target="_blank" download="video-${Date.now()}-${index}.${fileExtension}">Download</a>
|
|
1790
1856
|
</div>
|
|
1791
1857
|
</div>
|
|
1792
1858
|
</div>`;
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1859
|
+
})
|
|
1860
|
+
.join("")}</div></div>`
|
|
1861
|
+
: ""
|
|
1796
1862
|
}
|
|
1797
|
-
${
|
|
1798
|
-
|
|
1799
|
-
? `
|
|
1863
|
+
${test.tracePath
|
|
1864
|
+
? `
|
|
1800
1865
|
<div class="attachments-section">
|
|
1801
1866
|
<h4>Trace Files</h4>
|
|
1802
1867
|
<div class="attachments-grid">
|
|
@@ -1804,72 +1869,70 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1804
1869
|
<div class="trace-preview">
|
|
1805
1870
|
<span class="trace-icon">📄</span>
|
|
1806
1871
|
<span class="trace-name">${sanitizeHTML(
|
|
1807
|
-
|
|
1808
|
-
|
|
1872
|
+
path.basename(test.tracePath)
|
|
1873
|
+
)}</span>
|
|
1809
1874
|
</div>
|
|
1810
1875
|
<div class="attachment-info">
|
|
1811
1876
|
<div class="trace-actions">
|
|
1812
1877
|
<a href="${sanitizeHTML(
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1878
|
+
fixPath(test.tracePath)
|
|
1879
|
+
)}" target="_blank" download="${sanitizeHTML(
|
|
1880
|
+
path.basename(test.tracePath)
|
|
1881
|
+
)}" class="download-trace">Download Trace</a>
|
|
1817
1882
|
</div>
|
|
1818
1883
|
</div>
|
|
1819
1884
|
</div>
|
|
1820
1885
|
</div>
|
|
1821
1886
|
</div>
|
|
1822
1887
|
`
|
|
1823
|
-
|
|
1888
|
+
: ""
|
|
1824
1889
|
}
|
|
1825
|
-
${
|
|
1826
|
-
|
|
1827
|
-
? `
|
|
1890
|
+
${test.attachments && test.attachments.length > 0
|
|
1891
|
+
? `
|
|
1828
1892
|
<div class="attachments-section">
|
|
1829
1893
|
<h4>Other Attachments</h4>
|
|
1830
1894
|
<div class="attachments-grid">
|
|
1831
1895
|
${test.attachments
|
|
1832
|
-
|
|
1833
|
-
|
|
1896
|
+
.map(
|
|
1897
|
+
(attachment) => `
|
|
1834
1898
|
<div class="attachment-item generic-attachment">
|
|
1835
1899
|
<div class="attachment-icon">${getAttachmentIcon(
|
|
1836
|
-
|
|
1837
|
-
|
|
1900
|
+
attachment.contentType
|
|
1901
|
+
)}</div>
|
|
1838
1902
|
<div class="attachment-caption">
|
|
1839
1903
|
<span class="attachment-name" title="${sanitizeHTML(
|
|
1840
|
-
|
|
1841
|
-
|
|
1904
|
+
attachment.name
|
|
1905
|
+
)}">${sanitizeHTML(attachment.name)}</span>
|
|
1842
1906
|
<span class="attachment-type">${sanitizeHTML(
|
|
1843
|
-
|
|
1844
|
-
|
|
1907
|
+
attachment.contentType
|
|
1908
|
+
)}</span>
|
|
1845
1909
|
</div>
|
|
1846
1910
|
<div class="attachment-info">
|
|
1847
1911
|
<div class="trace-actions">
|
|
1848
1912
|
<a href="${sanitizeHTML(
|
|
1849
|
-
|
|
1850
|
-
|
|
1913
|
+
fixPath(attachment.path)
|
|
1914
|
+
)}" target="_blank" class="view-full">View</a>
|
|
1851
1915
|
<a href="${sanitizeHTML(
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1916
|
+
fixPath(attachment.path)
|
|
1917
|
+
)}" target="_blank" download="${sanitizeHTML(
|
|
1918
|
+
attachment.name
|
|
1919
|
+
)}" class="download-trace">Download</a>
|
|
1856
1920
|
</div>
|
|
1857
1921
|
</div>
|
|
1858
1922
|
</div>
|
|
1859
1923
|
`
|
|
1860
|
-
|
|
1861
|
-
|
|
1924
|
+
)
|
|
1925
|
+
.join("")}
|
|
1862
1926
|
</div>
|
|
1863
1927
|
</div>
|
|
1864
1928
|
`
|
|
1865
|
-
|
|
1929
|
+
: ""
|
|
1866
1930
|
}
|
|
1867
|
-
${
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
: ""
|
|
1931
|
+
${test.codeSnippet
|
|
1932
|
+
? `<div class="code-section"><h4>Code Snippet</h4><pre><code>${formatPlaywrightError(
|
|
1933
|
+
sanitizeHTML(test.codeSnippet)
|
|
1934
|
+
)}</code></pre></div>`
|
|
1935
|
+
: ""
|
|
1873
1936
|
}
|
|
1874
1937
|
</div>
|
|
1875
1938
|
</div>`;
|
|
@@ -2011,7 +2074,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2011
2074
|
.attachment-name { font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
2012
2075
|
.attachment-type { font-size: 0.8rem; color: var(--text-color-secondary); }
|
|
2013
2076
|
.trend-charts-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(480px, 1fr)); gap: 28px; margin-bottom: 35px; }
|
|
2014
|
-
.test-history-container h2.tab-main-title { font-size: 1.6em; margin-bottom: 18px; color: var(--primary-color); border-bottom: 1px solid var(--border-color); padding-bottom: 12px;}
|
|
2077
|
+
.test-history-container h2.tab-main-title, .ai-analyzer-container h2.tab-main-title { font-size: 1.6em; margin-bottom: 18px; color: var(--primary-color); border-bottom: 1px solid var(--border-color); padding-bottom: 12px;}
|
|
2015
2078
|
.test-history-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(380px, 1fr)); gap: 22px; margin-top: 22px; }
|
|
2016
2079
|
.test-history-card { background: var(--card-background-color); border: 1px solid var(--border-color); border-radius: var(--border-radius); padding: 22px; box-shadow: var(--box-shadow-light); display: flex; flex-direction: column; }
|
|
2017
2080
|
.test-history-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding-bottom: 14px; border-bottom: 1px solid var(--light-gray-color); }
|
|
@@ -2031,8 +2094,24 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2031
2094
|
.status-badge-small.status-unknown { background-color: var(--dark-gray-color); }
|
|
2032
2095
|
.no-data, .no-tests, .no-steps, .no-data-chart { padding: 28px; text-align: center; color: var(--dark-gray-color); font-style: italic; font-size:1.1em; background-color: var(--light-gray-color); border-radius: var(--border-radius); margin: 18px 0; border: 1px dashed var(--medium-gray-color); }
|
|
2033
2096
|
.no-data-chart {font-size: 0.95em; padding: 18px;}
|
|
2034
|
-
|
|
2035
|
-
|
|
2097
|
+
.ai-failure-cards-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(400px, 1fr)); gap: 22px; }
|
|
2098
|
+
.ai-failure-card { background: var(--card-background-color); border: 1px solid var(--border-color); border-left: 5px solid var(--danger-color); border-radius: var(--border-radius); box-shadow: var(--box-shadow-light); display: flex; flex-direction: column; }
|
|
2099
|
+
.ai-failure-card-header { padding: 15px 20px; border-bottom: 1px solid var(--light-gray-color); display: flex; align-items: center; justify-content: space-between; gap: 15px; }
|
|
2100
|
+
.ai-failure-card-header h3 { margin: 0; font-size: 1.1em; color: var(--text-color); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
2101
|
+
.ai-failure-card-body { padding: 20px; }
|
|
2102
|
+
.ai-fix-btn { background-color: var(--primary-color); color: white; border: none; padding: 10px 18px; font-size: 1em; font-weight: 600; border-radius: 6px; cursor: pointer; transition: background-color 0.2s ease, transform 0.2s ease; display: inline-flex; align-items: center; gap: 8px; }
|
|
2103
|
+
.ai-fix-btn:hover { background-color: var(--accent-color); transform: translateY(-2px); }
|
|
2104
|
+
.ai-modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.65); display: none; align-items: center; justify-content: center; z-index: 1050; animation: fadeIn 0.3s; }
|
|
2105
|
+
.ai-modal-content { background-color: var(--card-background-color); color: var(--text-color); border-radius: var(--border-radius); width: 90%; max-width: 800px; max-height: 90vh; box-shadow: 0 10px 30px rgba(0,0,0,0.2); display: flex; flex-direction: column; overflow: hidden; }
|
|
2106
|
+
.ai-modal-header { padding: 18px 25px; border-bottom: 1px solid var(--border-color); display: flex; justify-content: space-between; align-items: center; }
|
|
2107
|
+
.ai-modal-header h3 { margin: 0; font-size: 1.25em; }
|
|
2108
|
+
.ai-modal-close { font-size: 2rem; font-weight: 300; cursor: pointer; color: var(--dark-gray-color); line-height: 1; transition: color 0.2s; }
|
|
2109
|
+
.ai-modal-close:hover { color: var(--danger-color); }
|
|
2110
|
+
.ai-modal-body { padding: 25px; overflow-y: auto; }
|
|
2111
|
+
.ai-modal-body h4 { margin-top: 18px; margin-bottom: 10px; font-size: 1.1em; color: var(--primary-color); }
|
|
2112
|
+
.ai-modal-body p { margin-bottom: 15px; }
|
|
2113
|
+
.ai-loader { margin: 40px auto; border: 5px solid #f3f3f3; border-top: 5px solid var(--primary-color); border-radius: 50%; width: 50px; height: 50px; animation: spin 1s linear infinite; }
|
|
2114
|
+
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
|
|
2036
2115
|
.trace-preview { padding: 1rem; text-align: center; background: #f5f5f5; border-bottom: 1px solid #e1e1e1; }
|
|
2037
2116
|
.trace-icon { font-size: 2rem; display: block; margin-bottom: 0.5rem; }
|
|
2038
2117
|
.trace-name { word-break: break-word; font-size: 0.9rem; }
|
|
@@ -2045,9 +2124,216 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2045
2124
|
.filters button.clear-filters-btn { background-color: var(--medium-gray-color); color: var(--text-color); }
|
|
2046
2125
|
.filters button.clear-filters-btn:hover { background-color: var(--dark-gray-color); color: #fff; }
|
|
2047
2126
|
.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;}
|
|
2127
|
+
/* Compact AI Failure Analyzer Styles */
|
|
2128
|
+
.ai-analyzer-stats {
|
|
2129
|
+
display: flex;
|
|
2130
|
+
gap: 20px;
|
|
2131
|
+
margin-bottom: 25px;
|
|
2132
|
+
padding: 20px;
|
|
2133
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
2134
|
+
border-radius: var(--border-radius);
|
|
2135
|
+
justify-content: center;
|
|
2136
|
+
}
|
|
2137
|
+
.stat-item {
|
|
2138
|
+
text-align: center;
|
|
2139
|
+
color: white;
|
|
2140
|
+
}
|
|
2141
|
+
.stat-number {
|
|
2142
|
+
display: block;
|
|
2143
|
+
font-size: 2em;
|
|
2144
|
+
font-weight: 700;
|
|
2145
|
+
line-height: 1;
|
|
2146
|
+
}
|
|
2147
|
+
.stat-label {
|
|
2148
|
+
font-size: 0.9em;
|
|
2149
|
+
opacity: 0.9;
|
|
2150
|
+
font-weight: 500;
|
|
2151
|
+
}
|
|
2152
|
+
.ai-analyzer-description {
|
|
2153
|
+
margin-bottom: 25px;
|
|
2154
|
+
font-size: 1em;
|
|
2155
|
+
color: var(--text-color-secondary);
|
|
2156
|
+
text-align: center;
|
|
2157
|
+
max-width: 600px;
|
|
2158
|
+
margin-left: auto;
|
|
2159
|
+
margin-right: auto;
|
|
2160
|
+
}
|
|
2161
|
+
.compact-failure-list {
|
|
2162
|
+
display: flex;
|
|
2163
|
+
flex-direction: column;
|
|
2164
|
+
gap: 15px;
|
|
2165
|
+
}
|
|
2166
|
+
.compact-failure-item {
|
|
2167
|
+
background: var(--card-background-color);
|
|
2168
|
+
border: 1px solid var(--border-color);
|
|
2169
|
+
border-left: 4px solid var(--danger-color);
|
|
2170
|
+
border-radius: var(--border-radius);
|
|
2171
|
+
box-shadow: var(--box-shadow-light);
|
|
2172
|
+
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
|
2173
|
+
}
|
|
2174
|
+
.compact-failure-item:hover {
|
|
2175
|
+
transform: translateY(-2px);
|
|
2176
|
+
box-shadow: var(--box-shadow);
|
|
2177
|
+
}
|
|
2178
|
+
.failure-header {
|
|
2179
|
+
display: flex;
|
|
2180
|
+
justify-content: space-between;
|
|
2181
|
+
align-items: center;
|
|
2182
|
+
padding: 18px 20px;
|
|
2183
|
+
gap: 15px;
|
|
2184
|
+
}
|
|
2185
|
+
.failure-main-info {
|
|
2186
|
+
flex: 1;
|
|
2187
|
+
min-width: 0;
|
|
2188
|
+
}
|
|
2189
|
+
.failure-title {
|
|
2190
|
+
margin: 0 0 8px 0;
|
|
2191
|
+
font-size: 1.1em;
|
|
2192
|
+
font-weight: 600;
|
|
2193
|
+
color: var(--text-color);
|
|
2194
|
+
white-space: nowrap;
|
|
2195
|
+
overflow: hidden;
|
|
2196
|
+
text-overflow: ellipsis;
|
|
2197
|
+
}
|
|
2198
|
+
.failure-meta {
|
|
2199
|
+
display: flex;
|
|
2200
|
+
gap: 12px;
|
|
2201
|
+
align-items: center;
|
|
2202
|
+
}
|
|
2203
|
+
.browser-indicator, .duration-indicator {
|
|
2204
|
+
font-size: 0.85em;
|
|
2205
|
+
padding: 3px 8px;
|
|
2206
|
+
border-radius: 12px;
|
|
2207
|
+
font-weight: 500;
|
|
2208
|
+
}
|
|
2209
|
+
.browser-indicator {
|
|
2210
|
+
background: var(--info-color);
|
|
2211
|
+
color: white;
|
|
2212
|
+
}
|
|
2213
|
+
.duration-indicator {
|
|
2214
|
+
background: var(--medium-gray-color);
|
|
2215
|
+
color: var(--text-color);
|
|
2216
|
+
}
|
|
2217
|
+
.compact-ai-btn {
|
|
2218
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
2219
|
+
color: white;
|
|
2220
|
+
border: none;
|
|
2221
|
+
padding: 12px 18px;
|
|
2222
|
+
border-radius: 6px;
|
|
2223
|
+
cursor: pointer;
|
|
2224
|
+
font-weight: 600;
|
|
2225
|
+
display: flex;
|
|
2226
|
+
align-items: center;
|
|
2227
|
+
gap: 8px;
|
|
2228
|
+
transition: all 0.3s ease;
|
|
2229
|
+
white-space: nowrap;
|
|
2230
|
+
}
|
|
2231
|
+
.compact-ai-btn:hover {
|
|
2232
|
+
transform: translateY(-2px);
|
|
2233
|
+
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
|
|
2234
|
+
}
|
|
2235
|
+
.ai-icon {
|
|
2236
|
+
font-size: 1.2em;
|
|
2237
|
+
}
|
|
2238
|
+
.ai-text {
|
|
2239
|
+
font-size: 0.95em;
|
|
2240
|
+
}
|
|
2241
|
+
.failure-error-preview {
|
|
2242
|
+
padding: 0 20px 18px 20px;
|
|
2243
|
+
border-top: 1px solid var(--light-gray-color);
|
|
2244
|
+
}
|
|
2245
|
+
.error-snippet {
|
|
2246
|
+
background: rgba(244, 67, 54, 0.05);
|
|
2247
|
+
border: 1px solid rgba(244, 67, 54, 0.2);
|
|
2248
|
+
border-radius: 6px;
|
|
2249
|
+
padding: 12px;
|
|
2250
|
+
margin-bottom: 12px;
|
|
2251
|
+
font-family: monospace;
|
|
2252
|
+
font-size: 0.9em;
|
|
2253
|
+
color: var(--danger-color);
|
|
2254
|
+
line-height: 1.4;
|
|
2255
|
+
}
|
|
2256
|
+
.expand-error-btn {
|
|
2257
|
+
background: none;
|
|
2258
|
+
border: 1px solid var(--border-color);
|
|
2259
|
+
color: var(--text-color-secondary);
|
|
2260
|
+
padding: 6px 12px;
|
|
2261
|
+
border-radius: 4px;
|
|
2262
|
+
cursor: pointer;
|
|
2263
|
+
font-size: 0.85em;
|
|
2264
|
+
display: flex;
|
|
2265
|
+
align-items: center;
|
|
2266
|
+
gap: 6px;
|
|
2267
|
+
transition: all 0.2s ease;
|
|
2268
|
+
}
|
|
2269
|
+
.expand-error-btn:hover {
|
|
2270
|
+
background: var(--light-gray-color);
|
|
2271
|
+
border-color: var(--medium-gray-color);
|
|
2272
|
+
}
|
|
2273
|
+
.expand-icon {
|
|
2274
|
+
transition: transform 0.2s ease;
|
|
2275
|
+
font-size: 0.8em;
|
|
2276
|
+
}
|
|
2277
|
+
.expand-error-btn.expanded .expand-icon {
|
|
2278
|
+
transform: rotate(180deg);
|
|
2279
|
+
}
|
|
2280
|
+
.full-error-details {
|
|
2281
|
+
padding: 0 20px 20px 20px;
|
|
2282
|
+
border-top: 1px solid var(--light-gray-color);
|
|
2283
|
+
margin-top: 0;
|
|
2284
|
+
}
|
|
2285
|
+
.full-error-content {
|
|
2286
|
+
background: rgba(244, 67, 54, 0.05);
|
|
2287
|
+
border: 1px solid rgba(244, 67, 54, 0.2);
|
|
2288
|
+
border-radius: 6px;
|
|
2289
|
+
padding: 15px;
|
|
2290
|
+
font-family: monospace;
|
|
2291
|
+
font-size: 0.9em;
|
|
2292
|
+
color: var(--danger-color);
|
|
2293
|
+
line-height: 1.4;
|
|
2294
|
+
max-height: 300px;
|
|
2295
|
+
overflow-y: auto;
|
|
2296
|
+
}
|
|
2297
|
+
|
|
2298
|
+
/* Responsive adjustments for compact design */
|
|
2299
|
+
@media (max-width: 768px) {
|
|
2300
|
+
.ai-analyzer-stats {
|
|
2301
|
+
flex-direction: column;
|
|
2302
|
+
gap: 15px;
|
|
2303
|
+
text-align: center;
|
|
2304
|
+
}
|
|
2305
|
+
.failure-header {
|
|
2306
|
+
flex-direction: column;
|
|
2307
|
+
align-items: stretch;
|
|
2308
|
+
gap: 15px;
|
|
2309
|
+
}
|
|
2310
|
+
.failure-main-info {
|
|
2311
|
+
text-align: center;
|
|
2312
|
+
}
|
|
2313
|
+
.failure-meta {
|
|
2314
|
+
justify-content: center;
|
|
2315
|
+
}
|
|
2316
|
+
.compact-ai-btn {
|
|
2317
|
+
justify-content: center;
|
|
2318
|
+
padding: 12px 20px;
|
|
2319
|
+
}
|
|
2320
|
+
}
|
|
2321
|
+
@media (max-width: 480px) {
|
|
2322
|
+
.stat-item .stat-number {
|
|
2323
|
+
font-size: 1.5em;
|
|
2324
|
+
}
|
|
2325
|
+
.failure-header {
|
|
2326
|
+
padding: 15px;
|
|
2327
|
+
}
|
|
2328
|
+
.failure-error-preview, .full-error-details {
|
|
2329
|
+
padding-left: 15px;
|
|
2330
|
+
padding-right: 15px;
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
|
|
2048
2334
|
@media (max-width: 1200px) { .trend-charts-row { grid-template-columns: 1fr; } }
|
|
2049
2335
|
@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; } }
|
|
2050
|
-
@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;} }
|
|
2336
|
+
@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;} .ai-failure-cards-grid { grid-template-columns: 1fr; } }
|
|
2051
2337
|
@media (max-width: 480px) { body {font-size: 14px;} .container {padding: 15px;} .header h1 {font-size: 1.4em;} #report-logo { height: 35px; width: 50px; } .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;} }
|
|
2052
2338
|
</style>
|
|
2053
2339
|
</head>
|
|
@@ -2059,8 +2345,8 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2059
2345
|
<h1>Playwright Pulse Report</h1>
|
|
2060
2346
|
</div>
|
|
2061
2347
|
<div class="run-info"><strong>Run Date:</strong> ${formatDate(
|
|
2062
|
-
|
|
2063
|
-
|
|
2348
|
+
runSummary.timestamp
|
|
2349
|
+
)}<br><strong>Total Duration:</strong> ${formatDuration(
|
|
2064
2350
|
runSummary.duration
|
|
2065
2351
|
)}</div>
|
|
2066
2352
|
</header>
|
|
@@ -2068,44 +2354,39 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2068
2354
|
<button class="tab-button active" data-tab="dashboard">Dashboard</button>
|
|
2069
2355
|
<button class="tab-button" data-tab="test-runs">Test Run Summary</button>
|
|
2070
2356
|
<button class="tab-button" data-tab="test-history">Test History</button>
|
|
2071
|
-
<button class="tab-button" data-tab="
|
|
2357
|
+
<button class="tab-button" data-tab="ai-failure-analyzer">AI Failure Analyzer</button>
|
|
2072
2358
|
</div>
|
|
2073
2359
|
<div id="dashboard" class="tab-content active">
|
|
2074
2360
|
<div class="dashboard-grid">
|
|
2075
|
-
<div class="summary-card"><h3>Total Tests</h3><div class="value">${
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
<div class="summary-card status-
|
|
2082
|
-
|
|
2083
|
-
}</div><div class="trend-percentage">${failPercentage}%</div></div>
|
|
2084
|
-
<div class="summary-card status-skipped"><h3>Skipped</h3><div class="value">${
|
|
2085
|
-
runSummary.skipped || 0
|
|
2086
|
-
}</div><div class="trend-percentage">${skipPercentage}%</div></div>
|
|
2361
|
+
<div class="summary-card"><h3>Total Tests</h3><div class="value">${runSummary.totalTests
|
|
2362
|
+
}</div></div>
|
|
2363
|
+
<div class="summary-card status-passed"><h3>Passed</h3><div class="value">${runSummary.passed
|
|
2364
|
+
}</div><div class="trend-percentage">${passPercentage}%</div></div>
|
|
2365
|
+
<div class="summary-card status-failed"><h3>Failed</h3><div class="value">${runSummary.failed
|
|
2366
|
+
}</div><div class="trend-percentage">${failPercentage}%</div></div>
|
|
2367
|
+
<div class="summary-card status-skipped"><h3>Skipped</h3><div class="value">${runSummary.skipped || 0
|
|
2368
|
+
}</div><div class="trend-percentage">${skipPercentage}%</div></div>
|
|
2087
2369
|
<div class="summary-card"><h3>Avg. Test Time</h3><div class="value">${avgTestDuration}</div></div>
|
|
2088
2370
|
<div class="summary-card"><h3>Run Duration</h3><div class="value">${formatDuration(
|
|
2089
|
-
|
|
2090
|
-
|
|
2371
|
+
runSummary.duration
|
|
2372
|
+
)}</div></div>
|
|
2091
2373
|
</div>
|
|
2092
2374
|
<div class="dashboard-bottom-row">
|
|
2093
2375
|
<div style="display: grid; gap: 20px">
|
|
2094
2376
|
${generatePieChart(
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
${
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
}
|
|
2377
|
+
[
|
|
2378
|
+
{ label: "Passed", value: runSummary.passed },
|
|
2379
|
+
{ label: "Failed", value: runSummary.failed },
|
|
2380
|
+
{ label: "Skipped", value: runSummary.skipped || 0 },
|
|
2381
|
+
],
|
|
2382
|
+
400,
|
|
2383
|
+
390
|
|
2384
|
+
)}
|
|
2385
|
+
${runSummary.environment &&
|
|
2386
|
+
Object.keys(runSummary.environment).length > 0
|
|
2387
|
+
? generateEnvironmentDashboard(runSummary.environment)
|
|
2388
|
+
: '<div class="no-data">Environment data not available.</div>'
|
|
2389
|
+
}
|
|
2109
2390
|
</div>
|
|
2110
2391
|
${generateSuitesWidget(suitesData)}
|
|
2111
2392
|
</div>
|
|
@@ -2115,17 +2396,17 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2115
2396
|
<input type="text" id="filter-name" placeholder="Filter by test name/path..." style="border-color: black; border-style: outset;">
|
|
2116
2397
|
<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>
|
|
2117
2398
|
<select id="filter-browser"><option value="">All Browsers</option>${Array.from(
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2399
|
+
new Set(
|
|
2400
|
+
(results || []).map((test) => test.browser || "unknown")
|
|
2401
|
+
)
|
|
2402
|
+
)
|
|
2403
|
+
.map(
|
|
2404
|
+
(browser) =>
|
|
2405
|
+
`<option value="${sanitizeHTML(browser)}">${sanitizeHTML(
|
|
2406
|
+
browser
|
|
2407
|
+
)}</option>`
|
|
2408
|
+
)
|
|
2409
|
+
.join("")}</select>
|
|
2129
2410
|
<button id="expand-all-tests">Expand All</button> <button id="collapse-all-tests">Collapse All</button> <button id="clear-run-summary-filters" class="clear-filters-btn">Clear Filters</button>
|
|
2130
2411
|
</div>
|
|
2131
2412
|
<div class="test-cases-list">${generateTestCasesHTML()}</div>
|
|
@@ -2134,18 +2415,16 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2134
2415
|
<h2 class="tab-main-title">Execution Trends</h2>
|
|
2135
2416
|
<div class="trend-charts-row">
|
|
2136
2417
|
<div class="trend-chart"><h3 class="chart-title-header">Test Volume & Outcome Trends</h3>
|
|
2137
|
-
${
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
}
|
|
2418
|
+
${trendData && trendData.overall && trendData.overall.length > 0
|
|
2419
|
+
? generateTestTrendsChart(trendData)
|
|
2420
|
+
: '<div class="no-data">Overall trend data not available for test counts.</div>'
|
|
2421
|
+
}
|
|
2142
2422
|
</div>
|
|
2143
2423
|
<div class="trend-chart"><h3 class="chart-title-header">Execution Duration Trends</h3>
|
|
2144
|
-
${
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
}
|
|
2424
|
+
${trendData && trendData.overall && trendData.overall.length > 0
|
|
2425
|
+
? generateDurationTrendChart(trendData)
|
|
2426
|
+
: '<div class="no-data">Overall trend data not available for durations.</div>'
|
|
2427
|
+
}
|
|
2149
2428
|
</div>
|
|
2150
2429
|
</div>
|
|
2151
2430
|
<h2 class="tab-main-title">Test Distribution by Worker ${infoTooltip}</h2>
|
|
@@ -2155,16 +2434,15 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2155
2434
|
</div>
|
|
2156
2435
|
</div>
|
|
2157
2436
|
<h2 class="tab-main-title">Individual Test History</h2>
|
|
2158
|
-
${
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
}
|
|
2437
|
+
${trendData &&
|
|
2438
|
+
trendData.testRuns &&
|
|
2439
|
+
Object.keys(trendData.testRuns).length > 0
|
|
2440
|
+
? generateTestHistoryContent(trendData)
|
|
2441
|
+
: '<div class="no-data">Individual test history data not available.</div>'
|
|
2442
|
+
}
|
|
2165
2443
|
</div>
|
|
2166
|
-
<div id="
|
|
2167
|
-
|
|
2444
|
+
<div id="ai-failure-analyzer" class="tab-content">
|
|
2445
|
+
${generateAIFailureAnalyzerTab(results)}
|
|
2168
2446
|
</div>
|
|
2169
2447
|
<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;">
|
|
2170
2448
|
<div style="display: inline-flex; align-items: center; gap: 0.5rem; color: #333; font-size: 0.9rem; font-weight: 600; letter-spacing: 0.5px;">
|
|
@@ -2196,7 +2474,135 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2196
2474
|
button.textContent = 'Failed';
|
|
2197
2475
|
setTimeout(() => { button.textContent = 'Copy'; }, 2000);
|
|
2198
2476
|
});
|
|
2199
|
-
}
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2479
|
+
// --- AI Failure Analyzer Functions ---
|
|
2480
|
+
function getAIFix(button) {
|
|
2481
|
+
const modal = document.getElementById('ai-fix-modal');
|
|
2482
|
+
const modalContent = document.getElementById('ai-fix-modal-content');
|
|
2483
|
+
const modalTitle = document.getElementById('ai-fix-modal-title');
|
|
2484
|
+
|
|
2485
|
+
modal.style.display = 'flex';
|
|
2486
|
+
modalTitle.textContent = 'Analyzing...';
|
|
2487
|
+
modalContent.innerHTML = '<div class="ai-loader"></div>';
|
|
2488
|
+
|
|
2489
|
+
try {
|
|
2490
|
+
const testJson = button.dataset.testJson;
|
|
2491
|
+
const test = JSON.parse(atob(testJson));
|
|
2492
|
+
|
|
2493
|
+
const testName = test.name || 'Unknown Test';
|
|
2494
|
+
const failureLogsAndErrors = [
|
|
2495
|
+
'Error Message:',
|
|
2496
|
+
test.errorMessage || 'Not available.',
|
|
2497
|
+
'\\n\\n--- stdout ---',
|
|
2498
|
+
(test.stdout && test.stdout.length > 0) ? test.stdout.join('\\n') : 'Not available.',
|
|
2499
|
+
'\\n\\n--- stderr ---',
|
|
2500
|
+
(test.stderr && test.stderr.length > 0) ? test.stderr.join('\\n') : 'Not available.'
|
|
2501
|
+
].join('\\n');
|
|
2502
|
+
const codeSnippet = test.snippet || '';
|
|
2503
|
+
|
|
2504
|
+
const shortTestName = testName.split(' > ').pop();
|
|
2505
|
+
modalTitle.textContent = \`Analysis for: \${shortTestName}\`;
|
|
2506
|
+
|
|
2507
|
+
const apiUrl = 'https://ai-test-analyser.netlify.app/api/analyze';
|
|
2508
|
+
fetch(apiUrl, {
|
|
2509
|
+
method: 'POST',
|
|
2510
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2511
|
+
body: JSON.stringify({
|
|
2512
|
+
testName: testName,
|
|
2513
|
+
failureLogsAndErrors: failureLogsAndErrors,
|
|
2514
|
+
codeSnippet: codeSnippet,
|
|
2515
|
+
}),
|
|
2516
|
+
})
|
|
2517
|
+
.then(response => {
|
|
2518
|
+
if (!response.ok) {
|
|
2519
|
+
return response.text().then(text => {
|
|
2520
|
+
throw new Error(\`API request failed with status \${response.status}: \${text || response.statusText}\`);
|
|
2521
|
+
});
|
|
2522
|
+
}
|
|
2523
|
+
return response.text();
|
|
2524
|
+
})
|
|
2525
|
+
.then(text => {
|
|
2526
|
+
if (!text) {
|
|
2527
|
+
throw new Error("The AI analyzer returned an empty response. This might happen during high load or if the request was blocked. Please try again in a moment.");
|
|
2528
|
+
}
|
|
2529
|
+
try {
|
|
2530
|
+
return JSON.parse(text);
|
|
2531
|
+
} catch (e) {
|
|
2532
|
+
console.error("Failed to parse JSON:", text);
|
|
2533
|
+
throw new Error(\`The AI analyzer returned an invalid response. \${e.message}\`);
|
|
2534
|
+
}
|
|
2535
|
+
})
|
|
2536
|
+
.then(data => {
|
|
2537
|
+
// Helper function to prevent XSS by escaping HTML characters
|
|
2538
|
+
const escapeHtml = (unsafe) => {
|
|
2539
|
+
if (typeof unsafe !== 'string') return '';
|
|
2540
|
+
return unsafe
|
|
2541
|
+
.replace(/&/g, "&")
|
|
2542
|
+
.replace(/</g, "<")
|
|
2543
|
+
.replace(/>/g, ">")
|
|
2544
|
+
.replace(/"/g, """)
|
|
2545
|
+
.replace(/'/g, "'");
|
|
2546
|
+
};
|
|
2547
|
+
|
|
2548
|
+
// Build the "Analysis" part from the 'rootCause' field
|
|
2549
|
+
const analysisHtml = \`<h4>Analysis</h4><p>\${escapeHtml(data.rootCause) || 'No analysis provided.'}</p>\`;
|
|
2550
|
+
|
|
2551
|
+
// Build the "Suggestions" part by iterating through the 'suggestedFixes' array
|
|
2552
|
+
let suggestionsHtml = '<h4>Suggestions</h4>';
|
|
2553
|
+
if (data.suggestedFixes && data.suggestedFixes.length > 0) {
|
|
2554
|
+
suggestionsHtml += '<div class="suggestions-list" style="margin-top: 15px;">';
|
|
2555
|
+
data.suggestedFixes.forEach(fix => {
|
|
2556
|
+
suggestionsHtml += \`
|
|
2557
|
+
<div class="suggestion-item" style="margin-bottom: 22px; border-left: 3px solid var(--accent-color-alt); padding-left: 15px;">
|
|
2558
|
+
<p style="margin: 0 0 8px 0; font-weight: 500;">\${escapeHtml(fix.description)}</p>
|
|
2559
|
+
\${fix.codeSnippet ? \`<div class="code-section"><pre><code>\${escapeHtml(fix.codeSnippet)}</code></pre></div>\` : ''}
|
|
2560
|
+
</div>
|
|
2561
|
+
\`;
|
|
2562
|
+
});
|
|
2563
|
+
suggestionsHtml += '</div>';
|
|
2564
|
+
} else {
|
|
2565
|
+
// Fallback if there are no suggestions
|
|
2566
|
+
suggestionsHtml += \`<div class="code-section"><pre><code>No suggestion provided.</code></pre></div>\`;
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2569
|
+
// Combine both parts and set the modal content
|
|
2570
|
+
modalContent.innerHTML = analysisHtml + suggestionsHtml;
|
|
2571
|
+
})
|
|
2572
|
+
.catch(err => {
|
|
2573
|
+
console.error('AI Fix Error:', err);
|
|
2574
|
+
modalContent.innerHTML = \`<div class="test-error-summary"><strong>Error:</strong> Failed to get AI analysis. Please check the console for details. <br><br> \${err.message}</div>\`;
|
|
2575
|
+
});
|
|
2576
|
+
|
|
2577
|
+
} catch (e) {
|
|
2578
|
+
console.error('Error processing test data for AI Fix:', e);
|
|
2579
|
+
modalTitle.textContent = 'Error';
|
|
2580
|
+
modalContent.innerHTML = \`<div class="test-error-summary">Could not process test data. Is it formatted correctly?</div>\`;
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2583
|
+
|
|
2584
|
+
|
|
2585
|
+
function closeAiModal() {
|
|
2586
|
+
const modal = document.getElementById('ai-fix-modal');
|
|
2587
|
+
if(modal) modal.style.display = 'none';
|
|
2588
|
+
}
|
|
2589
|
+
|
|
2590
|
+
function toggleErrorDetails(button) {
|
|
2591
|
+
const errorDetails = button.closest('.compact-failure-item').querySelector('.full-error-details');
|
|
2592
|
+
const expandText = button.querySelector('.expand-text');
|
|
2593
|
+
const expandIcon = button.querySelector('.expand-icon');
|
|
2594
|
+
|
|
2595
|
+
if (errorDetails.style.display === 'none' || !errorDetails.style.display) {
|
|
2596
|
+
errorDetails.style.display = 'block';
|
|
2597
|
+
expandText.textContent = 'Hide Full Error';
|
|
2598
|
+
button.classList.add('expanded');
|
|
2599
|
+
} else {
|
|
2600
|
+
errorDetails.style.display = 'none';
|
|
2601
|
+
expandText.textContent = 'Show Full Error';
|
|
2602
|
+
button.classList.remove('expanded');
|
|
2603
|
+
}
|
|
2604
|
+
}
|
|
2605
|
+
|
|
2200
2606
|
function initializeReportInteractivity() {
|
|
2201
2607
|
const tabButtons = document.querySelectorAll('.tab-button');
|
|
2202
2608
|
const tabContents = document.querySelectorAll('.tab-content');
|
|
@@ -2209,9 +2615,9 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2209
2615
|
const activeContent = document.getElementById(tabId);
|
|
2210
2616
|
if (activeContent) {
|
|
2211
2617
|
activeContent.classList.add('active');
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2618
|
+
if ('IntersectionObserver' in window) {
|
|
2619
|
+
// Handled by observer
|
|
2620
|
+
}
|
|
2215
2621
|
}
|
|
2216
2622
|
});
|
|
2217
2623
|
});
|
|
@@ -2297,19 +2703,13 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2297
2703
|
if (expandAllBtn) expandAllBtn.addEventListener('click', () => setAllTestRunDetailsVisibility('block', 'true'));
|
|
2298
2704
|
if (collapseAllBtn) collapseAllBtn.addEventListener('click', () => setAllTestRunDetailsVisibility('none', 'false'));
|
|
2299
2705
|
// --- Intersection Observer for Lazy Loading ---
|
|
2300
|
-
const lazyLoadElements = document.querySelectorAll('.lazy-load-chart
|
|
2706
|
+
const lazyLoadElements = document.querySelectorAll('.lazy-load-chart');
|
|
2301
2707
|
if ('IntersectionObserver' in window) {
|
|
2302
2708
|
let lazyObserver = new IntersectionObserver((entries, observer) => {
|
|
2303
2709
|
entries.forEach(entry => {
|
|
2304
2710
|
if (entry.isIntersecting) {
|
|
2305
2711
|
const element = entry.target;
|
|
2306
|
-
if (element.classList.contains('lazy-load-
|
|
2307
|
-
if (element.dataset.src) {
|
|
2308
|
-
element.src = element.dataset.src;
|
|
2309
|
-
element.removeAttribute('data-src'); // Optional: remove data-src after loading
|
|
2310
|
-
console.log('Lazy loaded iframe:', element.title || 'Untitled Iframe');
|
|
2311
|
-
}
|
|
2312
|
-
} else if (element.classList.contains('lazy-load-chart')) {
|
|
2712
|
+
if (element.classList.contains('lazy-load-chart')) {
|
|
2313
2713
|
const renderFunctionName = element.dataset.renderFunctionName;
|
|
2314
2714
|
if (renderFunctionName && typeof window[renderFunctionName] === 'function') {
|
|
2315
2715
|
try {
|
|
@@ -2336,12 +2736,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2336
2736
|
} else { // Fallback for browsers without IntersectionObserver
|
|
2337
2737
|
console.warn("IntersectionObserver not supported. Loading all items immediately.");
|
|
2338
2738
|
lazyLoadElements.forEach(element => {
|
|
2339
|
-
if (element.classList.contains('lazy-load-
|
|
2340
|
-
if (element.dataset.src) {
|
|
2341
|
-
element.src = element.dataset.src;
|
|
2342
|
-
element.removeAttribute('data-src');
|
|
2343
|
-
}
|
|
2344
|
-
} else if (element.classList.contains('lazy-load-chart')) {
|
|
2739
|
+
if (element.classList.contains('lazy-load-chart')) {
|
|
2345
2740
|
const renderFunctionName = element.dataset.renderFunctionName;
|
|
2346
2741
|
if (renderFunctionName && typeof window[renderFunctionName] === 'function') {
|
|
2347
2742
|
try {
|