@arghajit/playwright-pulse-report 0.3.0 → 0.3.1

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/README.md CHANGED
@@ -90,13 +90,13 @@ All CLI scripts now support custom output directories, giving you full flexibili
90
90
 
91
91
  ```bash
92
92
  # Using custom directory
93
- npx generate-pulse-report --outputDir my-reports
93
+ npx generate-pulse-report --outputDir {YOUR_CUSTOM_REPORT_FOLDER}
94
94
  npx generate-report -o test-results/e2e
95
95
  npx send-email --outputDir custom-pulse-reports
96
96
 
97
97
  # Using nested paths
98
98
  npx generate-pulse-report --outputDir reports/integration
99
- npx merge-pulse-report --outputDir my-test-reports
99
+ npx merge-pulse-report --outputDir {YOUR_CUSTOM_REPORT_FOLDER}
100
100
  ```
101
101
 
102
102
  **Important:** Make sure your `playwright.config.ts` custom directory matches the CLI script:
@@ -105,7 +105,7 @@ npx merge-pulse-report --outputDir my-test-reports
105
105
  import { defineConfig } from "@playwright/test";
106
106
  import * as path from "path";
107
107
 
108
- const CUSTOM_REPORT_DIR = path.resolve(__dirname, "my-reports");
108
+ const CUSTOM_REPORT_DIR = path.resolve(__dirname, "{YOUR_CUSTOM_REPORT_FOLDER}");
109
109
 
