@arghajit/dummy 0.3.7 → 0.3.9

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@arghajit/dummy",
3
3
  "author": "Arghajit Singha",
4
- "version": "0.3.7",
4
+ "version": "0.3.9",
5
5
  "description": "A Playwright reporter and dashboard for visualizing test results.",
6
6
  "homepage": "https://playwright-pulse-report.netlify.app/",
7
7
  "keywords": [
@@ -1,4 +1,3 @@
1
- #!/usr/bin/env node
2
1
  import * as fs from "fs";
3
2
  import * as path from "path";
4
3
  import { pathToFileURL } from "url";
@@ -24,9 +23,61 @@ async function findPlaywrightConfig() {
24
23
  }
25
24
 
26
25
  async function extractOutputDirFromConfig(configPath) {
26
+ let fileContent = "";
27
27
  try {
28
- let config;
28
+ fileContent = fs.readFileSync(configPath, "utf-8");
29
+ } catch (e) {
30
+ // If we can't read the file, we can't parse or import it.
31
+ return null;
32
+ }
33
+
34
+ // 1. Strategy: Text Parsing (Safe & Fast)
35
+ // We try to read the file as text first. This finds the outputDir without
36
+ // triggering any Node.js warnings or errors.
37
+ try {
38
+ // Regex matches: outputDir: "value" or outputDir: 'value'
39
+ const match = fileContent.match(/outputDir:\s*["']([^"']+)["']/);
40
+
41
+ if (match && match[1]) {
42
+ return path.resolve(process.cwd(), match[1]);
43
+ }
44
+ } catch (e) {
45
+ // Ignore text reading errors
46
+ }
29
47
 
48
+ // 2. Safety Check: Detect ESM in CJS to Prevent Node Warnings
49
+ // The warning "To load an ES module..." happens when we try to import()
50
+ // a .js file containing ESM syntax (import/export) in a CJS package.
51
+ // We explicitly check for this and ABORT the import if found.
52
+ if (configPath.endsWith(".js")) {
53
+ let isModulePackage = false;
54
+ try {
55
+ const pkgPath = path.resolve(process.cwd(), "package.json");
56
+ if (fs.existsSync(pkgPath)) {
57
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
58
+ isModulePackage = pkg.type === "module";
59
+ }
60
+ } catch (e) {}
61
+
62
+ if (!isModulePackage) {
63
+ // Heuristic: Check for ESM syntax (import/export at start of lines)
64
+ const hasEsmSyntax =
65
+ /^\s*import\s+/m.test(fileContent) ||
66
+ /^\s*export\s+/m.test(fileContent);
67
+
68
+ if (hasEsmSyntax) {
69
+ // We found ESM syntax in a .js file within a CJS project.
70
+ // Attempting to import this WILL trigger the Node.js warning.
71
+ // Since regex failed to find outputDir, and we can't import safely, we abort now.
72
+ return null;
73
+ }
74
+ }
75
+ }
76
+
77
+ // 3. Strategy: Dynamic Import
78
+ // If we passed the safety check, we try to import the config.
79
+ try {
80
+ let config;
30
81
  const configDir = dirname(configPath);
31
82
  const originalDirname = global.__dirname;
32
83
  const originalFilename = global.__filename;
@@ -39,73 +90,70 @@ async function extractOutputDirFromConfig(configPath) {
39
90
  try {
40
91
  const { register } = await import("node:module");
41
92
  const { pathToFileURL } = await import("node:url");
42
-
43
93
  register("ts-node/esm", pathToFileURL("./"));
44
-
45
94
  config = await import(pathToFileURL(configPath).href);
46
95
  } 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
- }
96
+ const tsNode = await import("ts-node");
97
+ tsNode.register({
98
+ transpileOnly: true,
99
+ compilerOptions: { module: "commonjs" },
100
+ });
101
+ config = require(configPath);
60
102
  }
61
103
  } else {
104
+ // Try dynamic import for JS/MJS
62
105
  config = await import(pathToFileURL(configPath).href);
63
106
  }
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;
107
+
108
+ // Handle Default Export
109
+ if (config && config.default) {
110
+ config = config.default;
74
111
  }
75
- }
76
112
 
