@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 +192 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +26 -0
- package/dist/lib/report-types.d.ts +8 -0
- package/dist/lib/report-types.js +2 -0
- package/dist/playwright-pulse-reporter.d.ts +26 -0
- package/dist/playwright-pulse-reporter.js +304 -0
- package/dist/reporter/attachment-utils.d.ts +10 -0
- package/dist/reporter/attachment-utils.js +192 -0
- package/dist/reporter/index.d.ts +5 -0
- package/dist/reporter/index.js +9 -0
- package/dist/reporter/lib/report-types.d.ts +8 -0
- package/dist/reporter/lib/report-types.js +2 -0
- package/dist/reporter/playwright-pulse-reporter.d.ts +27 -0
- package/dist/reporter/playwright-pulse-reporter.js +419 -0
- package/dist/reporter/reporter/playwright-pulse-reporter.d.ts +1 -0
- package/dist/reporter/reporter/playwright-pulse-reporter.js +380 -0
- package/dist/reporter/tsconfig.reporter.tsbuildinfo +1 -0
- package/dist/reporter/types/index.d.ts +52 -0
- package/dist/reporter/types/index.js +2 -0
- package/dist/types/index.d.ts +61 -0
- package/dist/types/index.js +2 -0
- package/package.json +107 -0
- package/scripts/generate-static-report.mjs +1539 -0
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.
|
package/dist/index.d.ts
ADDED
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,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;
|