@arghajit/playwright-pulse-report 0.3.3 → 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/README.md +95 -85
- package/dist/lib/report-types.d.ts +2 -0
- package/dist/reporter/playwright-pulse-reporter.d.ts +18 -1
- package/dist/reporter/playwright-pulse-reporter.js +157 -129
- package/dist/reporter/tsconfig.reporter.tsbuildinfo +1 -0
- package/dist/types/index.d.ts +53 -9
- package/dist/utils/compression-utils.d.ts +19 -0
- package/dist/utils/compression-utils.js +112 -0
- package/package.json +18 -9
- package/scripts/config-reader.mjs +114 -124
- package/scripts/generate-email-report.mjs +96 -21
- package/scripts/generate-report.mjs +1172 -531
- package/scripts/generate-static-report.mjs +1269 -540
- package/scripts/generate-trend.mjs +8 -4
- package/scripts/{merge-pulse-report.js → merge-pulse-report.mjs} +64 -35
- package/scripts/merge-sequential-reports.mjs +172 -0
- package/scripts/sendReport.mjs +156 -202
- package/scripts/terminal-logo.mjs +51 -0
- package/dist/index.d.ts +0 -5
- package/dist/index.js +0 -26
- package/dist/playwright-pulse-reporter.d.ts +0 -26
- package/dist/playwright-pulse-reporter.js +0 -304
- package/dist/reporter/lib/report-types.d.ts +0 -8
- package/dist/reporter/lib/report-types.js +0 -2
- package/dist/reporter/reporter/playwright-pulse-reporter.d.ts +0 -1
- package/dist/reporter/reporter/playwright-pulse-reporter.js +0 -398
- package/dist/reporter/types/index.d.ts +0 -52
- package/dist/reporter/types/index.js +0 -2
package/dist/types/index.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
export type TestStatus = "passed" | "failed" | "skipped" | "expected-failure" | "unexpected-success" | "explicitly-skipped";
|
|
1
|
+
export type TestStatus = "passed" | "failed" | "skipped" | "expected-failure" | "unexpected-success" | "explicitly-skipped" | "flaky";
|
|
3
2
|
export interface TestStep {
|
|
4
3
|
id: string;
|
|
5
4
|
title: string;
|
|
@@ -11,6 +10,7 @@ export interface TestStep {
|
|
|
11
10
|
errorMessage?: string;
|
|
12
11
|
stackTrace?: string;
|
|
13
12
|
codeLocation?: string;
|
|
13
|
+
codeSnippet?: string;
|
|
14
14
|
isHook?: boolean;
|
|
15
15
|
hookType?: "before" | "after";
|
|
16
16
|
steps?: TestStep[];
|
|
@@ -35,6 +35,8 @@ export interface TestResult {
|
|
|
35
35
|
suiteName?: string;
|
|
36
36
|
runId: string;
|
|
37
37
|
browser: string;
|
|
38
|
+
outcome?: string;
|
|
39
|
+
final_status?: TestStatus;
|
|
38
40
|
screenshots?: string[];
|
|
39
41
|
videoPath?: string[];
|
|
40
42
|
tracePath?: string;
|
|
@@ -58,6 +60,7 @@ export interface TestResult {
|
|
|
58
60
|
column: number;
|
|
59
61
|
};
|
|
60
62
|
}[];
|
|
63
|
+
retryHistory?: TestResult[];
|
|
61
64
|
}
|
|
62
65
|
export interface TestRun {
|
|
63
66
|
id: string;
|
|
@@ -66,26 +69,67 @@ export interface TestRun {
|
|
|
66
69
|
passed: number;
|
|
67
70
|
failed: number;
|
|
68
71
|
skipped: number;
|
|
72
|
+
flaky?: number;
|
|
69
73
|
duration: number;
|
|
70
|
-
environment?: EnvDetails;
|
|
74
|
+
environment?: EnvDetails | EnvDetails[];
|
|
71
75
|
}
|
|
72
76
|
export interface TrendDataPoint {
|
|
73
77
|
date: string;
|
|
74
78
|
passed: number;
|
|
75
79
|
failed: number;
|
|
76
80
|
skipped: number;
|
|
77
|
-
|
|
78
|
-
export interface SummaryMetric {
|
|
79
|
-
label: string;
|
|
80
|
-
value: string | number;
|
|
81
|
-
icon: LucideIcon;
|
|
82
|
-
color?: string;
|
|
81
|
+
flaky?: number;
|
|
83
82
|
}
|
|
84
83
|
export interface PlaywrightPulseReporterOptions {
|
|
84
|
+
/**
|
|
85
|
+
* The name of the output JSON file. Kindly do not change.
|
|
86
|
+
* @default "playwright-pulse-report.json"
|
|
87
|
+
*/
|
|
85
88
|
outputFile?: string;
|
|
89
|
+
/**
|
|
90
|
+
* The directory where the report files will be generated.
|
|
91
|
+
*
|
|
92
|
+
* Mostly useful while using sharding
|
|
93
|
+
*
|
|
94
|
+
* @default "pulse-report"
|
|
95
|
+
*/
|
|
86
96
|
outputDir?: string;
|
|
97
|
+
/**
|
|
98
|
+
* Whether to embed images directly as base64 strings in the report.
|
|
99
|
+
* @default false
|
|
100
|
+
*/
|
|
87
101
|
base64Images?: boolean;
|
|
102
|
+
/**
|
|
103
|
+
* Whether to reset the output directory before each run.
|
|
104
|
+
*
|
|
105
|
+
* Mostly useful while running multiple test suites in a single run with `&&` operator.
|
|
106
|
+
*
|
|
107
|
+
* example: `npx playwright test test1.spec.ts && npx playwright test test2.spec.ts`
|
|
108
|
+
*
|
|
109
|
+
* If `resetOnEachRun` is set to `false`, then the report of `test2.spec.ts` will be merged with `test1.spec.ts` report.
|
|
110
|
+
*
|
|
111
|
+
* @default true
|
|
112
|
+
*/
|
|
88
113
|
resetOnEachRun?: boolean;
|
|
114
|
+
/**
|
|
115
|
+
* A custom description to embed or display in the report.
|
|
116
|
+
*
|
|
117
|
+
* If not added, the component will not appear in the html reports
|
|
118
|
+
*/
|
|
119
|
+
reportDescription?: string;
|
|
120
|
+
/**
|
|
121
|
+
* Path to a custom logo image file to use in the report, which will be displayed in the header of the html report's logo and favicon.
|
|
122
|
+
*
|
|
123
|
+
* If not added, the default logo will be used.
|
|
124
|
+
*/
|
|
125
|
+
logo?: string;
|
|
126
|
+
/**
|
|
127
|
+
* The subdirectory within `outputDir` where individual run reports are stored.
|
|
128
|
+
* Only used when `resetOnEachRun` is `false`.
|
|
129
|
+
*
|
|
130
|
+
* @default "pulse-results"
|
|
131
|
+
*/
|
|
132
|
+
individualReportsSubDir?: string;
|
|
89
133
|
}
|
|
90
134
|
export interface EnvDetails {
|
|
91
135
|
host: string;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compression utilities for images
|
|
3
|
+
* Uses sharp for image compression (works cross-platform with no external dependencies)
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Compress an image file in-place
|
|
7
|
+
* @param filePath - Absolute path to the image file
|
|
8
|
+
* @param options - Compression options
|
|
9
|
+
*/
|
|
10
|
+
export declare function compressImage(filePath: string, options?: {
|
|
11
|
+
quality?: number;
|
|
12
|
+
}): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Compress an attachment file (auto-detects type)
|
|
15
|
+
* Note: Only compresses images. Videos are already compressed by Playwright.
|
|
16
|
+
* @param filePath - Absolute path to the file
|
|
17
|
+
* @param contentType - MIME content type
|
|
18
|
+
*/
|
|
19
|
+
export declare function compressAttachment(filePath: string, contentType: string): Promise<void>;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/utils/compression-utils.ts
|
|
3
|
+
/**
|
|
4
|
+
* Compression utilities for images
|
|
5
|
+
* Uses sharp for image compression (works cross-platform with no external dependencies)
|
|
6
|
+
*/
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
+
}) : function(o, v) {
|
|
21
|
+
o["default"] = v;
|
|
22
|
+
});
|
|
23
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
24
|
+
var ownKeys = function(o) {
|
|
25
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
+
var ar = [];
|
|
27
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
+
return ar;
|
|
29
|
+
};
|
|
30
|
+
return ownKeys(o);
|
|
31
|
+
};
|
|
32
|
+
return function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
40
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
+
exports.compressImage = compressImage;
|
|
42
|
+
exports.compressAttachment = compressAttachment;
|
|
43
|
+
const fs = __importStar(require("fs/promises"));
|
|
44
|
+
const path = __importStar(require("path"));
|
|
45
|
+
/**
|
|
46
|
+
* Compress an image file in-place
|
|
47
|
+
* @param filePath - Absolute path to the image file
|
|
48
|
+
* @param options - Compression options
|
|
49
|
+
*/
|
|
50
|
+
async function compressImage(filePath, options = {}) {
|
|
51
|
+
try {
|
|
52
|
+
const sharp = require('sharp');
|
|
53
|
+
const quality = options.quality || 75;
|
|
54
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
55
|
+
// Read original file
|
|
56
|
+
const imageBuffer = await fs.readFile(filePath);
|
|
57
|
+
let compressedBuffer;
|
|
58
|
+
if (ext === '.png') {
|
|
59
|
+
// Compress PNG
|
|
60
|
+
compressedBuffer = await sharp(imageBuffer)
|
|
61
|
+
.png({ quality, compressionLevel: 9 })
|
|
62
|
+
.toBuffer();
|
|
63
|
+
}
|
|
64
|
+
else if (ext === '.jpg' || ext === '.jpeg') {
|
|
65
|
+
// Compress JPEG
|
|
66
|
+
compressedBuffer = await sharp(imageBuffer)
|
|
67
|
+
.jpeg({ quality, mozjpeg: true })
|
|
68
|
+
.toBuffer();
|
|
69
|
+
}
|
|
70
|
+
else if (ext === '.webp') {
|
|
71
|
+
// Compress WebP
|
|
72
|
+
compressedBuffer = await sharp(imageBuffer)
|
|
73
|
+
.webp({ quality })
|
|
74
|
+
.toBuffer();
|
|
75
|
+
}
|
|
76
|
+
else if (ext === '.gif') {
|
|
77
|
+
// Compress GIF
|
|
78
|
+
compressedBuffer = await sharp(imageBuffer)
|
|
79
|
+
.gif()
|
|
80
|
+
.toBuffer();
|
|
81
|
+
}
|
|
82
|
+
else if (ext === '.tiff' || ext === '.tif') {
|
|
83
|
+
// Compress TIFF
|
|
84
|
+
compressedBuffer = await sharp(imageBuffer)
|
|
85
|
+
.tiff({ quality, compression: 'lzw' })
|
|
86
|
+
.toBuffer();
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
// Unsupported format, skip compression
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
// Only overwrite if compression actually reduced size
|
|
93
|
+
if (compressedBuffer.length < imageBuffer.length) {
|
|
94
|
+
await fs.writeFile(filePath, compressedBuffer);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
// File remains unchanged
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Compress an attachment file (auto-detects type)
|
|
103
|
+
* Note: Only compresses images. Videos are already compressed by Playwright.
|
|
104
|
+
* @param filePath - Absolute path to the file
|
|
105
|
+
* @param contentType - MIME content type
|
|
106
|
+
*/
|
|
107
|
+
async function compressAttachment(filePath, contentType) {
|
|
108
|
+
if (contentType.startsWith('image/')) {
|
|
109
|
+
await compressImage(filePath, { quality: 75 });
|
|
110
|
+
}
|
|
111
|
+
// Videos are skipped - already compressed by Playwright as WebM
|
|
112
|
+
}
|
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
|
+
"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,12 +35,14 @@
|
|
|
34
35
|
],
|
|
35
36
|
"license": "MIT",
|
|
36
37
|
"bin": {
|
|
38
|
+
"logo": "scripts/terminal-logo.mjs",
|
|
37
39
|
"generate-pulse-report": "scripts/generate-static-report.mjs",
|
|
38
40
|
"generate-report": "scripts/generate-report.mjs",
|
|
39
|
-
"merge-pulse-report": "scripts/merge-pulse-report.
|
|
41
|
+
"merge-pulse-report": "scripts/merge-pulse-report.mjs",
|
|
40
42
|
"send-email": "scripts/sendReport.mjs",
|
|
43
|
+
"generate-email-report": "scripts/generate-email-report.mjs",
|
|
41
44
|
"generate-trend": "scripts/generate-trend.mjs",
|
|
42
|
-
"
|
|
45
|
+
"pulse-logo": "scripts/terminal-logo.mjs"
|
|
43
46
|
},
|
|
44
47
|
"exports": {
|
|
45
48
|
".": {
|
|
@@ -53,7 +56,7 @@
|
|
|
53
56
|
"prepublishOnly": "npm run build:reporter",
|
|
54
57
|
"report:static": "node ./scripts/generate-static-report.mjs",
|
|
55
58
|
"report:generate": "node ./scripts/generate-report.mjs",
|
|
56
|
-
"report:merge": "node ./scripts/merge-pulse-report.
|
|
59
|
+
"report:merge": "node ./scripts/merge-pulse-report.mjs",
|
|
57
60
|
"report:email": "node ./scripts/sendReport.mjs",
|
|
58
61
|
"report:minify": "node ./scripts/generate-email-report.mjs",
|
|
59
62
|
"generate-trend": "node ./scripts/generate-trend.mjs"
|
|
@@ -65,12 +68,13 @@
|
|
|
65
68
|
"d3": "^7.9.0",
|
|
66
69
|
"date-fns": "^3.6.0",
|
|
67
70
|
"dotenv": "^16.5.0",
|
|
68
|
-
"highcharts": "^12.
|
|
71
|
+
"highcharts": "^12.5.0",
|
|
69
72
|
"jsdom": "^26.1.0",
|
|
70
|
-
"lucide-react": "^0.475.0",
|
|
71
73
|
"node-fetch": "^3.3.2",
|
|
72
|
-
"nodemailer": "^7.0.
|
|
74
|
+
"nodemailer": "^7.0.12",
|
|
73
75
|
"patch-package": "^8.0.0",
|
|
76
|
+
"readable-stream": "^4.7.0",
|
|
77
|
+
"sharp": "^0.34.5",
|
|
74
78
|
"ua-parser-js": "^1.0.41",
|
|
75
79
|
"zod": "^3.24.2"
|
|
76
80
|
},
|
|
@@ -87,6 +91,11 @@
|
|
|
87
91
|
"@playwright/test": ">=1.40.0"
|
|
88
92
|
},
|
|
89
93
|
"overrides": {
|
|
90
|
-
"glob": "^13.0.
|
|
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"
|
|
91
100
|
}
|
|
92
101
|
}
|
|
@@ -22,159 +22,149 @@ async function findPlaywrightConfig() {
|
|
|
22
22
|
return { path: null, exists: false };
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
async function
|
|
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
|
-
|
|
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
|
-
//
|
|
39
|
-
|
|
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
|
|
42
|
-
|
|
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
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
|
145
|
+
return options;
|
|
157
146
|
}
|
|
158
147
|
|
|
159
|
-
export async function
|
|
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
|
-
|
|
167
|
-
`Config file search result: ${exists ? configPath : "not found"}`
|
|
168
|
-
);
|
|
150
|
+
let options = {};
|
|
169
151
|
|
|
170
152
|
if (exists) {
|
|
171
|
-
|
|
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
|
-
|
|
179
|
-
|
|
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
|
}
|