@arghajit/playwright-pulse-report 0.2.9 → 0.3.0

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
@@ -1,5 +1,9 @@
1
1
  # Playwright Pluse Report
2
2
 
3
+ [![NPM Version](https://img.shields.io/npm/v/@arghajit/playwright-pulse-report.svg)](https://www.npmjs.com/package/@arghajit/playwright-pulse-report)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ [![NPM Downloads](https://img.shields.io/npm/dm/@arghajit/playwright-pulse-report.svg)](https://www.npmjs.com/package/@arghajit/playwright-pulse-report)
6
+
3
7
  ![Playwright Pulse Report](https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images/pulse-report/playwright-pulse-report.png)
4
8
 
5
9
  _The ultimate Playwright reporter — Interactive dashboard with historical trend analytics, CI/CD-ready standalone HTML reports, and sharding support for scalable test execution._
@@ -80,6 +84,42 @@ npx generate-pulse-report # Generates static HTML
80
84
  npx send-email # Sends email report
81
85
  ```
82
86
 
87
+ ### 4. Custom Output Directory (Optional)
88
+
89
+ All CLI scripts now support custom output directories, giving you full flexibility over where reports are generated:
90
+
91
+ ```bash
92
+ # Using custom directory
93
+ npx generate-pulse-report --outputDir my-reports
94
+ npx generate-report -o test-results/e2e
95
+ npx send-email --outputDir custom-pulse-reports
96
+
97
+ # Using nested paths
98
+ npx generate-pulse-report --outputDir reports/integration
99
+ npx merge-pulse-report --outputDir my-test-reports
100
+ ```
101
+
102
+ **Important:** Make sure your `playwright.config.ts` custom directory matches the CLI script:
103
+
104
+ ```typescript
105
+ import { defineConfig } from "@playwright/test";
106
+ import * as path from "path";
107
+
108
+ const CUSTOM_REPORT_DIR = path.resolve(__dirname, "my-reports");
109
+
110
+ export default defineConfig({
111
+ reporter: [
112
+ ["list"],
113
+ [
114
+ "@arghajit/playwright-pulse-report",
115
+ {
116
+ outputDir: CUSTOM_REPORT_DIR, // Must match CLI --outputDir
117
+ },
118
+ ],
119
+ ],
120
+ });
121
+ ```
122
+
83
123
  ## 📊 Report Options
84
124
 
85
125
  ### Option 1: Static HTML Report (Embedded Attachments)
@@ -197,7 +197,7 @@ class PlaywrightPulseReporter {
197
197
  };
198
198
  }
199
199
  async onTestEnd(test, result) {
200
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
200
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
201
201
  const project = (_a = test.parent) === null || _a === void 0 ? void 0 : _a.project();
202
202
  const browserDetails = this.getBrowserDetails(test);
203
203
  const testStatus = convertStatus(result.status, test);
@@ -261,6 +261,7 @@ class PlaywrightPulseReporter {
261
261
  attachments: [],
262
262
  stdout: stdoutMessages.length > 0 ? stdoutMessages : undefined,
263
263
  stderr: stderrMessages.length > 0 ? stderrMessages : undefined,
264
+ annotations: ((_k = test.annotations) === null || _k === void 0 ? void 0 : _k.length) > 0 ? test.annotations : undefined,
264
265
  ...testSpecificData,
265
266
  };
266
267
  for (const [index, attachment] of result.attachments.entries()) {
@@ -277,16 +278,16 @@ class PlaywrightPulseReporter {
277
278
  await this._ensureDirExists(path.dirname(absoluteDestPath));
278
279
  await fs.copyFile(attachment.path, absoluteDestPath);
279
280
  if (attachment.contentType.startsWith("image/")) {
280
- (_k = pulseResult.screenshots) === null || _k === void 0 ? void 0 : _k.push(relativeDestPath);
281
+ (_l = pulseResult.screenshots) === null || _l === void 0 ? void 0 : _l.push(relativeDestPath);
281
282
  }
282
283
  else if (attachment.contentType.startsWith("video/")) {
283
- (_l = pulseResult.videoPath) === null || _l === void 0 ? void 0 : _l.push(relativeDestPath);
284
+ (_m = pulseResult.videoPath) === null || _m === void 0 ? void 0 : _m.push(relativeDestPath);
284
285
  }
285
286
  else if (attachment.name === "trace") {
286
287
  pulseResult.tracePath = relativeDestPath;
287
288
  }
288
289
  else {
289
- (_m = pulseResult.attachments) === null || _m === void 0 ? void 0 : _m.push({
290
+ (_o = pulseResult.attachments) === null || _o === void 0 ? void 0 : _o.push({
290
291
  name: attachment.name,
291
292
  path: relativeDestPath,
292
293
  contentType: attachment.contentType,
@@ -46,6 +46,15 @@ export interface TestResult {
46
46
  totalWorkers?: number;
47
47
  configFile?: string;
48
48
  metadata?: string;
49
+ annotations?: {
50
+ type: string;
51
+ description?: string;
52
+ location?: {
53
+ file: string;
54
+ line: number;
55
+ column: number;
56
+ };
57
+ }[];
49
58
  }
50
59
  export interface TestRun {
51
60
  id: 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.2.9",
4
+ "version": "0.3.0",
5
5
  "description": "A Playwright reporter and dashboard for visualizing test results.",
6
6
  "homepage": "https://playwright-pulse-report.netlify.app/",
7
7
  "keywords": [
@@ -18,14 +18,15 @@
18
18
  "send-report",
19
19
  "email",
20
20
  "playwright-report",
21
- "pulse"
21
+ "pulse",
22
+ "ai-failure-analysis"
22
23
  ],
23
24
  "main": "dist/reporter/index.js",
24
25
  "types": "dist/reporter/index.d.ts",
25
26
  "files": [
26
27
  "dist",
27
28
  "screenshots",
28
- "scripts/generate-static-report.mjs"
29
+ "scripts"
29
30
  ],
30
31
  "license": "MIT",
31
32
  "bin": {
@@ -0,0 +1,132 @@
1
+ #!/usr/bin/env node
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+ import { pathToFileURL } from "url";
5
+ import { dirname } from "path";
6
+
7
+ const DEFAULT_OUTPUT_DIR = "pulse-report";
8
+
9
+ async function findPlaywrightConfig() {
10
+ const possibleConfigs = [
11
+ "playwright.config.ts",
12
+ "playwright.config.js",
13
+ "playwright.config.mjs",
14
+ ];
15
+
16
+ for (const configFile of possibleConfigs) {
17
+ const configPath = path.resolve(process.cwd(), configFile);
18
+ if (fs.existsSync(configPath)) {
19
+ return { path: configPath, exists: true };
20
+ }
21
+ }
22
+
23
+ return { path: null, exists: false };
24
+ }
25
+
26
+ async function extractOutputDirFromConfig(configPath) {
27
+ try {
28
+ let config;
29
+
30
+ const configDir = dirname(configPath);
31
+ const originalDirname = global.__dirname;
32
+ const originalFilename = global.__filename;
33
+
34
+ try {
35
+ global.__dirname = configDir;
36
+ global.__filename = configPath;
37
+
38
+ if (configPath.endsWith(".ts")) {
39
+ try {
40
+ const { register } = await import("node:module");
41
+ const { pathToFileURL } = await import("node:url");
42
+
43
+ register("ts-node/esm", pathToFileURL("./"));
44
+
45
+ config = await import(pathToFileURL(configPath).href);
46
+ } catch (tsError) {
47
+ try {
48
+ const tsNode = await import("ts-node");
49
+ tsNode.register({
50
+ transpileOnly: true,
51
+ compilerOptions: {
52
+ module: "ESNext",
53
+ },
54
+ });
55
+ config = await import(pathToFileURL(configPath).href);
56
+ } catch (fallbackError) {
57
+ console.error("Failed to load TypeScript config:", fallbackError);
58
+ return null;
59
+ }
60
+ }
61
+ } else {
62
+ config = await import(pathToFileURL(configPath).href);
63
+ }
64
+ } finally {
65
+ if (originalDirname !== undefined) {
66
+ global.__dirname = originalDirname;
67
+ } else {
68
+ delete global.__dirname;
69
+ }
70
+ if (originalFilename !== undefined) {
71
+ global.__filename = originalFilename;
72
+ } else {
73
+ delete global.__filename;
74
+ }
75
+ }
76
+
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);
97
+ }
98
+ }
99
+ }
100
+ }
101
+ }
102
+
103
+ console.log("No matching reporter config found with outputDir");
104
+ return null;
105
+ } catch (error) {
106
+ console.error("Error extracting outputDir from config:", error);
107
+ return null;
108
+ }
109
+ }
110
+
111
+ export async function getOutputDir(customOutputDirFromArgs = null) {
112
+ if (customOutputDirFromArgs) {
113
+ console.log(`Using custom outputDir from CLI: ${customOutputDirFromArgs}`);
114
+ return path.resolve(process.cwd(), customOutputDirFromArgs);
115
+ }
116
+
117
+ const { path: configPath, exists } = await findPlaywrightConfig();
118
+ console.log(
119
+ `Config file search result: ${exists ? configPath : "not found"}`
120
+ );
121
+
122
+ if (exists) {
123
+ const outputDirFromConfig = await extractOutputDirFromConfig(configPath);
124
+ if (outputDirFromConfig) {
125
+ console.log(`Using outputDir from config: ${outputDirFromConfig}`);
126
+ return outputDirFromConfig;
127
+ }
128
+ }
129
+
130
+ console.log(`Using default outputDir: ${DEFAULT_OUTPUT_DIR}`);
131
+ return path.resolve(process.cwd(), DEFAULT_OUTPUT_DIR);
132
+ }
@@ -2,6 +2,7 @@
2
2
 
3
3
  import * as fs from "fs/promises";
4
4
  import path from "path";
5
+ import { getOutputDir } from "./config-reader.mjs";
5
6
 
6
7
  // Use dynamic import for chalk as it's ESM only
7
8
  let chalk;
@@ -23,6 +24,15 @@ const DEFAULT_OUTPUT_DIR = "pulse-report";
23
24
  const DEFAULT_JSON_FILE = "playwright-pulse-report.json";
24
25
  const MINIFIED_HTML_FILE = "pulse-email-summary.html"; // New minified report
25
26
 
27
+ const args = process.argv.slice(2);
28
+ let customOutputDir = null;
29
+ for (let i = 0; i < args.length; i++) {
30
+ if (args[i] === "--outputDir" || args[i] === "-o") {
31
+ customOutputDir = args[i + 1];
32
+ break;
33
+ }
34
+ }
35
+
26
36
  function sanitizeHTML(str) {
27
37
  if (str === null || str === undefined) return "";
28
38
  return String(str).replace(/[&<>"']/g, (match) => {
@@ -652,10 +662,20 @@ function generateMinifiedHTML(reportData) {
652
662
  `;
653
663
  }
654
664
  async function main() {
655
- const outputDir = path.resolve(process.cwd(), DEFAULT_OUTPUT_DIR);
665
+ const outputDir = await getOutputDir(customOutputDir);
656
666
  const reportJsonPath = path.resolve(outputDir, DEFAULT_JSON_FILE);
657
667
  const minifiedReportHtmlPath = path.resolve(outputDir, MINIFIED_HTML_FILE); // Path for the new minified HTML
658
668
 
669
+ console.log(chalk.blue(`Generating email report...`));
670
+ console.log(chalk.blue(`Output directory set to: ${outputDir}`));
671
+ if (customOutputDir) {
672
+ console.log(chalk.gray(` (from CLI argument)`));
673
+ } else {
674
+ console.log(
675
+ chalk.gray(` (auto-detected from playwright.config or using default)`)
676
+ );
677
+ }
678
+
659
679
  // Step 2: Load current run's data
660
680
  let currentRunReportData;
661
681
  try {