@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.
@@ -1519,7 +1519,9 @@ function getAttachmentIcon(contentType) {
1519
1519
  return "📎";
1520
1520
  }
1521
1521
  function generateAIFailureAnalyzerTab(results) {
1522
- const failedTests = (results || []).filter(test => test.status === 'failed');
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('base64');
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">${new Set(failedTests.map(t => t.browser)).size}</span>
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">${(Math.round(failedTests.reduce((sum, test) => sum + (test.duration || 0), 0) / 1000))}s</span>
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 specific failed test.
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.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
+ ${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(test.name)}">${sanitizeHTML(testTitle)}</h3>
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(test.browser || 'unknown')}</span>
1568
- <span class="duration-indicator">${formatDuration(test.duration)}</span>
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
- <button class="compact-ai-btn" onclick="getAIFix(this)" data-test-json="${testJson}">
1572
- <span class="ai-text">AI Fix</span>
1573
- </button>
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(truncatedError)}</div>
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(test.errorMessage || "No detailed error message available")}
1606
+ ${formatPlaywrightError(
1607
+ test.errorMessage || "No detailed error message available"
1608
+ )}
1585
1609
  </div>
1586
1610
  </div>
1587
1611
  </div>
1588
- `
1589
- }).join('')}
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
- ${step.codeLocation
1691
+ ${
1692
+ step.codeLocation
1667
1693
  ? `<div class="step-info code-section"><strong>Location:</strong> ${sanitizeHTML(
1668
- step.codeLocation
1669
- )}</div>`
1694
+ step.codeLocation
1695
+ )}</div>`
1670
1696
  : ""
1671
- }
1672
- ${step.errorMessage
1697
+ }
1698
+ ${
1699
+ step.errorMessage
1673
1700
  ? `<div class="test-error-summary">
1674
- ${step.stackTrace
1675
- ? `<div class="stack-trace">${formatPlaywrightError(
1676
- step.stackTrace
1677
- )}</div>`
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
- ${hasNestedSteps
1729
+ }
1730
+ ${
1731
+ hasNestedSteps
1703
1732
  ? `<div class="nested-steps">${generateStepsHTML(
1704
- step.steps,
1705
- depth + 1
1706
- )}</div>`
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="${test.status
1717
- }" data-browser="${sanitizeHTML(browser)}" data-tags="${(test.tags || [])
1718
- .join(",")
1719
- .toLowerCase()}">
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
- test.status
1724
- ).toUpperCase()}</span>
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
- ${test.tags && test.tags.length > 0
1732
- ? test.tags
1733
- .map((t) => `<span class="tag">${sanitizeHTML(t)}</span>`)
1734
- .join(" ")
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
- test.totalWorkers
1746
- )}]</p>
1747
- ${test.errorMessage
1748
- ? `<div class="test-error-summary">${formatPlaywrightError(
1749
- test.errorMessage
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
- ${test.snippet
1774
- ? `<div class="code-section"><h4>Error Snippet</h4><pre><code>${formatPlaywrightError(
1775
- test.snippet
1776
- )}</code></pre></div>`
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
- ${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
- : ""
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
- ${test.screenshots && test.screenshots.length > 0
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
- .map(
1809
- (screenshot, index) => `
1885
+ .map(
1886
+ (screenshot, index) => `
1810
1887
  <div class="attachment-item">
1811
- <img src="${fixPath(screenshot)}" alt="Screenshot ${index + 1}">
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(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>
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
- .join("")}
1903
+ )
1904
+ .join("")}
1822
1905
  </div>
1823
1906
  </div>
1824
1907
  `
1825
- : ""
1908
+ : ""
1826
1909
  }
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
- }">
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
- fixedVideoUrl
1848
- )}" type="${mimeType}">
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
- fixedVideoUrl
1855
- )}" target="_blank" download="video-${Date.now()}-${index}.${fileExtension}">Download</a>
1939
+ fixedVideoUrl
1940
+ )}" target="_blank" download="video-${Date.now()}-${index}.${fileExtension}">Download</a>
1856
1941
  </div>
1857
1942
  </div>
1858
1943
  </div>`;
1859
- })
1860
- .join("")}</div></div>`
1861
- : ""
1944
+ })
1945
+ .join("")}</div></div>`
1946
+ : ""
1862
1947
  }
1863
- ${test.tracePath
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
- path.basename(test.tracePath)
1873
- )}</span>
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
- fixPath(test.tracePath)
1879
- )}" target="_blank" download="${sanitizeHTML(
1880
- path.basename(test.tracePath)
1881
- )}" class="download-trace">Download Trace</a>
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
- ${test.attachments && test.attachments.length > 0
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
- .map(
1897
- (attachment) => `
1983
+ .map(
1984
+ (attachment) => `
1898
1985
  <div class="attachment-item generic-attachment">
1899
1986
  <div class="attachment-icon">${getAttachmentIcon(
1900
- attachment.contentType
1901
- )}</div>
1987
+ attachment.contentType
1988
+ )}</div>
1902
1989
  <div class="attachment-caption">
1903
1990
  <span class="attachment-name" title="${sanitizeHTML(
1904
- attachment.name
1905
- )}">${sanitizeHTML(attachment.name)}</span>
1991
+ attachment.name
1992
+ )}">${sanitizeHTML(attachment.name)}</span>
1906
1993
  <span class="attachment-type">${sanitizeHTML(
1907
- attachment.contentType
1908
- )}</span>
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
- fixPath(attachment.path)
1914
- )}" target="_blank" class="view-full">View</a>
2000
+ fixPath(attachment.path)
2001
+ )}" target="_blank" class="view-full">View</a>
1915
2002
  <a href="${sanitizeHTML(
1916
- fixPath(attachment.path)
1917
- )}" target="_blank" download="${sanitizeHTML(
1918
- attachment.name
1919
- )}" class="download-trace">Download</a>
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
- .join("")}
2011
+ )
2012
+ .join("")}
1926
2013
  </div>
1927
2014
  </div>
1928
2015
  `
1929
- : ""
2016
+ : ""
1930
2017
  }
1931
- ${test.codeSnippet
1932
- ? `<div class="code-section"><h4>Code Snippet</h4><pre><code>${formatPlaywrightError(
1933
- sanitizeHTML(test.codeSnippet)
1934
- )}</code></pre></div>`
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
- .compact-ai-btn {
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
- runSummary.timestamp
2349
- )}<br><strong>Total Duration:</strong> ${formatDuration(
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">${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>
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
- runSummary.duration
2372
- )}</div></div>
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
- { 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
- }
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
- 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>
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
- ${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
- }
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
- ${trendData && trendData.overall && trendData.overall.length > 0
2425
- ? generateDurationTrendChart(trendData)
2426
- : '<div class="no-data">Overall trend data not available for durations.</div>'
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
- ${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
- }
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 = path.resolve(process.cwd(), DEFAULT_OUTPUT_DIR);
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
- await runScript(archiveRunScriptPath); // This script now handles JSON history
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
  );