@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.
@@ -1720,7 +1720,7 @@ function generateAIFailureAnalyzerTab(results) {
1720
1720
  </div>
1721
1721
  </div>
1722
1722
  <p class="ai-analyzer-description">
1723
- Analyze failed tests using AI to get suggestions and potential fixes. Click the AI Fix button for specific failed test.
1723
+ 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.
1724
1724
  </p>
1725
1725
 
1726
1726
  <div class="compact-failure-list">
@@ -1748,9 +1748,14 @@ function generateAIFailureAnalyzerTab(results) {
1748
1748
  )}</span>
1749
1749
  </div>
1750
1750
  </div>
1751
- <button class="compact-ai-btn" onclick="getAIFix(this)" data-test-json="${testJson}">
1752
- <span class="ai-text">AI Fix</span>
1753
- </button>
1751
+ <div class="ai-buttons-group">
1752
+ <button class="compact-ai-btn" onclick="getAIFix(this)" data-test-json="${testJson}">
1753
+ <span class="ai-text">AI Fix</span>
1754
+ </button>
1755
+ <button class="copy-prompt-btn" onclick="copyAIPrompt(this)" data-test-json="${testJson}" title="Copy AI Prompt">
1756
+ <span class="copy-prompt-text">Copy AI Prompt</span>
1757
+ </button>
1758
+ </div>
1754
1759
  </div>
1755
1760
  <div class="failure-error-preview">
