@arghajit/dummy 0.3.9 → 0.3.11

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.
@@ -0,0 +1,12 @@
1
+ export type PulseSeverityLevel = "Minor" | "Low" | "Medium" | "High" | "Critical";
2
+ export declare const pulse: {
3
+ /**
4
+ * Sets the severity level for the current test.
5
+ * * @param level - The severity level ('Minor' | 'Low' | 'Medium' | 'High' | 'Critical')
6
+ * @example
7
+ * test('Login', async () => {
8
+ * pulse.severity('Critical');
9
+ * });
10
+ */
11
+ severity: (level: PulseSeverityLevel) => void;
12
+ };
package/dist/pulse.js ADDED
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.pulse = void 0;
4
+ const test_1 = require("@playwright/test");
5
+ exports.pulse = {
6
+ /**
7
+ * Sets the severity level for the current test.
8
+ * * @param level - The severity level ('Minor' | 'Low' | 'Medium' | 'High' | 'Critical')
9
+ * @example
10
+ * test('Login', async () => {
11
+ * pulse.severity('Critical');
12
+ * });
13
+ */
14
+ severity: (level) => {
15
+ const validLevels = ["Minor", "Low", "Medium", "High", "Critical"];
16
+ // Default to "Medium" if an invalid string is passed
17
+ const selectedLevel = validLevels.includes(level) ? level : "Medium";
18
+ // Add the annotation to Playwright's test info
19
+ test_1.test.info().annotations.push({
20
+ type: "pulse_severity",
21
+ description: selectedLevel,
22
+ });
23
+ },
24
+ };
@@ -3,3 +3,5 @@ export default PlaywrightPulseReporter;
3
3
  export { PlaywrightPulseReporter };
4
4
  export type { PlaywrightPulseReport } from "../lib/report-types";
5
5
  export type { TestResult, TestRun, TestStep, TestStatus } from "../types";
6
+ export { pulse } from "../pulse";
7
+ export type { PulseSeverityLevel } from "../pulse";
@@ -1,9 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PlaywrightPulseReporter = void 0;
3
+ exports.pulse = exports.PlaywrightPulseReporter = void 0;
4
4
  // src/reporter/index.ts
5
5
  const playwright_pulse_reporter_1 = require("./playwright-pulse-reporter");
6
6
  Object.defineProperty(exports, "PlaywrightPulseReporter", { enumerable: true, get: function () { return playwright_pulse_reporter_1.PlaywrightPulseReporter; } });
7
7
  // Export the reporter class as the default export for CommonJS compatibility
8
8
  // and also as a named export for potential ES module consumers.
9
9
  exports.default = playwright_pulse_reporter_1.PlaywrightPulseReporter;
