@arghajit/playwright-pulse-report 0.3.4 → 0.3.5

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/playwright-pulse-report",
3
3
  "author": "Arghajit Singha",
4
- "version": "0.3.4",
4
+ "version": "0.3.5",
5
5
  "description": "A Playwright reporter and dashboard for visualizing test results.",
6
6
  "homepage": "https://arghajit47.github.io/playwright-pulse/",
7
7
  "repository": {
@@ -23,7 +23,8 @@
23
23
  "email",
24
24
  "playwright-report",
25
25
  "pulse",
26
- "ai-failure-analysis"
26
+ "ai-failure-analysis",
27
+ "pulse-report"
27
28
  ],
28
29
  "main": "dist/reporter/index.js",
29
30
  "types": "dist/reporter/index.d.ts",
@@ -34,13 +35,13 @@
34
35
  ],
35
36
  "license": "MIT",
36
37
  "bin": {
37
- "logo": "node scripts/terminal-logo.mjs",
38
+ "logo": "scripts/terminal-logo.mjs",
38
39
  "generate-pulse-report": "scripts/generate-static-report.mjs",
39
40
  "generate-report": "scripts/generate-report.mjs",
40
- "merge-pulse-report": "scripts/merge-pulse-report.js",
41
+ "merge-pulse-report": "scripts/merge-pulse-report.mjs",
41
42
  "send-email": "scripts/sendReport.mjs",
42
- "generate-trend": "scripts/generate-trend.mjs",
43
43
  "generate-email-report": "scripts/generate-email-report.mjs",
44
+ "generate-trend": "scripts/generate-trend.mjs",
44
45
  "pulse-logo": "scripts/terminal-logo.mjs"
45
46
  },
46
47
  "exports": {
@@ -55,7 +56,7 @@
55
56
  "prepublishOnly": "npm run build:reporter",
56
57
  "report:static": "node ./scripts/generate-static-report.mjs",
57
58
  "report:generate": "node ./scripts/generate-report.mjs",
58
- "report:merge": "node ./scripts/merge-pulse-report.js",
59
+ "report:merge": "node ./scripts/merge-pulse-report.mjs",
59
60
  "report:email": "node ./scripts/sendReport.mjs",
60
61
  "report:minify": "node ./scripts/generate-email-report.mjs",
61
62
  "generate-trend": "node ./scripts/generate-trend.mjs"
@@ -67,13 +68,13 @@
67
68
  "d3": "^7.9.0",
68
69
  "date-fns": "^3.6.0",
69
70
  "dotenv": "^16.5.0",
70
- "highcharts": "^12.2.0",
71
+ "highcharts": "^12.5.0",
71
72
  "jsdom": "^26.1.0",
72
- "lucide-react": "^0.475.0",
73
73
  "node-fetch": "^3.3.2",
74
- "nodemailer": "^7.0.3",
74
+ "nodemailer": "^7.0.12",
75
75
  "patch-package": "^8.0.0",
76
- "sharp": "^0.33.5",
76
+ "readable-stream": "^4.7.0",
77
+ "sharp": "^0.34.5",
77
78
  "ua-parser-js": "^1.0.41",
78
79
  "zod": "^3.24.2"
79
80
  },
@@ -90,10 +91,11 @@
90
91
  "@playwright/test": ">=1.40.0"
91
92
  },
92
93
  "overrides": {
93
- "brace-expansion": "^2.0.2",
94
- "@isaacs/brace-expansion": "^5.0.1",
95
- "glob": "^13.0.0",
96
- "minimatch": "^10.1.2",
97
- "lodash": "^4.17.23"
94
+ "glob": "^13.0.6",
95
+ "minimatch": "^10.2.1",
96
+ "ajv": "^8.18.0",
97
+ "isarray": "^2.0.5",
98
+ "safe-buffer": "^5.2.1",
99
+ "string_decoder": "^1.3.0"
98
100
  }
99
101
  }
@@ -22,159 +22,149 @@ async function findPlaywrightConfig() {
22
22
  return { path: null, exists: false };
23
23
  }
24
24
 