1756
1761
  <div class="error-snippet">${formatPlaywrightError(
@@ -1850,43 +1855,53 @@ function generateHTML(reportData, trendData = null) {
1850
1855
  : ""
1851
1856
  }<button class="copy-error-btn" onclick="copyErrorToClipboard(this)">Copy Error Prompt</button></div>`
1852
1857
  : ""
1853
- }${
1854
- (() => {
1855
- if (!step.attachments || step.attachments.length === 0) return "";
1856
- return `<div class="attachments-section"><h4>Step Attachments</h4><div class="attachments-grid">${step.attachments
1857
- .map((attachment) => {
1858
- try {
1859
- const attachmentPath = path.resolve(
1860
- DEFAULT_OUTPUT_DIR,
1861
- attachment.path
1862
- );
1863
- if (!fsExistsSync(attachmentPath)) {
1864
- return `<div class="attachment-item error">Attachment not found: ${sanitizeHTML(
1865
- attachment.name
1866
- )}</div>`;
1867
- }
1868
- const attachmentBase64 = readFileSync(attachmentPath).toString("base64");
1869
- const attachmentDataUri = `data:${attachment.contentType};base64,${attachmentBase64}`;
1870
- return `<div class="attachment-item generic-attachment">
1871
- <div class="attachment-icon">${getAttachmentIcon(attachment.contentType)}</div>
1858
+ }${(() => {
1859
+ if (!step.attachments || step.attachments.length === 0)
1860
+ return "";
1861
+ return `<div class="attachments-section"><h4>Step Attachments</h4><div class="attachments-grid">${step.attachments
1862
+ .map((attachment) => {
1863
+ try {
1864
+ const attachmentPath = path.resolve(
1865
+ DEFAULT_OUTPUT_DIR,
1866
+ attachment.path
1867
+ );
1868
+ if (!fsExistsSync(attachmentPath)) {
1869
+ return `<div class="attachment-item error">Attachment not found: ${sanitizeHTML(
1870
+ attachment.name
1871
+ )}</div>`;
1872
+ }
1873
+ const attachmentBase64 =
1874
+ readFileSync(attachmentPath).toString("base64");
1875
+ const attachmentDataUri = `data:${attachment.contentType};base64,${attachmentBase64}`;
1876
+ return `<div class="attachment-item generic-attachment">
1877
+ <div class="attachment-icon">${getAttachmentIcon(
1878
+ attachment.contentType
1879
+ )}</div>
1872
1880
  <div class="attachment-caption">
1873
- <span class="attachment-name" title="${sanitizeHTML(attachment.name)}">${sanitizeHTML(attachment.name)}</span>
1874
- <span class="attachment-type">${sanitizeHTML(attachment.contentType)}</span>
1881
+ <span class="attachment-name" title="${sanitizeHTML(
1882
+ attachment.name
1883
+ )}">${sanitizeHTML(attachment.name)}</span>
1884
+ <span class="attachment-type">${sanitizeHTML(
1885
+ attachment.contentType
1886
+ )}</span>
1875
1887
  </div>
1876
1888
  <div class="attachment-info">
1877
1889
  <div class="trace-actions">
1878
1890
  <a href="#" data-href="${attachmentDataUri}" class="view-full lazy-load-attachment" target="_blank">View</a>
1879
- <a href="#" data-href="${attachmentDataUri}" class="lazy-load-attachment" download="${sanitizeHTML(attachment.name)}">Download</a>
1891
+ <a href="#" data-href="${attachmentDataUri}" class="lazy-load-attachment" download="${sanitizeHTML(
1892
+ attachment.name
1893
+ )}">Download</a>
1880
1894
  </div>
1881
1895
  </div>
1882
1896
  </div>`;
1883
- } catch (e) {
1884
- return `<div class="attachment-item error">Failed to load attachment: ${sanitizeHTML(attachment.name)}</div>`;
1885
- }
1886
- })
1887
- .join("")}</div></div>`;
1888
- })()
1889
- }${
1897
+ } catch (e) {
1898
+ return `<div class="attachment-item error">Failed to load attachment: ${sanitizeHTML(
1899
+ attachment.name
1900
+ )}</div>`;
1901
+ }
1902
+ })
1903
+ .join("")}</div></div>`;
1904
+ })()}${
1890
1905
  hasNestedSteps
1891
1906
  ? `<div class="nested-steps">${generateStepsHTML(
1892
1907
  step.steps,
@@ -1903,7 +1918,9 @@ function generateHTML(reportData, trendData = null) {
1903
1918
  test.tags || []
1904
1919
  )
1905
1920
  .join(",")
1906
- .toLowerCase()}" data-test-id="${sanitizeHTML(String(test.id || testIndex))}">
1921
+ .toLowerCase()}" data-test-id="${sanitizeHTML(
1922
+ String(test.id || testIndex)
1923
+ )}">
1907
1924
  <div class="test-case-header" role="button" aria-expanded="false"><div class="test-case-summary"><span class="status-badge ${getStatusClass(
1908
1925
  test.status
1909
1926
  )}">${String(
@@ -1927,18 +1944,66 @@ function generateHTML(reportData, trendData = null) {
1927
1944
  <p><strong>Full Path:</strong> ${sanitizeHTML(
1928
1945
  test.name
1929
1946
  )}</p>
1947
+ ${
1948
+ test.annotations && test.annotations.length > 0
1949
+ ? `<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;">
1950
+ <h4 style="margin-top: 0; margin-bottom: 10px; color: #8b5cf6; font-size: 1.1em;">📌 Annotations</h4>
1951
+ ${test.annotations
1952
+ .map((annotation, idx) => {
1953
+ const isIssueOrBug =
1954
+ annotation.type === "issue" ||
1955
+ annotation.type === "bug";
1956
+ const descriptionText =
1957
+ annotation.description || "";
1958
+ const typeLabel = sanitizeHTML(
1959
+ annotation.type
1960
+ );
1961
+ const descriptionHtml =
1962
+ isIssueOrBug &&
1963
+ descriptionText.match(/^[A-Z]+-\d+$/)
1964
+ ? `<a href="#" class="annotation-link" data-annotation="${sanitizeHTML(
1965
+ descriptionText
1966
+ )}" style="color: #3b82f6; text-decoration: underline; cursor: pointer;">${sanitizeHTML(
1967
+ descriptionText
1968
+ )}</a>`
1969
+ : sanitizeHTML(descriptionText);
1970
+ const locationText = annotation.location
1971
+ ? `<div style="font-size: 0.85em; color: #6b7280; margin-top: 4px;">Location: ${sanitizeHTML(
1972
+ annotation.location.file
1973
+ )}:${annotation.location.line}:${
1974
+ annotation.location.column
1975
+ }</div>`
1976
+ : "";
1977
+ return `<div style="margin-bottom: ${
1978
+ idx < test.annotations.length - 1
1979
+ ? "10px"
1980
+ : "0"
1981
+ };">
1982
+ <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>
1983
+ ${
1984
+ descriptionText
1985
+ ? `<br><strong style="color: #8b5cf6;">Description:</strong> ${descriptionHtml}`
1986
+ : ""
1987
+ }
1988
+ ${locationText}
1989
+ </div>`;
1990
+ })
1991
+ .join("")}
1992
+ </div>`
1993
+ : ""
1994
+ }
1930
1995
  <p><strong>Test run Worker ID:</strong> ${sanitizeHTML(
1931
1996
  test.workerId
1932
1997
  )} [<strong>Total No. of Workers:</strong> ${sanitizeHTML(
1933
1998
  test.totalWorkers
1934
1999
  )}]</p>
1935
- ${
1936
- test.errorMessage
1937
- ? `<div class="test-error-summary">${formatPlaywrightError(
1938
- test.errorMessage
1939
- )}<button class="copy-error-btn" onclick="copyErrorToClipboard(this)">Copy Error Prompt</button></div>`
1940
- : ""
1941
- }
2000
+ ${
2001
+ test.errorMessage
2002
+ ? `<div class="test-error-summary">${formatPlaywrightError(
2003
+ test.errorMessage
2004
+ )}<button class="copy-error-btn" onclick="copyErrorToClipboard(this)">Copy Error Prompt</button></div>`
2005
+ : ""
2006
+ }
1942
2007
  ${
1943
2008
  test.snippet
1944
2009
  ? `<div class="code-section"><h4>Error Snippet</h4><pre><code>${formatPlaywrightError(
@@ -1970,7 +2035,9 @@ function generateHTML(reportData, trendData = null) {
1970
2035
  ${
1971
2036
  test.stderr && test.stderr.length > 0
1972
2037
  ? (() => {
1973
- const logId = `stderr-log-${test.id || testIndex}`;
2038
+ const logId = `stderr-log-${
2039
+ test.id || testIndex
2040
+ }`;
1974
2041
  return `<div class="console-output-section"><h4>Console Output (stderr)</h4><pre id="${logId}" class="console-log stderr-log">${test.stderr
1975
2042
  .map((line) => sanitizeHTML(line))
1976
2043
  .join("\\n")}</pre></div>`;
@@ -2369,9 +2436,14 @@ aspect-ratio: 16 / 9;
2369
2436
  .browser-indicator { background: var(--info-color); color: white; }
2370
2437
  #load-more-tests { font-size: 16px; padding: 4px; background-color: var(--light-gray-color); border-radius: 4px; color: var(--text-color); }
2371
2438
  .duration-indicator { background: var(--medium-gray-color); color: var(--text-color); }
2439
+ .ai-buttons-group { display: flex; gap: 10px; flex-wrap: wrap; }
2372
2440
  .compact-ai-btn { background: linear-gradient(135deg, #374151 0%, #1f2937 100%); color: white; border: none; padding: 12px 18px; border-radius: 6px; cursor: pointer; font-weight: 600; display: flex; align-items: center; gap: 8px; transition: all 0.3s ease; white-space: nowrap;}
2373
2441
  .compact-ai-btn:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(55, 65, 81, 0.4); }
2374
2442
  .ai-text { font-size: 0.95em; }
2443
+ .copy-prompt-btn { background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%); color: white; border: none; padding: 12px 18px; border-radius: 6px; cursor: pointer; font-weight: 600; display: flex; align-items: center; gap: 8px; transition: all 0.3s ease; white-space: nowrap;}
2444
+ .copy-prompt-btn:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(37, 99, 235, 0.4); }
2445
+ .copy-prompt-btn.copied { background: linear-gradient(135deg, #10b981 0%, #059669 100%); }
2446
+ .copy-prompt-text { font-size: 0.95em; }
2375
2447
  .failure-error-preview { padding: 0 20px 18px 20px; border-top: 1px solid var(--light-gray-color);}
2376
2448
  .error-snippet { background: rgba(248, 113, 113, 0.1); border: 1px solid rgba(248, 113, 113, 0.3); border-radius: 6px; padding: 12px; margin-bottom: 12px; font-family: monospace; font-size: 0.9em; color: var(--danger-color); line-height: 1.4;}
2377
2449
  .expand-error-btn { background: none; border: 1px solid var(--border-color); color: var(--text-color-secondary); padding: 6px 12px; border-radius: 4px; cursor: pointer; font-size: 0.85em; display: flex; align-items: center; gap: 6px; transition: all 0.2s ease;}
@@ -2382,9 +2454,9 @@ aspect-ratio: 16 / 9;
2382
2454
  .full-error-content { background: rgba(248, 113, 113, 0.1); border: 1px solid rgba(248, 113, 113, 0.3); border-radius: 6px; padding: 15px; font-family: monospace; font-size: 0.9em; color: var(--danger-color); line-height: 1.4; max-height: 300px; overflow-y: auto;}
2383
2455
  @media (max-width: 1200px) { .trend-charts-row { grid-template-columns: 1fr; } }
2384
2456
  @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; } }
2385
- @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; } .ai-analyzer-stats { flex-direction: column; gap: 15px; text-align: center; } .failure-header { flex-direction: column; align-items: stretch; gap: 15px; } .failure-main-info { text-align: center; } .failure-meta { justify-content: center; } .compact-ai-btn { justify-content: center; padding: 12px 20px; } }
2457
+ @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; } .ai-analyzer-stats { flex-direction: column; gap: 15px; text-align: center; } .failure-header { flex-direction: column; align-items: stretch; gap: 15px; } .failure-main-info { text-align: center; } .failure-meta { justify-content: center; } .ai-buttons-group { flex-direction: column; width: 100%; } .compact-ai-btn, .copy-prompt-btn { justify-content: center; padding: 12px 20px; width: 100%; } }
2386
2458
  @media (max-width: 480px) { body {font-size: 14px;} .container {padding: 15px;} .header h1 {font-size: 1.4em;} #report-logo { height: 35px; width: 45px; } .tab-button {padding: 10px 15px; font-size: 1em;} .summary-card .value {font-size: 1.8em;} .attachments-grid {grid-template-columns: 1fr;} .step-item {padding-left: calc(var(--depth, 0) * 18px);} .test-case-content, .step-details {padding: 15px;} .trend-charts-row {gap: 20px;} .trend-chart {padding: 20px;} .stat-item .stat-number { font-size: 1.5em; } .failure-header { padding: 15px; } .failure-error-preview, .full-error-details { padding-left: 15px; padding-right: 15px; } }
2387
- .trace-actions a { text-decoration: none; color: var(--primary-color); font-weight: 500; font-size: 0.9em; }
2459
+ .trace-actions a { text-decoration: none; font-weight: 500; font-size: 0.9em; }
2388
2460
  .generic-attachment { text-align: center; padding: 1rem; justify-content: center; }
2389
2461
  .attachment-icon { font-size: 2.5rem; display: block; margin-bottom: 0.75rem; }
2390
2462
  .attachment-caption { display: flex; flex-direction: column; align-items: center; justify-content: center; flex-grow: 1; }
@@ -2651,6 +2723,81 @@ aspect-ratio: 16 / 9;
2651
2723
  }
2652
2724
  }
2653
2725
 
2726
+ function copyAIPrompt(button) {
2727
+ try {
2728
+ const testJson = button.dataset.testJson;
2729
+ const test = JSON.parse(atob(testJson));
2730
+
2731
+ const testName = test.name || 'Unknown Test';
2732
+ const failureLogsAndErrors = [
2733
+ 'Error Message:',
2734
+ test.errorMessage || 'Not available.',
2735
+ '\\n\\n--- stdout ---',
2736
+ (test.stdout && test.stdout.length > 0) ? test.stdout.join('\\n') : 'Not available.',
2737
+ '\\n\\n--- stderr ---',
2738
+ (test.stderr && test.stderr.length > 0) ? test.stderr.join('\\n') : 'Not available.'
2739
+ ].join('\\n');
2740
+ const codeSnippet = test.snippet || '';
2741
+
2742
+ const aiPrompt = \`You are an expert Playwright test automation engineer specializing in debugging test failures.
2743
+
2744
+ INSTRUCTIONS:
2745
+ 1. Analyze the test failure carefully
2746
+ 2. Provide a brief root cause analysis
2747
+ 3. Provide EXACTLY 5 specific, actionable fixes
2748
+ 4. Each fix MUST include a code snippet (codeSnippet field)
2749
+ 5. Return ONLY valid JSON, no markdown or extra text
2750
+
2751
+ REQUIRED JSON FORMAT:
2752
+ {
2753
+ "rootCause": "Brief explanation of why the test failed",
2754
+ "suggestedFixes": [
2755
+ {
2756
+ "description": "Clear explanation of the fix",
2757
+ "codeSnippet": "await page.waitForSelector('.button', { timeout: 5000 });"
2758
+ }
2759
+ ],
2760
+ "affectedTests": ["test1", "test2"]
2761
+ }
2762
+
2763
+ IMPORTANT:
2764
+ - Always return valid JSON only
2765
+ - Always provide exactly 5 fixes in suggestedFixes array
2766
+ - Each fix must have both description and codeSnippet fields
2767
+ - Make code snippets practical and Playwright-specific
2768
+
2769
+ ---
2770
+
2771
+ Test Name: \${testName}
2772
+
2773
+ Failure Logs and Errors:
2774
+ \${failureLogsAndErrors}
2775
+
2776
+ Code Snippet:
2777
+ \${codeSnippet}\`;
2778
+
2779
+ navigator.clipboard.writeText(aiPrompt).then(() => {
2780
+ const originalText = button.querySelector('.copy-prompt-text').textContent;
2781
+ button.querySelector('.copy-prompt-text').textContent = 'Copied!';
2782
+ button.classList.add('copied');
2783
+
2784
+ const shortTestName = testName.split(' > ').pop() || testName;
2785
+ alert(\`AI prompt to generate a suggested fix for "\${shortTestName}" has been copied to your clipboard.\`);
2786
+
2787
+ setTimeout(() => {
2788
+ button.querySelector('.copy-prompt-text').textContent = originalText;
2789
+ button.classList.remove('copied');
2790
+ }, 2000);
2791
+ }).catch(err => {
2792
+ console.error('Failed to copy AI prompt:', err);
2793
+ alert('Failed to copy AI prompt to clipboard. Please try again.');
2794
+ });
2795
+ } catch (e) {
2796
+ console.error('Error processing test data for AI Prompt copy:', e);
2797
+ alert('Could not process test data. Please try again.');
2798
+ }
2799
+ }
2800
+
2654
2801
  function closeAiModal() {
2655
2802
  const modal = document.getElementById('ai-fix-modal');
2656
2803
  if(modal) modal.style.display = 'none';
@@ -2826,6 +2973,18 @@ aspect-ratio: 16 / 9;
2826
2973
  }
2827
2974
  return;
2828
2975
  }
2976
+ const annotationLink = e.target.closest('a.annotation-link');
2977
+ if (annotationLink) {
2978
+ e.preventDefault();
2979
+ const annotationId = annotationLink.dataset.annotation;
2980
+ if (annotationId) {
2981
+ const jiraUrl = prompt('Enter your JIRA/Ticket system base URL (e.g., https://your-company.atlassian.net/browse/):', 'https://your-company.atlassian.net/browse/');
2982
+ if (jiraUrl) {
2983
+ window.open(jiraUrl + annotationId, '_blank');
2984
+ }
2985
+ }
2986
+ return;
2987
+ }
2829
2988
  const img = e.target.closest('img.lazy-load-image');
2830
2989
  if (img && img.dataset && img.dataset.src) {
2831
2990
  if (e.preventDefault) e.preventDefault();
@@ -2856,9 +3015,45 @@ aspect-ratio: 16 / 9;
2856
3015
  const a = e.target.closest('a.lazy-load-attachment');
2857
3016
  if (a && a.dataset && a.dataset.href) {
2858
3017
  e.preventDefault();
2859
- a.href = a.dataset.href;
2860
- a.removeAttribute('data-href');
2861
- a.click();
3018
+
3019
+ // Special handling for view-full links to avoid about:blank issue
3020
+ if (a.classList.contains('view-full')) {
3021
+ // Extract the data from the data URI
3022
+ const dataUri = a.dataset.href;
3023
+ const [header, base64Data] = dataUri.split(',');
3024
+ const mimeType = header.match(/data:([^;]+)/)[1];
3025
+
3026
+ try {
3027
+ // Convert base64 to blob
3028
+ const byteCharacters = atob(base64Data);
3029
+ const byteNumbers = new Array(byteCharacters.length);
3030
+ for (let i = 0; i < byteCharacters.length; i++) {
3031
+ byteNumbers[i] = byteCharacters.charCodeAt(i);
3032
+ }
3033
+ const byteArray = new Uint8Array(byteNumbers);
3034
+ const blob = new Blob([byteArray], { type: mimeType });
3035
+
3036
+ // Create a URL and open it
3037
+ const blobUrl = URL.createObjectURL(blob);
3038
+ const newWindow = window.open(blobUrl, '_blank');
3039
+
3040
+ // Clean up the URL after a delay
3041
+ setTimeout(() => {
3042
+ URL.revokeObjectURL(blobUrl);
3043
+ }, 1000);
3044
+ } catch (error) {
3045
+ console.error('Failed to open attachment:', error);
3046
+ // Fallback to original method
3047
+ a.href = a.dataset.href;
3048
+ a.removeAttribute('data-href');
3049
+ a.click();
3050
+ }
3051
+ } else {
3052
+ // For download links, use the original method
3053
+ a.href = a.dataset.href;
3054
+ a.removeAttribute('data-href');
3055
+ a.click();
3056
+ }
2862
3057
  return;
2863
3058
  }
2864
3059
  });
@@ -2958,10 +3153,10 @@ aspect-ratio: 16 / 9;
2958
3153
  </html>
2959
3154
  `;
2960
3155
  }
2961
- async function runScript(scriptPath) {
3156
+ async function runScript(scriptPath, args = []) {
2962
3157
  return new Promise((resolve, reject) => {
2963
3158
  console.log(chalk.blue(`Executing script: ${scriptPath}...`));
2964
- const process = fork(scriptPath, [], {
3159
+ const process = fork(scriptPath, args, {
2965
3160
  stdio: "inherit",
2966
3161
  });
2967
3162
 
@@ -2991,13 +3186,24 @@ async function main() {
2991
3186
  const __filename = fileURLToPath(import.meta.url);
2992
3187
  const __dirname = path.dirname(__filename);
2993
3188
 
3189
+ const args = process.argv.slice(2);
3190
+ let customOutputDir = null;
3191
+ for (let i = 0; i < args.length; i++) {
3192
+ if (args[i] === "--outputDir" || args[i] === "-o") {
3193
+ customOutputDir = args[i + 1];
3194
+ break;
3195
+ }
3196
+ }
3197
+
2994
3198
  // Script to archive current run to JSON history (this is your modified "generate-trend.mjs")
2995
3199
  const archiveRunScriptPath = path.resolve(
2996
3200
  __dirname,
2997
3201
  "generate-trend.mjs" // Keeping the filename as per your request
2998
3202
  );
2999
3203
 
3000
- const outputDir = path.resolve(process.cwd(), DEFAULT_OUTPUT_DIR);
3204
+ const outputDir = customOutputDir
3205
+ ? path.resolve(process.cwd(), customOutputDir)
3206
+ : path.resolve(process.cwd(), DEFAULT_OUTPUT_DIR);
3001
3207
  const reportJsonPath = path.resolve(outputDir, DEFAULT_JSON_FILE); // Current run's main JSON
3002
3208
  const reportHtmlPath = path.resolve(outputDir, DEFAULT_HTML_FILE);
3003
3209
 
@@ -3010,7 +3216,8 @@ async function main() {
3010
3216
 
3011
3217
  // Step 1: Ensure current run data is archived to the history folder
3012
3218
  try {
3013
- await runScript(archiveRunScriptPath); // This script now handles JSON history
3219
+ const archiveArgs = customOutputDir ? ["--outputDir", customOutputDir] : [];
3220
+ await runScript(archiveRunScriptPath, archiveArgs);
3014
3221
  console.log(
3015
3222
  chalk.green("Current run data archiving to history completed.")
3016
3223
  );
@@ -22,8 +22,19 @@ const HISTORY_SUBDIR = "history"; // Subdirectory for historical JSON files
22
22
  const HISTORY_FILE_PREFIX = "trend-";
23
23
  const MAX_HISTORY_FILES = 15; // Store last 15 runs
24
24
 
25
+ const args = process.argv.slice(2);
26
+ let customOutputDir = null;
27
+ for (let i = 0; i < args.length; i++) {
28
+ if (args[i] === '--outputDir' || args[i] === '-o') {
29
+ customOutputDir = args[i + 1];
30
+ break;
31
+ }
32
+ }
33
+
25
34
  async function archiveCurrentRunData() {
26
- const outputDir = path.resolve(process.cwd(), DEFAULT_OUTPUT_DIR);
35
+ const outputDir = customOutputDir
36
+ ? path.resolve(process.cwd(), customOutputDir)
37
+ : path.resolve(process.cwd(), DEFAULT_OUTPUT_DIR);
27
38
  const currentRunJsonPath = path.join(outputDir, CURRENT_RUN_JSON_FILE);
28
39
  const historyDir = path.join(outputDir, HISTORY_SUBDIR);
29
40
 
@@ -3,7 +3,16 @@
3
3
  const fs = require("fs");
4
4
  const path = require("path");
5
5
 
6
- const REPORT_DIR = "./pulse-report"; // Or change this to your reports directory
6
+ const args = process.argv.slice(2);
7
+ let customOutputDir = null;
8
+ for (let i = 0; i < args.length; i++) {
9
+ if (args[i] === '--outputDir' || args[i] === '-o') {
10
+ customOutputDir = args[i + 1];
11
+ break;
12
+ }
13
+ }
14
+
15
+ const REPORT_DIR = customOutputDir || "./pulse-report";
7
16
  const OUTPUT_FILE = "playwright-pulse-report.json";
8
17
 
9
18
  function getReportFiles(dir) {
@@ -28,7 +28,16 @@ try {
28
28
  };
29
29
  }
30
30
 
31
- const reportDir = "./pulse-report";
31
+ const args = process.argv.slice(2);
32
+ let customOutputDir = null;
33
+ for (let i = 0; i < args.length; i++) {
34
+ if (args[i] === "--outputDir" || args[i] === "-o") {
35
+ customOutputDir = args[i + 1];
36
+ break;
37
+ }
38
+ }
39
+
40
+ const reportDir = customOutputDir || "./pulse-report";
32
41
 
33
42
  let fetch;
34
43
  // Ensure fetch is imported and available before it's used in fetchCredentials
@@ -220,9 +229,9 @@ const archiveRunScriptPath = path.resolve(
220
229
  "generate-email-report.mjs" // Or input_file_0.mjs if you rename it, or input_file_0.js if you configure package.json
221
230
  );
222
231
 
223
- async function runScript(scriptPath) {
232
+ async function runScript(scriptPath, args = []) {
224
233
  return new Promise((resolve, reject) => {
225
- const childProcess = fork(scriptPath, [], {
234
+ const childProcess = fork(scriptPath, args, {
226
235
  // Renamed variable
227
236
  stdio: "inherit",
228
237
  });
@@ -245,7 +254,8 @@ async function runScript(scriptPath) {
245
254
  }
246
255
 
247
256
  const sendEmail = async (credentials) => {
248
- await runScript(archiveRunScriptPath);
257
+ const archiveArgs = customOutputDir ? ["--outputDir", customOutputDir] : [];
258
+ await runScript(archiveRunScriptPath, archiveArgs);
249
259
  try {
250
260
  console.log("Starting the sendEmail function...");
251
261
 
@@ -289,7 +299,7 @@ const sendEmail = async (credentials) => {
289
299
  }
290
300
  };
291
301
 
292
- async function fetchCredentials(retries = 6) {
302
+ async function fetchCredentials(retries = 10) {
293
303
  // Ensure fetch is initialized from the dynamic import before calling this
294
304
  if (!fetch) {
295
305
  try {
@@ -324,7 +334,7 @@ async function fetchCredentials(retries = 6) {
324
334
  });
325
335
 
326
336
  const fetchPromise = fetch(
327
- "https://test-dashboard-66zd.onrender.com/api/getcredentials",
337
+ "https://get-credentials.netlify.app/api/getcredentials",
328
338
  {
329
339
  method: "GET",
330
340
  headers: {