@arghajit/dummy 0.1.0-beta-18 → 0.1.0-beta-19
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.
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import * as fs from "fs/promises";
|
|
4
|
-
import { readFileSync, existsSync as fsExistsSync } from "fs";
|
|
4
|
+
import { readFileSync, existsSync as fsExistsSync } from "fs";
|
|
5
5
|
import path from "path";
|
|
6
|
-
import { fork } from "child_process";
|
|
7
|
-
import { fileURLToPath } from "url";
|
|
6
|
+
import { fork } from "child_process";
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
8
|
|
|
9
9
|
// Use dynamic import for chalk as it's ESM only
|
|
10
10
|
let chalk;
|
|
@@ -157,13 +157,13 @@ function sanitizeHTML(str) {
|
|
|
157
157
|
"<": "<",
|
|
158
158
|
">": ">",
|
|
159
159
|
'"': '"',
|
|
160
|
-
"'": "'",
|
|
160
|
+
"'": "'",
|
|
161
161
|
};
|
|
162
162
|
return replacements[match] || match;
|
|
163
163
|
});
|
|
164
164
|
}
|
|
165
165
|
function capitalize(str) {
|
|
166
|
-
if (!str) return "";
|
|
166
|
+
if (!str) return "";
|
|
167
167
|
return str[0].toUpperCase() + str.slice(1).toLowerCase();
|
|
168
168
|
}
|
|
169
169
|
function formatPlaywrightError(error) {
|
|
@@ -171,22 +171,18 @@ function formatPlaywrightError(error) {
|
|
|
171
171
|
return convertPlaywrightErrorToHTML(commandOutput);
|
|
172
172
|
}
|
|
173
173
|
function convertPlaywrightErrorToHTML(str) {
|
|
174
|
-
return
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
.replace(
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
.replace(/<\/intensity>/g, "</span>")
|
|
187
|
-
// Convert newlines to <br> after processing other replacements
|
|
188
|
-
.replace(/\n/g, "<br>")
|
|
189
|
-
);
|
|
174
|
+
if (!str) return "";
|
|
175
|
+
return str
|
|
176
|
+
.replace(/^(\s+)/gm, (match) =>
|
|
177
|
+
match.replace(/ /g, " ").replace(/\t/g, " ")
|
|
178
|
+
)
|
|
179
|
+
.replace(/<red>/g, '<span style="color: red;">')
|
|
180
|
+
.replace(/<green>/g, '<span style="color: green;">')
|
|
181
|
+
.replace(/<dim>/g, '<span style="opacity: 0.6;">')
|
|
182
|
+
.replace(/<intensity>/g, '<span style="font-weight: bold;">')
|
|
183
|
+
.replace(/<\/color>/g, "</span>")
|
|
184
|
+
.replace(/<\/intensity>/g, "</span>")
|
|
185
|
+
.replace(/\n/g, "<br>");
|
|
190
186
|
}
|
|
191
187
|
function formatDuration(ms, options = {}) {
|
|
192
188
|
const {
|
|
@@ -227,19 +223,12 @@ function formatDuration(ms, options = {}) {
|
|
|
227
223
|
|
|
228
224
|
const totalRawSeconds = numMs / MS_PER_SECOND;
|
|
229
225
|
|
|
230
|
-
// Decision: Are we going to display hours or minutes?
|
|
231
|
-
// This happens if the duration is inherently >= 1 minute OR
|
|
232
|
-
// if it's < 1 minute but ceiling the seconds makes it >= 1 minute.
|
|
233
226
|
if (
|
|
234
227
|
totalRawSeconds < SECONDS_PER_MINUTE &&
|
|
235
228
|
Math.ceil(totalRawSeconds) < SECONDS_PER_MINUTE
|
|
236
229
|
) {
|
|
237
|
-
// Strictly seconds-only display, use precision.
|
|
238
230
|
return `${totalRawSeconds.toFixed(validPrecision)}s`;
|
|
239
231
|
} else {
|
|
240
|
-
// Display will include minutes and/or hours, or seconds round up to a minute.
|
|
241
|
-
// Seconds part should be an integer (ceiling).
|
|
242
|
-
// Round the total milliseconds UP to the nearest full second.
|
|
243
232
|
const totalMsRoundedUpToSecond =
|
|
244
233
|
Math.ceil(numMs / MS_PER_SECOND) * MS_PER_SECOND;
|
|
245
234
|
|
|
@@ -251,21 +240,15 @@ function formatDuration(ms, options = {}) {
|
|
|
251
240
|
const m = Math.floor(remainingMs / (MS_PER_SECOND * SECONDS_PER_MINUTE));
|
|
252
241
|
remainingMs %= MS_PER_SECOND * SECONDS_PER_MINUTE;
|
|
253
242
|
|
|
254
|
-
const s = Math.floor(remainingMs / MS_PER_SECOND);
|
|
243
|
+
const s = Math.floor(remainingMs / MS_PER_SECOND);
|
|
255
244
|
|
|
256
245
|
const parts = [];
|
|
257
246
|
if (h > 0) {
|
|
258
247
|
parts.push(`${h}h`);
|
|
259
248
|
}
|
|
260
|
-
|
|
261
|
-
// Show minutes if:
|
|
262
|
-
// - hours are present (e.g., "1h 0m 5s")
|
|
263
|
-
// - OR minutes themselves are > 0 (e.g., "5m 10s")
|
|
264
|
-
// - OR the original duration was >= 1 minute (ensures "1m 0s" for 60000ms)
|
|
265
249
|
if (h > 0 || m > 0 || numMs >= MS_PER_SECOND * SECONDS_PER_MINUTE) {
|
|
266
250
|
parts.push(`${m}m`);
|
|
267
251
|
}
|
|
268
|
-
|
|
269
252
|
parts.push(`${s}s`);
|
|
270
253
|
|
|
271
254
|
return parts.join(" ");
|
|
@@ -1283,6 +1266,14 @@ function generateSuitesWidget(suitesData) {
|
|
|
1283
1266
|
</div>
|
|
1284
1267
|
</div>`;
|
|
1285
1268
|
}
|
|
1269
|
+
function getAttachmentIcon(contentType) {
|
|
1270
|
+
if (contentType.includes("pdf")) return "📄";
|
|
1271
|
+
if (contentType.includes("json")) return "{ }";
|
|
1272
|
+
if (contentType.includes("html") || contentType.includes("xml")) return "</>";
|
|
1273
|
+
if (contentType.includes("csv")) return "📊";
|
|
1274
|
+
if (contentType.startsWith("text/")) return "📝";
|
|
1275
|
+
return "📎";
|
|
1276
|
+
}
|
|
1286
1277
|
function generateHTML(reportData, trendData = null) {
|
|
1287
1278
|
const { run, results } = reportData;
|
|
1288
1279
|
const suitesData = getSuitesData(reportData.results || []);
|
|
@@ -1389,16 +1380,6 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1389
1380
|
.join("");
|
|
1390
1381
|
};
|
|
1391
1382
|
|
|
1392
|
-
// Local escapeHTML for screenshot rendering part, ensuring it uses proper entities
|
|
1393
|
-
const escapeHTMLForScreenshots = (str) => {
|
|
1394
|
-
if (str === null || str === undefined) return "";
|
|
1395
|
-
return String(str).replace(
|
|
1396
|
-
/[&<>"']/g,
|
|
1397
|
-
(match) =>
|
|
1398
|
-
({ "&": "&", "<": "<", ">": ">", '"': '"', "'": "'" }[match] ||
|
|
1399
|
-
match)
|
|
1400
|
-
);
|
|
1401
|
-
};
|
|
1402
1383
|
return `
|
|
1403
1384
|
<div class="test-case" data-status="${
|
|
1404
1385
|
test.status
|
|
@@ -1459,9 +1440,9 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1459
1440
|
<div class="steps-list">${generateStepsHTML(test.steps)}</div>
|
|
1460
1441
|
${
|
|
1461
1442
|
test.stdout && test.stdout.length > 0
|
|
1462
|
-
? `<div class="console-output-section"><h4>Console Output (stdout)</h4><pre class="console-log stdout-log" style="background-color: #2d2d2d; color: wheat; padding: 1.25em; border-radius: 0.85em; line-height: 1.2;">${
|
|
1463
|
-
.
|
|
1464
|
-
|
|
1443
|
+
? `<div class="console-output-section"><h4>Console Output (stdout)</h4><pre class="console-log stdout-log" style="background-color: #2d2d2d; color: wheat; padding: 1.25em; border-radius: 0.85em; line-height: 1.2;">${formatPlaywrightError(
|
|
1444
|
+
test.stdout.join("\n")
|
|
1445
|
+
)}</pre></div>`
|
|
1465
1446
|
: ""
|
|
1466
1447
|
}
|
|
1467
1448
|
${
|
|
@@ -1474,98 +1455,139 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1474
1455
|
${
|
|
1475
1456
|
test.screenshots && test.screenshots.length > 0
|
|
1476
1457
|
? `
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1458
|
+
<div class="attachments-section">
|
|
1459
|
+
<h4>Screenshots</h4>
|
|
1460
|
+
<div class="attachments-grid">
|
|
1461
|
+
${test.screenshots
|
|
1462
|
+
.map(
|
|
1463
|
+
(screenshot, index) => `
|
|
1464
|
+
<div class="attachment-item">
|
|
1465
|
+
<img src="${screenshot}" alt="Screenshot ${index + 1}">
|
|
1466
|
+
<div class="attachment-info">
|
|
1467
|
+
<div class="trace-actions">
|
|
1468
|
+
<a href="${screenshot}" target="_blank" class="view-full">View Full Image</a>
|
|
1469
|
+
<a href="${screenshot}" target="_blank" download="screenshot-${Date.now()}-${index}.png">Download</a>
|
|
1470
|
+
</div>
|
|
1471
|
+
</div>
|
|
1472
|
+
</div>
|
|
1473
|
+
`
|
|
1474
|
+
)
|
|
1475
|
+
.join("")}
|
|
1476
|
+
</div>
|
|
1491
1477
|
</div>
|
|
1492
|
-
|
|
1493
|
-
</div>
|
|
1494
|
-
`
|
|
1495
|
-
)
|
|
1496
|
-
.join("")}
|
|
1497
|
-
</div>
|
|
1498
|
-
</div>
|
|
1499
|
-
`
|
|
1478
|
+
`
|
|
1500
1479
|
: ""
|
|
1501
1480
|
}
|
|
1502
1481
|
${
|
|
1503
|
-
test.videoPath
|
|
1504
|
-
?
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1482
|
+
test.videoPath && test.videoPath.length > 0
|
|
1483
|
+
? `
|
|
1484
|
+
<div class="attachments-section">
|
|
1485
|
+
<h4>Videos</h4>
|
|
1486
|
+
<div class="attachments-grid">
|
|
1487
|
+
${test.videoPath
|
|
1488
|
+
.map((videoUrl, index) => {
|
|
1489
|
+
const fileExtension = String(videoUrl)
|
|
1490
|
+
.split(".")
|
|
1491
|
+
.pop()
|
|
1492
|
+
.toLowerCase();
|
|
1493
|
+
const mimeType =
|
|
1494
|
+
{
|
|
1495
|
+
mp4: "video/mp4",
|
|
1496
|
+
webm: "video/webm",
|
|
1497
|
+
ogg: "video/ogg",
|
|
1498
|
+
mov: "video/quicktime",
|
|
1499
|
+
avi: "video/x-msvideo",
|
|
1500
|
+
}[fileExtension] || "video/mp4";
|
|
1501
|
+
return `
|
|
1502
|
+
<div class="attachment-item video-item">
|
|
1503
|
+
<video controls width="100%" height="auto" title="Video ${
|
|
1504
|
+
index + 1
|
|
1505
|
+
}">
|
|
1506
|
+
<source src="${sanitizeHTML(
|
|
1507
|
+
videoUrl
|
|
1508
|
+
)}" type="${mimeType}">
|
|
1509
|
+
Your browser does not support the video tag.
|
|
1510
|
+
</video>
|
|
1511
|
+
<div class="attachment-info">
|
|
1512
|
+
<div class="trace-actions">
|
|
1513
|
+
<a href="${sanitizeHTML(
|
|
1514
|
+
videoUrl
|
|
1515
|
+
)}" target="_blank" download="video-${Date.now()}-${index}.${fileExtension}">Download</a>
|
|
1516
|
+
</div>
|
|
1517
|
+
</div>
|
|
1518
|
+
</div>`;
|
|
1519
|
+
})
|
|
1520
|
+
.join("")}
|
|
1521
|
+
</div>
|
|
1522
|
+
</div>
|
|
1523
|
+
`
|
|
1541
1524
|
: ""
|
|
1542
1525
|
}
|
|
1543
1526
|
${
|
|
1544
1527
|
test.tracePath
|
|
1545
|
-
?
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1528
|
+
? `
|
|
1529
|
+
<div class="attachments-section">
|
|
1530
|
+
<h4>Trace Files</h4>
|
|
1531
|
+
<div class="attachments-grid">
|
|
1532
|
+
<div class="attachment-item trace-item">
|
|
1533
|
+
<div class="trace-preview">
|
|
1534
|
+
<span class="trace-icon">📄</span>
|
|
1535
|
+
<span class="trace-name">${sanitizeHTML(
|
|
1536
|
+
path.basename(test.tracePath)
|
|
1537
|
+
)}</span>
|
|
1538
|
+
</div>
|
|
1539
|
+
<div class="attachment-info">
|
|
1540
|
+
<div class="trace-actions">
|
|
1541
|
+
<a href="${sanitizeHTML(
|
|
1542
|
+
test.tracePath
|
|
1543
|
+
)}" target="_blank" download="${sanitizeHTML(
|
|
1544
|
+
path.basename(test.tracePath)
|
|
1545
|
+
)}" class="download-trace">Download Trace</a>
|
|
1546
|
+
</div>
|
|
1547
|
+
</div>
|
|
1548
|
+
</div>
|
|
1549
|
+
</div>
|
|
1550
|
+
</div>
|
|
1551
|
+
`
|
|
1552
|
+
: ""
|
|
1553
|
+
}
|
|
1554
|
+
${
|
|
1555
|
+
test.attachments && test.attachments.length > 0
|
|
1556
|
+
? `
|
|
1557
|
+
<div class="attachments-section">
|
|
1558
|
+
<h4>Other Attachments</h4>
|
|
1559
|
+
<div class="attachments-grid">
|
|
1560
|
+
${test.attachments
|
|
1561
|
+
.map(
|
|
1562
|
+
(attachment) => `
|
|
1563
|
+
<div class="attachment-item generic-attachment">
|
|
1564
|
+
<div class="attachment-icon">${getAttachmentIcon(
|
|
1565
|
+
attachment.contentType
|
|
1566
|
+
)}</div>
|
|
1567
|
+
<div class="attachment-caption">
|
|
1568
|
+
<span class="attachment-name" title="${sanitizeHTML(
|
|
1569
|
+
attachment.name
|
|
1570
|
+
)}">${sanitizeHTML(attachment.name)}</span>
|
|
1571
|
+
<span class="attachment-type">${sanitizeHTML(
|
|
1572
|
+
attachment.contentType
|
|
1573
|
+
)}</span>
|
|
1574
|
+
</div>
|
|
1575
|
+
<div class="attachment-info">
|
|
1576
|
+
<div class="trace-actions">
|
|
1577
|
+
<a href="${sanitizeHTML(
|
|
1578
|
+
attachment.path
|
|
1579
|
+
)}" target="_blank" download="${sanitizeHTML(
|
|
1580
|
+
attachment.name
|
|
1581
|
+
)}" class="download-trace">Download</a>
|
|
1582
|
+
</div>
|
|
1583
|
+
</div>
|
|
1584
|
+
</div>
|
|
1585
|
+
`
|
|
1586
|
+
)
|
|
1587
|
+
.join("")}
|
|
1588
|
+
</div>
|
|
1589
|
+
</div>
|
|
1590
|
+
`
|
|
1569
1591
|
: ""
|
|
1570
1592
|
}
|
|
1571
1593
|
${
|
|
@@ -1601,14 +1623,11 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1601
1623
|
}
|
|
1602
1624
|
.trend-chart-container, .test-history-trend div[id^="testHistoryChart-"] { min-height: 100px; }
|
|
1603
1625
|
.lazy-load-chart .no-data, .lazy-load-chart .no-data-chart { display: flex; align-items: center; justify-content: center; height: 100%; font-style: italic; color: var(--dark-gray-color); }
|
|
1604
|
-
|
|
1605
|
-
/* General Highcharts styling */
|
|
1606
1626
|
.highcharts-background { fill: transparent; }
|
|
1607
1627
|
.highcharts-title, .highcharts-subtitle { font-family: var(--font-family); }
|
|
1608
1628
|
.highcharts-axis-labels text, .highcharts-legend-item text { fill: var(--text-color-secondary) !important; font-size: 12px !important; }
|
|
1609
1629
|
.highcharts-axis-title { fill: var(--text-color) !important; }
|
|
1610
1630
|
.highcharts-tooltip > span { background-color: rgba(10,10,10,0.92) !important; border-color: rgba(10,10,10,0.92) !important; color: #f5f5f5 !important; padding: 10px !important; border-radius: 6px !important; }
|
|
1611
|
-
|
|
1612
1631
|
body { font-family: var(--font-family); margin: 0; background-color: var(--background-color); color: var(--text-color); line-height: 1.65; font-size: 16px; }
|
|
1613
1632
|
.container { padding: 30px; border-radius: var(--border-radius); box-shadow: var(--box-shadow); background: repeating-linear-gradient(#f1f8e9, #f9fbe7, #fce4ec); }
|
|
1614
1633
|
.header { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; padding-bottom: 25px; border-bottom: 1px solid var(--border-color); margin-bottom: 25px; }
|
|
@@ -1704,11 +1723,19 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1704
1723
|
.attachment-item { border: 1px solid var(--border-color); border-radius: var(--border-radius); background-color: #fff; box-shadow: var(--box-shadow-light); overflow: hidden; display: flex; flex-direction: column; transition: transform 0.2s ease-out, box-shadow 0.2s ease-out; }
|
|
1705
1724
|
.attachment-item:hover { transform: translateY(-4px); box-shadow: var(--box-shadow); }
|
|
1706
1725
|
.attachment-item img { width: 100%; height: 180px; object-fit: cover; display: block; border-bottom: 1px solid var(--border-color); transition: opacity 0.3s ease; }
|
|
1726
|
+
.attachment-info { padding: 12px; margin-top: auto; background-color: #fafafa;}
|
|
1707
1727
|
.attachment-item a:hover img { opacity: 0.85; }
|
|
1708
1728
|
.attachment-caption { padding: 12px 15px; font-size: 0.9em; text-align: center; color: var(--text-color-secondary); word-break: break-word; background-color: var(--light-gray-color); }
|
|
1709
1729
|
.video-item a, .trace-item a { display: block; margin-bottom: 8px; color: var(--primary-color); text-decoration: none; font-weight: 500; }
|
|
1710
1730
|
.video-item a:hover, .trace-item a:hover { text-decoration: underline; }
|
|
1711
1731
|
.code-section pre { background-color: #2d2d2d; color: #f0f0f0; padding: 20px; border-radius: 6px; overflow-x: auto; font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace; font-size: 0.95em; line-height:1.6;}
|
|
1732
|
+
.trace-actions { display: flex; justify-content: center; }
|
|
1733
|
+
.trace-actions a { text-decoration: none; color: var(--primary-color); font-weight: 500; font-size: 0.9em; }
|
|
1734
|
+
.generic-attachment { text-align: center; padding: 1rem; justify-content: center; }
|
|
1735
|
+
.attachment-icon { font-size: 2.5rem; display: block; margin-bottom: 0.75rem; }
|
|
1736
|
+
.attachment-caption { display: flex; flex-direction: column; }
|
|
1737
|
+
.attachment-name { font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
1738
|
+
.attachment-type { font-size: 0.8rem; color: var(--text-color-secondary); }
|
|
1712
1739
|
.trend-charts-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(480px, 1fr)); gap: 28px; margin-bottom: 35px; }
|
|
1713
1740
|
.test-history-container h2.tab-main-title { font-size: 1.6em; margin-bottom: 18px; color: var(--primary-color); border-bottom: 1px solid var(--border-color); padding-bottom: 12px;}
|
|
1714
1741
|
.test-history-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(380px, 1fr)); gap: 22px; margin-top: 22px; }
|
|
@@ -2035,24 +2062,64 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2035
2062
|
}
|
|
2036
2063
|
document.addEventListener('DOMContentLoaded', initializeReportInteractivity);
|
|
2037
2064
|
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2065
|
+
function copyErrorToClipboard(button) {
|
|
2066
|
+
// 1. Find the main error container, which should always be present.
|
|
2067
|
+
const errorContainer = button.closest('.step-error');
|
|
2068
|
+
if (!errorContainer) {
|
|
2069
|
+
console.error("Could not find '.step-error' container. The report's HTML structure might have changed.");
|
|
2070
|
+
return;
|
|
2071
|
+
}
|
|
2072
|
+
|
|
2073
|
+
let errorText;
|
|
2074
|
+
|
|
2075
|
+
// 2. First, try to find the preferred .stack-trace element (the "happy path").
|
|
2076
|
+
const stackTraceElement = errorContainer.querySelector('.stack-trace');
|
|
2077
|
+
|
|
2078
|
+
if (stackTraceElement) {
|
|
2079
|
+
// If it exists, use its text content. This handles standard assertion errors.
|
|
2080
|
+
errorText = stackTraceElement.textContent;
|
|
2081
|
+
} else {
|
|
2082
|
+
// 3. FALLBACK: If .stack-trace doesn't exist, this is likely an unstructured error.
|
|
2083
|
+
// We clone the container to avoid manipulating the live DOM or copying the button's own text.
|
|
2084
|
+
const clonedContainer = errorContainer.cloneNode(true);
|
|
2085
|
+
|
|
2086
|
+
// Remove the button from our clone before extracting the text.
|
|
2087
|
+
const buttonInClone = clonedContainer.querySelector('button');
|
|
2088
|
+
if (buttonInClone) {
|
|
2089
|
+
buttonInClone.remove();
|
|
2090
|
+
}
|
|
2091
|
+
|
|
2092
|
+
// Use the text content of the cleaned container as the fallback.
|
|
2093
|
+
errorText = clonedContainer.textContent;
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2096
|
+
// 4. Proceed with the clipboard logic, ensuring text is not null and is trimmed.
|
|
2097
|
+
if (!errorText) {
|
|
2098
|
+
console.error('Could not extract error text.');
|
|
2099
|
+
button.textContent = 'Nothing to copy';
|
|
2100
|
+
setTimeout(() => { button.textContent = 'Copy Error'; }, 2000);
|
|
2101
|
+
return;
|
|
2102
|
+
}
|
|
2103
|
+
|
|
2104
|
+
const textarea = document.createElement('textarea');
|
|
2105
|
+
textarea.value = errorText.trim(); // Trim whitespace for a cleaner copy.
|
|
2106
|
+
textarea.style.position = 'fixed'; // Prevent screen scroll
|
|
2107
|
+
textarea.style.top = '-9999px';
|
|
2108
|
+
document.body.appendChild(textarea);
|
|
2109
|
+
textarea.select();
|
|
2110
|
+
|
|
2111
|
+
try {
|
|
2112
|
+
const successful = document.execCommand('copy');
|
|
2113
|
+
const originalText = button.textContent;
|
|
2114
|
+
button.textContent = successful ? 'Copied!' : 'Failed';
|
|
2115
|
+
setTimeout(() => {
|
|
2116
|
+
button.textContent = originalText;
|
|
2117
|
+
}, 2000);
|
|
2118
|
+
} catch (err) {
|
|
2119
|
+
console.error('Failed to copy: ', err);
|
|
2120
|
+
button.textContent = 'Failed';
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2056
2123
|
document.body.removeChild(textarea);
|
|
2057
2124
|
}
|
|
2058
2125
|
</script>
|