110
110
  export default defineConfig({
111
111
  reporter: [
@@ -174,6 +174,67 @@ The dashboard includes AI-powered test analysis that provides:
174
174
  - Failure pattern recognition
175
175
  - Suggested optimizations
176
176
 
177
+ ## 📧 Send Report to Mail
178
+
179
+ The `send-email` CLI wraps the full email flow:
180
+
181
+ - Generates a lightweight HTML summary (`pulse-email-summary.html`) from the latest `playwright-pulse-report.json`.
182
+ - Builds a stats table (start time, duration, total, passed, failed, skipped, percentages).
183
+ - Sends an email with that summary as both the body and an HTML attachment.
184
+
185
+ ### 1. Configure Recipients
186
+
187
+ Set up to 5 recipients via environment variables:
188
+
189
+ ```bash
190
+ RECIPIENT_EMAIL_1=recipient1@example.com
191
+ RECIPIENT_EMAIL_2=recipient2@example.com
192
+ RECIPIENT_EMAIL_3=recipient3@example.com
193
+ RECIPIENT_EMAIL_4=recipient4@example.com
194
+ RECIPIENT_EMAIL_5=recipient5@example.com
195
+ ```
196
+
197
+ ### 2. Choose Credential Flow
198
+
199
+ The script supports two ways to obtain SMTP credentials:
200
+
201
+ **Flow A – Environment-based credentials (recommended)**
202
+
203
+ Provide mail host and credentials via environment variables:
204
+
205
+ ```bash
206
+ PULSE_MAIL_HOST=gmail # or: outlook
207
+ PULSE_MAIL_USERNAME=you@example.com
208
+ PULSE_MAIL_PASSWORD=your_app_password
209
+ ```
210
+
211
+ - `PULSE_MAIL_HOST` supports `gmail` or `outlook` only.
212
+ - For Gmail/Outlook, use an app password or SMTP-enabled credentials.
213
+
214
+ **Flow B – Default Flow (fallback)**
215
+
216
+ If the above variables are not set, the script fallbacks to default the mail host for compatibility.
217
+
218
+ ### 3. Run the CLI
219
+
220
+ Use the default output directory:
221
+
222
+ ```bash
223
+ npx send-email
224
+ ```
225
+
226
+ Or point to a custom report directory (must contain `playwright-pulse-report.json`):
227
+
228
+ ```bash
229
+ npx send-email --outputDir <YOUR_CUSTOM_REPORT_FOLDER>
230
+ ```
231
+
232
+ Under the hood, this will:
233
+
234
+ - Resolve the report directory (from `--outputDir` or `playwright.config.ts`).
235
+ - Run `generate-email-report.mjs` to create `pulse-email-summary.html`.
236
+ - Use Nodemailer to send the email via the selected provider (Gmail or Outlook).
237
+
177
238
  ## ⚙️ CI/CD Integration
178
239
 
179
240
  ### Basic Workflow
@@ -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();
@@ -197,7 +201,7 @@ class PlaywrightPulseReporter {
197
201
  };
198
202
  }
199
203
  async onTestEnd(test, result) {
200
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
204
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
201
205
  const project = (_a = test.parent) === null || _a === void 0 ? void 0 : _a.project();
202
206
  const browserDetails = this.getBrowserDetails(test);
203
207
  const testStatus = convertStatus(result.status, test);
@@ -224,6 +228,16 @@ class PlaywrightPulseReporter {
224
228
  catch (e) {
225
229
  console.warn(`Pulse Reporter: Could not extract code snippet for ${test.title}`, e);
226
230
  }
231
+ // 1. Get Spec File Name
232
+ const specFileName = ((_e = test.location) === null || _e === void 0 ? void 0 : _e.file)
233
+ ? path.basename(test.location.file)
234
+ : "n/a";
235
+ // 2. Get Describe Block Name
236
+ // Check if the immediate parent is a 'describe' block
237
+ let describeBlockName = "n/a";
238
+ if (((_f = test.parent) === null || _f === void 0 ? void 0 : _f.type) === "describe") {
239
+ describeBlockName = test.parent.title;
240
+ }
227
241
  const stdoutMessages = result.stdout.map((item) => typeof item === "string" ? item : item.toString());
228
242
  const stderrMessages = result.stderr.map((item) => typeof item === "string" ? item : item.toString());
229
243
  const maxWorkers = this.config.workers;
@@ -241,27 +255,30 @@ class PlaywrightPulseReporter {
241
255
  const pulseResult = {
242
256
  id: test.id,
243
257
  runId: "TBD",
258
+ describe: describeBlockName,
259
+ spec_file: specFileName,
244
260
  name: test.titlePath().join(" > "),
245
- suiteName: (project === null || project === void 0 ? void 0 : project.name) || ((_e = this.config.projects[0]) === null || _e === void 0 ? void 0 : _e.name) || "Default Suite",
261
+ suiteName: (project === null || project === void 0 ? void 0 : project.name) || ((_g = this.config.projects[0]) === null || _g === void 0 ? void 0 : _g.name) || "Default Suite",
246
262
  status: testStatus,
247
263
  duration: result.duration,
248
264
  startTime: startTime,
249
265
  endTime: endTime,
250
266
  browser: browserDetails,
251
267
  retries: result.retry,
252
- steps: ((_f = result.steps) === null || _f === void 0 ? void 0 : _f.length) ? await processAllSteps(result.steps) : [],
253
- errorMessage: (_g = result.error) === null || _g === void 0 ? void 0 : _g.message,
254
- stackTrace: (_h = result.error) === null || _h === void 0 ? void 0 : _h.stack,
255
- snippet: (_j = result.error) === null || _j === void 0 ? void 0 : _j.snippet,
268
+ steps: ((_h = result.steps) === null || _h === void 0 ? void 0 : _h.length) ? await processAllSteps(result.steps) : [],
269
+ errorMessage: (_j = result.error) === null || _j === void 0 ? void 0 : _j.message,
270
+ stackTrace: (_k = result.error) === null || _k === void 0 ? void 0 : _k.stack,
271
+ snippet: (_l = result.error) === null || _l === void 0 ? void 0 : _l.snippet,
256
272
  codeSnippet: codeSnippet,
257
273
  tags: test.tags.map((tag) => tag.startsWith("@") ? tag.substring(1) : tag),
274
+ severity: this._getSeverity(test.annotations),
258
275
  screenshots: [],
259
276
  videoPath: [],
260
277
  tracePath: undefined,
261
278
  attachments: [],
262
279
  stdout: stdoutMessages.length > 0 ? stdoutMessages : undefined,
263
280
  stderr: stderrMessages.length > 0 ? stderrMessages : undefined,
264
- annotations: ((_k = test.annotations) === null || _k === void 0 ? void 0 : _k.length) > 0 ? test.annotations : undefined,
281
+ annotations: ((_m = test.annotations) === null || _m === void 0 ? void 0 : _m.length) > 0 ? test.annotations : undefined,
265
282
  ...testSpecificData,
266
283
  };
267
284
  for (const [index, attachment] of result.attachments.entries()) {
@@ -278,16 +295,16 @@ class PlaywrightPulseReporter {
278
295
  await this._ensureDirExists(path.dirname(absoluteDestPath));
279
296
  await fs.copyFile(attachment.path, absoluteDestPath);
280
297
  if (attachment.contentType.startsWith("image/")) {
281
- (_l = pulseResult.screenshots) === null || _l === void 0 ? void 0 : _l.push(relativeDestPath);
298
+ (_o = pulseResult.screenshots) === null || _o === void 0 ? void 0 : _o.push(relativeDestPath);
282
299
  }
283
300
  else if (attachment.contentType.startsWith("video/")) {
284
- (_m = pulseResult.videoPath) === null || _m === void 0 ? void 0 : _m.push(relativeDestPath);
301
+ (_p = pulseResult.videoPath) === null || _p === void 0 ? void 0 : _p.push(relativeDestPath);
285
302
  }
286
303
  else if (attachment.name === "trace") {
287
304
  pulseResult.tracePath = relativeDestPath;
288
305
  }
289
306
  else {
290
- (_o = pulseResult.attachments) === null || _o === void 0 ? void 0 : _o.push({
307
+ (_q = pulseResult.attachments) === null || _q === void 0 ? void 0 : _q.push({
291
308
  name: attachment.name,
292
309
  path: relativeDestPath,
293
310
  contentType: attachment.contentType,
@@ -17,6 +17,8 @@ export interface TestStep {
17
17
  }
18
18
  export interface TestResult {
19
19
  id: string;
20
+ describe?: string;
21
+ spec_file?: string;
20
22
  name: string;
21
23
  status: TestStatus;
22
24
  duration: number;
@@ -29,6 +31,7 @@ export interface TestResult {
29
31
  snippet?: string;
30
32
  codeSnippet?: string;
31
33
  tags?: string[];
34
+ severity?: "Minor" | "Low" | "Medium" | "High" | "Critical";
32
35
  suiteName?: string;
33
36
  runId: string;
34
37
  browser: string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@arghajit/playwright-pulse-report",
3
3
  "author": "Arghajit Singha",
4
- "version": "0.3.0",
4
+ "version": "0.3.1",
5
5
  "description": "A Playwright reporter and dashboard for visualizing test results.",
6
6
  "homepage": "https://playwright-pulse-report.netlify.app/",
7
7
  "keywords": [
@@ -30,12 +30,12 @@
30
30
  ],
31
31
  "license": "MIT",
32
32
  "bin": {
33
- "generate-pulse-report": "./scripts/generate-static-report.mjs",
34
- "generate-report": "./scripts/generate-report.mjs",
35
- "merge-pulse-report": "./scripts/merge-pulse-report.js",
36
- "send-email": "./scripts/sendReport.mjs",
37
- "generate-trend": "./scripts/generate-trend.mjs",
38
- "generate-email-report": "./scripts/generate-email-report.mjs"
33
+ "generate-pulse-report": "scripts/generate-static-report.mjs",
34
+ "generate-report": "scripts/generate-report.mjs",
35
+ "merge-pulse-report": "scripts/merge-pulse-report.js",
36
+ "send-email": "scripts/sendReport.mjs",
37
+ "generate-trend": "scripts/generate-trend.mjs",
38
+ "generate-email-report": "scripts/generate-email-report.mjs"
39
39
  },
40
40
  "exports": {
41
41
  ".": {
@@ -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) {
@@ -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">