77
- const playwrightConfig = config.default || config;
78
-
79
- if (playwrightConfig && Array.isArray(playwrightConfig.reporter)) {
80
- for (const reporterConfig of playwrightConfig.reporter) {
81
- if (Array.isArray(reporterConfig)) {
82
- const [reporterPath, options] = reporterConfig;
83
-
84
- if (
85
- typeof reporterPath === "string" &&
86
- (reporterPath.includes("playwright-pulse-report") ||
87
- reporterPath.includes("@arghajit/playwright-pulse-report") ||
88
- reporterPath.includes("@arghajit/dummy"))
89
- ) {
90
- if (options && options.outputDir) {
91
- const resolvedPath =
92
- typeof options.outputDir === "string"
93
- ? options.outputDir
94
- : options.outputDir;
95
- console.log(`Found outputDir in config: ${resolvedPath}`);
96
- return path.resolve(process.cwd(), resolvedPath);
113
+ if (config) {
114
+ // Check for Reporter Config
115
+ if (config.reporter) {
116
+ const reporters = Array.isArray(config.reporter)
117
+ ? config.reporter
118
+ : [config.reporter];
119
+
120
+ for (const reporter of reporters) {
121
+ const reporterName = Array.isArray(reporter)
122
+ ? reporter[0]
123
+ : reporter;
124
+ const reporterOptions = Array.isArray(reporter)
125
+ ? reporter[1]
126
+ : null;
127
+
128
+ if (
129
+ typeof reporterName === "string" &&
130
+ (reporterName.includes("playwright-pulse-report") ||
131
+ reporterName.includes("@arghajit/playwright-pulse-report") ||
132
+ reporterName.includes("@arghajit/dummy"))
133
+ ) {
134
+ if (reporterOptions && reporterOptions.outputDir) {
135
+ return path.resolve(process.cwd(), reporterOptions.outputDir);
136
+ }
97
137
  }
98
138
  }
99
139
  }
140
+
141
+ // Check for Global outputDir
142
+ if (config.outputDir) {
143
+ return path.resolve(process.cwd(), config.outputDir);
144
+ }
100
145
  }
146
+ } finally {
147
+ // Clean up globals
148
+ global.__dirname = originalDirname;
149
+ global.__filename = originalFilename;
101
150
  }
102
-
103
- console.log("No matching reporter config found with outputDir");
104
- return null;
105
151
  } catch (error) {
106
- console.error("Error extracting outputDir from config:", error);
152
+ // SILENT CATCH: Do NOT log anything here.
107
153
  return null;
108
154
  }
155
+
156
+ return null;
109
157
  }
110
158
 
111
159
  export async function getOutputDir(customOutputDirFromArgs = null) {
@@ -350,6 +350,7 @@ function generateTestTrendsChart(trendData) {
350
350
  </script>
351
351
  `;
352
352
  }
353
+ const accentColorAltRGB = "255, 152, 0"; // Assuming var(--accent-color-alt) is Orange #FF9800
353
354
  function generateDurationTrendChart(trendData) {
354
355
  if (!trendData || !trendData.overall || trendData.overall.length === 0) {
355
356
  return '<div class="no-data">No overall trend data available for durations.</div>';
@@ -363,8 +364,6 @@ function generateDurationTrendChart(trendData) {
363
364
  )}`;
364
365
  const runs = trendData.overall;
365
366
 
366
- const accentColorAltRGB = "255, 152, 0"; // Assuming var(--accent-color-alt) is Orange #FF9800
367
-
368
367
  const chartDataString = JSON.stringify(runs.map((run) => run.duration));
369
368
  const categoriesString = JSON.stringify(runs.map((run, i) => `Run ${i + 1}`));
370
369
  const runsForTooltip = runs.map((r) => ({
@@ -1644,28 +1643,28 @@ function generateAIFailureAnalyzerTab(results) {
1644
1643
  </div>
1645
1644
  `;
1646
1645
  }
1647
- // --- NEW CHARTS HELPER FUNCTIONS (Style: Line Chart + Enhanced Tooltips) ---
1648
-
1646
+ /**
1647
+ * Generates a area chart showing the total duration per spec file.
1648
+ * The chart is lazy-loaded and rendered with Highcharts when scrolled into view.
1649
+ *
1650
+ * @param {Array<object>} results - Array of test result objects.
1651
+ * @returns {string} HTML string containing the chart container and lazy-loading script.
1652
+ */
1649
1653
  function generateSpecDurationChart(results) {
1650
1654
  if (!results || results.length === 0)
1651
1655
  return '<div class="no-data">No results available.</div>';
1652
1656
 
1653
1657
  const specDurations = {};
1654
1658
  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
- }
1659
+ // Use the dedicated 'spec_file' key
1660
+ const fileName = test.spec_file || "Unknown File";
1662
1661
 
1663
1662
  if (!specDurations[fileName]) specDurations[fileName] = 0;
1664
1663
  specDurations[fileName] += test.duration;
1665
1664
  });
1666
1665
 
1667
1666
  const categories = Object.keys(specDurations);
1668
- // Map data to objects to pass metadata if needed, though categories usually handle this.
1667
+ // We map 'name' here, which we will use in the tooltip later
1669
1668
  const data = categories.map((cat) => ({
1670
1669
  y: specDurations[cat],
1671
1670
  name: cat,
@@ -1694,24 +1693,21 @@ function generateSpecDurationChart(results) {
1694
1693
  try {
1695
1694
  chartContainer.innerHTML = '';
1696
1695
  Highcharts.chart('${chartId}', {
1697
- chart: { type: 'line', height: 350, backgroundColor: 'transparent' },
1696
+ chart: { type: 'area', height: 350, backgroundColor: 'transparent' },
1698
1697
  title: { text: null },
1699
1698
  xAxis: {
1700
1699
  categories: ${categoriesStr},
1700
+ visible: false, // 1. HIDE THE X-AXIS
1701
1701
  title: { text: null },
1702
- crosshair: true,
1703
- labels: { style: { color: 'var(--text-color-secondary)', fontSize: '12px' } }
1702
+ crosshair: true
1704
1703
  },
1705
1704
  yAxis: {
1706
1705
  min: 0,
1707
1706
  title: { text: 'Total Duration', style: { color: 'var(--text-color)' } },
1708
1707
  labels: { formatter: function() { return formatDuration(this.value); }, style: { color: 'var(--text-color-secondary)' } }
1709
1708
  },
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
- },
1709
+ legend: { layout: 'horizontal', align: 'center', verticalAlign: 'bottom', itemStyle: { fontSize: '12px', color: 'var(--text-color)' }},
1710
+ plotOptions: { area: { lineWidth: 2.5, states: { hover: { lineWidthPlus: 0 } }, threshold: null }},
1715
1711
  tooltip: {
1716
1712
  shared: true,
1717
1713
  useHTML: true,
@@ -1719,19 +1715,22 @@ function generateSpecDurationChart(results) {
1719
1715
  borderColor: 'rgba(10,10,10,0.92)',
1720
1716
  style: { color: '#f5f5f5' },
1721
1717
  formatter: function() {
1722
- // 'this.x' is the File Name (category)
1723
1718
  const point = this.points ? this.points[0].point : this.point;
1724
1719
  const color = point.color || point.series.color;
1725
1720
 
1726
- return '<span style="color:' + color + '">●</span> <b>File: ' + this.x + '</b><br/>' +
1727
- 'Duration: <b>' + formatDuration(this.y) + '</b>';
1728
- }
1721
+ // 2. FIX: Use 'point.name' instead of 'this.x' to get the actual filename
1722
+ return '<span style="color:' + color + '">●</span> <b>File: ' + point.name + '</b><br/>' +
1723
+ 'Duration: <b>' + formatDuration(this.y) + '</b>';
1724
+ }
1729
1725
  },
1730
1726
  series: [{
1731
1727
  name: 'Duration',
1732
1728
  data: ${dataStr},
1733
- color: 'var(--accent-color-alt)', // Orange theme
1734
- marker: { symbol: 'circle' }
1729
+ color: 'var(--accent-color-alt)',
1730
+ type: 'area',
1731
+ marker: { symbol: 'circle', enabled: true, radius: 4, states: { hover: { radius: 6, lineWidthPlus: 0 } } },
1732
+ fillColor: { linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 }, stops: [[0, 'rgba(${accentColorAltRGB}, 0.4)'], [1, 'rgba(${accentColorAltRGB}, 0.05)']] },
1733
+ lineWidth: 2.5
1735
1734
  }],
1736
1735
  credits: { enabled: false }
1737
1736
  });
@@ -1741,31 +1740,33 @@ function generateSpecDurationChart(results) {
1741
1740
  </script>
1742
1741
  `;
1743
1742
  }
1744
-
1743
+ /**
1744
+ * Generates a vertical bar chart showing the total duration of each test describe block.
1745
+ * Tests without a describe block or with "n/a" / empty describe names are ignored.
1746
+ * @param {Array<object>} results - Array of test result objects.
1747
+ * @returns {string} HTML string containing the chart container and lazy-loading script.
1748
+ */
1745
1749
  function generateDescribeDurationChart(results) {
1746
1750
  if (!results || results.length === 0)
1747
1751
  return '<div class="no-data">No results available.</div>';
1748
1752
 
1749
- // We need to group by (File + Describe) to handle same-named describes in different files
1750
1753
  const describeMap = new Map();
1751
1754
  let foundAnyDescribe = false;
1752
1755
 
1753
1756
  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];
1757
+ if (test.describe) {
1758
+ const describeName = test.describe;
1759
+ // Filter out invalid describe blocks
1760
+ if (
1761
+ !describeName ||
1762
+ describeName.trim().toLowerCase() === "n/a" ||
1763
+ describeName.trim() === ""
1764
+ ) {
1765
+ return;
1764
1766
  }
1765
1767
 
1766
- const describeName = parts.slice(1, parts.length - 1).join(" > ");
1767
-
1768
- // Create a unique key for the map
1768
+ foundAnyDescribe = true;
1769
+ const fileName = test.spec_file || "Unknown File";
1769
1770
  const key = fileName + "::" + describeName;
1770
1771
 
1771
1772
  if (!describeMap.has(key)) {
@@ -1780,16 +1781,17 @@ function generateDescribeDurationChart(results) {
1780
1781
  });
1781
1782
 
1782
1783
  if (!foundAnyDescribe) {
1783
- return '<div class="no-data">No test describe block found through out the executed test suite</div>';
1784
+ return '<div class="no-data">No valid test describe blocks found.</div>';
1784
1785
  }
1785
1786
 
1786
1787
  const categories = [];
1787
1788
  const data = [];
1788
1789
 
1789
1790
  for (const [key, val] of describeMap.entries()) {
1790
- categories.push(val.describe); // X-Axis label
1791
+ categories.push(val.describe);
1791
1792
  data.push({
1792
1793
  y: val.duration,
1794
+ name: val.describe,
1793
1795
  custom: {
1794
1796
  fileName: val.file,
1795
1797
  describeName: val.describe,
@@ -1817,13 +1819,17 @@ function generateDescribeDurationChart(results) {
1817
1819
  try {
1818
1820
  chartContainer.innerHTML = '';
1819
1821
  Highcharts.chart('${chartId}', {
1820
- chart: { type: 'line', height: 350, backgroundColor: 'transparent' },
1822
+ chart: {
1823
+ type: 'column', // 1. CHANGED: 'bar' -> 'column' for vertical bars
1824
+ height: 400, // 2. CHANGED: Fixed height works better for vertical charts
1825
+ backgroundColor: 'transparent'
1826
+ },
1821
1827
  title: { text: null },
1822
1828
  xAxis: {
1823
1829
  categories: ${categoriesStr},
1830
+ visible: false, // Hidden as requested
1824
1831
  title: { text: null },
1825
- crosshair: true,
1826
- labels: { style: { color: 'var(--text-color-secondary)', fontSize: '12px' } }
1832
+ crosshair: true
1827
1833
  },
1828
1834
  yAxis: {
1829
1835
  min: 0,
@@ -1832,8 +1838,12 @@ function generateDescribeDurationChart(results) {
1832
1838
  },
1833
1839
  legend: { enabled: false },
1834
1840
  plotOptions: {
1835
- series: { marker: { radius: 4, states: { hover: { radius: 6 }}}, states: { hover: { halo: { size: 5, opacity: 0.1 }}}},
1836
- line: { lineWidth: 2.5 }
1841
+ series: {
1842
+ borderRadius: 4,
1843
+ borderWidth: 0,
1844
+ states: { hover: { brightness: 0.1 }}
1845
+ },
1846
+ column: { pointPadding: 0.2, groupPadding: 0.1 } // Adjust spacing for columns
1837
1847
  },
1838
1848
  tooltip: {
1839
1849
  shared: true,
@@ -1842,24 +1852,20 @@ function generateDescribeDurationChart(results) {
1842
1852
  borderColor: 'rgba(10,10,10,0.92)',
1843
1853
  style: { color: '#f5f5f5' },
1844
1854
  formatter: function() {
1845
- // Retrieve custom data stored in the point
1846
1855
  const point = this.points ? this.points[0].point : this.point;
1847
-
1848
- // Safety check for your custom data
1849
1856
  const file = (point.custom && point.custom.fileName) ? point.custom.fileName : 'Unknown';
1850
- const desc = (point.custom && point.custom.describeName) ? point.custom.describeName : this.x;
1857
+ const desc = point.name || 'Unknown';
1851
1858
  const color = point.color || point.series.color;
1852
1859
 
1853
1860
  return '<span style="color:' + color + '">●</span> <b>Describe: ' + desc + '</b><br/>' +
1854
1861
  '<span style="opacity: 0.8; font-size: 0.9em; color: #ddd;">File: ' + file + '</span><br/>' +
1855
1862
  'Duration: <b>' + formatDuration(point.y) + '</b>';
1856
- }
1863
+ }
1857
1864
  },
1858
1865
  series: [{
1859
1866
  name: 'Duration',
1860
1867
  data: ${dataStr},
1861
- color: 'var(--accent-color-alt)', // Orange theme
1862
- marker: { symbol: 'circle' }
1868
+ color: 'var(--accent-color-alt)',
1863
1869
  }],
1864
1870
  credits: { enabled: false }
1865
1871
  });
@@ -1869,7 +1875,12 @@ function generateDescribeDurationChart(results) {
1869
1875
  </script>
1870
1876
  `;
1871
1877
  }
1872
- // --- END NEW CHARTS HELPER FUNCTIONS ---
1878
+ /**
1879
+ * Generates the HTML content for the report.
1880
+ * @param {object} reportData - The report data object containing run and results.
1881
+ * @param {object} trendData - Optional trend data object for additional trends.
1882
+ * @returns {string} HTML string for the report.
1883
+ */
1873
1884
  function generateHTML(reportData, trendData = null) {
1874
1885
  const { run, results } = reportData;
1875
1886
  const suitesData = getSuitesData(reportData.results || []);
@@ -394,6 +394,7 @@ function generateTestTrendsChart(trendData) {
394
394
  </script>
395
395
  `;
396
396
  }
397
+ const accentColorAltRGB = "255, 152, 0"; // Assuming var(--accent-color-alt) is Orange #FF9800
397
398
  /**
398
399
  * Generates HTML and JavaScript for a Highcharts area chart to display test duration trends.
399
400
  * @param {object} trendData Data for duration trends.
@@ -413,8 +414,6 @@ function generateDurationTrendChart(trendData) {
413
414
  )}`;
414
415
  const runs = trendData.overall;
415
416
 
416
- const accentColorAltRGB = "255, 152, 0"; // Assuming var(--accent-color-alt) is Orange #FF9800
417
-
418
417
  const chartDataString = JSON.stringify(runs.map((run) => run.duration));
419
418
  const categoriesString = JSON.stringify(runs.map((run, i) => `Run ${i + 1}`));
420
419
  const runsForTooltip = runs.map((r) => ({
@@ -1781,35 +1780,39 @@ function generateAIFailureAnalyzerTab(results) {
1781
1780
  </div>
1782
1781
  `;
1783
1782
  }
1784
- // --- NEW CHARTS HELPER FUNCTIONS (Style: Line Chart + Enhanced Tooltips) ---
1785
-
1783
+ /**
1784
+ * Generates a area chart showing the total duration per spec file.
1785
+ * The chart is lazy-loaded and rendered with Highcharts when scrolled into view.
1786
+ *
1787
+ * @param {Array<object>} results - Array of test result objects.
1788
+ * @returns {string} HTML string containing the chart container and lazy-loading script.
1789
+ */
1786
1790
  function generateSpecDurationChart(results) {
1787
- if (!results || results.length === 0) return '<div class="no-data">No results available.</div>';
1791
+ if (!results || results.length === 0)
1792
+ return '<div class="no-data">No results available.</div>';
1788
1793
 
1789
1794
  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
-
1795
+ results.forEach((test) => {
1796
+ // Use the dedicated 'spec_file' key
1797
+ const fileName = test.spec_file || "Unknown File";
1798
+
1799
1799
  if (!specDurations[fileName]) specDurations[fileName] = 0;
1800
1800
  specDurations[fileName] += test.duration;
1801
1801
  });
1802
1802
 
1803
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
1804
+ // We map 'name' here, which we will use in the tooltip later
1805
+ const data = categories.map((cat) => ({
1806
+ y: specDurations[cat],
1807
+ name: cat,
1808
1808
  }));
1809
1809
 
1810
- if (categories.length === 0) return '<div class="no-data">No spec data found.</div>';
1810
+ if (categories.length === 0)
1811
+ return '<div class="no-data">No spec data found.</div>';
1811
1812
 
1812
- const chartId = `specDurChart-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`;
1813
+ const chartId = `specDurChart-${Date.now()}-${Math.random()
1814
+ .toString(36)
1815
+ .substring(2, 7)}`;
1813
1816
  const renderFunctionName = `renderSpecDurChart_${chartId.replace(/-/g, "_")}`;
1814
1817
 
1815
1818
  const categoriesStr = JSON.stringify(categories);
@@ -1827,36 +1830,44 @@ function generateSpecDurationChart(results) {
1827
1830
  try {
1828
1831
  chartContainer.innerHTML = '';
1829
1832
  Highcharts.chart('${chartId}', {
1830
- chart: { type: 'line', height: 350, backgroundColor: 'transparent' },
1833
+ chart: { type: 'area', height: 350, backgroundColor: 'transparent' },
1831
1834
  title: { text: null },
1832
1835
  xAxis: {
1833
1836
  categories: ${categoriesStr},
1837
+ visible: false, // 1. HIDE THE X-AXIS
1834
1838
  title: { text: null },
1835
- crosshair: true,
1836
- labels: { style: { color: 'var(--text-color-secondary)', fontSize: '12px' } }
1839
+ crosshair: true
1837
1840
  },
1838
1841
  yAxis: {
1839
1842
  min: 0,
1840
1843
  title: { text: 'Total Duration', style: { color: 'var(--text-color)' } },
1841
1844
  labels: { formatter: function() { return formatDuration(this.value); }, style: { color: 'var(--text-color-secondary)' } }
1842
1845
  },
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
- },
1846
+ legend: { layout: 'horizontal', align: 'center', verticalAlign: 'bottom', itemStyle: { fontSize: '12px', color: 'var(--text-color)' }},
1847
+ plotOptions: { area: { lineWidth: 2.5, states: { hover: { lineWidthPlus: 0 } }, threshold: null }},
1848
1848
  tooltip: {
1849
- shared: true, useHTML: true, backgroundColor: 'rgba(10,10,10,0.92)', borderColor: 'rgba(10,10,10,0.92)', style: { color: '#f5f5f5' },
1849
+ shared: true,
1850
+ useHTML: true,
1851
+ backgroundColor: 'rgba(10,10,10,0.92)',
1852
+ borderColor: 'rgba(10,10,10,0.92)',
1853
+ style: { color: '#f5f5f5' },
1850
1854
  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);
1855
+ const point = this.points ? this.points[0].point : this.point;
1856
+ const color = point.color || point.series.color;
1857
+
1858
+ // 2. FIX: Use 'point.name' instead of 'this.x' to get the actual filename
1859
+ return '<span style="color:' + color + '">●</span> <b>File: ' + point.name + '</b><br/>' +
1860
+ 'Duration: <b>' + formatDuration(this.y) + '</b>';
1853
1861
  }
1854
1862
  },
1855
1863
  series: [{
1856
1864
  name: 'Duration',
1857
1865
  data: ${dataStr},
1858
- color: 'var(--accent-color-alt)', // Orange theme
1859
- marker: { symbol: 'circle' }
1866
+ color: 'var(--accent-color-alt)',
1867
+ type: 'area',
1868
+ marker: { symbol: 'circle', enabled: true, radius: 4, states: { hover: { radius: 6, lineWidthPlus: 0 } } },
1869
+ fillColor: { linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 }, stops: [[0, 'rgba(${accentColorAltRGB}, 0.4)'], [1, 'rgba(${accentColorAltRGB}, 0.05)']] },
1870
+ lineWidth: 2.5
1860
1871
  }],
1861
1872
  credits: { enabled: false }
1862
1873
  });
@@ -1866,60 +1877,70 @@ function generateSpecDurationChart(results) {
1866
1877
  </script>
1867
1878
  `;
1868
1879
  }
1869
-
1880
+ /**
1881
+ * Generates a vertical bar chart showing the total duration of each test describe block.
1882
+ * Tests without a describe block or with "n/a" / empty describe names are ignored.
1883
+ * @param {Array<object>} results - Array of test result objects.
1884
+ * @returns {string} HTML string containing the chart container and lazy-loading script.
1885
+ */
1870
1886
  function generateDescribeDurationChart(results) {
1871
- if (!results || results.length === 0) return '<div class="no-data">No results available.</div>';
1887
+ if (!results || results.length === 0)
1888
+ return '<div class="no-data">No results available.</div>';
1872
1889
 
1873
- // We need to group by (File + Describe) to handle same-named describes in different files
1874
1890
  const describeMap = new Map();
1875
1891
  let foundAnyDescribe = false;
1876
1892
 
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];
1893
+ results.forEach((test) => {
1894
+ if (test.describe) {
1895
+ const describeName = test.describe;
1896
+ // Filter out invalid describe blocks
1897
+ if (
1898
+ !describeName ||
1899
+ describeName.trim().toLowerCase() === "n/a" ||
1900
+ describeName.trim() === ""
1901
+ ) {
1902
+ return;
1888
1903
  }
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
-
1904
+
1905
+ foundAnyDescribe = true;
1906
+ const fileName = test.spec_file || "Unknown File";
1907
+ const key = fileName + "::" + describeName;
1908
+
1895
1909
  if (!describeMap.has(key)) {
1896
- describeMap.set(key, { duration: 0, file: fileName, describe: describeName });
1910
+ describeMap.set(key, {
1911
+ duration: 0,
1912
+ file: fileName,
1913
+ describe: describeName,
1914
+ });
1897
1915
  }
1898
1916
  describeMap.get(key).duration += test.duration;
1899
1917
  }
1900
1918
  });
1901
1919
 
1902
1920
  if (!foundAnyDescribe) {
1903
- return '<div class="no-data">No test describe block found through out the executed test suite</div>';
1921
+ return '<div class="no-data">No valid test describe blocks found.</div>';
1904
1922
  }
1905
1923
 
1906
1924
  const categories = [];
1907
1925
  const data = [];
1908
1926
 
1909
1927
  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
- });
1928
+ categories.push(val.describe);
1929
+ data.push({
1930
+ y: val.duration,
1931
+ name: val.describe,
1932
+ custom: {
1933
+ fileName: val.file,
1934
+ describeName: val.describe,
1935
+ },
1936
+ });
1918
1937
  }
1919
1938
 
1920
- const chartId = `descDurChart-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`;
1939
+ const chartId = `descDurChart-${Date.now()}-${Math.random()
1940
+ .toString(36)
1941
+ .substring(2, 7)}`;
1921
1942
  const renderFunctionName = `renderDescDurChart_${chartId.replace(/-/g, "_")}`;
1922
-
1943
+
1923
1944
  const categoriesStr = JSON.stringify(categories);
1924
1945
  const dataStr = JSON.stringify(data);
1925
1946
 
@@ -1935,13 +1956,17 @@ function generateDescribeDurationChart(results) {
1935
1956
  try {
1936
1957
  chartContainer.innerHTML = '';
1937
1958
  Highcharts.chart('${chartId}', {
1938
- chart: { type: 'line', height: 350, backgroundColor: 'transparent' },
1959
+ chart: {
1960
+ type: 'column', // 1. CHANGED: 'bar' -> 'column' for vertical bars
1961
+ height: 400, // 2. CHANGED: Fixed height works better for vertical charts
1962
+ backgroundColor: 'transparent'
1963
+ },
1939
1964
  title: { text: null },
1940
1965
  xAxis: {
1941
1966
  categories: ${categoriesStr},
1967
+ visible: false, // Hidden as requested
1942
1968
  title: { text: null },
1943
- crosshair: true,
1944
- labels: { style: { color: 'var(--text-color-secondary)', fontSize: '12px' } }
1969
+ crosshair: true
1945
1970
  },
1946
1971
  yAxis: {
1947
1972
  min: 0,
@@ -1950,27 +1975,34 @@ function generateDescribeDurationChart(results) {
1950
1975
  },
1951
1976
  legend: { enabled: false },
1952
1977
  plotOptions: {
1953
- series: { marker: { radius: 4, states: { hover: { radius: 6 }}}, states: { hover: { halo: { size: 5, opacity: 0.1 }}}},
1954
- line: { lineWidth: 2.5 }
1978
+ series: {
1979
+ borderRadius: 4,
1980
+ borderWidth: 0,
1981
+ states: { hover: { brightness: 0.1 }}
1982
+ },
1983
+ column: { pointPadding: 0.2, groupPadding: 0.1 } // Adjust spacing for columns
1955
1984
  },
1956
1985
  tooltip: {
1957
- shared: true, useHTML: true, backgroundColor: 'rgba(10,10,10,0.92)', borderColor: 'rgba(10,10,10,0.92)', style: { color: '#f5f5f5' },
1986
+ shared: true,
1987
+ useHTML: true,
1988
+ backgroundColor: 'rgba(10,10,10,0.92)',
1989
+ borderColor: 'rgba(10,10,10,0.92)',
1990
+ style: { color: '#f5f5f5' },
1958
1991
  formatter: function() {
1959
- // Retrieve custom data stored in the point
1960
1992
  const point = this.points ? this.points[0].point : this.point;
1961
1993
  const file = (point.custom && point.custom.fileName) ? point.custom.fileName : 'Unknown';
1962
- const desc = (point.custom && point.custom.describeName) ? point.custom.describeName : this.x;
1994
+ const desc = point.name || 'Unknown';
1995
+ const color = point.color || point.series.color;
1963
1996
 
1964
- return '<b>File:</b> ' + file + '<br/>' +
1965
- '<b>Describe:</b> ' + desc + '<br/>' +
1966
- 'Duration: ' + formatDuration(point.y);
1997
+ return '<span style="color:' + color + '">●</span> <b>Describe: ' + desc + '</b><br/>' +
1998
+ '<span style="opacity: 0.8; font-size: 0.9em; color: #ddd;">File: ' + file + '</span><br/>' +
1999
+ 'Duration: <b>' + formatDuration(point.y) + '</b>';
1967
2000
  }
1968
2001
  },
1969
2002
  series: [{
1970
2003
  name: 'Duration',
1971
2004
  data: ${dataStr},
1972
- color: 'var(--accent-color-alt)', // Orange theme
1973
- marker: { symbol: 'circle' }
2005
+ color: 'var(--accent-color-alt)',
1974
2006
  }],
1975
2007
  credits: { enabled: false }
1976
2008
  });
@@ -1980,7 +2012,6 @@ function generateDescribeDurationChart(results) {
1980
2012
  </script>
1981
2013
  `;
1982
2014
  }
1983
- // --- END NEW CHARTS HELPER FUNCTIONS ---
1984
2015
  /**
1985
2016
  * Generates the HTML report.
1986
2017
  * @param {object} reportData - The data for the report.