25
- async function extractOutputDirFromConfig(configPath) {
25
+ async function extractReporterOptionsFromConfig(configPath) {
26
26
  let fileContent = "";
27
27
  try {
28
28
  fileContent = fs.readFileSync(configPath, "utf-8");
29
29
  } catch (e) {
30
- // If we can't read the file, we can't parse or import it.
31
- return null;
30
+ return {};
32
31
  }
33
32
 
33
+ const options = {};
34
+
34
35
  // 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
36
  try {
38
- // Regex matches: outputDir: "value" or outputDir: 'value'
39
- const match = fileContent.match(/outputDir:\s*["']([^"']+)["']/);
37
+ // Find the Pulse/Dummy reporter block
38
+ // It usually looks like ["@arghajit/playwright-pulse-report", { ... }] or ["playwright-pulse-report", { ... }]
39
+ const reporterBlockRegex = /\[\s*["'](?:@arghajit\/)?(?:playwright-pulse-report|dummy)["']\s*,\s*(\{[\s\S]*?\})\s*,?\s*\]/g;
40
+ let match;
41
+ while ((match = reporterBlockRegex.exec(fileContent)) !== null) {
42
+ const block = match[1];
43
+
44
+ const outputDirMatch = block.match(/outputDir:\s*["']([^"']+)["']/);
45
+ if (outputDirMatch && outputDirMatch[1]) options.outputDir = outputDirMatch[1];
46
+
47
+ const outputFileMatch = block.match(/outputFile:\s*["']([^"']+)["']/);
48
+ if (outputFileMatch && outputFileMatch[1]) options.outputFile = outputFileMatch[1];
49
+
50
+ const resetOnEachRunMatch = block.match(/resetOnEachRun:\s*(true|false)/);
51
+ if (resetOnEachRunMatch) options.resetOnEachRun = resetOnEachRunMatch[1] === "true";
52
+
53
+ const individualReportsSubDirMatch = block.match(/individualReportsSubDir:\s*["']([^"']+)["']/);
54
+ if (individualReportsSubDirMatch && individualReportsSubDirMatch[1]) options.individualReportsSubDir = individualReportsSubDirMatch[1];
55
+
56
+ // If we found any Pulse-specific options, we're likely in the right block
57
+ if (Object.keys(options).length > 0) break;
58
+ }
40
59
 
41
- if (match && match[1]) {
42
- return path.resolve(process.cwd(), match[1]);
60
+ // Fallback for global outputDir if not found in reporter block
61
+ if (!options.outputDir) {
62
+ const globalOutputDirMatch = fileContent.match(/outputDir:\s*["']([^"']+)["']/);
63
+ if (globalOutputDirMatch && globalOutputDirMatch[1]) options.outputDir = globalOutputDirMatch[1];
43
64
  }
44
- } catch (e) {
45
- // Ignore text reading errors
46
- }
65
+ } catch (e) { }
66
+
67
+ // 2. Safety Check and Dynamic Import
68
+ if (Object.keys(options).length < 3) {
69
+ // Check if we can safely import()
70
+ let canImport = true;
71
+ if (configPath.endsWith(".js")) {
72
+ let isModulePackage = false;
73
+ try {
74
+ const pkgPath = path.resolve(process.cwd(), "package.json");
75
+ if (fs.existsSync(pkgPath)) {
76
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
77
+ isModulePackage = pkg.type === "module";
78
+ }
79
+ } catch (e) {}
47
80
 
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;
81
+ if (!isModulePackage) {
82
+ const hasEsmSyntax = /^\s*import\s+/m.test(fileContent) || /^\s*export\s+/m.test(fileContent);
83
+ if (hasEsmSyntax) canImport = false;
73
84
  }
74
85
  }
75
- }
76
86
 
77
- // 3. Strategy: Dynamic Import
78
- // If we passed the safety check, we try to import the config.
79
- try {
80
- let config;
81
- const configDir = dirname(configPath);
82
- const originalDirname = global.__dirname;
83
- const originalFilename = global.__filename;
84
-
85
- try {
86
- global.__dirname = configDir;
87
- global.__filename = configPath;
87
+ if (canImport) {
88
+ try {
89
+ let config;
90
+ const configDir = dirname(configPath);
91
+ const originalDirname = global.__dirname;
92
+ const originalFilename = global.__filename;
88
93
 
89
- if (configPath.endsWith(".ts")) {
90
94
  try {
91
- const { register } = await import("node:module");
92
- const { pathToFileURL } = await import("node:url");
93
- register("ts-node/esm", pathToFileURL("./"));
94
- config = await import(pathToFileURL(configPath).href);
95
- } catch (tsError) {
96
- const tsNode = await import("ts-node");
97
- tsNode.register({
98
- transpileOnly: true,
99
- compilerOptions: { module: "commonjs" },
100
- });
101
- config = require(configPath);
102
- }
103
- } else {
104
- // Try dynamic import for JS/MJS
105
- config = await import(pathToFileURL(configPath).href);
106
- }
107
-
108
- // Handle Default Export
109
- if (config && config.default) {
110
- config = config.default;
111
- }
95
+ global.__dirname = configDir;
96
+ global.__filename = configPath;
97
+
98
+ if (configPath.endsWith(".ts")) {
99
+ try {
100
+ const { register } = await import("node:module");
101
+ const { pathToFileURL } = await import("node:url");
102
+ register("ts-node/esm", pathToFileURL("./"));
103
+ config = await import(pathToFileURL(configPath).href);
104
+ } catch (tsError) {
105
+ const tsNode = await import("ts-node");
106
+ tsNode.register({ transpileOnly: true, compilerOptions: { module: "commonjs" } });
107
+ config = require(configPath);
108
+ }
109
+ } else {
110
+ config = await import(pathToFileURL(configPath).href);
111
+ }
112
112
 
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);
113
+ if (config && config.default) config = config.default;
114
+
115
+ if (config) {
116
+ if (config.reporter) {
117
+ const reporters = Array.isArray(config.reporter) ? config.reporter : [config.reporter];
118
+ for (const reporter of reporters) {
119
+ const reporterName = Array.isArray(reporter) ? reporter[0] : reporter;
120
+ const reporterOptions = Array.isArray(reporter) ? reporter[1] : null;
121
+
122
+ if (typeof reporterName === "string" &&
123
+ (reporterName.includes("playwright-pulse-report") ||
124
+ reporterName.includes("@arghajit/playwright-pulse-report") ||
125
+ reporterName.includes("@arghajit/dummy"))) {
126
+ if (reporterOptions) {
127
+ if (reporterOptions.outputDir) options.outputDir = reporterOptions.outputDir;
128
+ if (reporterOptions.outputFile) options.outputFile = reporterOptions.outputFile;
129
+ if (reporterOptions.resetOnEachRun !== undefined) options.resetOnEachRun = reporterOptions.resetOnEachRun;
130
+ if (reporterOptions.individualReportsSubDir) options.individualReportsSubDir = reporterOptions.individualReportsSubDir;
131
+ }
132
+ }
136
133
  }
137
134
  }
135
+ if (config.outputDir && !options.outputDir) options.outputDir = config.outputDir;
138
136
  }
137
+ } finally {
138
+ global.__dirname = originalDirname;
139
+ global.__filename = originalFilename;
139
140
  }
140
-
141
- // Check for Global outputDir
142
- if (config.outputDir) {
143
- return path.resolve(process.cwd(), config.outputDir);
144
- }
145
- }
146
- } finally {
147
- // Clean up globals
148
- global.__dirname = originalDirname;
149
- global.__filename = originalFilename;
141
+ } catch (error) {}
150
142
  }
151
- } catch (error) {
152
- // SILENT CATCH: Do NOT log anything here.
153
- return null;
154
143
  }
155
144
 
156
- return null;
145
+ return options;
157
146
  }
158
147
 
159
- export async function getOutputDir(customOutputDirFromArgs = null) {
160
- if (customOutputDirFromArgs) {
161
- console.log(`Using custom outputDir from CLI: ${customOutputDirFromArgs}`);
162
- return path.resolve(process.cwd(), customOutputDirFromArgs);
163
- }
164
-
148
+ export async function getReporterConfig(customOutputDirFromArgs = null) {
165
149
  const { path: configPath, exists } = await findPlaywrightConfig();
166
- console.log(
167
- `Config file search result: ${exists ? configPath : "not found"}`
168
- );
150
+ let options = {};
169
151
 
170
152
  if (exists) {
171
- const outputDirFromConfig = await extractOutputDirFromConfig(configPath);
172
- if (outputDirFromConfig) {
173
- console.log(`Using outputDir from config: ${outputDirFromConfig}`);
174
- return outputDirFromConfig;
175
- }
153
+ options = await extractReporterOptionsFromConfig(configPath);
176
154
  }
177
155
 
178
- console.log(`Using default outputDir: ${DEFAULT_OUTPUT_DIR}`);
179
- return path.resolve(process.cwd(), DEFAULT_OUTPUT_DIR);
156
+ const outputDir = customOutputDirFromArgs
157
+ ? path.resolve(process.cwd(), customOutputDirFromArgs)
158
+ : path.resolve(process.cwd(), options.outputDir || DEFAULT_OUTPUT_DIR);
159
+
160
+ const outputFile = options.outputFile || "playwright-pulse-report.json";
161
+ const resetOnEachRun = options.resetOnEachRun !== undefined ? options.resetOnEachRun : true;
162
+ const individualReportsSubDir = options.individualReportsSubDir || "pulse-results";
163
+
164
+ return { outputDir, outputFile, resetOnEachRun, individualReportsSubDir };
165
+ }
166
+
167
+ export async function getOutputDir(customOutputDirFromArgs = null) {
168
+ const config = await getReporterConfig(customOutputDirFromArgs);
169
+ return config.outputDir;
180
170
  }