@arghajit/dummy 0.3.10 → 0.3.12

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.10",
4
+ "version": "0.3.12",
5
5
  "description": "A Playwright reporter and dashboard for visualizing test results.",
6
6
  "homepage": "https://playwright-pulse-report.netlify.app/",
7
7
  "keywords": [
@@ -225,15 +225,15 @@ function generateMinifiedHTML(reportData) {
225
225
  case "Minor":
226
226
  return "#006064";
227
227
  case "Low":
228
- return "#E65100";
229
- case "Medium":
230
228
  return "#FFA07A";
229
+ case "Medium":
230
+ return "#577A11";
231
231
  case "High":
232
232
  return "#B71C1C";
233
233
  case "Critical":
234
- return "#3E0000";
234
+ return "#64158A";
235
235
  default:
236
- return "#FFA07A";
236
+ return "#577A11";
237
237
  }
238
238
  };
239
239
  // We use inline styles here to ensure they render correctly in emails
@@ -286,7 +286,7 @@ function generateMinifiedHTML(reportData) {
286
286
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
287
287
  <link rel="icon" type="image/png" href="https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images/pulse-report/playwright_pulse_icon.png">
288
288
  <link rel="apple-touch-icon" href="https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images/pulse-report/playwright_pulse_icon.png">
289
- <title>Playwright Pulse Summary Report</title>
289
+ <title>Pulse Summary Report</title>
290
290
  <style>
291
291
  :root {
292
292
  --primary-color: #2c3e50; /* Dark Blue/Grey */
@@ -527,7 +527,7 @@ function generateMinifiedHTML(reportData) {
527
527
  <header class="report-header">
528
528
  <div class="report-header-title">
529
529
  <img id="report-logo" src="https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images/pulse-report/playwright_pulse_icon.png" alt="Report Logo">
530
- <h1>Playwright Pulse Summary</h1>
530
+ <h1>Pulse Summary Report</h1>
531
531
  </div>
532
532
  <div class="run-info">
533
533
  <strong>Run Date:</strong> ${formatDate(
@@ -569,10 +569,10 @@ function generateMinifiedHTML(reportData) {
569
569
  <span style="margin-left: 4px; font-weight: 600; color: var(--text-color);">Severity:</span>
570
570
 
571
571
  <span style="background-color: #006064; color: #fff; padding: 2px 6px; border-radius: 3px;">Minor</span>
572
- <span style="background-color: #E65100; color: #fff; padding: 2px 6px; border-radius: 3px;">Low</span>
573
- <span style="background-color: #FFA07A; color: #fff; padding: 2px 6px; border-radius: 3px;">Medium</span>
572
+ <span style="background-color: #FFA07A; color: #fff; padding: 2px 6px; border-radius: 3px;">Low</span>
573
+ <span style="background-color: #577A11; color: #fff; padding: 2px 6px; border-radius: 3px;">Medium</span>
574
574
  <span style="background-color: #B71C1C; color: #fff; padding: 2px 6px; border-radius: 3px;">High</span>
575
- <span style="background-color: #3E0000; color: #fff; padding: 2px 6px; border-radius: 3px;">Critical</span>
575
+ <span style="background-color: #64158A; color: #fff; padding: 2px 6px; border-radius: 3px;">Critical</span>
576
576
 
577
577
  <span style="border-left: 1px solid #ccc; height: 14px; margin: 0 4px;"></span>
578
578
 
@@ -702,6 +702,9 @@ function generateEnvironmentDashboard(environment, dashboardHeight = 600) {
702
702
  const cardHeight = Math.floor(dashboardHeight * 0.44);
703
703
  const cardContentPadding = 16; // px
704
704
 
705
+ // Logic for Run Context
706
+ const runContext = process.env.CI ? "CI" : "Local Test";
707
+
705
708
  return `
706
709
  <div class="environment-dashboard-wrapper" id="${dashboardId}">
707
710
  <style>
@@ -744,6 +747,20 @@ function generateEnvironmentDashboard(environment, dashboardHeight = 600) {
744
747
  gap: 20px;
745
748
  font-size: 14px;
746
749
  }
750
+
751
+ /* Mobile Responsiveness */
752
+ @media (max-width: 768px) {
753
+ .environment-dashboard-wrapper {
754
+ grid-template-columns: 1fr; /* Stack columns on mobile */
755
+ grid-template-rows: auto;
756
+ padding: 16px;
757
+ height: auto !important; /* Allow height to grow */
758
+ }
759
+ .env-card {
760
+ height: auto !important; /* Allow cards to grow based on content */
761
+ min-height: 200px;
762
+ }
763
+ }
747
764
 
748
765
  .env-dashboard-header {
749
766
  grid-column: 1 / -1;
@@ -753,6 +770,8 @@ function generateEnvironmentDashboard(environment, dashboardHeight = 600) {
753
770
  border-bottom: 1px solid var(--border-color);
754
771
  padding-bottom: 16px;
755
772
  margin-bottom: 8px;
773
+ flex-wrap: wrap; /* Allow wrapping header items */
774
+ gap: 10px;
756
775
  }
757
776
 
758
777
  .env-dashboard-title {
@@ -810,6 +829,8 @@ function generateEnvironmentDashboard(environment, dashboardHeight = 600) {
810
829
  padding: 10px 0;
811
830
  border-bottom: 1px solid var(--border-light-color);
812
831
  font-size: 0.875rem;
832
+ flex-wrap: wrap; /* Allow details to wrap on very small screens */
833
+ gap: 8px;
813
834
  }
814
835
 
815
836
  .env-detail-row:last-child {
@@ -827,6 +848,7 @@ function generateEnvironmentDashboard(environment, dashboardHeight = 600) {
827
848
  font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
828
849
  text-align: right;
829
850
  word-break: break-all;
851
+ margin-left: auto; /* Push to right */
830
852
  }
831
853
 
832
854
  .env-chip {
@@ -1012,7 +1034,7 @@ function generateEnvironmentDashboard(environment, dashboardHeight = 600) {
1012
1034
  </div>
1013
1035
  <div class="env-detail-row">
1014
1036
  <span class="env-detail-label">Run Context</span>
1015
- <span class="env-detail-value">CI/Local Test</span>
1037
+ <span class="env-detail-value">${runContext}</span>
1016
1038
  </div>
1017
1039
  </div>
1018
1040
  </div>
@@ -1466,11 +1488,14 @@ function getSuitesData(results) {
1466
1488
  }
1467
1489
  function generateSuitesWidget(suitesData) {
1468
1490
  if (!suitesData || suitesData.length === 0) {
1469
- return `<div class="suites-widget"><div class="suites-header"><h2>Test Suites</h2></div><div class="no-data">No suite data available.</div></div>`;
1491
+ // Maintain height consistency even if empty
1492
+ return `<div class="suites-widget" style="height: 450px;"><div class="suites-header"><h2>Test Suites</h2></div><div class="no-data">No suite data available.</div></div>`;
1470
1493
  }
1494
+
1495
+ // Added inline styles for height consistency with Pie Chart (approx 450px) and scrolling
1471
1496
  return `
1472
- <div class="suites-widget">
1473
- <div class="suites-header">
1497
+ <div class="suites-widget" style="height: 450px; display: flex; flex-direction: column;">
1498
+ <div class="suites-header" style="flex-shrink: 0;">
1474
1499
  <h2>Test Suites</h2>
1475
1500
  <span class="summary-badge">${
1476
1501
  suitesData.length
@@ -1479,44 +1504,49 @@ function generateSuitesWidget(suitesData) {
1479
1504
  0
1480
1505
  )} tests</span>
1481
1506
  </div>
1482
- <div class="suites-grid">
1483
- ${suitesData
1484
- .map(
1485
- (suite) => `
1486
- <div class="suite-card status-${suite.statusOverall}">
1487
- <div class="suite-card-header">
1488
- <h3 class="suite-name" title="${sanitizeHTML(
1489
- suite.name
1490
- )} (${sanitizeHTML(suite.browser)})">${sanitizeHTML(suite.name)}</h3>
1491
- </div>
1492
- <div>🖥️ <span class="browser-tag">${sanitizeHTML(
1493
- suite.browser
1494
- )}</span></div>
1495
- <div class="suite-card-body">
1496
- <span class="test-count">${suite.count} test${
1497
- suite.count !== 1 ? "s" : ""
1498
- }</span>
1499
- <div class="suite-stats">
1500
- ${
1501
- suite.passed > 0
1502
- ? `<span class="stat-passed" title="Passed"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" class="bi bi-check-circle-fill" viewBox="0 0 16 16"><path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z"/></svg> ${suite.passed}</span>`
1503
- : ""
1504
- }
1505
- ${
1506
- suite.failed > 0
1507
- ? `<span class="stat-failed" title="Failed"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" class="bi bi-x-circle-fill" viewBox="0 0 16 16"><path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM5.354 4.646a.5.5 0 1 0-.708.708L7.293 8l-2.647 2.646a.5.5 0 0 0 .708.708L8 8.707l2.646 2.647a.5.5 0 0 0 .708-.708L8.707 8l2.647-2.646a.5.5 0 0 0-.708-.708L8 7.293 5.354 4.646z"/></svg> ${suite.failed}</span>`
1508
- : ""
1509
- }
1510
- ${
1511
- suite.skipped > 0
1512
- ? `<span class="stat-skipped" title="Skipped"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" class="bi bi-exclamation-triangle-fill" viewBox="0 0 16 16"><path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"/></svg> ${suite.skipped}</span>`
1513
- : ""
1514
- }
1515
- </div>
1507
+
1508
+ <div class="suites-grid-container" style="flex-grow: 1; overflow-y: auto; padding-right: 5px;">
1509
+ <div class="suites-grid">
1510
+ ${suitesData
1511
+ .map(
1512
+ (suite) => `
1513
+ <div class="suite-card status-${suite.statusOverall}">
1514
+ <div class="suite-card-header">
1515
+ <h3 class="suite-name" title="${sanitizeHTML(
1516
+ suite.name
1517
+ )} (${sanitizeHTML(suite.browser)})">${sanitizeHTML(
1518
+ suite.name
1519
+ )}</h3>
1520
+ </div>
1521
+ <div>🖥️ <span class="browser-tag">${sanitizeHTML(
1522
+ suite.browser
1523
+ )}</span></div>
1524
+ <div class="suite-card-body">
1525
+ <span class="test-count">${suite.count} test${
1526
+ suite.count !== 1 ? "s" : ""
1527
+ }</span>
1528
+ <div class="suite-stats">
1529
+ ${
1530
+ suite.passed > 0
1531
+ ? `<span class="stat-passed" title="Passed"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" class="bi bi-check-circle-fill" viewBox="0 0 16 16"><path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z"/></svg> ${suite.passed}</span>`
1532
+ : ""
1533
+ }
1534
+ ${
1535
+ suite.failed > 0
1536
+ ? `<span class="stat-failed" title="Failed"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" class="bi bi-x-circle-fill" viewBox="0 0 16 16"><path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM5.354 4.646a.5.5 0 1 0-.708.708L7.293 8l-2.647 2.646a.5.5 0 0 0 .708.708L8 8.707l2.646 2.647a.5.5 0 0 0 .708-.708L8.707 8l2.647-2.646a.5.5 0 0 0-.708-.708L8 7.293 5.354 4.646z"/></svg> ${suite.failed}</span>`
1537
+ : ""
1538
+ }
1539
+ ${
1540
+ suite.skipped > 0
1541
+ ? `<span class="stat-skipped" title="Skipped"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" class="bi bi-exclamation-triangle-fill" viewBox="0 0 16 16"><path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"/></svg> ${suite.skipped}</span>`
1542
+ : ""
1543
+ }
1544
+ </div>
1545
+ </div>
1546
+ </div>`
1547
+ )
1548
+ .join("")}
1516
1549
  </div>
1517
- </div>`
1518
- )
1519
- .join("")}
1520
1550
  </div>
1521
1551
  </div>`;
1522
1552
  }
@@ -1875,6 +1905,140 @@ function generateDescribeDurationChart(results) {
1875
1905
  </script>
1876
1906
  `;
1877
1907
  }
1908
+ /**
1909
+ * Generates a stacked column chart showing test results distributed by severity.
1910
+ * Matches dimensions of the System Environment section (~600px).
1911
+ * Lazy-loaded for performance.
1912
+ */
1913
+ function generateSeverityDistributionChart(results) {
1914
+ if (!results || results.length === 0) {
1915
+ return '<div class="trend-chart" style="height: 600px;"><div class="no-data">No results available for severity distribution.</div></div>';
1916
+ }
1917
+
1918
+ const severityLevels = ["Critical", "High", "Medium", "Low", "Minor"];
1919
+ const data = {
1920
+ passed: [0, 0, 0, 0, 0],
1921
+ failed: [0, 0, 0, 0, 0],
1922
+ skipped: [0, 0, 0, 0, 0],
1923
+ };
1924
+
1925
+ results.forEach((test) => {
1926
+ const sev = test.severity || "Medium";
1927
+ const status = String(test.status).toLowerCase();
1928
+
1929
+ let index = severityLevels.indexOf(sev);
1930
+ if (index === -1) index = 2; // Default to Medium
1931
+
1932
+ if (status === "passed") {
1933
+ data.passed[index]++;
1934
+ } else if (
1935
+ status === "failed" ||
1936
+ status === "timedout" ||
1937
+ status === "interrupted"
1938
+ ) {
1939
+ data.failed[index]++;
1940
+ } else {
1941
+ data.skipped[index]++;
1942
+ }
1943
+ });
1944
+
1945
+ const chartId = `sevDistChart-${Date.now()}-${Math.random()
1946
+ .toString(36)
1947
+ .substring(2, 7)}`;
1948
+ const renderFunctionName = `renderSevDistChart_${chartId.replace(/-/g, "_")}`;
1949
+
1950
+ const seriesData = [
1951
+ { name: "Passed", data: data.passed, color: "var(--success-color)" },
1952
+ { name: "Failed", data: data.failed, color: "var(--danger-color)" },
1953
+ { name: "Skipped", data: data.skipped, color: "var(--warning-color)" },
1954
+ ];
1955
+
1956
+ const seriesDataStr = JSON.stringify(seriesData);
1957
+ const categoriesStr = JSON.stringify(severityLevels);
1958
+
1959
+ return `
1960
+ <div class="trend-chart" style="height: 600px; padding: 28px; box-sizing: border-box;">
1961
+ <h3 class="chart-title-header">Severity Distribution</h3>
1962
+ <div id="${chartId}" class="lazy-load-chart" data-render-function-name="${renderFunctionName}" style="width: 100%; height: 100%;">
1963
+ <div class="no-data">Loading Severity Chart...</div>
1964
+ </div>
1965
+ <script>
1966
+ window.${renderFunctionName} = function() {
1967
+ const chartContainer = document.getElementById('${chartId}');
1968
+ if (!chartContainer) return;
1969
+
1970
+ if (typeof Highcharts !== 'undefined') {
1971
+ try {
1972
+ chartContainer.innerHTML = '';
1973
+ Highcharts.chart('${chartId}', {
1974
+ chart: { type: 'column', backgroundColor: 'transparent' },
1975
+ title: { text: null },
1976
+ xAxis: {
1977
+ categories: ${categoriesStr},
1978
+ crosshair: true,
1979
+ labels: { style: { color: 'var(--text-color-secondary)' } }
1980
+ },
1981
+ yAxis: {
1982
+ min: 0,
1983
+ title: { text: 'Test Count', style: { color: 'var(--text-color)' } },
1984
+ stackLabels: { enabled: true, style: { fontWeight: 'bold', color: 'var(--text-color)' } },
1985
+ labels: { style: { color: 'var(--text-color-secondary)' } }
1986
+ },
1987
+ legend: {
1988
+ itemStyle: { color: 'var(--text-color)' }
1989
+ },
1990
+ tooltip: {
1991
+ shared: true,
1992
+ useHTML: true,
1993
+ backgroundColor: 'rgba(10,10,10,0.92)',
1994
+ style: { color: '#f5f5f5' },
1995
+ formatter: function() {
1996
+ // Custom formatter to HIDE 0 values
1997
+ let tooltip = '';
1998
+ let hasItems = false;
1999
+
2000
+ this.points.forEach(point => {
2001
+ if (point.y > 0) { // ONLY show if count > 0
2002
+ tooltip += '<span style="color:' + point.series.color + '">●</span> ' +
2003
+ point.series.name + ': <b>' + point.y + '</b><br/>';
2004
+ hasItems = true;
2005
+ }
2006
+ });
2007
+
2008
+ if (!hasItems) return false; // Hide tooltip entirely if no data
2009
+
2010
+ // Calculate total from visible points to ensure accuracy or use stackTotal
2011
+ tooltip += 'Total: ' + this.points[0].total;
2012
+ return tooltip;
2013
+ }
2014
+ },
2015
+ plotOptions: {
2016
+ column: {
2017
+ stacking: 'normal',
2018
+ dataLabels: {
2019
+ enabled: true,
2020
+ color: '#fff',
2021
+ style: { textOutline: 'none' },
2022
+ formatter: function() {
2023
+ return (this.y > 0) ? this.y : null; // Hide 0 labels on chart bars
2024
+ }
2025
+ },
2026
+ borderRadius: 3
2027
+ }
2028
+ },
2029
+ series: ${seriesDataStr},
2030
+ credits: { enabled: false }
2031
+ });
2032
+ } catch(e) {
2033
+ console.error("Error rendering severity chart:", e);
2034
+ chartContainer.innerHTML = '<div class="no-data">Error rendering chart.</div>';
2035
+ }
2036
+ }
2037
+ };
2038
+ </script>
2039
+ </div>
2040
+ `;
2041
+ }
1878
2042
  /**
1879
2043
  * Generates the HTML content for the report.
1880
2044
  * @param {object} reportData - The report data object containing run and results.
@@ -1923,15 +2087,17 @@ function generateHTML(reportData, trendData = null) {
1923
2087
  const getSeverityColor = (level) => {
1924
2088
  switch (level) {
1925
2089
  case "Minor":
1926
- return "#006064"; // contrast ~7.35:1 vs white
2090
+ return "#006064";
1927
2091
  case "Low":
1928
- return "#E65100"; // contrast ~4.9:1 vs white
2092
+ return "#FFA07A";
2093
+ case "Medium":
2094
+ return "#577A11";
1929
2095
  case "High":
1930
- return "#B71C1C"; // contrast ~6.57:1 vs white
2096
+ return "#B71C1C";
1931
2097
  case "Critical":
1932
- return "#3E0000"; // contrast ~17.4:1 vs white
2098
+ return "#64158A";
1933
2099
  default:
1934
- return "#BF360C"; // Medium, contrast ~5.6:1 vs white
2100
+ return "#577A11";
1935
2101
  }
1936
2102
  };
1937
2103
  const severityColor = getSeverityColor(severity);
@@ -2307,10 +2473,10 @@ function generateHTML(reportData, trendData = null) {
2307
2473
  <head>
2308
2474
  <meta charset="UTF-8">
2309
2475
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
2310
- <link rel="icon" type="image/png" href="https://i.postimg.cc/v817w4sg/logo.png">
2311
- <link rel="apple-touch-icon" href="https://i.postimg.cc/v817w4sg/logo.png">
2476
+ <link rel="icon" type="image/png" href="https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images/pulse-report/playwright_pulse_icon.png">
2477
+ <link rel="apple-touch-icon" href="https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images/pulse-report/playwright_pulse_icon.png">
2312
2478
  <script src="https://code.highcharts.com/highcharts.js" defer></script>
2313
- <title>Playwright Pulse Report</title>
2479
+ <title>Pulse Report</title>
2314
2480
  <style>
2315
2481
  :root {
2316
2482
  --primary-color: #3f51b5; --secondary-color: #ff4081; --accent-color: #673ab7; --accent-color-alt: #FF9800;
@@ -2351,7 +2517,7 @@ function generateHTML(reportData, trendData = null) {
2351
2517
  .status-passed .value, .stat-passed svg { color: var(--success-color); }
2352
2518
  .status-failed .value, .stat-failed svg { color: var(--danger-color); }
2353
2519
  .status-skipped .value, .stat-skipped svg { color: var(--warning-color); }
2354
- .dashboard-bottom-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); gap: 28px; align-items: stretch; }
2520
+ .dashboard-bottom-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); gap: 28px; align-items: start; }
2355
2521
  .pie-chart-wrapper, .suites-widget, .trend-chart { background-color: var(--card-background-color); padding: 28px; border-radius: var(--border-radius); box-shadow: var(--box-shadow-light); display: flex; flex-direction: column; }
2356
2522
  .pie-chart-wrapper h3, .suites-header h2, .trend-chart h3 { text-align: center; margin-top: 0; margin-bottom: 25px; font-size: 1.25em; font-weight: 600; color: var(--text-color); }
2357
2523
  .trend-chart-container, .pie-chart-wrapper div[id^="pieChart-"] { flex-grow: 1; min-height: 250px; }
@@ -2738,8 +2904,8 @@ function generateHTML(reportData, trendData = null) {
2738
2904
  <div class="container">
2739
2905
  <header class="header">
2740
2906
  <div class="header-title">
2741
- <img id="report-logo" src="https://i.postimg.cc/v817w4sg/logo.png" alt="Report Logo">
2742
- <h1>Playwright Pulse Report</h1>
2907
+ <img id="report-logo" src="https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images/pulse-report/playwright_pulse_icon.png" alt="Report Logo">
2908
+ <h1>Pulse Report</h1>
2743
2909
  </div>
2744
2910
  <div class="run-info"><strong>Run Date:</strong> ${formatDate(
2745
2911
  runSummary.timestamp
@@ -2773,7 +2939,7 @@ function generateHTML(reportData, trendData = null) {
2773
2939
  )}</div></div>
2774
2940
  </div>
2775
2941
  <div class="dashboard-bottom-row">
2776
- <div style="display: grid; gap: 20px">
2942
+ <div style="display: flex; flex-direction: column; gap: 28px;">
2777
2943
  ${generatePieChart(
2778
2944
  [
2779
2945
  { label: "Passed", value: runSummary.passed },
@@ -2790,9 +2956,13 @@ function generateHTML(reportData, trendData = null) {
2790
2956
  : '<div class="no-data">Environment data not available.</div>'
2791
2957
  }
2792
2958
  </div>
2959
+
2960
+ <div style="display: flex; flex-direction: column; gap: 28px;">
2793
2961
  ${generateSuitesWidget(suitesData)}
2962
+ ${generateSeverityDistributionChart(results)}
2963
+ </div>
2794
2964
  </div>
2795
- </div>
2965
+ </div>
2796
2966
  <div id="test-runs" class="tab-content">
2797
2967
  <div class="filters">
2798
2968
  <input type="text" id="filter-name" placeholder="Filter by test name/path..." style="border-color: black; border-style: outset;">
@@ -776,6 +776,9 @@ function generateEnvironmentDashboard(environment, dashboardHeight = 600) {
776
776
  const cardHeight = Math.floor(dashboardHeight * 0.44);
777
777
  const cardContentPadding = 16; // px
778
778
 
779
+ // Logic for Run Context
780
+ const runContext = process.env.CI ? "CI" : "Local Test";
781
+
779
782
  return `
780
783
  <div class="environment-dashboard-wrapper" id="${dashboardId}">
781
784
  <style>
@@ -819,6 +822,20 @@ gap: 20px;
819
822
  font-size: 14px;
820
823
  }
821
824
 
825
+ /* Mobile Responsiveness */
826
+ @media (max-width: 768px) {
827
+ .environment-dashboard-wrapper {
828
+ grid-template-columns: 1fr; /* Stack columns on mobile */
829
+ grid-template-rows: auto;
830
+ padding: 16px;
831
+ height: auto !important; /* Allow height to grow */
832
+ }
833
+ .env-card {
834
+ height: auto !important; /* Allow cards to grow based on content */
835
+ min-height: 200px;
836
+ }
837
+ }
838
+
822
839
  .env-dashboard-header {
823
840
  grid-column: 1 / -1;
824
841
  display: flex;
@@ -827,6 +844,8 @@ align-items: center;
827
844
  border-bottom: 1px solid var(--border-color);
828
845
  padding-bottom: 16px;
829
846
  margin-bottom: 8px;
847
+ flex-wrap: wrap; /* Allow wrapping header items */
848
+ gap: 10px;
830
849
  }
831
850
 
832
851
  .env-dashboard-title {
@@ -1086,7 +1105,7 @@ border-color: var(--border-color);
1086
1105
  </div>
1087
1106
  <div class="env-detail-row">
1088
1107
  <span class="env-detail-label">Run Context</span>
1089
- <span class="env-detail-value">CI/Local Test</span>
1108
+ <span class="env-detail-value">${runContext}</span>
1090
1109
  </div>
1091
1110
  </div>
1092
1111
  </div>
@@ -2012,6 +2031,140 @@ function generateDescribeDurationChart(results) {
2012
2031
  </script>
2013
2032
  `;
2014
2033
  }
2034
+ /**
2035
+ * Generates a stacked column chart showing test results distributed by severity.
2036
+ * Matches dimensions of the System Environment section (~600px).
2037
+ * Lazy-loaded for performance.
2038
+ */
2039
+ function generateSeverityDistributionChart(results) {
2040
+ if (!results || results.length === 0) {
2041
+ return '<div class="trend-chart" style="height: 600px;"><div class="no-data">No results available for severity distribution.</div></div>';
2042
+ }
2043
+
2044
+ const severityLevels = ["Critical", "High", "Medium", "Low", "Minor"];
2045
+ const data = {
2046
+ passed: [0, 0, 0, 0, 0],
2047
+ failed: [0, 0, 0, 0, 0],
2048
+ skipped: [0, 0, 0, 0, 0],
2049
+ };
2050
+
2051
+ results.forEach((test) => {
2052
+ const sev = test.severity || "Medium";
2053
+ const status = String(test.status).toLowerCase();
2054
+
2055
+ let index = severityLevels.indexOf(sev);
2056
+ if (index === -1) index = 2; // Default to Medium
2057
+
2058
+ if (status === "passed") {
2059
+ data.passed[index]++;
2060
+ } else if (
2061
+ status === "failed" ||
2062
+ status === "timedout" ||
2063
+ status === "interrupted"
2064
+ ) {
2065
+ data.failed[index]++;
2066
+ } else {
2067
+ data.skipped[index]++;
2068
+ }
2069
+ });
2070
+
2071
+ const chartId = `sevDistChart-${Date.now()}-${Math.random()
2072
+ .toString(36)
2073
+ .substring(2, 7)}`;
2074
+ const renderFunctionName = `renderSevDistChart_${chartId.replace(/-/g, "_")}`;
2075
+
2076
+ const seriesData = [
2077
+ { name: "Passed", data: data.passed, color: "var(--success-color)" },
2078
+ { name: "Failed", data: data.failed, color: "var(--danger-color)" },
2079
+ { name: "Skipped", data: data.skipped, color: "var(--warning-color)" },
2080
+ ];
2081
+
2082
+ const seriesDataStr = JSON.stringify(seriesData);
2083
+ const categoriesStr = JSON.stringify(severityLevels);
2084
+
2085
+ return `
2086
+ <div class="trend-chart" style="height: 600px; padding: 28px; box-sizing: border-box;">
2087
+ <h3 class="chart-title-header">Severity Distribution</h3>
2088
+ <div id="${chartId}" class="lazy-load-chart" data-render-function-name="${renderFunctionName}" style="width: 100%; height: 100%;">
2089
+ <div class="no-data">Loading Severity Chart...</div>
2090
+ </div>
2091
+ <script>
2092
+ window.${renderFunctionName} = function() {
2093
+ const chartContainer = document.getElementById('${chartId}');
2094
+ if (!chartContainer) return;
2095
+
2096
+ if (typeof Highcharts !== 'undefined') {
2097
+ try {
2098
+ chartContainer.innerHTML = '';
2099
+ Highcharts.chart('${chartId}', {
2100
+ chart: { type: 'column', backgroundColor: 'transparent' },
2101
+ title: { text: null },
2102
+ xAxis: {
2103
+ categories: ${categoriesStr},
2104
+ crosshair: true,
2105
+ labels: { style: { color: 'var(--text-color-secondary)' } }
2106
+ },
2107
+ yAxis: {
2108
+ min: 0,
2109
+ title: { text: 'Test Count', style: { color: 'var(--text-color)' } },
2110
+ stackLabels: { enabled: true, style: { fontWeight: 'bold', color: 'var(--text-color)' } },
2111
+ labels: { style: { color: 'var(--text-color-secondary)' } }
2112
+ },
2113
+ legend: {
2114
+ itemStyle: { color: 'var(--text-color)' }
2115
+ },
2116
+ tooltip: {
2117
+ shared: true,
2118
+ useHTML: true,
2119
+ backgroundColor: 'rgba(10,10,10,0.92)',
2120
+ style: { color: '#f5f5f5' },
2121
+ formatter: function() {
2122
+ // Custom formatter to HIDE 0 values
2123
+ let tooltip = '';
2124
+ let hasItems = false;
2125
+
2126
+ this.points.forEach(point => {
2127
+ if (point.y > 0) { // ONLY show if count > 0
2128
+ tooltip += '<span style="color:' + point.series.color + '">●</span> ' +
2129
+ point.series.name + ': <b>' + point.y + '</b><br/>';
2130
+ hasItems = true;
2131
+ }
2132
+ });
2133
+
2134
+ if (!hasItems) return false; // Hide tooltip entirely if no data
2135
+
2136
+ // Calculate total from visible points to ensure accuracy or use stackTotal
2137
+ tooltip += 'Total: ' + this.points[0].total;
2138
+ return tooltip;
2139
+ }
2140
+ },
2141
+ plotOptions: {
2142
+ column: {
2143
+ stacking: 'normal',
2144
+ dataLabels: {
2145
+ enabled: true,
2146
+ color: '#fff',
2147
+ style: { textOutline: 'none' },
2148
+ formatter: function() {
2149
+ return (this.y > 0) ? this.y : null; // Hide 0 labels on chart bars
2150
+ }
2151
+ },
2152
+ borderRadius: 3
2153
+ }
2154
+ },
2155
+ series: ${seriesDataStr},
2156
+ credits: { enabled: false }
2157
+ });
2158
+ } catch(e) {
2159
+ console.error("Error rendering severity chart:", e);
2160
+ chartContainer.innerHTML = '<div class="no-data">Error rendering chart.</div>';
2161
+ }
2162
+ }
2163
+ };
2164
+ </script>
2165
+ </div>
2166
+ `;
2167
+ }
2015
2168
  /**
2016
2169
  * Generates the HTML report.
2017
2170
  * @param {object} reportData - The data for the report.
@@ -2059,15 +2212,17 @@ function generateHTML(reportData, trendData = null) {
2059
2212
  const getSeverityColor = (level) => {
2060
2213
  switch (level) {
2061
2214
  case "Minor":
2062
- return "#006064"; // contrast ~7.35:1 vs white
2215
+ return "#006064";
2063
2216
  case "Low":
2064
- return "#E65100"; // contrast ~4.9:1 vs white
2217
+ return "#FFA07A";
2218
+ case "Medium":
2219
+ return "#577A11";
2065
2220
  case "High":
2066
- return "#B71C1C"; // contrast ~6.57:1 vs white
2221
+ return "#B71C1C";
2067
2222
  case "Critical":
2068
- return "#3E0000"; // contrast ~17.4:1 vs white
2223
+ return "#64158A";
2069
2224
  default:
2070
- return "#BF360C"; // Medium, contrast ~5.6:1 vs white
2225
+ return "#577A11";
2071
2226
  }
2072
2227
  };
2073
2228
  const severityColor = getSeverityColor(severity);
@@ -2476,10 +2631,10 @@ function generateHTML(reportData, trendData = null) {
2476
2631
  <head>
2477
2632
  <meta charset="UTF-8">
2478
2633
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
2479
- <link rel="icon" type="image/png" href="https://i.postimg.cc/v817w4sg/logo.png">
2480
- <link rel="apple-touch-icon" href="https://i.postimg.cc/v817w4sg/logo.png">
2634
+ <link rel="icon" type="image/png" href="https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images/pulse-report/playwright_pulse_icon.png">
2635
+ <link rel="apple-touch-icon" href="https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images/pulse-report/playwright_pulse_icon.png">
2481
2636
  <script src="https://code.highcharts.com/highcharts.js" defer></script>
2482
- <title>Playwright Pulse Report (Static Report)</title>
2637
+ <title>Pulse Static Report</title>
2483
2638
 
2484
2639
  <style>
2485
2640
  :root {
@@ -2521,7 +2676,7 @@ body { font-family: var(--font-family); margin: 0; background-color: var(--backg
2521
2676
  .status-passed .value, .stat-passed svg { color: var(--success-color); }
2522
2677
  .status-failed .value, .stat-failed svg { color: var(--danger-color); }
2523
2678
  .status-skipped .value, .stat-skipped svg { color: var(--warning-color); }
2524
- .dashboard-bottom-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); gap: 28px; align-items: stretch; }
2679
+ .dashboard-bottom-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); gap: 28px; align-items: start; }
2525
2680
  .pie-chart-wrapper, .suites-widget, .trend-chart { background-color: var(--card-background-color); padding: 28px; border-radius: var(--border-radius); box-shadow: var(--box-shadow-light); display: flex; flex-direction: column; }
2526
2681
  .pie-chart-wrapper h3, .suites-header h2, .trend-chart h3 { text-align: center; margin-top: 0; margin-bottom: 25px; font-size: 1.25em; font-weight: 600; color: var(--text-color); }
2527
2682
  .trend-chart-container, .pie-chart-wrapper div[id^="pieChart-"] { flex-grow: 1; min-height: 250px; }
@@ -2725,8 +2880,8 @@ aspect-ratio: 16 / 9;
2725
2880
  <div class="container">
2726
2881
  <header class="header">
2727
2882
  <div class="header-title">
2728
- <img id="report-logo" src="https://i.postimg.cc/v817w4sg/logo.png" alt="Report Logo">
2729
- <h1>Playwright Pulse Report</h1>
2883
+ <img id="report-logo" src="https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images/pulse-report/playwright_pulse_icon.png" alt="Report Logo">
2884
+ <h1>Pulse Static Report</h1>
2730
2885
  </div>
2731
2886
  <div class="run-info"><strong>Run Date:</strong> ${formatDate(
2732
2887
  runSummary.timestamp
@@ -2777,7 +2932,10 @@ aspect-ratio: 16 / 9;
2777
2932
  : '<div class="no-data">Environment data not available.</div>'
2778
2933
  }
2779
2934
  </div>
2780
- ${generateSuitesWidget(suitesData)}
2935
+ <div style="display: flex; flex-direction: column; gap: 28px;">
2936
+ ${generateSuitesWidget(suitesData)}
2937
+ ${generateSeverityDistributionChart(results)}
2938
+ </div>
2781
2939
  </div>
2782
2940
  </div>
2783
2941
  <div id="test-runs" class="tab-content">
@@ -3658,4 +3816,4 @@ main().catch((err) => {
3658
3816
  );
3659
3817
  console.error(err.stack);
3660
3818
  process.exit(1);
3661
- });
3819
+ });