@arghajit/dummy 0.3.5 → 0.3.7
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.
|
@@ -197,7 +197,7 @@ class PlaywrightPulseReporter {
|
|
|
197
197
|
};
|
|
198
198
|
}
|
|
199
199
|
async onTestEnd(test, result) {
|
|
200
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
200
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
|
|
201
201
|
const project = (_a = test.parent) === null || _a === void 0 ? void 0 : _a.project();
|
|
202
202
|
const browserDetails = this.getBrowserDetails(test);
|
|
203
203
|
const testStatus = convertStatus(result.status, test);
|
|
@@ -224,6 +224,16 @@ class PlaywrightPulseReporter {
|
|
|
224
224
|
catch (e) {
|
|
225
225
|
console.warn(`Pulse Reporter: Could not extract code snippet for ${test.title}`, e);
|
|
226
226
|
}
|
|
227
|
+
// 1. Get Spec File Name
|
|
228
|
+
const specFileName = ((_e = test.location) === null || _e === void 0 ? void 0 : _e.file)
|
|
229
|
+
? path.basename(test.location.file)
|
|
230
|
+
: "n/a";
|
|
231
|
+
// 2. Get Describe Block Name
|
|
232
|
+
// Check if the immediate parent is a 'describe' block
|
|
233
|
+
let describeBlockName = "n/a";
|
|
234
|
+
if (((_f = test.parent) === null || _f === void 0 ? void 0 : _f.type) === "describe") {
|
|
235
|
+
describeBlockName = test.parent.title;
|
|
236
|
+
}
|
|
227
237
|
const stdoutMessages = result.stdout.map((item) => typeof item === "string" ? item : item.toString());
|
|
228
238
|
const stderrMessages = result.stderr.map((item) => typeof item === "string" ? item : item.toString());
|
|
229
239
|
const maxWorkers = this.config.workers;
|
|
@@ -241,18 +251,20 @@ class PlaywrightPulseReporter {
|
|
|
241
251
|
const pulseResult = {
|
|
242
252
|
id: test.id,
|
|
243
253
|
runId: "TBD",
|
|
254
|
+
describe: describeBlockName,
|
|
255
|
+
spec_file: specFileName,
|
|
244
256
|
name: test.titlePath().join(" > "),
|
|
245
|
-
suiteName: (project === null || project === void 0 ? void 0 : project.name) || ((
|
|
257
|
+
suiteName: (project === null || project === void 0 ? void 0 : project.name) || ((_g = this.config.projects[0]) === null || _g === void 0 ? void 0 : _g.name) || "Default Suite",
|
|
246
258
|
status: testStatus,
|
|
247
259
|
duration: result.duration,
|
|
248
260
|
startTime: startTime,
|
|
249
261
|
endTime: endTime,
|
|
250
262
|
browser: browserDetails,
|
|
251
263
|
retries: result.retry,
|
|
252
|
-
steps: ((
|
|
253
|
-
errorMessage: (
|
|
254
|
-
stackTrace: (
|
|
255
|
-
snippet: (
|
|
264
|
+
steps: ((_h = result.steps) === null || _h === void 0 ? void 0 : _h.length) ? await processAllSteps(result.steps) : [],
|
|
265
|
+
errorMessage: (_j = result.error) === null || _j === void 0 ? void 0 : _j.message,
|
|
266
|
+
stackTrace: (_k = result.error) === null || _k === void 0 ? void 0 : _k.stack,
|
|
267
|
+
snippet: (_l = result.error) === null || _l === void 0 ? void 0 : _l.snippet,
|
|
256
268
|
codeSnippet: codeSnippet,
|
|
257
269
|
tags: test.tags.map((tag) => tag.startsWith("@") ? tag.substring(1) : tag),
|
|
258
270
|
screenshots: [],
|
|
@@ -261,7 +273,7 @@ class PlaywrightPulseReporter {
|
|
|
261
273
|
attachments: [],
|
|
262
274
|
stdout: stdoutMessages.length > 0 ? stdoutMessages : undefined,
|
|
263
275
|
stderr: stderrMessages.length > 0 ? stderrMessages : undefined,
|
|
264
|
-
annotations: ((
|
|
276
|
+
annotations: ((_m = test.annotations) === null || _m === void 0 ? void 0 : _m.length) > 0 ? test.annotations : undefined,
|
|
265
277
|
...testSpecificData,
|
|
266
278
|
};
|
|
267
279
|
for (const [index, attachment] of result.attachments.entries()) {
|
|
@@ -278,16 +290,16 @@ class PlaywrightPulseReporter {
|
|
|
278
290
|
await this._ensureDirExists(path.dirname(absoluteDestPath));
|
|
279
291
|
await fs.copyFile(attachment.path, absoluteDestPath);
|
|
280
292
|
if (attachment.contentType.startsWith("image/")) {
|
|
281
|
-
(
|
|
293
|
+
(_o = pulseResult.screenshots) === null || _o === void 0 ? void 0 : _o.push(relativeDestPath);
|
|
282
294
|
}
|
|
283
295
|
else if (attachment.contentType.startsWith("video/")) {
|
|
284
|
-
(
|
|
296
|
+
(_p = pulseResult.videoPath) === null || _p === void 0 ? void 0 : _p.push(relativeDestPath);
|
|
285
297
|
}
|
|
286
298
|
else if (attachment.name === "trace") {
|
|
287
299
|
pulseResult.tracePath = relativeDestPath;
|
|
288
300
|
}
|
|
289
301
|
else {
|
|
290
|
-
(
|
|
302
|
+
(_q = pulseResult.attachments) === null || _q === void 0 ? void 0 : _q.push({
|
|
291
303
|
name: attachment.name,
|
|
292
304
|
path: relativeDestPath,
|
|
293
305
|
contentType: attachment.contentType,
|
package/dist/types/index.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arghajit/dummy",
|
|
3
3
|
"author": "Arghajit Singha",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.7",
|
|
5
5
|
"description": "A Playwright reporter and dashboard for visualizing test results.",
|
|
6
6
|
"homepage": "https://playwright-pulse-report.netlify.app/",
|
|
7
7
|
"keywords": [
|
|
@@ -30,12 +30,12 @@
|
|
|
30
30
|
],
|
|
31
31
|
"license": "MIT",
|
|
32
32
|
"bin": {
|
|
33
|
-
"generate-pulse-report": "
|
|
34
|
-
"generate-report": "
|
|
35
|
-
"merge-pulse-report": "
|
|
36
|
-
"send-email": "
|
|
37
|
-
"generate-trend": "
|
|
38
|
-
"generate-email-report": "
|
|
33
|
+
"generate-pulse-report": "scripts/generate-static-report.mjs",
|
|
34
|
+
"generate-report": "scripts/generate-report.mjs",
|
|
35
|
+
"merge-pulse-report": "scripts/merge-pulse-report.js",
|
|
36
|
+
"send-email": "scripts/sendReport.mjs",
|
|
37
|
+
"generate-trend": "scripts/generate-trend.mjs",
|
|
38
|
+
"generate-email-report": "scripts/generate-email-report.mjs"
|
|
39
39
|
},
|
|
40
40
|
"exports": {
|
|
41
41
|
".": {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import * as fs from "fs";
|
|
3
3
|
import * as path from "path";
|
|
4
4
|
import { pathToFileURL } from "url";
|
|
5
|
+
import { dirname } from "path";
|
|
5
6
|
|
|
6
7
|
const DEFAULT_OUTPUT_DIR = "pulse-report";
|
|
7
8
|
|
|
@@ -26,31 +27,51 @@ async function extractOutputDirFromConfig(configPath) {
|
|
|
26
27
|
try {
|
|
27
28
|
let config;
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const { pathToFileURL } = await import("node:url");
|
|
30
|
+
const configDir = dirname(configPath);
|
|
31
|
+
const originalDirname = global.__dirname;
|
|
32
|
+
const originalFilename = global.__filename;
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
try {
|
|
35
|
+
global.__dirname = configDir;
|
|
36
|
+
global.__filename = configPath;
|
|
35
37
|
|
|
36
|
-
|
|
37
|
-
} catch (tsError) {
|
|
38
|
+
if (configPath.endsWith(".ts")) {
|
|
38
39
|
try {
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
},
|
|
45
|
-
});
|
|
40
|
+
const { register } = await import("node:module");
|
|
41
|
+
const { pathToFileURL } = await import("node:url");
|
|
42
|
+
|
|
43
|
+
register("ts-node/esm", pathToFileURL("./"));
|
|
44
|
+
|
|
46
45
|
config = await import(pathToFileURL(configPath).href);
|
|
47
|
-
} catch (
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
} catch (tsError) {
|
|
47
|
+
try {
|
|
48
|
+
const tsNode = await import("ts-node");
|
|
49
|
+
tsNode.register({
|
|
50
|
+
transpileOnly: true,
|
|
51
|
+
compilerOptions: {
|
|
52
|
+
module: "ESNext",
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
config = await import(pathToFileURL(configPath).href);
|
|
56
|
+
} catch (fallbackError) {
|
|
57
|
+
console.error("Failed to load TypeScript config:", fallbackError);
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
50
60
|
}
|
|
61
|
+
} else {
|
|
62
|
+
config = await import(pathToFileURL(configPath).href);
|
|
63
|
+
}
|
|
64
|
+
} finally {
|
|
65
|
+
if (originalDirname !== undefined) {
|
|
66
|
+
global.__dirname = originalDirname;
|
|
67
|
+
} else {
|
|
68
|
+
delete global.__dirname;
|
|
69
|
+
}
|
|
70
|
+
if (originalFilename !== undefined) {
|
|
71
|
+
global.__filename = originalFilename;
|
|
72
|
+
} else {
|
|
73
|
+
delete global.__filename;
|
|
51
74
|
}
|
|
52
|
-
} else {
|
|
53
|
-
config = await import(pathToFileURL(configPath).href);
|
|
54
75
|
}
|
|
55
76
|
|
|
56
77
|
const playwrightConfig = config.default || config;
|
|
@@ -1644,6 +1644,232 @@ function generateAIFailureAnalyzerTab(results) {
|
|
|
1644
1644
|
</div>
|
|
1645
1645
|
`;
|
|
1646
1646
|
}
|
|
1647
|
+
// --- NEW CHARTS HELPER FUNCTIONS (Style: Line Chart + Enhanced Tooltips) ---
|
|
1648
|
+
|
|
1649
|
+
function generateSpecDurationChart(results) {
|
|
1650
|
+
if (!results || results.length === 0)
|
|
1651
|
+
return '<div class="no-data">No results available.</div>';
|
|
1652
|
+
|
|
1653
|
+
const specDurations = {};
|
|
1654
|
+
results.forEach((test) => {
|
|
1655
|
+
let fileName = "Unknown";
|
|
1656
|
+
if (test.location && test.location.file) {
|
|
1657
|
+
fileName = test.location.file.split(path.sep).pop();
|
|
1658
|
+
} else {
|
|
1659
|
+
const parts = test.name.split(" > ");
|
|
1660
|
+
fileName = parts[0];
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
if (!specDurations[fileName]) specDurations[fileName] = 0;
|
|
1664
|
+
specDurations[fileName] += test.duration;
|
|
1665
|
+
});
|
|
1666
|
+
|
|
1667
|
+
const categories = Object.keys(specDurations);
|
|
1668
|
+
// Map data to objects to pass metadata if needed, though categories usually handle this.
|
|
1669
|
+
const data = categories.map((cat) => ({
|
|
1670
|
+
y: specDurations[cat],
|
|
1671
|
+
name: cat,
|
|
1672
|
+
}));
|
|
1673
|
+
|
|
1674
|
+
if (categories.length === 0)
|
|
1675
|
+
return '<div class="no-data">No spec data found.</div>';
|
|
1676
|
+
|
|
1677
|
+
const chartId = `specDurChart-${Date.now()}-${Math.random()
|
|
1678
|
+
.toString(36)
|
|
1679
|
+
.substring(2, 7)}`;
|
|
1680
|
+
const renderFunctionName = `renderSpecDurChart_${chartId.replace(/-/g, "_")}`;
|
|
1681
|
+
|
|
1682
|
+
const categoriesStr = JSON.stringify(categories);
|
|
1683
|
+
const dataStr = JSON.stringify(data);
|
|
1684
|
+
|
|
1685
|
+
return `
|
|
1686
|
+
<div id="${chartId}" class="trend-chart-container lazy-load-chart" data-render-function-name="${renderFunctionName}">
|
|
1687
|
+
<div class="no-data">Loading Spec Duration Chart...</div>
|
|
1688
|
+
</div>
|
|
1689
|
+
<script>
|
|
1690
|
+
window.${renderFunctionName} = function() {
|
|
1691
|
+
const chartContainer = document.getElementById('${chartId}');
|
|
1692
|
+
if (!chartContainer) return;
|
|
1693
|
+
if (typeof Highcharts !== 'undefined' && typeof formatDuration !== 'undefined') {
|
|
1694
|
+
try {
|
|
1695
|
+
chartContainer.innerHTML = '';
|
|
1696
|
+
Highcharts.chart('${chartId}', {
|
|
1697
|
+
chart: { type: 'line', height: 350, backgroundColor: 'transparent' },
|
|
1698
|
+
title: { text: null },
|
|
1699
|
+
xAxis: {
|
|
1700
|
+
categories: ${categoriesStr},
|
|
1701
|
+
title: { text: null },
|
|
1702
|
+
crosshair: true,
|
|
1703
|
+
labels: { style: { color: 'var(--text-color-secondary)', fontSize: '12px' } }
|
|
1704
|
+
},
|
|
1705
|
+
yAxis: {
|
|
1706
|
+
min: 0,
|
|
1707
|
+
title: { text: 'Total Duration', style: { color: 'var(--text-color)' } },
|
|
1708
|
+
labels: { formatter: function() { return formatDuration(this.value); }, style: { color: 'var(--text-color-secondary)' } }
|
|
1709
|
+
},
|
|
1710
|
+
legend: { enabled: false },
|
|
1711
|
+
plotOptions: {
|
|
1712
|
+
series: { marker: { radius: 4, states: { hover: { radius: 6 }}}, states: { hover: { halo: { size: 5, opacity: 0.1 }}}},
|
|
1713
|
+
line: { lineWidth: 2.5 }
|
|
1714
|
+
},
|
|
1715
|
+
tooltip: {
|
|
1716
|
+
shared: true,
|
|
1717
|
+
useHTML: true,
|
|
1718
|
+
backgroundColor: 'rgba(10,10,10,0.92)',
|
|
1719
|
+
borderColor: 'rgba(10,10,10,0.92)',
|
|
1720
|
+
style: { color: '#f5f5f5' },
|
|
1721
|
+
formatter: function() {
|
|
1722
|
+
// 'this.x' is the File Name (category)
|
|
1723
|
+
const point = this.points ? this.points[0].point : this.point;
|
|
1724
|
+
const color = point.color || point.series.color;
|
|
1725
|
+
|
|
1726
|
+
return '<span style="color:' + color + '">●</span> <b>File: ' + this.x + '</b><br/>' +
|
|
1727
|
+
'Duration: <b>' + formatDuration(this.y) + '</b>';
|
|
1728
|
+
}
|
|
1729
|
+
},
|
|
1730
|
+
series: [{
|
|
1731
|
+
name: 'Duration',
|
|
1732
|
+
data: ${dataStr},
|
|
1733
|
+
color: 'var(--accent-color-alt)', // Orange theme
|
|
1734
|
+
marker: { symbol: 'circle' }
|
|
1735
|
+
}],
|
|
1736
|
+
credits: { enabled: false }
|
|
1737
|
+
});
|
|
1738
|
+
} catch (e) { console.error("Error rendering spec chart:", e); }
|
|
1739
|
+
}
|
|
1740
|
+
};
|
|
1741
|
+
</script>
|
|
1742
|
+
`;
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
function generateDescribeDurationChart(results) {
|
|
1746
|
+
if (!results || results.length === 0)
|
|
1747
|
+
return '<div class="no-data">No results available.</div>';
|
|
1748
|
+
|
|
1749
|
+
// We need to group by (File + Describe) to handle same-named describes in different files
|
|
1750
|
+
const describeMap = new Map();
|
|
1751
|
+
let foundAnyDescribe = false;
|
|
1752
|
+
|
|
1753
|
+
results.forEach((test) => {
|
|
1754
|
+
const parts = test.name.split(" > ");
|
|
1755
|
+
if (parts.length > 2) {
|
|
1756
|
+
foundAnyDescribe = true;
|
|
1757
|
+
|
|
1758
|
+
// Extract Filename
|
|
1759
|
+
let fileName = "Unknown";
|
|
1760
|
+
if (test.location && test.location.file) {
|
|
1761
|
+
fileName = test.location.file.split(path.sep).pop();
|
|
1762
|
+
} else {
|
|
1763
|
+
fileName = parts[0];
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
const describeName = parts.slice(1, parts.length - 1).join(" > ");
|
|
1767
|
+
|
|
1768
|
+
// Create a unique key for the map
|
|
1769
|
+
const key = fileName + "::" + describeName;
|
|
1770
|
+
|
|
1771
|
+
if (!describeMap.has(key)) {
|
|
1772
|
+
describeMap.set(key, {
|
|
1773
|
+
duration: 0,
|
|
1774
|
+
file: fileName,
|
|
1775
|
+
describe: describeName,
|
|
1776
|
+
});
|
|
1777
|
+
}
|
|
1778
|
+
describeMap.get(key).duration += test.duration;
|
|
1779
|
+
}
|
|
1780
|
+
});
|
|
1781
|
+
|
|
1782
|
+
if (!foundAnyDescribe) {
|
|
1783
|
+
return '<div class="no-data">No test describe block found through out the executed test suite</div>';
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
const categories = [];
|
|
1787
|
+
const data = [];
|
|
1788
|
+
|
|
1789
|
+
for (const [key, val] of describeMap.entries()) {
|
|
1790
|
+
categories.push(val.describe); // X-Axis label
|
|
1791
|
+
data.push({
|
|
1792
|
+
y: val.duration,
|
|
1793
|
+
custom: {
|
|
1794
|
+
fileName: val.file,
|
|
1795
|
+
describeName: val.describe,
|
|
1796
|
+
},
|
|
1797
|
+
});
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
const chartId = `descDurChart-${Date.now()}-${Math.random()
|
|
1801
|
+
.toString(36)
|
|
1802
|
+
.substring(2, 7)}`;
|
|
1803
|
+
const renderFunctionName = `renderDescDurChart_${chartId.replace(/-/g, "_")}`;
|
|
1804
|
+
|
|
1805
|
+
const categoriesStr = JSON.stringify(categories);
|
|
1806
|
+
const dataStr = JSON.stringify(data);
|
|
1807
|
+
|
|
1808
|
+
return `
|
|
1809
|
+
<div id="${chartId}" class="trend-chart-container lazy-load-chart" data-render-function-name="${renderFunctionName}">
|
|
1810
|
+
<div class="no-data">Loading Describe Duration Chart...</div>
|
|
1811
|
+
</div>
|
|
1812
|
+
<script>
|
|
1813
|
+
window.${renderFunctionName} = function() {
|
|
1814
|
+
const chartContainer = document.getElementById('${chartId}');
|
|
1815
|
+
if (!chartContainer) return;
|
|
1816
|
+
if (typeof Highcharts !== 'undefined' && typeof formatDuration !== 'undefined') {
|
|
1817
|
+
try {
|
|
1818
|
+
chartContainer.innerHTML = '';
|
|
1819
|
+
Highcharts.chart('${chartId}', {
|
|
1820
|
+
chart: { type: 'line', height: 350, backgroundColor: 'transparent' },
|
|
1821
|
+
title: { text: null },
|
|
1822
|
+
xAxis: {
|
|
1823
|
+
categories: ${categoriesStr},
|
|
1824
|
+
title: { text: null },
|
|
1825
|
+
crosshair: true,
|
|
1826
|
+
labels: { style: { color: 'var(--text-color-secondary)', fontSize: '12px' } }
|
|
1827
|
+
},
|
|
1828
|
+
yAxis: {
|
|
1829
|
+
min: 0,
|
|
1830
|
+
title: { text: 'Total Duration', style: { color: 'var(--text-color)' } },
|
|
1831
|
+
labels: { formatter: function() { return formatDuration(this.value); }, style: { color: 'var(--text-color-secondary)' } }
|
|
1832
|
+
},
|
|
1833
|
+
legend: { enabled: false },
|
|
1834
|
+
plotOptions: {
|
|
1835
|
+
series: { marker: { radius: 4, states: { hover: { radius: 6 }}}, states: { hover: { halo: { size: 5, opacity: 0.1 }}}},
|
|
1836
|
+
line: { lineWidth: 2.5 }
|
|
1837
|
+
},
|
|
1838
|
+
tooltip: {
|
|
1839
|
+
shared: true,
|
|
1840
|
+
useHTML: true,
|
|
1841
|
+
backgroundColor: 'rgba(10,10,10,0.92)',
|
|
1842
|
+
borderColor: 'rgba(10,10,10,0.92)',
|
|
1843
|
+
style: { color: '#f5f5f5' },
|
|
1844
|
+
formatter: function() {
|
|
1845
|
+
// Retrieve custom data stored in the point
|
|
1846
|
+
const point = this.points ? this.points[0].point : this.point;
|
|
1847
|
+
|
|
1848
|
+
// Safety check for your custom data
|
|
1849
|
+
const file = (point.custom && point.custom.fileName) ? point.custom.fileName : 'Unknown';
|
|
1850
|
+
const desc = (point.custom && point.custom.describeName) ? point.custom.describeName : this.x;
|
|
1851
|
+
const color = point.color || point.series.color;
|
|
1852
|
+
|
|
1853
|
+
return '<span style="color:' + color + '">●</span> <b>Describe: ' + desc + '</b><br/>' +
|
|
1854
|
+
'<span style="opacity: 0.8; font-size: 0.9em; color: #ddd;">File: ' + file + '</span><br/>' +
|
|
1855
|
+
'Duration: <b>' + formatDuration(point.y) + '</b>';
|
|
1856
|
+
}
|
|
1857
|
+
},
|
|
1858
|
+
series: [{
|
|
1859
|
+
name: 'Duration',
|
|
1860
|
+
data: ${dataStr},
|
|
1861
|
+
color: 'var(--accent-color-alt)', // Orange theme
|
|
1862
|
+
marker: { symbol: 'circle' }
|
|
1863
|
+
}],
|
|
1864
|
+
credits: { enabled: false }
|
|
1865
|
+
});
|
|
1866
|
+
} catch (e) { console.error("Error rendering describe chart:", e); }
|
|
1867
|
+
}
|
|
1868
|
+
};
|
|
1869
|
+
</script>
|
|
1870
|
+
`;
|
|
1871
|
+
}
|
|
1872
|
+
// --- END NEW CHARTS HELPER FUNCTIONS ---
|
|
1647
1873
|
function generateHTML(reportData, trendData = null) {
|
|
1648
1874
|
const { run, results } = reportData;
|
|
1649
1875
|
const suitesData = getSuitesData(reportData.results || []);
|
|
@@ -2572,6 +2798,16 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2572
2798
|
}
|
|
2573
2799
|
</div>
|
|
2574
2800
|
</div>
|
|
2801
|
+
<div class="trend-charts-row">
|
|
2802
|
+
<div class="trend-chart">
|
|
2803
|
+
<h3 class="chart-title-header">Duration by Spec files</h3>
|
|
2804
|
+
${generateSpecDurationChart(results)}
|
|
2805
|
+
</div>
|
|
2806
|
+
<div class="trend-chart">
|
|
2807
|
+
<h3 class="chart-title-header">Duration by Test Describe</h3>
|
|
2808
|
+
${generateDescribeDurationChart(results)}
|
|
2809
|
+
</div>
|
|
2810
|
+
</div>
|
|
2575
2811
|
<h2 class="tab-main-title">Test Distribution by Worker ${infoTooltip}</h2>
|
|
2576
2812
|
<div class="trend-charts-row">
|
|
2577
2813
|
<div class="trend-chart">
|
|
@@ -3282,4 +3518,4 @@ main().catch((err) => {
|
|
|
3282
3518
|
);
|
|
3283
3519
|
console.error(err.stack);
|
|
3284
3520
|
process.exit(1);
|
|
3285
|
-
});
|
|
3521
|
+
});
|
|
@@ -1781,6 +1781,206 @@ function generateAIFailureAnalyzerTab(results) {
|
|
|
1781
1781
|
</div>
|
|
1782
1782
|
`;
|
|
1783
1783
|
}
|
|
1784
|
+
// --- NEW CHARTS HELPER FUNCTIONS (Style: Line Chart + Enhanced Tooltips) ---
|
|
1785
|
+
|
|
1786
|
+
function generateSpecDurationChart(results) {
|
|
1787
|
+
if (!results || results.length === 0) return '<div class="no-data">No results available.</div>';
|
|
1788
|
+
|
|
1789
|
+
const specDurations = {};
|
|
1790
|
+
results.forEach(test => {
|
|
1791
|
+
let fileName = 'Unknown';
|
|
1792
|
+
if (test.location && test.location.file) {
|
|
1793
|
+
fileName = test.location.file.split(path.sep).pop();
|
|
1794
|
+
} else {
|
|
1795
|
+
const parts = test.name.split(' > ');
|
|
1796
|
+
fileName = parts[0];
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
if (!specDurations[fileName]) specDurations[fileName] = 0;
|
|
1800
|
+
specDurations[fileName] += test.duration;
|
|
1801
|
+
});
|
|
1802
|
+
|
|
1803
|
+
const categories = Object.keys(specDurations);
|
|
1804
|
+
// Map data to objects to pass metadata if needed, though categories usually handle this.
|
|
1805
|
+
const data = categories.map(cat => ({
|
|
1806
|
+
y: specDurations[cat],
|
|
1807
|
+
name: cat
|
|
1808
|
+
}));
|
|
1809
|
+
|
|
1810
|
+
if (categories.length === 0) return '<div class="no-data">No spec data found.</div>';
|
|
1811
|
+
|
|
1812
|
+
const chartId = `specDurChart-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`;
|
|
1813
|
+
const renderFunctionName = `renderSpecDurChart_${chartId.replace(/-/g, "_")}`;
|
|
1814
|
+
|
|
1815
|
+
const categoriesStr = JSON.stringify(categories);
|
|
1816
|
+
const dataStr = JSON.stringify(data);
|
|
1817
|
+
|
|
1818
|
+
return `
|
|
1819
|
+
<div id="${chartId}" class="trend-chart-container lazy-load-chart" data-render-function-name="${renderFunctionName}">
|
|
1820
|
+
<div class="no-data">Loading Spec Duration Chart...</div>
|
|
1821
|
+
</div>
|
|
1822
|
+
<script>
|
|
1823
|
+
window.${renderFunctionName} = function() {
|
|
1824
|
+
const chartContainer = document.getElementById('${chartId}');
|
|
1825
|
+
if (!chartContainer) return;
|
|
1826
|
+
if (typeof Highcharts !== 'undefined' && typeof formatDuration !== 'undefined') {
|
|
1827
|
+
try {
|
|
1828
|
+
chartContainer.innerHTML = '';
|
|
1829
|
+
Highcharts.chart('${chartId}', {
|
|
1830
|
+
chart: { type: 'line', height: 350, backgroundColor: 'transparent' },
|
|
1831
|
+
title: { text: null },
|
|
1832
|
+
xAxis: {
|
|
1833
|
+
categories: ${categoriesStr},
|
|
1834
|
+
title: { text: null },
|
|
1835
|
+
crosshair: true,
|
|
1836
|
+
labels: { style: { color: 'var(--text-color-secondary)', fontSize: '12px' } }
|
|
1837
|
+
},
|
|
1838
|
+
yAxis: {
|
|
1839
|
+
min: 0,
|
|
1840
|
+
title: { text: 'Total Duration', style: { color: 'var(--text-color)' } },
|
|
1841
|
+
labels: { formatter: function() { return formatDuration(this.value); }, style: { color: 'var(--text-color-secondary)' } }
|
|
1842
|
+
},
|
|
1843
|
+
legend: { enabled: false },
|
|
1844
|
+
plotOptions: {
|
|
1845
|
+
series: { marker: { radius: 4, states: { hover: { radius: 6 }}}, states: { hover: { halo: { size: 5, opacity: 0.1 }}}},
|
|
1846
|
+
line: { lineWidth: 2.5 }
|
|
1847
|
+
},
|
|
1848
|
+
tooltip: {
|
|
1849
|
+
shared: true, useHTML: true, backgroundColor: 'rgba(10,10,10,0.92)', borderColor: 'rgba(10,10,10,0.92)', style: { color: '#f5f5f5' },
|
|
1850
|
+
formatter: function() {
|
|
1851
|
+
// Use 'this.x' which contains the category name (File Name)
|
|
1852
|
+
return '<b>File: ' + this.x + '</b><br/>Duration: ' + formatDuration(this.y);
|
|
1853
|
+
}
|
|
1854
|
+
},
|
|
1855
|
+
series: [{
|
|
1856
|
+
name: 'Duration',
|
|
1857
|
+
data: ${dataStr},
|
|
1858
|
+
color: 'var(--accent-color-alt)', // Orange theme
|
|
1859
|
+
marker: { symbol: 'circle' }
|
|
1860
|
+
}],
|
|
1861
|
+
credits: { enabled: false }
|
|
1862
|
+
});
|
|
1863
|
+
} catch (e) { console.error("Error rendering spec chart:", e); }
|
|
1864
|
+
}
|
|
1865
|
+
};
|
|
1866
|
+
</script>
|
|
1867
|
+
`;
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
function generateDescribeDurationChart(results) {
|
|
1871
|
+
if (!results || results.length === 0) return '<div class="no-data">No results available.</div>';
|
|
1872
|
+
|
|
1873
|
+
// We need to group by (File + Describe) to handle same-named describes in different files
|
|
1874
|
+
const describeMap = new Map();
|
|
1875
|
+
let foundAnyDescribe = false;
|
|
1876
|
+
|
|
1877
|
+
results.forEach(test => {
|
|
1878
|
+
const parts = test.name.split(' > ');
|
|
1879
|
+
if (parts.length > 2) {
|
|
1880
|
+
foundAnyDescribe = true;
|
|
1881
|
+
|
|
1882
|
+
// Extract Filename
|
|
1883
|
+
let fileName = 'Unknown';
|
|
1884
|
+
if (test.location && test.location.file) {
|
|
1885
|
+
fileName = test.location.file.split(path.sep).pop();
|
|
1886
|
+
} else {
|
|
1887
|
+
fileName = parts[0];
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
const describeName = parts.slice(1, parts.length - 1).join(' > ');
|
|
1891
|
+
|
|
1892
|
+
// Create a unique key for the map
|
|
1893
|
+
const key = fileName + '::' + describeName;
|
|
1894
|
+
|
|
1895
|
+
if (!describeMap.has(key)) {
|
|
1896
|
+
describeMap.set(key, { duration: 0, file: fileName, describe: describeName });
|
|
1897
|
+
}
|
|
1898
|
+
describeMap.get(key).duration += test.duration;
|
|
1899
|
+
}
|
|
1900
|
+
});
|
|
1901
|
+
|
|
1902
|
+
if (!foundAnyDescribe) {
|
|
1903
|
+
return '<div class="no-data">No test describe block found through out the executed test suite</div>';
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
const categories = [];
|
|
1907
|
+
const data = [];
|
|
1908
|
+
|
|
1909
|
+
for (const [key, val] of describeMap.entries()) {
|
|
1910
|
+
categories.push(val.describe); // X-Axis label
|
|
1911
|
+
data.push({
|
|
1912
|
+
y: val.duration,
|
|
1913
|
+
custom: {
|
|
1914
|
+
fileName: val.file,
|
|
1915
|
+
describeName: val.describe
|
|
1916
|
+
}
|
|
1917
|
+
});
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
const chartId = `descDurChart-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`;
|
|
1921
|
+
const renderFunctionName = `renderDescDurChart_${chartId.replace(/-/g, "_")}`;
|
|
1922
|
+
|
|
1923
|
+
const categoriesStr = JSON.stringify(categories);
|
|
1924
|
+
const dataStr = JSON.stringify(data);
|
|
1925
|
+
|
|
1926
|
+
return `
|
|
1927
|
+
<div id="${chartId}" class="trend-chart-container lazy-load-chart" data-render-function-name="${renderFunctionName}">
|
|
1928
|
+
<div class="no-data">Loading Describe Duration Chart...</div>
|
|
1929
|
+
</div>
|
|
1930
|
+
<script>
|
|
1931
|
+
window.${renderFunctionName} = function() {
|
|
1932
|
+
const chartContainer = document.getElementById('${chartId}');
|
|
1933
|
+
if (!chartContainer) return;
|
|
1934
|
+
if (typeof Highcharts !== 'undefined' && typeof formatDuration !== 'undefined') {
|
|
1935
|
+
try {
|
|
1936
|
+
chartContainer.innerHTML = '';
|
|
1937
|
+
Highcharts.chart('${chartId}', {
|
|
1938
|
+
chart: { type: 'line', height: 350, backgroundColor: 'transparent' },
|
|
1939
|
+
title: { text: null },
|
|
1940
|
+
xAxis: {
|
|
1941
|
+
categories: ${categoriesStr},
|
|
1942
|
+
title: { text: null },
|
|
1943
|
+
crosshair: true,
|
|
1944
|
+
labels: { style: { color: 'var(--text-color-secondary)', fontSize: '12px' } }
|
|
1945
|
+
},
|
|
1946
|
+
yAxis: {
|
|
1947
|
+
min: 0,
|
|
1948
|
+
title: { text: 'Total Duration', style: { color: 'var(--text-color)' } },
|
|
1949
|
+
labels: { formatter: function() { return formatDuration(this.value); }, style: { color: 'var(--text-color-secondary)' } }
|
|
1950
|
+
},
|
|
1951
|
+
legend: { enabled: false },
|
|
1952
|
+
plotOptions: {
|
|
1953
|
+
series: { marker: { radius: 4, states: { hover: { radius: 6 }}}, states: { hover: { halo: { size: 5, opacity: 0.1 }}}},
|
|
1954
|
+
line: { lineWidth: 2.5 }
|
|
1955
|
+
},
|
|
1956
|
+
tooltip: {
|
|
1957
|
+
shared: true, useHTML: true, backgroundColor: 'rgba(10,10,10,0.92)', borderColor: 'rgba(10,10,10,0.92)', style: { color: '#f5f5f5' },
|
|
1958
|
+
formatter: function() {
|
|
1959
|
+
// Retrieve custom data stored in the point
|
|
1960
|
+
const point = this.points ? this.points[0].point : this.point;
|
|
1961
|
+
const file = (point.custom && point.custom.fileName) ? point.custom.fileName : 'Unknown';
|
|
1962
|
+
const desc = (point.custom && point.custom.describeName) ? point.custom.describeName : this.x;
|
|
1963
|
+
|
|
1964
|
+
return '<b>File:</b> ' + file + '<br/>' +
|
|
1965
|
+
'<b>Describe:</b> ' + desc + '<br/>' +
|
|
1966
|
+
'Duration: ' + formatDuration(point.y);
|
|
1967
|
+
}
|
|
1968
|
+
},
|
|
1969
|
+
series: [{
|
|
1970
|
+
name: 'Duration',
|
|
1971
|
+
data: ${dataStr},
|
|
1972
|
+
color: 'var(--accent-color-alt)', // Orange theme
|
|
1973
|
+
marker: { symbol: 'circle' }
|
|
1974
|
+
}],
|
|
1975
|
+
credits: { enabled: false }
|
|
1976
|
+
});
|
|
1977
|
+
} catch (e) { console.error("Error rendering describe chart:", e); }
|
|
1978
|
+
}
|
|
1979
|
+
};
|
|
1980
|
+
</script>
|
|
1981
|
+
`;
|
|
1982
|
+
}
|
|
1983
|
+
// --- END NEW CHARTS HELPER FUNCTIONS ---
|
|
1784
1984
|
/**
|
|
1785
1985
|
* Generates the HTML report.
|
|
1786
1986
|
* @param {object} reportData - The data for the report.
|
|
@@ -2574,6 +2774,16 @@ aspect-ratio: 16 / 9;
|
|
|
2574
2774
|
}
|
|
2575
2775
|
</div>
|
|
2576
2776
|
</div>
|
|
2777
|
+
<div class="trend-charts-row">
|
|
2778
|
+
<div class="trend-chart">
|
|
2779
|
+
<h3 class="chart-title-header">Duration by Spec files</h3>
|
|
2780
|
+
${generateSpecDurationChart(results)}
|
|
2781
|
+
</div>
|
|
2782
|
+
<div class="trend-chart">
|
|
2783
|
+
<h3 class="chart-title-header">Duration by Test Describe</h3>
|
|
2784
|
+
${generateDescribeDurationChart(results)}
|
|
2785
|
+
</div>
|
|
2786
|
+
</div>
|
|
2577
2787
|
<h2 class="tab-main-title">Test Distribution by Worker ${infoTooltip}</h2>
|
|
2578
2788
|
<div class="trend-charts-row">
|
|
2579
2789
|
<div class="trend-chart">
|