@arghajit/dummy 0.1.3 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -3
- package/dist/reporter/playwright-pulse-reporter.js +5 -4
- package/dist/types/index.d.ts +9 -0
- package/package.json +3 -2
- package/scripts/generate-email-report.mjs +12 -1
- package/scripts/generate-report.mjs +423 -193
- package/scripts/generate-static-report.mjs +258 -51
- package/scripts/generate-trend.mjs +12 -1
- package/scripts/merge-pulse-report.js +10 -1
- package/scripts/sendReport.mjs +16 -6
|
@@ -1519,7 +1519,9 @@ function getAttachmentIcon(contentType) {
|
|
|
1519
1519
|
return "📎";
|
|
1520
1520
|
}
|
|
1521
1521
|
function generateAIFailureAnalyzerTab(results) {
|
|
1522
|
-
const failedTests = (results || []).filter(
|
|
1522
|
+
const failedTests = (results || []).filter(
|
|
1523
|
+
(test) => test.status === "failed"
|
|
1524
|
+
);
|
|
1523
1525
|
|
|
1524
1526
|
if (failedTests.length === 0) {
|
|
1525
1527
|
return `
|
|
@@ -1529,7 +1531,7 @@ function generateAIFailureAnalyzerTab(results) {
|
|
|
1529
1531
|
}
|
|
1530
1532
|
|
|
1531
1533
|
// btoa is not available in Node.js environment, so we define a simple polyfill for it.
|
|
1532
|
-
const btoa = (str) => Buffer.from(str).toString(
|
|
1534
|
+
const btoa = (str) => Buffer.from(str).toString("base64");
|
|
1533
1535
|
|
|
1534
1536
|
return `
|
|
1535
1537
|
<h2 class="tab-main-title">AI Failure Analysis</h2>
|
|
@@ -1539,41 +1541,61 @@ function generateAIFailureAnalyzerTab(results) {
|
|
|
1539
1541
|
<span class="stat-label">Failed Tests</span>
|
|
1540
1542
|
</div>
|
|
1541
1543
|
<div class="stat-item">
|
|
1542
|
-
<span class="stat-number">${
|
|
1544
|
+
<span class="stat-number">${
|
|
1545
|
+
new Set(failedTests.map((t) => t.browser)).size
|
|
1546
|
+
}</span>
|
|
1543
1547
|
<span class="stat-label">Browsers</span>
|
|
1544
1548
|
</div>
|
|
1545
1549
|
<div class="stat-item">
|
|
1546
|
-
<span class="stat-number">${
|
|
1550
|
+
<span class="stat-number">${Math.round(
|
|
1551
|
+
failedTests.reduce((sum, test) => sum + (test.duration || 0), 0) /
|
|
1552
|
+
1000
|
|
1553
|
+
)}s</span>
|
|
1547
1554
|
<span class="stat-label">Total Duration</span>
|
|
1548
1555
|
</div>
|
|
1549
1556
|
</div>
|
|
1550
1557
|
<p class="ai-analyzer-description">
|
|
1551
|
-
Analyze failed tests using AI to get suggestions and potential fixes. Click the AI Fix button for
|
|
1558
|
+
Analyze failed tests using AI to get suggestions and potential fixes. Click the AI Fix button for instant analysis or use Copy AI Prompt to analyze with your preferred AI tool.
|
|
1552
1559
|
</p>
|
|
1553
1560
|
|
|
1554
1561
|
<div class="compact-failure-list">
|
|
1555
|
-
${failedTests
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
+
${failedTests
|
|
1563
|
+
.map((test) => {
|
|
1564
|
+
const testTitle = test.name.split(" > ").pop() || "Unnamed Test";
|
|
1565
|
+
const testJson = btoa(JSON.stringify(test)); // Base64 encode the test object
|
|
1566
|
+
const truncatedError =
|
|
1567
|
+
(test.errorMessage || "No error message").slice(0, 150) +
|
|
1568
|
+
(test.errorMessage && test.errorMessage.length > 150 ? "..." : "");
|
|
1569
|
+
|
|
1570
|
+
return `
|
|
1562
1571
|
<div class="compact-failure-item">
|
|
1563
1572
|
<div class="failure-header">
|
|
1564
1573
|
<div class="failure-main-info">
|
|
1565
|
-
<h3 class="failure-title" title="${sanitizeHTML(
|
|
1574
|
+
<h3 class="failure-title" title="${sanitizeHTML(
|
|
1575
|
+
test.name
|
|
1576
|
+
)}">${sanitizeHTML(testTitle)}</h3>
|
|
1566
1577
|
<div class="failure-meta">
|
|
1567
|
-
<span class="browser-indicator">${sanitizeHTML(
|
|
1568
|
-
|
|
1578
|
+
<span class="browser-indicator">${sanitizeHTML(
|
|
1579
|
+
test.browser || "unknown"
|
|
1580
|
+
)}</span>
|
|
1581
|
+
<span class="duration-indicator">${formatDuration(
|
|
1582
|
+
test.duration
|
|
1583
|
+
)}</span>
|
|
1569
1584
|
</div>
|
|
1570
1585
|
</div>
|
|
1571
|
-
<
|
|
1572
|
-
<
|
|
1573
|
-
|
|
1586
|
+
<div class="ai-buttons-group">
|
|
1587
|
+
<button class="compact-ai-btn" onclick="getAIFix(this)" data-test-json="${testJson}">
|
|
1588
|
+
<span class="ai-text">AI Fix</span>
|
|
1589
|
+
</button>
|
|
1590
|
+
<button class="copy-prompt-btn" onclick="copyAIPrompt(this)" data-test-json="${testJson}" title="Copy AI Prompt">
|
|
1591
|
+
<span class="copy-prompt-text">Copy AI Prompt</span>
|
|
1592
|
+
</button>
|
|
1593
|
+
</div>
|
|
1574
1594
|
</div>
|
|
1575
1595
|
<div class="failure-error-preview">
|
|
1576
|
-
<div class="error-snippet">${formatPlaywrightError(
|
|
1596
|
+
<div class="error-snippet">${formatPlaywrightError(
|
|
1597
|
+
truncatedError
|
|
1598
|
+
)}</div>
|
|
1577
1599
|
<button class="expand-error-btn" onclick="toggleErrorDetails(this)">
|
|
1578
1600
|
<span class="expand-text">Show Full Error</span>
|
|
1579
1601
|
<span class="expand-icon">▼</span>
|
|
@@ -1581,12 +1603,15 @@ function generateAIFailureAnalyzerTab(results) {
|
|
|
1581
1603
|
</div>
|
|
1582
1604
|
<div class="full-error-details" style="display: none;">
|
|
1583
1605
|
<div class="full-error-content">
|
|
1584
|
-
${formatPlaywrightError(
|
|
1606
|
+
${formatPlaywrightError(
|
|
1607
|
+
test.errorMessage || "No detailed error message available"
|
|
1608
|
+
)}
|
|
1585
1609
|
</div>
|
|
1586
1610
|
</div>
|
|
1587
1611
|
</div>
|
|
1588
|
-
|
|
1589
|
-
|
|
1612
|
+
`;
|
|
1613
|
+
})
|
|
1614
|
+
.join("")}
|
|
1590
1615
|
</div>
|
|
1591
1616
|
|
|
1592
1617
|
<!-- AI Fix Modal -->
|
|
@@ -1618,7 +1643,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1618
1643
|
const fixPath = (p) => {
|
|
1619
1644
|
if (!p) return "";
|
|
1620
1645
|
// This regex handles both forward slashes and backslashes
|
|
1621
|
-
return p.replace(new RegExp(`^${DEFAULT_OUTPUT_DIR}[\\\\/]`),
|
|
1646
|
+
return p.replace(new RegExp(`^${DEFAULT_OUTPUT_DIR}[\\\\/]`), "");
|
|
1622
1647
|
};
|
|
1623
1648
|
|
|
1624
1649
|
const totalTestsOr1 = runSummary.totalTests || 1;
|
|
@@ -1663,20 +1688,23 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1663
1688
|
)}</span>
|
|
1664
1689
|
</div>
|
|
1665
1690
|
<div class="step-details" style="display: none;">
|
|
1666
|
-
${
|
|
1691
|
+
${
|
|
1692
|
+
step.codeLocation
|
|
1667
1693
|
? `<div class="step-info code-section"><strong>Location:</strong> ${sanitizeHTML(
|
|
1668
|
-
|
|
1669
|
-
|
|
1694
|
+
step.codeLocation
|
|
1695
|
+
)}</div>`
|
|
1670
1696
|
: ""
|
|
1671
|
-
|
|
1672
|
-
${
|
|
1697
|
+
}
|
|
1698
|
+
${
|
|
1699
|
+
step.errorMessage
|
|
1673
1700
|
? `<div class="test-error-summary">
|
|
1674
|
-
${
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1701
|
+
${
|
|
1702
|
+
step.stackTrace
|
|
1703
|
+
? `<div class="stack-trace">${formatPlaywrightError(
|
|
1704
|
+
step.stackTrace
|
|
1705
|
+
)}</div>`
|
|
1706
|
+
: ""
|
|
1707
|
+
}
|
|
1680
1708
|
<button
|
|
1681
1709
|
class="copy-error-btn"
|
|
1682
1710
|
onclick="copyErrorToClipboard(this)"
|
|
@@ -1698,14 +1726,15 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1698
1726
|
</button>
|
|
1699
1727
|
</div>`
|
|
1700
1728
|
: ""
|
|
1701
|
-
|
|
1702
|
-
${
|
|
1729
|
+
}
|
|
1730
|
+
${
|
|
1731
|
+
hasNestedSteps
|
|
1703
1732
|
? `<div class="nested-steps">${generateStepsHTML(
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1733
|
+
step.steps,
|
|
1734
|
+
depth + 1
|
|
1735
|
+
)}</div>`
|
|
1707
1736
|
: ""
|
|
1708
|
-
|
|
1737
|
+
}
|
|
1709
1738
|
</div>
|
|
1710
1739
|
</div>`;
|
|
1711
1740
|
})
|
|
@@ -1713,41 +1742,86 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1713
1742
|
};
|
|
1714
1743
|
|
|
1715
1744
|
return `
|
|
1716
|
-
<div class="test-case" data-status="${
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1745
|
+
<div class="test-case" data-status="${
|
|
1746
|
+
test.status
|
|
1747
|
+
}" data-browser="${sanitizeHTML(browser)}" data-tags="${(test.tags || [])
|
|
1748
|
+
.join(",")
|
|
1749
|
+
.toLowerCase()}">
|
|
1720
1750
|
<div class="test-case-header" role="button" aria-expanded="false">
|
|
1721
1751
|
<div class="test-case-summary">
|
|
1722
1752
|
<span class="status-badge ${getStatusClass(test.status)}">${String(
|
|
1723
|
-
|
|
1724
|
-
|
|
1753
|
+
test.status
|
|
1754
|
+
).toUpperCase()}</span>
|
|
1725
1755
|
<span class="test-case-title" title="${sanitizeHTML(
|
|
1726
1756
|
test.name
|
|
1727
1757
|
)}">${sanitizeHTML(testTitle)}</span>
|
|
1728
1758
|
<span class="test-case-browser">(${sanitizeHTML(browser)})</span>
|
|
1729
1759
|
</div>
|
|
1730
1760
|
<div class="test-case-meta">
|
|
1731
|
-
${
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1761
|
+
${
|
|
1762
|
+
test.tags && test.tags.length > 0
|
|
1763
|
+
? test.tags
|
|
1764
|
+
.map((t) => `<span class="tag">${sanitizeHTML(t)}</span>`)
|
|
1765
|
+
.join(" ")
|
|
1766
|
+
: ""
|
|
1767
|
+
}
|
|
1737
1768
|
<span class="test-duration">${formatDuration(test.duration)}</span>
|
|
1738
1769
|
</div>
|
|
1739
1770
|
</div>
|
|
1740
1771
|
<div class="test-case-content" style="display: none;">
|
|
1741
1772
|
<p><strong>Full Path:</strong> ${sanitizeHTML(test.name)}</p>
|
|
1773
|
+
${
|
|
1774
|
+
test.annotations && test.annotations.length > 0
|
|
1775
|
+
? `<div class="annotations-section" style="margin: 12px 0; padding: 12px; background-color: rgba(139, 92, 246, 0.1); border: 1px solid rgba(139, 92, 246, 0.3); border-left: 4px solid #8b5cf6; border-radius: 4px;">
|
|
1776
|
+
<h4 style="margin-top: 0; margin-bottom: 10px; color: #8b5cf6; font-size: 1.1em;">📌 Annotations</h4>
|
|
1777
|
+
${test.annotations
|
|
1778
|
+
.map((annotation, idx) => {
|
|
1779
|
+
const isIssueOrBug =
|
|
1780
|
+
annotation.type === "issue" ||
|
|
1781
|
+
annotation.type === "bug";
|
|
1782
|
+
const descriptionText = annotation.description || "";
|
|
1783
|
+
const typeLabel = sanitizeHTML(annotation.type);
|
|
1784
|
+
const descriptionHtml =
|
|
1785
|
+
isIssueOrBug && descriptionText.match(/^[A-Z]+-\d+$/)
|
|
1786
|
+
? `<a href="#" class="annotation-link" data-annotation="${sanitizeHTML(
|
|
1787
|
+
descriptionText
|
|
1788
|
+
)}" style="color: #3b82f6; text-decoration: underline; cursor: pointer;">${sanitizeHTML(
|
|
1789
|
+
descriptionText
|
|
1790
|
+
)}</a>`
|
|
1791
|
+
: sanitizeHTML(descriptionText);
|
|
1792
|
+
const locationText = annotation.location
|
|
1793
|
+
? `<div style="font-size: 0.85em; color: #6b7280; margin-top: 4px;">Location: ${sanitizeHTML(
|
|
1794
|
+
annotation.location.file
|
|
1795
|
+
)}:${annotation.location.line}:${
|
|
1796
|
+
annotation.location.column
|
|
1797
|
+
}</div>`
|
|
1798
|
+
: "";
|
|
1799
|
+
return `<div style="margin-bottom: ${
|
|
1800
|
+
idx < test.annotations.length - 1 ? "10px" : "0"
|
|
1801
|
+
};">
|
|
1802
|
+
<strong style="color: #8b5cf6;">Type:</strong> <span style="background-color: rgba(139, 92, 246, 0.2); padding: 2px 8px; border-radius: 4px; font-size: 0.9em;">${typeLabel}</span>
|
|
1803
|
+
${
|
|
1804
|
+
descriptionText
|
|
1805
|
+
? `<br><strong style="color: #8b5cf6;">Description:</strong> ${descriptionHtml}`
|
|
1806
|
+
: ""
|
|
1807
|
+
}
|
|
1808
|
+
${locationText}
|
|
1809
|
+
</div>`;
|
|
1810
|
+
})
|
|
1811
|
+
.join("")}
|
|
1812
|
+
</div>`
|
|
1813
|
+
: ""
|
|
1814
|
+
}
|
|
1742
1815
|
<p><strong>Test run Worker ID:</strong> ${sanitizeHTML(
|
|
1743
1816
|
test.workerId
|
|
1744
1817
|
)} [<strong>Total No. of Workers:</strong> ${sanitizeHTML(
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
${
|
|
1748
|
-
|
|
1749
|
-
test
|
|
1750
|
-
|
|
1818
|
+
test.totalWorkers
|
|
1819
|
+
)}]</p>
|
|
1820
|
+
${
|
|
1821
|
+
test.errorMessage
|
|
1822
|
+
? `<div class="test-error-summary">${formatPlaywrightError(
|
|
1823
|
+
test.errorMessage
|
|
1824
|
+
)}
|
|
1751
1825
|
<button
|
|
1752
1826
|
class="copy-error-btn"
|
|
1753
1827
|
onclick="copyErrorToClipboard(this)"
|
|
@@ -1768,13 +1842,14 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1768
1842
|
Copy Error Prompt
|
|
1769
1843
|
</button>
|
|
1770
1844
|
</div>`
|
|
1771
|
-
|
|
1845
|
+
: ""
|
|
1772
1846
|
}
|
|
1773
|
-
${
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1847
|
+
${
|
|
1848
|
+
test.snippet
|
|
1849
|
+
? `<div class="code-section"><h4>Error Snippet</h4><pre><code>${formatPlaywrightError(
|
|
1850
|
+
test.snippet
|
|
1851
|
+
)}</code></pre></div>`
|
|
1852
|
+
: ""
|
|
1778
1853
|
}
|
|
1779
1854
|
<h4>Steps</h4>
|
|
1780
1855
|
<div class="steps-list">${generateStepsHTML(test.steps)}</div>
|
|
@@ -1793,75 +1868,86 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1793
1868
|
</div>
|
|
1794
1869
|
</div>`;
|
|
1795
1870
|
})()}
|
|
1796
|
-
${
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1871
|
+
${
|
|
1872
|
+
test.stderr && test.stderr.length > 0
|
|
1873
|
+
? `<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(
|
|
1874
|
+
test.stderr.map((line) => sanitizeHTML(line)).join("\n")
|
|
1875
|
+
)}</pre></div>`
|
|
1876
|
+
: ""
|
|
1801
1877
|
}
|
|
1802
|
-
${
|
|
1803
|
-
|
|
1878
|
+
${
|
|
1879
|
+
test.screenshots && test.screenshots.length > 0
|
|
1880
|
+
? `
|
|
1804
1881
|
<div class="attachments-section">
|
|
1805
1882
|
<h4>Screenshots</h4>
|
|
1806
1883
|
<div class="attachments-grid">
|
|
1807
1884
|
${test.screenshots
|
|
1808
|
-
|
|
1809
|
-
|
|
1885
|
+
.map(
|
|
1886
|
+
(screenshot, index) => `
|
|
1810
1887
|
<div class="attachment-item">
|
|
1811
|
-
<img src="${fixPath(screenshot)}" alt="Screenshot ${
|
|
1888
|
+
<img src="${fixPath(screenshot)}" alt="Screenshot ${
|
|
1889
|
+
index + 1
|
|
1890
|
+
}">
|
|
1812
1891
|
<div class="attachment-info">
|
|
1813
1892
|
<div class="trace-actions">
|
|
1814
|
-
<a href="${fixPath(
|
|
1815
|
-
|
|
1893
|
+
<a href="${fixPath(
|
|
1894
|
+
screenshot
|
|
1895
|
+
)}" target="_blank" class="view-full">View Full Image</a>
|
|
1896
|
+
<a href="${fixPath(
|
|
1897
|
+
screenshot
|
|
1898
|
+
)}" target="_blank" download="screenshot-${Date.now()}-${index}.png">Download</a>
|
|
1816
1899
|
</div>
|
|
1817
1900
|
</div>
|
|
1818
1901
|
</div>
|
|
1819
1902
|
`
|
|
1820
|
-
|
|
1821
|
-
|
|
1903
|
+
)
|
|
1904
|
+
.join("")}
|
|
1822
1905
|
</div>
|
|
1823
1906
|
</div>
|
|
1824
1907
|
`
|
|
1825
|
-
|
|
1908
|
+
: ""
|
|
1826
1909
|
}
|
|
1827
|
-
${
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1910
|
+
${
|
|
1911
|
+
test.videoPath && test.videoPath.length > 0
|
|
1912
|
+
? `<div class="attachments-section"><h4>Videos</h4><div class="attachments-grid">${test.videoPath
|
|
1913
|
+
.map((videoUrl, index) => {
|
|
1914
|
+
const fixedVideoUrl = fixPath(videoUrl);
|
|
1915
|
+
const fileExtension = String(fixedVideoUrl)
|
|
1916
|
+
.split(".")
|
|
1917
|
+
.pop()
|
|
1918
|
+
.toLowerCase();
|
|
1919
|
+
const mimeType =
|
|
1920
|
+
{
|
|
1921
|
+
mp4: "video/mp4",
|
|
1922
|
+
webm: "video/webm",
|
|
1923
|
+
ogg: "video/ogg",
|
|
1924
|
+
mov: "video/quicktime",
|
|
1925
|
+
avi: "video/x-msvideo",
|
|
1926
|
+
}[fileExtension] || "video/mp4";
|
|
1927
|
+
return `<div class="attachment-item video-item">
|
|
1928
|
+
<video controls width="100%" height="auto" title="Video ${
|
|
1929
|
+
index + 1
|
|
1930
|
+
}">
|
|
1846
1931
|
<source src="${sanitizeHTML(
|
|
1847
|
-
|
|
1848
|
-
|
|
1932
|
+
fixedVideoUrl
|
|
1933
|
+
)}" type="${mimeType}">
|
|
1849
1934
|
Your browser does not support the video tag.
|
|
1850
1935
|
</video>
|
|
1851
1936
|
<div class="attachment-info">
|
|
1852
1937
|
<div class="trace-actions">
|
|
1853
1938
|
<a href="${sanitizeHTML(
|
|
1854
|
-
|
|
1855
|
-
|
|
1939
|
+
fixedVideoUrl
|
|
1940
|
+
)}" target="_blank" download="video-${Date.now()}-${index}.${fileExtension}">Download</a>
|
|
1856
1941
|
</div>
|
|
1857
1942
|
</div>
|
|
1858
1943
|
</div>`;
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1944
|
+
})
|
|
1945
|
+
.join("")}</div></div>`
|
|
1946
|
+
: ""
|
|
1862
1947
|
}
|
|
1863
|
-
${
|
|
1864
|
-
|
|
1948
|
+
${
|
|
1949
|
+
test.tracePath
|
|
1950
|
+
? `
|
|
1865
1951
|
<div class="attachments-section">
|
|
1866
1952
|
<h4>Trace Files</h4>
|
|
1867
1953
|
<div class="attachments-grid">
|
|
@@ -1869,70 +1955,72 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1869
1955
|
<div class="trace-preview">
|
|
1870
1956
|
<span class="trace-icon">📄</span>
|
|
1871
1957
|
<span class="trace-name">${sanitizeHTML(
|
|
1872
|
-
|
|
1873
|
-
|
|
1958
|
+
path.basename(test.tracePath)
|
|
1959
|
+
)}</span>
|
|
1874
1960
|
</div>
|
|
1875
1961
|
<div class="attachment-info">
|
|
1876
1962
|
<div class="trace-actions">
|
|
1877
1963
|
<a href="${sanitizeHTML(
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1964
|
+
fixPath(test.tracePath)
|
|
1965
|
+
)}" target="_blank" download="${sanitizeHTML(
|
|
1966
|
+
path.basename(test.tracePath)
|
|
1967
|
+
)}" class="download-trace">Download Trace</a>
|
|
1882
1968
|
</div>
|
|
1883
1969
|
</div>
|
|
1884
1970
|
</div>
|
|
1885
1971
|
</div>
|
|
1886
1972
|
</div>
|
|
1887
1973
|
`
|
|
1888
|
-
|
|
1974
|
+
: ""
|
|
1889
1975
|
}
|
|
1890
|
-
${
|
|
1891
|
-
|
|
1976
|
+
${
|
|
1977
|
+
test.attachments && test.attachments.length > 0
|
|
1978
|
+
? `
|
|
1892
1979
|
<div class="attachments-section">
|
|
1893
1980
|
<h4>Other Attachments</h4>
|
|
1894
1981
|
<div class="attachments-grid">
|
|
1895
1982
|
${test.attachments
|
|
1896
|
-
|
|
1897
|
-
|
|
1983
|
+
.map(
|
|
1984
|
+
(attachment) => `
|
|
1898
1985
|
<div class="attachment-item generic-attachment">
|
|
1899
1986
|
<div class="attachment-icon">${getAttachmentIcon(
|
|
1900
|
-
|
|
1901
|
-
|
|
1987
|
+
attachment.contentType
|
|
1988
|
+
)}</div>
|
|
1902
1989
|
<div class="attachment-caption">
|
|
1903
1990
|
<span class="attachment-name" title="${sanitizeHTML(
|
|
1904
|
-
|
|
1905
|
-
|
|
1991
|
+
attachment.name
|
|
1992
|
+
)}">${sanitizeHTML(attachment.name)}</span>
|
|
1906
1993
|
<span class="attachment-type">${sanitizeHTML(
|
|
1907
|
-
|
|
1908
|
-
|
|
1994
|
+
attachment.contentType
|
|
1995
|
+
)}</span>
|
|
1909
1996
|
</div>
|
|
1910
1997
|
<div class="attachment-info">
|
|
1911
1998
|
<div class="trace-actions">
|
|
1912
1999
|
<a href="${sanitizeHTML(
|
|
1913
|
-
|
|
1914
|
-
|
|
2000
|
+
fixPath(attachment.path)
|
|
2001
|
+
)}" target="_blank" class="view-full">View</a>
|
|
1915
2002
|
<a href="${sanitizeHTML(
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
2003
|
+
fixPath(attachment.path)
|
|
2004
|
+
)}" target="_blank" download="${sanitizeHTML(
|
|
2005
|
+
attachment.name
|
|
2006
|
+
)}" class="download-trace">Download</a>
|
|
1920
2007
|
</div>
|
|
1921
2008
|
</div>
|
|
1922
2009
|
</div>
|
|
1923
2010
|
`
|
|
1924
|
-
|
|
1925
|
-
|
|
2011
|
+
)
|
|
2012
|
+
.join("")}
|
|
1926
2013
|
</div>
|
|
1927
2014
|
</div>
|
|
1928
2015
|
`
|
|
1929
|
-
|
|
2016
|
+
: ""
|
|
1930
2017
|
}
|
|
1931
|
-
${
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
2018
|
+
${
|
|
2019
|
+
test.codeSnippet
|
|
2020
|
+
? `<div class="code-section"><h4>Code Snippet</h4><pre><code>${formatPlaywrightError(
|
|
2021
|
+
sanitizeHTML(test.codeSnippet)
|
|
2022
|
+
)}</code></pre></div>`
|
|
2023
|
+
: ""
|
|
1936
2024
|
}
|
|
1937
2025
|
</div>
|
|
1938
2026
|
</div>`;
|
|
@@ -2238,6 +2326,35 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2238
2326
|
.ai-text {
|
|
2239
2327
|
font-size: 0.95em;
|
|
2240
2328
|
}
|
|
2329
|
+
.ai-buttons-group {
|
|
2330
|
+
display: flex;
|
|
2331
|
+
gap: 10px;
|
|
2332
|
+
flex-wrap: wrap;
|
|
2333
|
+
}
|
|
2334
|
+
.copy-prompt-btn {
|
|
2335
|
+
background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);
|
|
2336
|
+
color: white;
|
|
2337
|
+
border: none;
|
|
2338
|
+
padding: 12px 18px;
|
|
2339
|
+
border-radius: 6px;
|
|
2340
|
+
cursor: pointer;
|
|
2341
|
+
font-weight: 600;
|
|
2342
|
+
display: flex;
|
|
2343
|
+
align-items: center;
|
|
2344
|
+
gap: 8px;
|
|
2345
|
+
transition: all 0.3s ease;
|
|
2346
|
+
white-space: nowrap;
|
|
2347
|
+
}
|
|
2348
|
+
.copy-prompt-btn:hover {
|
|
2349
|
+
transform: translateY(-2px);
|
|
2350
|
+
box-shadow: 0 6px 20px rgba(37, 99, 235, 0.4);
|
|
2351
|
+
}
|
|
2352
|
+
.copy-prompt-btn.copied {
|
|
2353
|
+
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
|
2354
|
+
}
|
|
2355
|
+
.copy-prompt-text {
|
|
2356
|
+
font-size: 0.95em;
|
|
2357
|
+
}
|
|
2241
2358
|
.failure-error-preview {
|
|
2242
2359
|
padding: 0 20px 18px 20px;
|
|
2243
2360
|
border-top: 1px solid var(--light-gray-color);
|
|
@@ -2313,9 +2430,14 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2313
2430
|
.failure-meta {
|
|
2314
2431
|
justify-content: center;
|
|
2315
2432
|
}
|
|
2316
|
-
.
|
|
2433
|
+
.ai-buttons-group {
|
|
2434
|
+
flex-direction: column;
|
|
2435
|
+
width: 100%;
|
|
2436
|
+
}
|
|
2437
|
+
.compact-ai-btn, .copy-prompt-btn {
|
|
2317
2438
|
justify-content: center;
|
|
2318
2439
|
padding: 12px 20px;
|
|
2440
|
+
width: 100%;
|
|
2319
2441
|
}
|
|
2320
2442
|
}
|
|
2321
2443
|
@media (max-width: 480px) {
|
|
@@ -2345,8 +2467,8 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2345
2467
|
<h1>Playwright Pulse Report</h1>
|
|
2346
2468
|
</div>
|
|
2347
2469
|
<div class="run-info"><strong>Run Date:</strong> ${formatDate(
|
|
2348
|
-
|
|
2349
|
-
|
|
2470
|
+
runSummary.timestamp
|
|
2471
|
+
)}<br><strong>Total Duration:</strong> ${formatDuration(
|
|
2350
2472
|
runSummary.duration
|
|
2351
2473
|
)}</div>
|
|
2352
2474
|
</header>
|
|
@@ -2358,35 +2480,40 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2358
2480
|
</div>
|
|
2359
2481
|
<div id="dashboard" class="tab-content active">
|
|
2360
2482
|
<div class="dashboard-grid">
|
|
2361
|
-
<div class="summary-card"><h3>Total Tests</h3><div class="value">${
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
<div class="summary-card status-
|
|
2368
|
-
|
|
2483
|
+
<div class="summary-card"><h3>Total Tests</h3><div class="value">${
|
|
2484
|
+
runSummary.totalTests
|
|
2485
|
+
}</div></div>
|
|
2486
|
+
<div class="summary-card status-passed"><h3>Passed</h3><div class="value">${
|
|
2487
|
+
runSummary.passed
|
|
2488
|
+
}</div><div class="trend-percentage">${passPercentage}%</div></div>
|
|
2489
|
+
<div class="summary-card status-failed"><h3>Failed</h3><div class="value">${
|
|
2490
|
+
runSummary.failed
|
|
2491
|
+
}</div><div class="trend-percentage">${failPercentage}%</div></div>
|
|
2492
|
+
<div class="summary-card status-skipped"><h3>Skipped</h3><div class="value">${
|
|
2493
|
+
runSummary.skipped || 0
|
|
2494
|
+
}</div><div class="trend-percentage">${skipPercentage}%</div></div>
|
|
2369
2495
|
<div class="summary-card"><h3>Avg. Test Time</h3><div class="value">${avgTestDuration}</div></div>
|
|
2370
2496
|
<div class="summary-card"><h3>Run Duration</h3><div class="value">${formatDuration(
|
|
2371
|
-
|
|
2372
|
-
|
|
2497
|
+
runSummary.duration
|
|
2498
|
+
)}</div></div>
|
|
2373
2499
|
</div>
|
|
2374
2500
|
<div class="dashboard-bottom-row">
|
|
2375
2501
|
<div style="display: grid; gap: 20px">
|
|
2376
2502
|
${generatePieChart(
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
${
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2503
|
+
[
|
|
2504
|
+
{ label: "Passed", value: runSummary.passed },
|
|
2505
|
+
{ label: "Failed", value: runSummary.failed },
|
|
2506
|
+
{ label: "Skipped", value: runSummary.skipped || 0 },
|
|
2507
|
+
],
|
|
2508
|
+
400,
|
|
2509
|
+
390
|
|
2510
|
+
)}
|
|
2511
|
+
${
|
|
2512
|
+
runSummary.environment &&
|
|
2513
|
+
Object.keys(runSummary.environment).length > 0
|
|
2514
|
+
? generateEnvironmentDashboard(runSummary.environment)
|
|
2515
|
+
: '<div class="no-data">Environment data not available.</div>'
|
|
2516
|
+
}
|
|
2390
2517
|
</div>
|
|
2391
2518
|
${generateSuitesWidget(suitesData)}
|
|
2392
2519
|
</div>
|
|
@@ -2396,17 +2523,17 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2396
2523
|
<input type="text" id="filter-name" placeholder="Filter by test name/path..." style="border-color: black; border-style: outset;">
|
|
2397
2524
|
<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>
|
|
2398
2525
|
<select id="filter-browser"><option value="">All Browsers</option>${Array.from(
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2526
|
+
new Set(
|
|
2527
|
+
(results || []).map((test) => test.browser || "unknown")
|
|
2528
|
+
)
|
|
2529
|
+
)
|
|
2530
|
+
.map(
|
|
2531
|
+
(browser) =>
|
|
2532
|
+
`<option value="${sanitizeHTML(browser)}">${sanitizeHTML(
|
|
2533
|
+
browser
|
|
2534
|
+
)}</option>`
|
|
2535
|
+
)
|
|
2536
|
+
.join("")}</select>
|
|
2410
2537
|
<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>
|
|
2411
2538
|
</div>
|
|
2412
2539
|
<div class="test-cases-list">${generateTestCasesHTML()}</div>
|
|
@@ -2415,16 +2542,18 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2415
2542
|
<h2 class="tab-main-title">Execution Trends</h2>
|
|
2416
2543
|
<div class="trend-charts-row">
|
|
2417
2544
|
<div class="trend-chart"><h3 class="chart-title-header">Test Volume & Outcome Trends</h3>
|
|
2418
|
-
${
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2545
|
+
${
|
|
2546
|
+
trendData && trendData.overall && trendData.overall.length > 0
|
|
2547
|
+
? generateTestTrendsChart(trendData)
|
|
2548
|
+
: '<div class="no-data">Overall trend data not available for test counts.</div>'
|
|
2549
|
+
}
|
|
2422
2550
|
</div>
|
|
2423
2551
|
<div class="trend-chart"><h3 class="chart-title-header">Execution Duration Trends</h3>
|
|
2424
|
-
${
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2552
|
+
${
|
|
2553
|
+
trendData && trendData.overall && trendData.overall.length > 0
|
|
2554
|
+
? generateDurationTrendChart(trendData)
|
|
2555
|
+
: '<div class="no-data">Overall trend data not available for durations.</div>'
|
|
2556
|
+
}
|
|
2428
2557
|
</div>
|
|
2429
2558
|
</div>
|
|
2430
2559
|
<h2 class="tab-main-title">Test Distribution by Worker ${infoTooltip}</h2>
|
|
@@ -2434,12 +2563,13 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2434
2563
|
</div>
|
|
2435
2564
|
</div>
|
|
2436
2565
|
<h2 class="tab-main-title">Individual Test History</h2>
|
|
2437
|
-
${
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2566
|
+
${
|
|
2567
|
+
trendData &&
|
|
2568
|
+
trendData.testRuns &&
|
|
2569
|
+
Object.keys(trendData.testRuns).length > 0
|
|
2570
|
+
? generateTestHistoryContent(trendData)
|
|
2571
|
+
: '<div class="no-data">Individual test history data not available.</div>'
|
|
2572
|
+
}
|
|
2443
2573
|
</div>
|
|
2444
2574
|
<div id="ai-failure-analyzer" class="tab-content">
|
|
2445
2575
|
${generateAIFailureAnalyzerTab(results)}
|
|
@@ -2582,6 +2712,81 @@ function getAIFix(button) {
|
|
|
2582
2712
|
}
|
|
2583
2713
|
|
|
2584
2714
|
|
|
2715
|
+
function copyAIPrompt(button) {
|
|
2716
|
+
try {
|
|
2717
|
+
const testJson = button.dataset.testJson;
|
|
2718
|
+
const test = JSON.parse(atob(testJson));
|
|
2719
|
+
|
|
2720
|
+
const testName = test.name || 'Unknown Test';
|
|
2721
|
+
const failureLogsAndErrors = [
|
|
2722
|
+
'Error Message:',
|
|
2723
|
+
test.errorMessage || 'Not available.',
|
|
2724
|
+
'\\n\\n--- stdout ---',
|
|
2725
|
+
(test.stdout && test.stdout.length > 0) ? test.stdout.join('\\n') : 'Not available.',
|
|
2726
|
+
'\\n\\n--- stderr ---',
|
|
2727
|
+
(test.stderr && test.stderr.length > 0) ? test.stderr.join('\\n') : 'Not available.'
|
|
2728
|
+
].join('\\n');
|
|
2729
|
+
const codeSnippet = test.snippet || '';
|
|
2730
|
+
|
|
2731
|
+
const aiPrompt = \`You are an expert Playwright test automation engineer specializing in debugging test failures.
|
|
2732
|
+
|
|
2733
|
+
INSTRUCTIONS:
|
|
2734
|
+
1. Analyze the test failure carefully
|
|
2735
|
+
2. Provide a brief root cause analysis
|
|
2736
|
+
3. Provide EXACTLY 5 specific, actionable fixes
|
|
2737
|
+
4. Each fix MUST include a code snippet (codeSnippet field)
|
|
2738
|
+
5. Return ONLY valid JSON, no markdown or extra text
|
|
2739
|
+
|
|
2740
|
+
REQUIRED JSON FORMAT:
|
|
2741
|
+
{
|
|
2742
|
+
"rootCause": "Brief explanation of why the test failed",
|
|
2743
|
+
"suggestedFixes": [
|
|
2744
|
+
{
|
|
2745
|
+
"description": "Clear explanation of the fix",
|
|
2746
|
+
"codeSnippet": "await page.waitForSelector('.button', { timeout: 5000 });"
|
|
2747
|
+
}
|
|
2748
|
+
],
|
|
2749
|
+
"affectedTests": ["test1", "test2"]
|
|
2750
|
+
}
|
|
2751
|
+
|
|
2752
|
+
IMPORTANT:
|
|
2753
|
+
- Always return valid JSON only
|
|
2754
|
+
- Always provide exactly 5 fixes in suggestedFixes array
|
|
2755
|
+
- Each fix must have both description and codeSnippet fields
|
|
2756
|
+
- Make code snippets practical and Playwright-specific
|
|
2757
|
+
|
|
2758
|
+
---
|
|
2759
|
+
|
|
2760
|
+
Test Name: \${testName}
|
|
2761
|
+
|
|
2762
|
+
Failure Logs and Errors:
|
|
2763
|
+
\${failureLogsAndErrors}
|
|
2764
|
+
|
|
2765
|
+
Code Snippet:
|
|
2766
|
+
\${codeSnippet}\`;
|
|
2767
|
+
|
|
2768
|
+
navigator.clipboard.writeText(aiPrompt).then(() => {
|
|
2769
|
+
const originalText = button.querySelector('.copy-prompt-text').textContent;
|
|
2770
|
+
button.querySelector('.copy-prompt-text').textContent = 'Copied!';
|
|
2771
|
+
button.classList.add('copied');
|
|
2772
|
+
|
|
2773
|
+
const shortTestName = testName.split(' > ').pop() || testName;
|
|
2774
|
+
alert(\`AI prompt to generate a suggested fix for "\${shortTestName}" has been copied to your clipboard.\`);
|
|
2775
|
+
|
|
2776
|
+
setTimeout(() => {
|
|
2777
|
+
button.querySelector('.copy-prompt-text').textContent = originalText;
|
|
2778
|
+
button.classList.remove('copied');
|
|
2779
|
+
}, 2000);
|
|
2780
|
+
}).catch(err => {
|
|
2781
|
+
console.error('Failed to copy AI prompt:', err);
|
|
2782
|
+
alert('Failed to copy AI prompt to clipboard. Please try again.');
|
|
2783
|
+
});
|
|
2784
|
+
} catch (e) {
|
|
2785
|
+
console.error('Error processing test data for AI Prompt copy:', e);
|
|
2786
|
+
alert('Could not process test data. Please try again.');
|
|
2787
|
+
}
|
|
2788
|
+
}
|
|
2789
|
+
|
|
2585
2790
|
function closeAiModal() {
|
|
2586
2791
|
const modal = document.getElementById('ai-fix-modal');
|
|
2587
2792
|
if(modal) modal.style.display = 'none';
|
|
@@ -2702,6 +2907,19 @@ function getAIFix(button) {
|
|
|
2702
2907
|
}
|
|
2703
2908
|
if (expandAllBtn) expandAllBtn.addEventListener('click', () => setAllTestRunDetailsVisibility('block', 'true'));
|
|
2704
2909
|
if (collapseAllBtn) collapseAllBtn.addEventListener('click', () => setAllTestRunDetailsVisibility('none', 'false'));
|
|
2910
|
+
// --- Annotation Link Handler ---
|
|
2911
|
+
document.querySelectorAll('a.annotation-link').forEach(link => {
|
|
2912
|
+
link.addEventListener('click', (e) => {
|
|
2913
|
+
e.preventDefault();
|
|
2914
|
+
const annotationId = link.dataset.annotation;
|
|
2915
|
+
if (annotationId) {
|
|
2916
|
+
const jiraUrl = prompt('Enter your JIRA/Ticket system base URL (e.g., https://your-company.atlassian.net/browse/):', 'https://your-company.atlassian.net/browse/');
|
|
2917
|
+
if (jiraUrl) {
|
|
2918
|
+
window.open(jiraUrl + annotationId, '_blank');
|
|
2919
|
+
}
|
|
2920
|
+
}
|
|
2921
|
+
});
|
|
2922
|
+
});
|
|
2705
2923
|
// --- Intersection Observer for Lazy Loading ---
|
|
2706
2924
|
const lazyLoadElements = document.querySelectorAll('.lazy-load-chart');
|
|
2707
2925
|
if ('IntersectionObserver' in window) {
|
|
@@ -2817,10 +3035,10 @@ function copyErrorToClipboard(button) {
|
|
|
2817
3035
|
</html>
|
|
2818
3036
|
`;
|
|
2819
3037
|
}
|
|
2820
|
-
async function runScript(scriptPath) {
|
|
3038
|
+
async function runScript(scriptPath, args = []) {
|
|
2821
3039
|
return new Promise((resolve, reject) => {
|
|
2822
3040
|
console.log(chalk.blue(`Executing script: ${scriptPath}...`));
|
|
2823
|
-
const process = fork(scriptPath,
|
|
3041
|
+
const process = fork(scriptPath, args, {
|
|
2824
3042
|
stdio: "inherit",
|
|
2825
3043
|
});
|
|
2826
3044
|
|
|
@@ -2845,13 +3063,24 @@ async function main() {
|
|
|
2845
3063
|
const __filename = fileURLToPath(import.meta.url);
|
|
2846
3064
|
const __dirname = path.dirname(__filename);
|
|
2847
3065
|
|
|
3066
|
+
const args = process.argv.slice(2);
|
|
3067
|
+
let customOutputDir = null;
|
|
3068
|
+
for (let i = 0; i < args.length; i++) {
|
|
3069
|
+
if (args[i] === "--outputDir" || args[i] === "-o") {
|
|
3070
|
+
customOutputDir = args[i + 1];
|
|
3071
|
+
break;
|
|
3072
|
+
}
|
|
3073
|
+
}
|
|
3074
|
+
|
|
2848
3075
|
// Script to archive current run to JSON history (this is your modified "generate-trend.mjs")
|
|
2849
3076
|
const archiveRunScriptPath = path.resolve(
|
|
2850
3077
|
__dirname,
|
|
2851
3078
|
"generate-trend.mjs" // Keeping the filename as per your request
|
|
2852
3079
|
);
|
|
2853
3080
|
|
|
2854
|
-
const outputDir =
|
|
3081
|
+
const outputDir = customOutputDir
|
|
3082
|
+
? path.resolve(process.cwd(), customOutputDir)
|
|
3083
|
+
: path.resolve(process.cwd(), DEFAULT_OUTPUT_DIR);
|
|
2855
3084
|
const reportJsonPath = path.resolve(outputDir, DEFAULT_JSON_FILE); // Current run's main JSON
|
|
2856
3085
|
const reportHtmlPath = path.resolve(outputDir, DEFAULT_HTML_FILE);
|
|
2857
3086
|
|
|
@@ -2864,7 +3093,8 @@ async function main() {
|
|
|
2864
3093
|
|
|
2865
3094
|
// Step 1: Ensure current run data is archived to the history folder
|
|
2866
3095
|
try {
|
|
2867
|
-
|
|
3096
|
+
const archiveArgs = customOutputDir ? ["--outputDir", customOutputDir] : [];
|
|
3097
|
+
await runScript(archiveRunScriptPath, archiveArgs);
|
|
2868
3098
|
console.log(
|
|
2869
3099
|
chalk.green("Current run data archiving to history completed.")
|
|
2870
3100
|
);
|