@arghajit/playwright-pulse-report 0.1.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 ADDED
@@ -0,0 +1,192 @@
1
+
2
+ # Playwright Pulse Reporter & Dashboard
3
+
4
+ This project provides both a custom Playwright reporter and a Next.js web dashboard to visualize your Playwright test results, now with support for **Playwright sharding** and an option for a **standalone HTML report**.
5
+
6
+ ## How it Works
7
+
8
+ 1. **Reporter (`playwright-pulse-reporter.ts`):**
9
+ * A custom reporter that collects detailed results during your Playwright test run.
10
+ * **Sharding Support:** If tests are sharded, each shard process writes its results to a temporary file (`.pulse-shard-results-*.json`) in the specified output directory. The main reporter process then merges these files upon completion.
11
+ 2. **JSON Output:** On completion (`onEnd`), the reporter writes all collected (and potentially merged) data into a single `playwright-pulse-report.json` file in your project's specified output directory (defaults to `pulse-report-output` in the project root).
12
+ 3. **Next.js Dashboard (Option 2 - Interactive):** A web application built with Next.js that reads the final `playwright-pulse-report.json` file from the *dashboard project's root* and presents the test results in a dynamic, interactive dashboard interface.
13
+ 4. **Standalone HTML Report (Option 1 - Static):** A script (`generate-static-report.mjs`) that reads the `playwright-pulse-report.json` from the *Playwright project's output directory* (e.g., `pulse-report-output`) and generates a single, self-contained `playwright-pulse-static-report.html` file in the *same directory*. This static report mimics the key information and layout of the dashboard, including:
14
+ * Run summary metrics (Total, Passed, Failed, Skipped, Duration).
15
+ * Filtering controls for test results.
16
+ * List of individual test results with status, duration, name, suite, and errors.
17
+ * Expandable details for each test, including steps, error messages, stack traces, attachments (links), and source location.
18
+
19
+ ## Setup
20
+
21
+ ### 1. Install the Reporter Package
22
+
23
+ In your main Playwright project (the one containing your tests), install this reporter package:
24
+
25
+ ```bash
26
+ npm install @arghajit/playwright-pulse-reporter --save-dev
27
+ # or
28
+ yarn add @arghajit/playwright-pulse-reporter --dev
29
+ # or
30
+ pnpm add @arghajit/playwright-pulse-reporter --save-dev
31
+ ```
32
+
33
+ *(Replace `@arghajit/playwright-pulse-reporter` with the actual published package name if you customized it)*
34
+
35
+ ### 2. Configure Playwright
36
+
37
+ In your `playwright.config.ts` (or `.js`) file, add the custom reporter to the `reporter` array:
38
+
39
+ ```typescript
40
+ // playwright.config.ts
41
+ import { defineConfig, devices } from '@playwright/test';
42
+ import * as path from 'path';
43
+
44
+ // Define where the final report JSON and HTML should go
45
+ const PULSE_REPORT_DIR = path.resolve(__dirname, 'pulse-report-output'); // Example: a directory in your project root
46
+
47
+ export default defineConfig({
48
+ // ... other configurations like projects, testDir, etc.
49
+
50
+ // Define the output directory for Playwright's own artifacts (traces, screenshots)
51
+ // This is separate from the Pulse reporter's output directory.
52
+ outputDir: './test-results/',
53
+
54
+ reporter: [
55
+ ['list'], // Keep other reporters like 'list' or 'html' if desired
56
+
57
+ // Add the Playwright Pulse Reporter
58
+ ['@arghajit/playwright-pulse-reporter', {
59
+ // Optional: Specify the output file name (defaults to 'playwright-pulse-report.json')
60
+ // outputFile: 'my-custom-report-name.json',
61
+
62
+ // REQUIRED: Specify the directory for the final JSON report
63
+ // The static HTML report will also be generated here.
64
+ // It's recommended to use an absolute path or one relative to the config file.
65
+ outputDir: PULSE_REPORT_DIR
66
+ }]
67
+ ],
68
+
69
+ // Enable sharding if needed
70
+ // fullyParallel: true, // Often used with sharding
71
+ // workers: process.env.CI ? 4 : undefined, // Example worker count
72
+
73
+ // ... other configurations
74
+ });
75
+ ```
76
+
77
+ **Explanation:**
78
+
79
+ * `outputDir` in the main `defineConfig`: This is where Playwright stores its own artifacts like traces and screenshots.
80
+ * `outputDir` inside the `@arghajit/playwright-pulse-reporter` options: This tells *our reporter* where to save the final `playwright-pulse-report.json`. Using a dedicated directory like `pulse-report-output` is **required** for the reporter and static report generation to work correctly.
81
+
82
+ ### 3. Run Your Tests
83
+
84
+ Execute your Playwright tests as usual. This command works whether you use sharding or not:
85
+
86
+ ```bash
87
+ npx playwright test
88
+ # or specific configurations like:
89
+ # npx playwright test --project=chromium --shard=1/3
90
+ ```
91
+
92
+ The `@arghajit/playwright-pulse-reporter` will automatically handle sharding if Playwright is configured to use it. Upon completion, the final `playwright-pulse-report.json` will be generated in the directory you specified (e.g., `pulse-report-output`).
93
+
94
+ ### 4. Generate the Static HTML Report (Option 1)
95
+
96
+ After your tests run and `playwright-pulse-report.json` is created, you can generate the standalone HTML report using the command provided by the package:
97
+
98
+ 1. **Navigate to your Playwright project directory** (the one where you ran the tests).
99
+ 2. **Run the generation command:**
100
+ ```bash
101
+ npx generate-pulse-report
102
+ ```
103
+ This command executes the `scripts/generate-static-report.mjs` script included in the `@arghajit/playwright-pulse-reporter` package. It reads the `pulse-report-output/playwright-pulse-report.json` file (relative to your current directory) and creates `pulse-report-output/playwright-pulse-static-report.html`.
104
+ 3. **Open the HTML file:** Open the generated `pulse-report-output/playwright-pulse-static-report.html` in your browser.
105
+
106
+ This HTML file is self-contained and provides a detailed, interactive dashboard-like overview suitable for sharing or archiving.
107
+
108
+ ### 5. View the Next.js Dashboard (Option 2 - *Currently Part of the Same Project*)
109
+
110
+ **Note:** The Next.js dashboard is currently part of the reporter project itself. To view it:
111
+
112
+ 1. **Navigate to the Reporter/Dashboard Project:**
113
+ ```bash
114
+ cd path/to/playwright-pulse-reporter # The directory containing THIS dashboard code
115
+ ```
116
+ 2. **Install Dependencies:**
117
+ ```bash
118
+ npm install
119
+ # or yarn install or pnpm install
120
+ ```
121
+ 3. **Copy the Report File:** Copy the `playwright-pulse-report.json` file generated by your tests (e.g., from your main project's `pulse-report-output` directory) into the **root directory** of *this dashboard project*.
122
+ ```bash
123
+ # Example: Copying from your main project to the dashboard project directory
124
+ cp ../my-playwright-project/pulse-report-output/playwright-pulse-report.json ./
125
+ ```
126
+ **Tip for Development:** You can also use the `sample-report.json` file included in this project for development:
127
+ ```bash
128
+ cp sample-report.json ./playwright-pulse-report.json
129
+ ```
130
+ 4. **Start the Dashboard:**
131
+ ```bash
132
+ npm run dev
133
+ # or yarn dev or pnpm dev
134
+ ```
135
+
136
+ This will start the Next.js dashboard (usually on `http://localhost:9002`).
137
+
138
+ **Alternatively, build and start for production:**
139
+
140
+ ```bash
141
+ # Ensure the report JSON is in the root first
142
+ npm run build
143
+ npm run start
144
+ ```
145
+
146
+ ## Development (Contributing to this Project)
147
+
148
+ To work on the reporter or the dashboard itself:
149
+
150
+ 1. **Clone the repository:**
151
+ ```bash
152
+ git clone <repository-url>
153
+ cd playwright-pulse-reporter
154
+ ```
155
+ 2. **Install dependencies:**
156
+ ```bash
157
+ npm install
158
+ ```
159
+ 3. **Build the reporter:** (Needed if you make changes to the reporter code)
160
+ ```bash
161
+ npm run build:reporter
162
+ ```
163
+ 4. **Run the dashboard in development mode:**
164
+ ```bash
165
+ # Make sure a playwright-pulse-report.json exists in the root
166
+ # Using the sample data:
167
+ cp sample-report.json ./playwright-pulse-report.json
168
+ npm run dev
169
+ ```
170
+ 5. **Test static report generation:**
171
+ ```bash
172
+ # 1. Ensure playwright-pulse-report.json exists in pulse-report-output
173
+ mkdir -p pulse-report-output
174
+ cp sample-report.json pulse-report-output/playwright-pulse-report.json
175
+ # 2. Run the generation script directly using node (or via the bin command)
176
+ node ./scripts/generate-static-report.mjs
177
+ # or
178
+ # npx generate-pulse-report
179
+ ```
180
+
181
+ ## Key Files
182
+
183
+ * `src/reporter/index.ts`: The entry point for the Playwright reporter logic (exports the class).
184
+ * `src/reporter/playwright-pulse-reporter.ts`: The core Playwright reporter implementation (handles sharding, generates JSON).
185
+ * `scripts/generate-static-report.mjs`: Script to generate the standalone HTML report (executed via `npx generate-pulse-report`).
186
+ * `src/lib/data-reader.ts`: Server-side logic for reading the JSON report file (used by Next.js dashboard).
187
+ * `src/lib/data.ts`: Data fetching functions used by the Next.js dashboard components.
188
+ * `src/app/`: Contains the Next.js dashboard pages and components.
189
+ * `pulse-report-output/playwright-pulse-report.json`: (Generated by the reporter in the *user's project*) The primary data source.
190
+ * `pulse-report-output/playwright-pulse-static-report.html`: (Generated by the script in the *user's project*) The standalone HTML report.
191
+ * `playwright-pulse-report.json`: (Manually copied to the *dashboard project root*) Used by the Next.js dashboard.
192
+ * `sample-report.json`: (Included in this project) Dummy data for development/testing visualization.
@@ -0,0 +1,5 @@
1
+ import { PlaywrightPulseReporter } from "./playwright-pulse-reporter";
2
+ export default PlaywrightPulseReporter;
3
+ export { PlaywrightPulseReporter };
4
+ export * from "../types";
5
+ export * from "../lib/report-types";
package/dist/index.js ADDED
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.PlaywrightPulseReporter = void 0;
18
+ // src/reporter/index.ts
19
+ const playwright_pulse_reporter_1 = require("./playwright-pulse-reporter");
20
+ Object.defineProperty(exports, "PlaywrightPulseReporter", { enumerable: true, get: function () { return playwright_pulse_reporter_1.PlaywrightPulseReporter; } });
21
+ // Export the reporter class as the default export for CommonJS compatibility
22
+ // and also as a named export for potential ES module consumers.
23
+ exports.default = playwright_pulse_reporter_1.PlaywrightPulseReporter;
24
+ // You can also export other related types or utilities if needed
25
+ __exportStar(require("../types"), exports); // Re-export shared types if they are used by the reporter consumers
26
+ __exportStar(require("../lib/report-types"), exports);
@@ -0,0 +1,8 @@
1
+ import type { TestResult, TestRun } from '@/types';
2
+ export interface PlaywrightPulseReport {
3
+ run: TestRun | null;
4
+ results: TestResult[];
5
+ metadata: {
6
+ generatedAt: string;
7
+ };
8
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,26 @@
1
+ import type { FullConfig, FullResult, Reporter, Suite, TestCase, TestResult as PwTestResult } from "@playwright/test/reporter";
2
+ export declare class PlaywrightPulseReporter implements Reporter {
3
+ private config;
4
+ private suite;
5
+ private results;
6
+ private runStartTime;
7
+ private outputDir;
8
+ private baseOutputFile;
9
+ private isSharded;
10
+ private shardIndex;
11
+ constructor(options?: {
12
+ outputFile?: string;
13
+ outputDir?: string;
14
+ });
15
+ printsToStdio(): boolean;
16
+ onBegin(config: FullConfig, suite: Suite): void;
17
+ onTestBegin(test: TestCase): void;
18
+ private processStep;
19
+ onTestEnd(test: TestCase, result: PwTestResult): void;
20
+ onError(error: any): void;
21
+ private _writeShardResults;
22
+ private _mergeShardResults;
23
+ private _cleanupTemporaryFiles;
24
+ private _ensureDirExists;
25
+ onEnd(result: FullResult): Promise<void>;
26
+ }
@@ -0,0 +1,304 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.PlaywrightPulseReporter = void 0;
37
+ const fs = __importStar(require("fs/promises"));
38
+ const path = __importStar(require("path"));
39
+ // Helper to convert Playwright status to Pulse status
40
+ const convertStatus = (status) => {
41
+ if (status === "passed")
42
+ return "passed";
43
+ if (status === "failed" || status === "timedOut" || status === "interrupted")
44
+ return "failed";
45
+ return "skipped";
46
+ };
47
+ const TEMP_SHARD_FILE_PREFIX = ".pulse-shard-results-";
48
+ // Use standard ES module export
49
+ class PlaywrightPulseReporter {
50
+ constructor(options = {}) {
51
+ var _a;
52
+ this.results = []; // Holds results *per process* (main or shard)
53
+ this.baseOutputFile = "playwright-pulse-report.json";
54
+ this.isSharded = false;
55
+ this.shardIndex = undefined;
56
+ this.baseOutputFile = (_a = options.outputFile) !== null && _a !== void 0 ? _a : this.baseOutputFile;
57
+ // Initial outputDir setup (will be refined in onBegin)
58
+ const baseDir = options.outputDir
59
+ ? path.resolve(options.outputDir)
60
+ : process.cwd();
61
+ this.outputDir = baseDir;
62
+ console.log(`PlaywrightPulseReporter: Initial Output dir configured to ${this.outputDir}`);
63
+ }
64
+ printsToStdio() {
65
+ return this.shardIndex === undefined || this.shardIndex === 0;
66
+ }
67
+ onBegin(config, suite) {
68
+ this.config = config;
69
+ this.suite = suite;
70
+ this.runStartTime = Date.now();
71
+ const totalShards = parseInt(process.env.PLAYWRIGHT_SHARD_TOTAL || "1", 10);
72
+ this.isSharded = totalShards > 1;
73
+ if (process.env.PLAYWRIGHT_SHARD_INDEX !== undefined) {
74
+ this.shardIndex = parseInt(process.env.PLAYWRIGHT_SHARD_INDEX, 10);
75
+ }
76
+ const configDir = this.config.rootDir;
77
+ this.outputDir = this.outputDir
78
+ ? path.resolve(configDir, this.outputDir)
79
+ : path.resolve(configDir, "pulse-report-output");
80
+ console.log(`PlaywrightPulseReporter: Final Output dir resolved to ${this.outputDir}`);
81
+ if (this.shardIndex === undefined) {
82
+ // Main process
83
+ console.log(`PlaywrightPulseReporter: Starting test run with ${suite.allTests().length} tests${this.isSharded ? ` across ${totalShards} shards` : ""}. Outputting to ${this.outputDir}`);
84
+ this._cleanupTemporaryFiles().catch((err) => console.error("Pulse Reporter: Error cleaning up temp files:", err));
85
+ }
86
+ else {
87
+ // Shard process
88
+ console.log(`PlaywrightPulseReporter: Shard ${this.shardIndex + 1}/${totalShards} starting. Outputting temp results to ${this.outputDir}`);
89
+ }
90
+ }
91
+ onTestBegin(test) {
92
+ // Optional: Log test start
93
+ }
94
+ processStep(step, parentStatus) {
95
+ var _a;
96
+ const inherentStatus = parentStatus === "failed" || parentStatus === "skipped"
97
+ ? parentStatus
98
+ : convertStatus(step.error ? "failed" : "passed");
99
+ const duration = step.duration;
100
+ const startTime = new Date(step.startTime);
101
+ const endTime = new Date(startTime.getTime() + Math.max(0, duration));
102
+ return {
103
+ id: `${step.title}-${startTime.toISOString()}-${duration}-${Math.random()
104
+ .toString(16)
105
+ .slice(2)}`,
106
+ title: step.title,
107
+ status: inherentStatus,
108
+ duration: duration,
109
+ startTime: startTime,
110
+ endTime: endTime,
111
+ errorMessage: (_a = step.error) === null || _a === void 0 ? void 0 : _a.message,
112
+ screenshot: undefined, // Placeholder
113
+ };
114
+ }
115
+ onTestEnd(test, result) {
116
+ var _a, _b, _c, _d, _e;
117
+ const testStatus = convertStatus(result.status);
118
+ const startTime = new Date(result.startTime);
119
+ const endTime = new Date(startTime.getTime() + result.duration);
120
+ const processAllSteps = (steps, parentTestStatus) => {
121
+ let processed = [];
122
+ for (const step of steps) {
123
+ const processedStep = this.processStep(step, parentTestStatus);
124
+ processed.push(processedStep);
125
+ if (step.steps.length > 0) {
126
+ processed = processed.concat(processAllSteps(step.steps, processedStep.status));
127
+ }
128
+ }
129
+ return processed;
130
+ };
131
+ let codeSnippet = undefined;
132
+ try {
133
+ if ((_a = test.location) === null || _a === void 0 ? void 0 : _a.file) {
134
+ codeSnippet = `Test defined at: ${test.location.file}:${test.location.line}:${test.location.column}`;
135
+ }
136
+ }
137
+ catch (e) {
138
+ console.warn(`Pulse Reporter: Could not extract code snippet for ${test.title}`, e);
139
+ }
140
+ const pulseResult = {
141
+ id: test.id ||
142
+ `${test.title}-${startTime.toISOString()}-${Math.random()
143
+ .toString(16)
144
+ .slice(2)}`,
145
+ runId: "TBD",
146
+ name: test.titlePath().join(" > "),
147
+ suiteName: test.parent.title,
148
+ status: testStatus,
149
+ duration: result.duration,
150
+ startTime: startTime,
151
+ endTime: endTime,
152
+ retries: result.retry,
153
+ steps: processAllSteps(result.steps, testStatus),
154
+ errorMessage: (_b = result.error) === null || _b === void 0 ? void 0 : _b.message,
155
+ stackTrace: (_c = result.error) === null || _c === void 0 ? void 0 : _c.stack,
156
+ codeSnippet: codeSnippet,
157
+ screenshot: (_d = result.attachments.find((a) => a.name === "screenshot")) === null || _d === void 0 ? void 0 : _d.path,
158
+ video: (_e = result.attachments.find((a) => a.name === "video")) === null || _e === void 0 ? void 0 : _e.path,
159
+ tags: test.tags.map((tag) => tag.startsWith("@") ? tag.substring(1) : tag),
160
+ };
161
+ this.results.push(pulseResult);
162
+ }
163
+ onError(error) {
164
+ var _a;
165
+ console.error(`PlaywrightPulseReporter: Error encountered (Shard: ${(_a = this.shardIndex) !== null && _a !== void 0 ? _a : "Main"}):`, error);
166
+ }
167
+ async _writeShardResults() {
168
+ if (this.shardIndex === undefined) {
169
+ console.warn("Pulse Reporter: _writeShardResults called in main process. Skipping.");
170
+ return;
171
+ }
172
+ const tempFilePath = path.join(this.outputDir, `${TEMP_SHARD_FILE_PREFIX}${this.shardIndex}.json`);
173
+ try {
174
+ await this._ensureDirExists(this.outputDir);
175
+ await fs.writeFile(tempFilePath, JSON.stringify(this.results, null, 2));
176
+ }
177
+ catch (error) {
178
+ console.error(`Pulse Reporter: Shard ${this.shardIndex} failed to write temporary results to ${tempFilePath}`, error);
179
+ }
180
+ }
181
+ async _mergeShardResults(finalRunData) {
182
+ console.log("Pulse Reporter: Merging results from shards...");
183
+ let allResults = [];
184
+ const totalShards = parseInt(process.env.PLAYWRIGHT_SHARD_TOTAL || "1", 10);
185
+ for (let i = 0; i < totalShards; i++) {
186
+ const tempFilePath = path.join(this.outputDir, `${TEMP_SHARD_FILE_PREFIX}${i}.json`);
187
+ try {
188
+ const content = await fs.readFile(tempFilePath, "utf-8");
189
+ const shardResults = JSON.parse(content);
190
+ shardResults.forEach((r) => (r.runId = finalRunData.id));
191
+ allResults = allResults.concat(shardResults);
192
+ }
193
+ catch (error) {
194
+ if (error && error.code === "ENOENT") {
195
+ console.warn(`Pulse Reporter: Shard results file not found: ${tempFilePath}.`);
196
+ }
197
+ else {
198
+ console.warn(`Pulse Reporter: Could not read or parse results from shard ${i} (${tempFilePath}). Error: ${error}`);
199
+ }
200
+ }
201
+ }
202
+ console.log(`Pulse Reporter: Merged a total of ${allResults.length} results from ${totalShards} shards.`);
203
+ finalRunData.passed = allResults.filter((r) => r.status === "passed").length;
204
+ finalRunData.failed = allResults.filter((r) => r.status === "failed").length;
205
+ finalRunData.skipped = allResults.filter((r) => r.status === "skipped").length;
206
+ finalRunData.totalTests = allResults.length;
207
+ return {
208
+ run: finalRunData,
209
+ results: allResults,
210
+ metadata: { generatedAt: new Date().toISOString() },
211
+ };
212
+ }
213
+ async _cleanupTemporaryFiles() {
214
+ try {
215
+ await this._ensureDirExists(this.outputDir);
216
+ const files = await fs.readdir(this.outputDir);
217
+ const tempFiles = files.filter((f) => f.startsWith(TEMP_SHARD_FILE_PREFIX));
218
+ if (tempFiles.length > 0) {
219
+ console.log(`Pulse Reporter: Cleaning up ${tempFiles.length} temporary shard files...`);
220
+ await Promise.all(tempFiles.map((f) => fs.unlink(path.join(this.outputDir, f))));
221
+ }
222
+ }
223
+ catch (error) {
224
+ if (error && error.code !== "ENOENT") {
225
+ console.error("Pulse Reporter: Error cleaning up temporary files:", error);
226
+ }
227
+ }
228
+ }
229
+ async _ensureDirExists(dirPath) {
230
+ try {
231
+ await fs.mkdir(dirPath, { recursive: true });
232
+ }
233
+ catch (error) {
234
+ if (error && error.code !== "EEXIST") {
235
+ console.error(`Pulse Reporter: Failed to ensure directory exists: ${dirPath}`, error);
236
+ throw error;
237
+ }
238
+ }
239
+ }
240
+ async onEnd(result) {
241
+ var _a, _b, _c, _d, _e, _f;
242
+ if (this.shardIndex !== undefined) {
243
+ await this._writeShardResults();
244
+ console.log(`PlaywrightPulseReporter: Shard ${this.shardIndex + 1} finished.`);
245
+ return;
246
+ }
247
+ const runEndTime = Date.now();
248
+ const duration = runEndTime - this.runStartTime;
249
+ const runId = `run-${this.runStartTime}-${Math.random()
250
+ .toString(16)
251
+ .slice(2)}`;
252
+ const runData = {
253
+ id: runId,
254
+ timestamp: new Date(this.runStartTime),
255
+ totalTests: 0,
256
+ passed: 0,
257
+ failed: 0,
258
+ skipped: 0,
259
+ duration,
260
+ };
261
+ let finalReport;
262
+ if (this.isSharded) {
263
+ finalReport = await this._mergeShardResults(runData);
264
+ }
265
+ else {
266
+ this.results.forEach((r) => (r.runId = runId));
267
+ runData.passed = this.results.filter((r) => r.status === "passed").length;
268
+ runData.failed = this.results.filter((r) => r.status === "failed").length;
269
+ runData.skipped = this.results.filter((r) => r.status === "skipped").length;
270
+ runData.totalTests = this.results.length;
271
+ finalReport = {
272
+ run: runData,
273
+ results: this.results,
274
+ metadata: { generatedAt: new Date().toISOString() },
275
+ };
276
+ }
277
+ const finalRunStatus = ((_b = (_a = finalReport.run) === null || _a === void 0 ? void 0 : _a.failed) !== null && _b !== void 0 ? _b : 0 > 0) ? "failed" : "passed";
278
+ console.log(`PlaywrightPulseReporter: Test run finished with overall status: ${finalRunStatus}`);
279
+ console.log(` Passed: ${(_c = finalReport.run) === null || _c === void 0 ? void 0 : _c.passed}, Failed: ${(_d = finalReport.run) === null || _d === void 0 ? void 0 : _d.failed}, Skipped: ${(_e = finalReport.run) === null || _e === void 0 ? void 0 : _e.skipped}`);
280
+ console.log(` Total tests: ${(_f = finalReport.run) === null || _f === void 0 ? void 0 : _f.totalTests}`);
281
+ console.log(` Total time: ${(duration / 1000).toFixed(2)}s`);
282
+ const finalOutputPath = path.join(this.outputDir, this.baseOutputFile);
283
+ try {
284
+ await this._ensureDirExists(this.outputDir);
285
+ await fs.writeFile(finalOutputPath, JSON.stringify(finalReport, (key, value) => {
286
+ if (value instanceof Date) {
287
+ return value.toISOString();
288
+ }
289
+ return value;
290
+ }, 2));
291
+ console.log(`PlaywrightPulseReporter: Final report written to ${finalOutputPath}`);
292
+ }
293
+ catch (error) {
294
+ console.error(`PlaywrightPulseReporter: Failed to write final report to ${finalOutputPath}`, error);
295
+ }
296
+ finally {
297
+ if (this.isSharded) {
298
+ await this._cleanupTemporaryFiles();
299
+ }
300
+ }
301
+ }
302
+ }
303
+ exports.PlaywrightPulseReporter = PlaywrightPulseReporter;
304
+ // No module.exports needed for ES modules
@@ -0,0 +1,10 @@
1
+ import type { TestResult as PwTestResult } from "@playwright/test/reporter";
2
+ import type { TestResult, PlaywrightPulseReporterOptions } from "../types";
3
+ /**
4
+ * Processes attachments from a Playwright TestResult and updates the PulseTestResult.
5
+ * @param testId A unique identifier for the test, used for folder naming.
6
+ * @param pwResult The TestResult object from Playwright.
7
+ * @param pulseResult The internal test result structure to update.
8
+ * @param config The reporter configuration options.
9
+ */
10
+ export declare function attachFiles(testId: string, pwResult: PwTestResult, pulseResult: TestResult, config: PlaywrightPulseReporterOptions): void;