10
+ // --- NEW: Export the pulse helper ---
11
+ // This allows: import { pulse } from '@arghajit/playwright-pulse-report';
12
+ var pulse_1 = require("../pulse"); // Adjust path based on where you placed pulse.ts
13
+ Object.defineProperty(exports, "pulse", { enumerable: true, get: function () { return pulse_1.pulse; } });
@@ -16,6 +16,7 @@ export declare class PlaywrightPulseReporter implements Reporter {
16
16
  printsToStdio(): boolean;
17
17
  onBegin(config: FullConfig, suite: Suite): void;
18
18
  onTestBegin(test: TestCase): void;
19
+ private _getSeverity;
19
20
  private getBrowserDetails;
20
21
  private processStep;
21
22
  onTestEnd(test: TestCase, result: PwTestResult): Promise<void>;
@@ -109,6 +109,10 @@ class PlaywrightPulseReporter {
109
109
  onTestBegin(test) {
110
110
  console.log(`Starting test: ${test.title}`);
111
111
  }
112
+ _getSeverity(annotations) {
113
+ const severityAnnotation = annotations.find((a) => a.type === "pulse_severity");
114
+ return (severityAnnotation === null || severityAnnotation === void 0 ? void 0 : severityAnnotation.description) || "Medium";
115
+ }
112
116
  getBrowserDetails(test) {
113
117
  var _a, _b, _c, _d;
114
118
  const project = (_a = test.parent) === null || _a === void 0 ? void 0 : _a.project();
@@ -267,6 +271,7 @@ class PlaywrightPulseReporter {
267
271
  snippet: (_l = result.error) === null || _l === void 0 ? void 0 : _l.snippet,
268
272
  codeSnippet: codeSnippet,
269
273
  tags: test.tags.map((tag) => tag.startsWith("@") ? tag.substring(1) : tag),
274
+ severity: this._getSeverity(test.annotations),
270
275
  screenshots: [],
271
276
  videoPath: [],
272
277
  tracePath: undefined,
@@ -31,6 +31,7 @@ export interface TestResult {
31
31
  snippet?: string;
32
32
  codeSnippet?: string;
33
33
  tags?: string[];
34
+ severity?: "Minor" | "Low" | "Medium" | "High" | "Critical";
34
35
  suiteName?: string;
35
36
  runId: string;
36
37
  browser: string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@arghajit/dummy",
3
3
  "author": "Arghajit Singha",
4
- "version": "0.3.9",
4
+ "version": "0.3.11",
5
5
  "description": "A Playwright reporter and dashboard for visualizing test results.",
6
6
  "homepage": "https://playwright-pulse-report.netlify.app/",
7
7
  "keywords": [
@@ -217,6 +217,40 @@ function generateMinifiedHTML(reportData) {
217
217
  const testFileParts = test.name.split(" > ");
218
218
  const testTitle =
219
219
  testFileParts[testFileParts.length - 1] || "Unnamed Test";
220
+
221
+ // --- NEW: Severity Logic ---
222
+ const severity = test.severity || "Medium";
223
+ const getSeverityColor = (level) => {
224
+ switch (level) {
225
+ case "Minor":
226
+ return "#006064";
227
+ case "Low":
228
+ return "#FFA07A";
229
+ case "Medium":
230
+ return "#577A11";
231
+ case "High":
232
+ return "#B71C1C";
233
+ case "Critical":
234
+ return "#64158A";
235
+ default:
236
+ return "#577A11";
237
+ }
238
+ };
239
+ // We use inline styles here to ensure they render correctly in emails
240
+ const severityBadge = `<span style="background-color: ${getSeverityColor(
241
+ severity
242
+ )}; font-size: 0.8em; font-weight: 600; padding: 3px 8px; border-radius: 4px; color: #fff; margin-left: 10px; white-space: nowrap;">${severity}</span>`;
243
+
244
+ // --- NEW: Tags Logic ---
245
+ const tagsBadges = (test.tags || [])
246
+ .map(
247
+ (tag) =>
248
+ `<span style="background-color: #7f8c8d; font-size: 0.8em; font-weight: 600; padding: 3px 8px; border-radius: 4px; color: #fff; margin-left: 5px; white-space: nowrap;">${sanitizeHTML(
249
+ tag
250
+ )}</span>`
251
+ )
252
+ .join("");
253
+
220
254
  html += `
221
255
  <li class="test-item ${getStatusClass(test.status)}"
222
256
  data-test-name-min="${sanitizeHTML(testTitle.toLowerCase())}"
@@ -230,9 +264,9 @@ function generateMinifiedHTML(reportData) {
230
264
  <span class="test-title-text" title="${sanitizeHTML(
231
265
  test.name
232
266
  )}">${sanitizeHTML(testTitle)}</span>
233
- <span class="test-status-label">${String(
234
- test.status
235
- ).toUpperCase()}</span>
267
+
268
+ ${severityBadge}
269
+ ${tagsBadges}
236
270
  </li>
237
271
  `;
238
272
  });
@@ -250,9 +284,9 @@ function generateMinifiedHTML(reportData) {
250
284
  <head>
251
285
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
252
286
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
253
- <link rel="icon" type="image/png" href="https://i.postimg.cc/v817w4sg/logo.png">
254
- <link rel="apple-touch-icon" href="https://i.postimg.cc/v817w4sg/logo.png">
255
- <title>Playwright Pulse Summary Report</title>
287
+ <link rel="icon" type="image/png" href="https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images/pulse-report/playwright_pulse_icon.png">
288
+ <link rel="apple-touch-icon" href="https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images/pulse-report/playwright_pulse_icon.png">
289
+ <title>Pulse Summary Report</title>
256
290
  <style>
257
291
  :root {
258
292
  --primary-color: #2c3e50; /* Dark Blue/Grey */
@@ -492,8 +526,8 @@ function generateMinifiedHTML(reportData) {
492
526
  <div class="container">
493
527
  <header class="report-header">
494
528
  <div class="report-header-title">
495
- <img id="report-logo" src="https://i.postimg.cc/v817w4sg/logo.png" alt="Report Logo">
496
- <h1>Playwright Pulse Summary</h1>
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>Pulse Summary Report</h1>
497
531
  </div>
498
532
  <div class="run-info">
499
533
  <strong>Run Date:</strong> ${formatDate(
@@ -527,8 +561,24 @@ function generateMinifiedHTML(reportData) {
527
561
  </section>
528
562
 
529
563
  <section class="test-results-section">
530
- <h1 class="section-title">Test Case Summary</h1>
531
-
564
+ <div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 15px; margin-top: 30px; margin-bottom: 15px; padding-bottom: 10px; border-bottom: 2px solid var(--secondary-color);">
565
+ <h1 style="margin: 0; font-size: 1.5em; color: var(--primary-color);">Test Case Summary</h1>
566
+ <div style="display: flex; flex-wrap: wrap; gap: 8px; align-items: center; font-size: 0.75em;">
567
+ <span style="font-weight: 600; color: var(--dark-gray-color);">Legend:</span>
568
+
569
+ <span style="margin-left: 4px; font-weight: 600; color: var(--text-color);">Severity:</span>
570
+
571
+ <span style="background-color: #006064; color: #fff; padding: 2px 6px; border-radius: 3px;">Minor</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
+ <span style="background-color: #B71C1C; color: #fff; padding: 2px 6px; border-radius: 3px;">High</span>
575
+ <span style="background-color: #64158A; color: #fff; padding: 2px 6px; border-radius: 3px;">Critical</span>
576
+
577
+ <span style="border-left: 1px solid #ccc; height: 14px; margin: 0 4px;"></span>
578
+
579
+ <span style="background-color: #7f8c8d; color: #fff; padding: 2px 6px; border-radius: 3px;">Tags</span>
580
+ </div>
581
+ </div>
532
582
  <div class="filters-section">
533
583
  <input type="text" id="filter-min-name" placeholder="Search by test name...">
534
584
  <select id="filter-min-status">
@@ -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
  }
@@ -1748,7 +1778,7 @@ function generateSpecDurationChart(results) {
1748
1778
  */
1749
1779
  function generateDescribeDurationChart(results) {
1750
1780
  if (!results || results.length === 0)
1751
- return '<div class="no-data">No results available.</div>';
1781
+ return '<div class="no-data">Seems like there is test describe block available in the executed test suite.</div>';
1752
1782
 
1753
1783
  const describeMap = new Map();
1754
1784
  let foundAnyDescribe = false;
@@ -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 = '<b>' + this.x + '</b><br/>';
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.
@@ -1918,6 +2082,28 @@ function generateHTML(reportData, trendData = null) {
1918
2082
  const testFileParts = test.name.split(" > ");
1919
2083
  const testTitle =
1920
2084
  testFileParts[testFileParts.length - 1] || "Unnamed Test";
2085
+ // --- NEW: Severity Logic ---
2086
+ const severity = test.severity || "Medium";
2087
+ const getSeverityColor = (level) => {
2088
+ switch (level) {
2089
+ case "Minor":
2090
+ return "#006064";
2091
+ case "Low":
2092
+ return "#FFA07A";
2093
+ case "Medium":
2094
+ return "#577A11";
2095
+ case "High":
2096
+ return "#B71C1C";
2097
+ case "Critical":
2098
+ return "#64158A";
2099
+ default:
2100
+ return "#577A11";
2101
+ }
2102
+ };
2103
+ const severityColor = getSeverityColor(severity);
2104
+ // We reuse 'status-badge' class for size/font consistency, but override background color
2105
+ const severityBadge = `<span class="status-badge" style="background-color: ${severityColor}; margin-right: 8px;">${severity}</span>`;
2106
+ // ---------------------------
1921
2107
  const generateStepsHTML = (steps, depth = 0) => {
1922
2108
  if (!steps || steps.length === 0)
1923
2109
  return "<div class='no-steps'>No steps recorded for this test.</div>";
@@ -2011,6 +2197,7 @@ function generateHTML(reportData, trendData = null) {
2011
2197
  <span class="test-case-browser">(${sanitizeHTML(browser)})</span>
2012
2198
  </div>
2013
2199
  <div class="test-case-meta">
2200
+ ${severityBadge}
2014
2201
  ${
2015
2202
  test.tags && test.tags.length > 0
2016
2203
  ? test.tags
@@ -2286,10 +2473,10 @@ function generateHTML(reportData, trendData = null) {
2286
2473
  <head>
2287
2474
  <meta charset="UTF-8">
2288
2475
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
2289
- <link rel="icon" type="image/png" href="https://i.postimg.cc/v817w4sg/logo.png">
2290
- <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">
2291
2478
  <script src="https://code.highcharts.com/highcharts.js" defer></script>
2292
- <title>Playwright Pulse Report</title>
2479
+ <title>Pulse Report</title>
2293
2480
  <style>
2294
2481
  :root {
2295
2482
  --primary-color: #3f51b5; --secondary-color: #ff4081; --accent-color: #673ab7; --accent-color-alt: #FF9800;
@@ -2330,7 +2517,7 @@ function generateHTML(reportData, trendData = null) {
2330
2517
  .status-passed .value, .stat-passed svg { color: var(--success-color); }
2331
2518
  .status-failed .value, .stat-failed svg { color: var(--danger-color); }
2332
2519
  .status-skipped .value, .stat-skipped svg { color: var(--warning-color); }
2333
- .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; }
2334
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; }
2335
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); }
2336
2523
  .trend-chart-container, .pie-chart-wrapper div[id^="pieChart-"] { flex-grow: 1; min-height: 250px; }
@@ -2433,6 +2620,7 @@ function generateHTML(reportData, trendData = null) {
2433
2620
  .status-badge-small.status-failed { background-color: var(--danger-color); }
2434
2621
  .status-badge-small.status-skipped { background-color: var(--warning-color); }
2435
2622
  .status-badge-small.status-unknown { background-color: var(--dark-gray-color); }
2623
+ .badge-severity { display: inline-block; padding: 4px 8px; border-radius: 4px; font-size: 11px; font-weight: 700; color: white; text-transform: uppercase; margin-right: 8px; vertical-align: middle; }
2436
2624
  .no-data, .no-tests, .no-steps, .no-data-chart { padding: 28px; text-align: center; color: var(--dark-gray-color); font-style: italic; font-size:1.1em; background-color: var(--light-gray-color); border-radius: var(--border-radius); margin: 18px 0; border: 1px dashed var(--medium-gray-color); }
2437
2625
  .no-data-chart {font-size: 0.95em; padding: 18px;}
2438
2626
  .ai-failure-cards-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(400px, 1fr)); gap: 22px; }
@@ -2716,8 +2904,8 @@ function generateHTML(reportData, trendData = null) {
2716
2904
  <div class="container">
2717
2905
  <header class="header">
2718
2906
  <div class="header-title">
2719
- <img id="report-logo" src="https://i.postimg.cc/v817w4sg/logo.png" alt="Report Logo">
2720
- <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>
2721
2909
  </div>
2722
2910
  <div class="run-info"><strong>Run Date:</strong> ${formatDate(
2723
2911
  runSummary.timestamp
@@ -2751,7 +2939,7 @@ function generateHTML(reportData, trendData = null) {
2751
2939
  )}</div></div>
2752
2940
  </div>
2753
2941
  <div class="dashboard-bottom-row">
2754
- <div style="display: grid; gap: 20px">
2942
+ <div style="display: flex; flex-direction: column; gap: 28px;">
2755
2943
  ${generatePieChart(
2756
2944
  [
2757
2945
  { label: "Passed", value: runSummary.passed },
@@ -2768,9 +2956,13 @@ function generateHTML(reportData, trendData = null) {
2768
2956
  : '<div class="no-data">Environment data not available.</div>'
2769
2957
  }
2770
2958
  </div>
2959
+
2960
+ <div style="display: flex; flex-direction: column; gap: 28px;">
2771
2961
  ${generateSuitesWidget(suitesData)}
2962
+ ${generateSeverityDistributionChart(results)}
2963
+ </div>
2772
2964
  </div>
2773
- </div>
2965
+ </div>
2774
2966
  <div id="test-runs" class="tab-content">
2775
2967
  <div class="filters">
2776
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>
@@ -1885,7 +1904,7 @@ function generateSpecDurationChart(results) {
1885
1904
  */
1886
1905
  function generateDescribeDurationChart(results) {
1887
1906
  if (!results || results.length === 0)
1888
- return '<div class="no-data">No results available.</div>';
1907
+ return '<div class="no-data">Seems like there is test describe block available in the executed test suite.</div>';
1889
1908
 
1890
1909
  const describeMap = new Map();
1891
1910
  let foundAnyDescribe = false;
@@ -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 = '<b>' + this.x + '</b><br/>';
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.
@@ -2054,6 +2207,28 @@ function generateHTML(reportData, trendData = null) {
2054
2207
  const testFileParts = test.name.split(" > ");
2055
2208
  const testTitle =
2056
2209
  testFileParts[testFileParts.length - 1] || "Unnamed Test";
2210
+ // --- NEW: Severity Logic ---
2211
+ const severity = test.severity || "Medium";
2212
+ const getSeverityColor = (level) => {
2213
+ switch (level) {
2214
+ case "Minor":
2215
+ return "#006064";
2216
+ case "Low":
2217
+ return "#FFA07A";
2218
+ case "Medium":
2219
+ return "#577A11";
2220
+ case "High":
2221
+ return "#B71C1C";
2222
+ case "Critical":
2223
+ return "#64158A";
2224
+ default:
2225
+ return "#577A11";
2226
+ }
2227
+ };
2228
+ const severityColor = getSeverityColor(severity);
2229
+ // We reuse 'status-badge' class for size/font consistency, but override background color
2230
+ const severityBadge = `<span class="status-badge" style="background-color: ${severityColor}; margin-right: 8px;">${severity}</span>`;
2231
+ // ---------------------------
2057
2232
  const generateStepsHTML = (steps, depth = 0) => {
2058
2233
  if (!steps || steps.length === 0)
2059
2234
  return "<div class='no-steps'>No steps recorded for this test.</div>";
@@ -2163,13 +2338,16 @@ function generateHTML(reportData, trendData = null) {
2163
2338
  testTitle
2164
2339
  )}</span><span class="test-case-browser">(${sanitizeHTML(
2165
2340
  browser
2166
- )})</span></div><div class="test-case-meta">${
2341
+ )})</span></div><div class="test-case-meta">
2342
+ ${severityBadge}
2343
+ ${
2167
2344
  test.tags && test.tags.length > 0
2168
2345
  ? test.tags
2169
2346
  .map((t) => `<span class="tag">${sanitizeHTML(t)}</span>`)
2170
2347
  .join(" ")
2171
2348
  : ""
2172
- }<span class="test-duration">${formatDuration(
2349
+ }
2350
+ <span class="test-duration">${formatDuration(
2173
2351
  test.duration
2174
2352
  )}</span></div></div>
2175
2353
  <div class="test-case-content" style="display: none;">
@@ -2453,10 +2631,10 @@ function generateHTML(reportData, trendData = null) {
2453
2631
  <head>
2454
2632
  <meta charset="UTF-8">
2455
2633
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
2456
- <link rel="icon" type="image/png" href="https://i.postimg.cc/v817w4sg/logo.png">
2457
- <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">
2458
2636
  <script src="https://code.highcharts.com/highcharts.js" defer></script>
2459
- <title>Playwright Pulse Report (Static Report)</title>
2637
+ <title>Pulse Static Report</title>
2460
2638
 
2461
2639
  <style>
2462
2640
  :root {
@@ -2498,7 +2676,7 @@ body { font-family: var(--font-family); margin: 0; background-color: var(--backg
2498
2676
  .status-passed .value, .stat-passed svg { color: var(--success-color); }
2499
2677
  .status-failed .value, .stat-failed svg { color: var(--danger-color); }
2500
2678
  .status-skipped .value, .stat-skipped svg { color: var(--warning-color); }
2501
- .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; }
2502
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; }
2503
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); }
2504
2682
  .trend-chart-container, .pie-chart-wrapper div[id^="pieChart-"] { flex-grow: 1; min-height: 250px; }
@@ -2620,6 +2798,7 @@ aspect-ratio: 16 / 9;
2620
2798
  .status-badge-small.status-failed { background-color: var(--danger-color); }
2621
2799
  .status-badge-small.status-skipped { background-color: var(--warning-color); }
2622
2800
  .status-badge-small.status-unknown { background-color: var(--dark-gray-color); }
2801
+ .badge-severity { display: inline-block; padding: 4px 8px; border-radius: 4px; font-size: 11px; font-weight: 700; color: white; text-transform: uppercase; margin-right: 8px; vertical-align: middle; }
2623
2802
  .no-data, .no-tests, .no-steps, .no-data-chart { padding: 28px; text-align: center; color: var(--dark-gray-color); font-style: italic; font-size:1.1em; background-color: var(--light-gray-color); border-radius: var(--border-radius); margin: 18px 0; border: 1px dashed var(--medium-gray-color); }
2624
2803
  .no-data-chart {font-size: 0.95em; padding: 18px;}
2625
2804
  .ai-failure-cards-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(400px, 1fr)); gap: 22px; }
@@ -2701,8 +2880,8 @@ aspect-ratio: 16 / 9;
2701
2880
  <div class="container">
2702
2881
  <header class="header">
2703
2882
  <div class="header-title">
2704
- <img id="report-logo" src="https://i.postimg.cc/v817w4sg/logo.png" alt="Report Logo">
2705
- <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>
2706
2885
  </div>
2707
2886
  <div class="run-info"><strong>Run Date:</strong> ${formatDate(
2708
2887
  runSummary.timestamp
@@ -2753,7 +2932,10 @@ aspect-ratio: 16 / 9;
2753
2932
  : '<div class="no-data">Environment data not available.</div>'
2754
2933
  }
2755
2934
  </div>
2756
- ${generateSuitesWidget(suitesData)}
2935
+ <div style="display: flex; flex-direction: column; gap: 28px;">
2936
+ ${generateSuitesWidget(suitesData)}
2937
+ ${generateSeverityDistributionChart(results)}
2938
+ </div>
2757
2939
  </div>
2758
2940
  </div>
2759
2941
  <div id="test-runs" class="tab-content">
@@ -3634,4 +3816,4 @@ main().catch((err) => {
3634
3816
  );
3635
3817
  console.error(err.stack);
3636
3818
  process.exit(1);
3637
- });
3819
+ });