@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
|
@@ -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
|
|
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
|
-
<
|
|
1752
|
-
<
|
|
1753
|
-
|
|
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
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
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(
|
|
1874
|
-
|
|
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(
|
|
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
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
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(
|
|
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
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
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-${
|
|
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;
|
|
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
|
-
|
|
2860
|
-
|
|
2861
|
-
a.
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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) {
|
package/scripts/sendReport.mjs
CHANGED
|
@@ -28,7 +28,16 @@ try {
|
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
const
|
|
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
|
-
|
|
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 =
|
|
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://
|
|
337
|
+
"https://get-credentials.netlify.app/api/getcredentials",
|
|
328
338
|
{
|
|
329
339
|
method: "GET",
|
|
330
340
|
headers: {
|