@arghajit/dummy 0.1.0-beta-26 → 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.
|
@@ -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,14 +1768,20 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1698
1768
|
Copy Error Prompt
|
|
1699
1769
|
</button>
|
|
1700
1770
|
</div>`
|
|
1701
|
-
|
|
1771
|
+
: ""
|
|
1772
|
+
}
|
|
1773
|
+
${test.snippet
|
|
1774
|
+
? `<div class="code-section"><h4>Error Snippet</h4><pre><code>${formatPlaywrightError(
|
|
1775
|
+
test.snippet
|
|
1776
|
+
)}</code></pre></div>`
|
|
1777
|
+
: ""
|
|
1702
1778
|
}
|
|
1703
1779
|
<h4>Steps</h4>
|
|
1704
1780
|
<div class="steps-list">${generateStepsHTML(test.steps)}</div>
|
|
1705
1781
|
${(() => {
|
|
1706
1782
|
if (!test.stdout || test.stdout.length === 0) return "";
|
|
1707
1783
|
// Create a unique ID for the <pre> element to target it for copying
|
|
1708
|
-
const logId = `stdout-log-${test.id ||
|
|
1784
|
+
const logId = `stdout-log-${test.id || index}`;
|
|
1709
1785
|
return `<div class="console-output-section">
|
|
1710
1786
|
<h4>Console Output (stdout)
|
|
1711
1787
|
<button class="copy-btn" onclick="copyLogContent('${logId}', this)">Copy Console</button>
|
|
@@ -1717,79 +1793,75 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1717
1793
|
</div>
|
|
1718
1794
|
</div>`;
|
|
1719
1795
|
})()}
|
|
1720
|
-
${
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
: ""
|
|
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
|
+
: ""
|
|
1726
1801
|
}
|
|
1727
|
-
${
|
|
1728
|
-
|
|
1729
|
-
? `
|
|
1802
|
+
${test.screenshots && test.screenshots.length > 0
|
|
1803
|
+
? `
|
|
1730
1804
|
<div class="attachments-section">
|
|
1731
1805
|
<h4>Screenshots</h4>
|
|
1732
1806
|
<div class="attachments-grid">
|
|
1733
1807
|
${test.screenshots
|
|
1734
|
-
|
|
1735
|
-
|
|
1808
|
+
.map(
|
|
1809
|
+
(screenshot, index) => `
|
|
1736
1810
|
<div class="attachment-item">
|
|
1737
|
-
<img src="${screenshot}" alt="Screenshot ${index + 1}">
|
|
1811
|
+
<img src="${fixPath(screenshot)}" alt="Screenshot ${index + 1}">
|
|
1738
1812
|
<div class="attachment-info">
|
|
1739
1813
|
<div class="trace-actions">
|
|
1740
|
-
<a href="${screenshot}" target="_blank" class="view-full">View Full Image</a>
|
|
1741
|
-
<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>
|
|
1742
1816
|
</div>
|
|
1743
1817
|
</div>
|
|
1744
1818
|
</div>
|
|
1745
1819
|
`
|
|
1746
|
-
|
|
1747
|
-
|
|
1820
|
+
)
|
|
1821
|
+
.join("")}
|
|
1748
1822
|
</div>
|
|
1749
1823
|
</div>
|
|
1750
1824
|
`
|
|
1751
|
-
|
|
1825
|
+
: ""
|
|
1752
1826
|
}
|
|
1753
|
-
${
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
<video controls width="100%" height="auto" title="Video ${
|
|
1771
|
-
|
|
1772
|
-
}">
|
|
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
|
+
}">
|
|
1773
1846
|
<source src="${sanitizeHTML(
|
|
1774
|
-
|
|
1775
|
-
|
|
1847
|
+
fixedVideoUrl
|
|
1848
|
+
)}" type="${mimeType}">
|
|
1776
1849
|
Your browser does not support the video tag.
|
|
1777
1850
|
</video>
|
|
1778
1851
|
<div class="attachment-info">
|
|
1779
1852
|
<div class="trace-actions">
|
|
1780
1853
|
<a href="${sanitizeHTML(
|
|
1781
|
-
|
|
1782
|
-
|
|
1854
|
+
fixedVideoUrl
|
|
1855
|
+
)}" target="_blank" download="video-${Date.now()}-${index}.${fileExtension}">Download</a>
|
|
1783
1856
|
</div>
|
|
1784
1857
|
</div>
|
|
1785
1858
|
</div>`;
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1859
|
+
})
|
|
1860
|
+
.join("")}</div></div>`
|
|
1861
|
+
: ""
|
|
1789
1862
|
}
|
|
1790
|
-
${
|
|
1791
|
-
|
|
1792
|
-
? `
|
|
1863
|
+
${test.tracePath
|
|
1864
|
+
? `
|
|
1793
1865
|
<div class="attachments-section">
|
|
1794
1866
|
<h4>Trace Files</h4>
|
|
1795
1867
|
<div class="attachments-grid">
|
|
@@ -1797,72 +1869,70 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1797
1869
|
<div class="trace-preview">
|
|
1798
1870
|
<span class="trace-icon">📄</span>
|
|
1799
1871
|
<span class="trace-name">${sanitizeHTML(
|
|
1800
|
-
|
|
1801
|
-
|
|
1872
|
+
path.basename(test.tracePath)
|
|
1873
|
+
)}</span>
|
|
1802
1874
|
</div>
|
|
1803
1875
|
<div class="attachment-info">
|
|
1804
1876
|
<div class="trace-actions">
|
|
1805
1877
|
<a href="${sanitizeHTML(
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1878
|
+
fixPath(test.tracePath)
|
|
1879
|
+
)}" target="_blank" download="${sanitizeHTML(
|
|
1880
|
+
path.basename(test.tracePath)
|
|
1881
|
+
)}" class="download-trace">Download Trace</a>
|
|
1810
1882
|
</div>
|
|
1811
1883
|
</div>
|
|
1812
1884
|
</div>
|
|
1813
1885
|
</div>
|
|
1814
1886
|
</div>
|
|
1815
1887
|
`
|
|
1816
|
-
|
|
1888
|
+
: ""
|
|
1817
1889
|
}
|
|
1818
|
-
${
|
|
1819
|
-
|
|
1820
|
-
? `
|
|
1890
|
+
${test.attachments && test.attachments.length > 0
|
|
1891
|
+
? `
|
|
1821
1892
|
<div class="attachments-section">
|
|
1822
1893
|
<h4>Other Attachments</h4>
|
|
1823
1894
|
<div class="attachments-grid">
|
|
1824
1895
|
${test.attachments
|
|
1825
|
-
|
|
1826
|
-
|
|
1896
|
+
.map(
|
|
1897
|
+
(attachment) => `
|
|
1827
1898
|
<div class="attachment-item generic-attachment">
|
|
1828
1899
|
<div class="attachment-icon">${getAttachmentIcon(
|
|
1829
|
-
|
|
1830
|
-
|
|
1900
|
+
attachment.contentType
|
|
1901
|
+
)}</div>
|
|
1831
1902
|
<div class="attachment-caption">
|
|
1832
1903
|
<span class="attachment-name" title="${sanitizeHTML(
|
|
1833
|
-
|
|
1834
|
-
|
|
1904
|
+
attachment.name
|
|
1905
|
+
)}">${sanitizeHTML(attachment.name)}</span>
|
|
1835
1906
|
<span class="attachment-type">${sanitizeHTML(
|
|
1836
|
-
|
|
1837
|
-
|
|
1907
|
+
attachment.contentType
|
|
1908
|
+
)}</span>
|
|
1838
1909
|
</div>
|
|
1839
1910
|
<div class="attachment-info">
|
|
1840
1911
|
<div class="trace-actions">
|
|
1841
1912
|
<a href="${sanitizeHTML(
|
|
1842
|
-
|
|
1843
|
-
|
|
1913
|
+
fixPath(attachment.path)
|
|
1914
|
+
)}" target="_blank" class="view-full">View</a>
|
|
1844
1915
|
<a href="${sanitizeHTML(
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1916
|
+
fixPath(attachment.path)
|
|
1917
|
+
)}" target="_blank" download="${sanitizeHTML(
|
|
1918
|
+
attachment.name
|
|
1919
|
+
)}" class="download-trace">Download</a>
|
|
1849
1920
|
</div>
|
|
1850
1921
|
</div>
|
|
1851
1922
|
</div>
|
|
1852
1923
|
`
|
|
1853
|
-
|
|
1854
|
-
|
|
1924
|
+
)
|
|
1925
|
+
.join("")}
|
|
1855
1926
|
</div>
|
|
1856
1927
|
</div>
|
|
1857
1928
|
`
|
|
1858
|
-
|
|
1929
|
+
: ""
|
|
1859
1930
|
}
|
|
1860
|
-
${
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
: ""
|
|
1931
|
+
${test.codeSnippet
|
|
1932
|
+
? `<div class="code-section"><h4>Code Snippet</h4><pre><code>${formatPlaywrightError(
|
|
1933
|
+
sanitizeHTML(test.codeSnippet)
|
|
1934
|
+
)}</code></pre></div>`
|
|
1935
|
+
: ""
|
|
1866
1936
|
}
|
|
1867
1937
|
</div>
|
|
1868
1938
|
</div>`;
|
|
@@ -2004,7 +2074,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2004
2074
|
.attachment-name { font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
2005
2075
|
.attachment-type { font-size: 0.8rem; color: var(--text-color-secondary); }
|
|
2006
2076
|
.trend-charts-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(480px, 1fr)); gap: 28px; margin-bottom: 35px; }
|
|
2007
|
-
.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;}
|
|
2008
2078
|
.test-history-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(380px, 1fr)); gap: 22px; margin-top: 22px; }
|
|
2009
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; }
|
|
2010
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); }
|
|
@@ -2024,8 +2094,24 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2024
2094
|
.status-badge-small.status-unknown { background-color: var(--dark-gray-color); }
|
|
2025
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); }
|
|
2026
2096
|
.no-data-chart {font-size: 0.95em; padding: 18px;}
|
|
2027
|
-
|
|
2028
|
-
|
|
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); } }
|
|
2029
2115
|
.trace-preview { padding: 1rem; text-align: center; background: #f5f5f5; border-bottom: 1px solid #e1e1e1; }
|
|
2030
2116
|
.trace-icon { font-size: 2rem; display: block; margin-bottom: 0.5rem; }
|
|
2031
2117
|
.trace-name { word-break: break-word; font-size: 0.9rem; }
|
|
@@ -2038,9 +2124,216 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2038
2124
|
.filters button.clear-filters-btn { background-color: var(--medium-gray-color); color: var(--text-color); }
|
|
2039
2125
|
.filters button.clear-filters-btn:hover { background-color: var(--dark-gray-color); color: #fff; }
|
|
2040
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
|
+
|
|
2041
2334
|
@media (max-width: 1200px) { .trend-charts-row { grid-template-columns: 1fr; } }
|
|
2042
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; } }
|
|
2043
|
-
@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; } }
|
|
2044
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;} }
|
|
2045
2338
|
</style>
|
|
2046
2339
|
</head>
|
|
@@ -2052,8 +2345,8 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2052
2345
|
<h1>Playwright Pulse Report</h1>
|
|
2053
2346
|
</div>
|
|
2054
2347
|
<div class="run-info"><strong>Run Date:</strong> ${formatDate(
|
|
2055
|
-
|
|
2056
|
-
|
|
2348
|
+
runSummary.timestamp
|
|
2349
|
+
)}<br><strong>Total Duration:</strong> ${formatDuration(
|
|
2057
2350
|
runSummary.duration
|
|
2058
2351
|
)}</div>
|
|
2059
2352
|
</header>
|
|
@@ -2061,44 +2354,39 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2061
2354
|
<button class="tab-button active" data-tab="dashboard">Dashboard</button>
|
|
2062
2355
|
<button class="tab-button" data-tab="test-runs">Test Run Summary</button>
|
|
2063
2356
|
<button class="tab-button" data-tab="test-history">Test History</button>
|
|
2064
|
-
<button class="tab-button" data-tab="
|
|
2357
|
+
<button class="tab-button" data-tab="ai-failure-analyzer">AI Failure Analyzer</button>
|
|
2065
2358
|
</div>
|
|
2066
2359
|
<div id="dashboard" class="tab-content active">
|
|
2067
2360
|
<div class="dashboard-grid">
|
|
2068
|
-
<div class="summary-card"><h3>Total Tests</h3><div class="value">${
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
<div class="summary-card status-
|
|
2075
|
-
|
|
2076
|
-
}</div><div class="trend-percentage">${failPercentage}%</div></div>
|
|
2077
|
-
<div class="summary-card status-skipped"><h3>Skipped</h3><div class="value">${
|
|
2078
|
-
runSummary.skipped || 0
|
|
2079
|
-
}</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>
|
|
2080
2369
|
<div class="summary-card"><h3>Avg. Test Time</h3><div class="value">${avgTestDuration}</div></div>
|
|
2081
2370
|
<div class="summary-card"><h3>Run Duration</h3><div class="value">${formatDuration(
|
|
2082
|
-
|
|
2083
|
-
|
|
2371
|
+
runSummary.duration
|
|
2372
|
+
)}</div></div>
|
|
2084
2373
|
</div>
|
|
2085
2374
|
<div class="dashboard-bottom-row">
|
|
2086
2375
|
<div style="display: grid; gap: 20px">
|
|
2087
2376
|
${generatePieChart(
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
${
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
}
|
|
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
|
+
}
|
|
2102
2390
|
</div>
|
|
2103
2391
|
${generateSuitesWidget(suitesData)}
|
|
2104
2392
|
</div>
|
|
@@ -2108,17 +2396,17 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2108
2396
|
<input type="text" id="filter-name" placeholder="Filter by test name/path..." style="border-color: black; border-style: outset;">
|
|
2109
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>
|
|
2110
2398
|
<select id="filter-browser"><option value="">All Browsers</option>${Array.from(
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
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>
|
|
2122
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>
|
|
2123
2411
|
</div>
|
|
2124
2412
|
<div class="test-cases-list">${generateTestCasesHTML()}</div>
|
|
@@ -2127,18 +2415,16 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2127
2415
|
<h2 class="tab-main-title">Execution Trends</h2>
|
|
2128
2416
|
<div class="trend-charts-row">
|
|
2129
2417
|
<div class="trend-chart"><h3 class="chart-title-header">Test Volume & Outcome Trends</h3>
|
|
2130
|
-
${
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
}
|
|
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
|
+
}
|
|
2135
2422
|
</div>
|
|
2136
2423
|
<div class="trend-chart"><h3 class="chart-title-header">Execution Duration Trends</h3>
|
|
2137
|
-
${
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
}
|
|
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
|
+
}
|
|
2142
2428
|
</div>
|
|
2143
2429
|
</div>
|
|
2144
2430
|
<h2 class="tab-main-title">Test Distribution by Worker ${infoTooltip}</h2>
|
|
@@ -2148,16 +2434,15 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2148
2434
|
</div>
|
|
2149
2435
|
</div>
|
|
2150
2436
|
<h2 class="tab-main-title">Individual Test History</h2>
|
|
2151
|
-
${
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
}
|
|
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
|
+
}
|
|
2158
2443
|
</div>
|
|
2159
|
-
<div id="
|
|
2160
|
-
|
|
2444
|
+
<div id="ai-failure-analyzer" class="tab-content">
|
|
2445
|
+
${generateAIFailureAnalyzerTab(results)}
|
|
2161
2446
|
</div>
|
|
2162
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;">
|
|
2163
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;">
|
|
@@ -2189,7 +2474,135 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2189
2474
|
button.textContent = 'Failed';
|
|
2190
2475
|
setTimeout(() => { button.textContent = 'Copy'; }, 2000);
|
|
2191
2476
|
});
|
|
2192
|
-
}
|
|
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
|
+
|
|
2193
2606
|
function initializeReportInteractivity() {
|
|
2194
2607
|
const tabButtons = document.querySelectorAll('.tab-button');
|
|
2195
2608
|
const tabContents = document.querySelectorAll('.tab-content');
|
|
@@ -2202,9 +2615,9 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2202
2615
|
const activeContent = document.getElementById(tabId);
|
|
2203
2616
|
if (activeContent) {
|
|
2204
2617
|
activeContent.classList.add('active');
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2618
|
+
if ('IntersectionObserver' in window) {
|
|
2619
|
+
// Handled by observer
|
|
2620
|
+
}
|
|
2208
2621
|
}
|
|
2209
2622
|
});
|
|
2210
2623
|
});
|
|
@@ -2290,19 +2703,13 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2290
2703
|
if (expandAllBtn) expandAllBtn.addEventListener('click', () => setAllTestRunDetailsVisibility('block', 'true'));
|
|
2291
2704
|
if (collapseAllBtn) collapseAllBtn.addEventListener('click', () => setAllTestRunDetailsVisibility('none', 'false'));
|
|
2292
2705
|
// --- Intersection Observer for Lazy Loading ---
|
|
2293
|
-
const lazyLoadElements = document.querySelectorAll('.lazy-load-chart
|
|
2706
|
+
const lazyLoadElements = document.querySelectorAll('.lazy-load-chart');
|
|
2294
2707
|
if ('IntersectionObserver' in window) {
|
|
2295
2708
|
let lazyObserver = new IntersectionObserver((entries, observer) => {
|
|
2296
2709
|
entries.forEach(entry => {
|
|
2297
2710
|
if (entry.isIntersecting) {
|
|
2298
2711
|
const element = entry.target;
|
|
2299
|
-
if (element.classList.contains('lazy-load-
|
|
2300
|
-
if (element.dataset.src) {
|
|
2301
|
-
element.src = element.dataset.src;
|
|
2302
|
-
element.removeAttribute('data-src'); // Optional: remove data-src after loading
|
|
2303
|
-
console.log('Lazy loaded iframe:', element.title || 'Untitled Iframe');
|
|
2304
|
-
}
|
|
2305
|
-
} else if (element.classList.contains('lazy-load-chart')) {
|
|
2712
|
+
if (element.classList.contains('lazy-load-chart')) {
|
|
2306
2713
|
const renderFunctionName = element.dataset.renderFunctionName;
|
|
2307
2714
|
if (renderFunctionName && typeof window[renderFunctionName] === 'function') {
|
|
2308
2715
|
try {
|
|
@@ -2329,12 +2736,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2329
2736
|
} else { // Fallback for browsers without IntersectionObserver
|
|
2330
2737
|
console.warn("IntersectionObserver not supported. Loading all items immediately.");
|
|
2331
2738
|
lazyLoadElements.forEach(element => {
|
|
2332
|
-
if (element.classList.contains('lazy-load-
|
|
2333
|
-
if (element.dataset.src) {
|
|
2334
|
-
element.src = element.dataset.src;
|
|
2335
|
-
element.removeAttribute('data-src');
|
|
2336
|
-
}
|
|
2337
|
-
} else if (element.classList.contains('lazy-load-chart')) {
|
|
2739
|
+
if (element.classList.contains('lazy-load-chart')) {
|
|
2338
2740
|
const renderFunctionName = element.dataset.renderFunctionName;
|
|
2339
2741
|
if (renderFunctionName && typeof window[renderFunctionName] === 'function') {
|
|
2340
2742
|
try {
|