playwright-ctrf-json-reporter 0.0.1
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 +162 -0
- package/dist/generate-report.d.ts +47 -0
- package/dist/generate-report.js +256 -0
- package/dist/generateReport.d.ts +10 -0
- package/dist/generateReport.js +82 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +8 -0
- package/package.json +37 -0
package/README.md
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# Playwright JSON Reporter - CTRF
|
|
2
|
+
|
|
3
|
+
A Playwright test reporter to generate JSON test reports that are [CTRF](https://ctrf.io) compliant.
|
|
4
|
+
|
|
5
|
+
[Common Test Report Format](https://ctrf.io) helps you generate consistent JSON reports that are agnostic of specific programming languages or test frameworks.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- Generate JSON test reports that are [CTRF](https://ctrf.io) compliant
|
|
10
|
+
- Customizable output options, minimal or comprehensive reports
|
|
11
|
+
- Straightforward integration with Playwright
|
|
12
|
+
- Enhanced test insights with detailed test information, environment details, and more.
|
|
13
|
+
|
|
14
|
+
## What is CTRF?
|
|
15
|
+
|
|
16
|
+
A JSON test report schema that is the same structure, no matter which testing tool is used. It's created to provide consistent test reporting agnostic of specific programming languages or testing frameworks. Where many testing frameworks exist, each generating JSON reports in their own way, CTRF provides a standardised schema helping you generate the same report anywhere.
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install --save-dev playwright-ctrf-json-reporter
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Add the reporter to your playwright.config.ts file:
|
|
25
|
+
|
|
26
|
+
```javascript
|
|
27
|
+
reporter: [
|
|
28
|
+
['list'], // You can combine multiple reporters
|
|
29
|
+
['playwright-ctrf-json-reporter', {}]
|
|
30
|
+
],
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Run your tests:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npx playwright test
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
You'll find a JSON file named `ctrf-report.json` in the root of your project.
|
|
40
|
+
|
|
41
|
+
## Reporter Options
|
|
42
|
+
|
|
43
|
+
The reporter supports several configuration options:
|
|
44
|
+
|
|
45
|
+
```javascript
|
|
46
|
+
reporter: [
|
|
47
|
+
['playwright-ctrf-json-reporter', {
|
|
48
|
+
outputFile: 'custom-name.json', // Optional: Output file name. Defaults to 'ctrf-report.json'.
|
|
49
|
+
outputDir: 'custom-directory', // Optional: Output directory path. Defaults to '.' (project root).
|
|
50
|
+
minimal: true, // Optional: Generate a minimal report. Defaults to 'false'. Overrides screenshot and testType when set to true
|
|
51
|
+
screenshot: false, // Optional: Include screenshots in the report. Defaults to 'false'.
|
|
52
|
+
testType: 'e2e', // Optional: Specify the test type (e.g., 'api', 'e2e'). Defaults to 'e2e'.
|
|
53
|
+
appName: 'MyApp', // Optional: Specify the name of the application under test.
|
|
54
|
+
appVersion: '1.0.0', // Optional: Specify the version of the application under test.
|
|
55
|
+
osPlatform: 'linux', // Optional: Specify the OS platform.
|
|
56
|
+
osRelease: '18.04', // Optional: Specify the OS release version.
|
|
57
|
+
osVersion: '5.4.0', // Optional: Specify the OS version.
|
|
58
|
+
buildName: 'MyApp Build', // Optional: Specify the build name.
|
|
59
|
+
buildNumber: '100', // Optional: Specify the build number.
|
|
60
|
+
}]
|
|
61
|
+
],
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
A comprehensive report is generated by default, with the exception of screenshots, which you must explicitly set to true.
|
|
65
|
+
|
|
66
|
+
## Test Object Properties
|
|
67
|
+
|
|
68
|
+
The test object in the report includes the following [CTRF properties](https://ctrf.io/docs/schema/test):
|
|
69
|
+
|
|
70
|
+
| Name | Type | Required | Details |
|
|
71
|
+
| ------------ | ---------------- | -------- | ----------------------------------------------------------------------------------- |
|
|
72
|
+
| `name` | String | Required | The name of the test. |
|
|
73
|
+
| `status` | String | Required | The outcome of the test. One of: `passed`, `failed`, `skipped`, `pending`, `other`. |
|
|
74
|
+
| `duration` | Number | Required | The time taken for the test execution, in milliseconds. |
|
|
75
|
+
| `start` | Number | Optional | The start time of the test as a Unix epoch timestamp. |
|
|
76
|
+
| `stop` | Number | Optional | The end time of the test as a Unix epoch timestamp. |
|
|
77
|
+
| `suite` | String | Optional | The suite or group to which the test belongs. |
|
|
78
|
+
| `message` | String | Optional | The failure message if the test failed. |
|
|
79
|
+
| `trace` | String | Optional | The stack trace captured if the test failed. |
|
|
80
|
+
| `rawStatus` | String | Optional | The original playwright status of the test before mapping to CTRF status. |
|
|
81
|
+
| `tags` | Array of Strings | Optional | The tags retrieved from the test name |
|
|
82
|
+
| `type` | String | Optional | The type of test (e.g., `api`, `e2e`). |
|
|
83
|
+
| `filepath` | String | Optional | The file path where the test is located in the project. |
|
|
84
|
+
| `retry` | Number | Optional | The number of retries attempted for the test. |
|
|
85
|
+
| `flake` | Boolean | Optional | Indicates whether the test result is flaky. |
|
|
86
|
+
| `attempts` | Array of Test | Optional | Previous attempts of the test during this run. |
|
|
87
|
+
| `browser` | String | Optional | The browser used for the test. |
|
|
88
|
+
| `screenshot` | String | Optional | A base64 encoded screenshot taken during the test. |
|
|
89
|
+
|
|
90
|
+
|
|
|
91
|
+
|
|
92
|
+
## Advanced usage
|
|
93
|
+
|
|
94
|
+
Some features require additional setup or usage considerations.
|
|
95
|
+
|
|
96
|
+
### Screenshots
|
|
97
|
+
|
|
98
|
+
You can include base-64 screenshots in your test report, you'll need to capture and attach screenshots in your Playwright tests:
|
|
99
|
+
|
|
100
|
+
```javascript
|
|
101
|
+
import { test, expect } from '@playwright/test'
|
|
102
|
+
|
|
103
|
+
test('basic test', async ({ page }, testInfo) => {
|
|
104
|
+
await page.goto('https://playwright.dev')
|
|
105
|
+
const screenshot = await page.screenshot({ quality: 50, type: 'jpeg' })
|
|
106
|
+
await testInfo.attach('screenshot', {
|
|
107
|
+
body: screenshot,
|
|
108
|
+
contentType: 'image/jpeg',
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
#### Supported Formats
|
|
114
|
+
|
|
115
|
+
Both JPEG and PNG formats are supported, only the last screenshot attached from each test will be included in the report.
|
|
116
|
+
|
|
117
|
+
#### Size Considerations
|
|
118
|
+
|
|
119
|
+
Base64-encoded image data can greatly increase the size of your report, it's recommended to use screenshots with a lower quality setting (less than 50%) to reduce file size, particularly if you are generating JPEG images.
|
|
120
|
+
|
|
121
|
+
### Browser
|
|
122
|
+
|
|
123
|
+
You can include browser information in your test report. You will need to extend Playwright's test object to capture and attach browser metadata for each test:
|
|
124
|
+
|
|
125
|
+
```javascript
|
|
126
|
+
// tests/helpers.ts
|
|
127
|
+
import { test as _test, expect } from '@playwright/test';
|
|
128
|
+
import os from 'os';
|
|
129
|
+
|
|
130
|
+
export const test = _test.extend<{ _autoAttachMetadata: void }>({
|
|
131
|
+
_autoAttachMetadata: [async ({ browser, browserName }, use, testInfo) => {
|
|
132
|
+
// BEFORE: Generate an attachment for the test with the required info
|
|
133
|
+
await testInfo.attach('metadata.json', {
|
|
134
|
+
body: JSON.stringify({
|
|
135
|
+
name: browserName,
|
|
136
|
+
version: browser.version(),
|
|
137
|
+
})
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
// ---------------------------------------------------------
|
|
141
|
+
await use(/** our test doesn't need this fixture direcly */);
|
|
142
|
+
// ---------------------------------------------------------
|
|
143
|
+
|
|
144
|
+
// AFTER: There's nothing to cleanup in this fixutre
|
|
145
|
+
}, { auto: true }],
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
export { expect };
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Replace the standard Playwright test import with the custom test fixture in your test files:
|
|
152
|
+
|
|
153
|
+
```javascript
|
|
154
|
+
// tests/my-test.spec.ts
|
|
155
|
+
import { test, expect } from './helpers' // Adjust the path as necessary
|
|
156
|
+
|
|
157
|
+
test('example test', async ({ page }) => {
|
|
158
|
+
// ... your test logic ...
|
|
159
|
+
})
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
The browser metadata file must be called metadata.json and contain properties name and version in the body.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { type Suite, type Reporter, type TestCase, type TestResult, type FullConfig } from '@playwright/test/reporter';
|
|
2
|
+
import { type CtrfTestState, type CtrfReport, type CtrfTest, type CtrfEnvironment } from '../types/ctrf';
|
|
3
|
+
interface ReporterConfigOptions {
|
|
4
|
+
outputFile?: string;
|
|
5
|
+
outputDir?: string;
|
|
6
|
+
minimal?: boolean;
|
|
7
|
+
screenshot?: boolean;
|
|
8
|
+
testType?: string;
|
|
9
|
+
appName?: string | undefined;
|
|
10
|
+
appVersion?: string | undefined;
|
|
11
|
+
osPlatform?: string | undefined;
|
|
12
|
+
osRelease?: string | undefined;
|
|
13
|
+
osVersion?: string | undefined;
|
|
14
|
+
buildName?: string | undefined;
|
|
15
|
+
buildNumber?: string | undefined;
|
|
16
|
+
}
|
|
17
|
+
declare class GenerateCtrfReport implements Reporter {
|
|
18
|
+
readonly ctrfReport: CtrfReport;
|
|
19
|
+
readonly ctrfEnvironment: CtrfEnvironment;
|
|
20
|
+
readonly reporterConfigOptions: ReporterConfigOptions;
|
|
21
|
+
readonly reporterName = "playwright-ctrf-json-reporter";
|
|
22
|
+
readonly defaultOutputFile = "ctrf-report.json";
|
|
23
|
+
readonly defaultOutputDir = ".";
|
|
24
|
+
private suite;
|
|
25
|
+
private startTime;
|
|
26
|
+
constructor(config?: Partial<ReporterConfigOptions>);
|
|
27
|
+
onBegin(_config: FullConfig, suite: Suite): void;
|
|
28
|
+
onEnd(): void;
|
|
29
|
+
processSuite(suite: Suite): void;
|
|
30
|
+
processTest(testCase: TestCase): void;
|
|
31
|
+
setFilename(filename: string): void;
|
|
32
|
+
updateCtrfTestResultsFromTestResult(testCase: TestCase, testResult: TestResult, ctrfReport: CtrfReport): void;
|
|
33
|
+
updateSummaryFromTestResult(testResult: TestResult, ctrfReport: CtrfReport): void;
|
|
34
|
+
mapPlaywrightStatusToCtrf(testStatus: string): CtrfTestState;
|
|
35
|
+
setEnvironmentDetails(reporterConfigOptions: ReporterConfigOptions): void;
|
|
36
|
+
hasEnvironmentDetails(environment: CtrfEnvironment): boolean;
|
|
37
|
+
extractMetadata(testResult: TestResult): any;
|
|
38
|
+
updateStart(startTime: Date): number;
|
|
39
|
+
calculateStopTime(startTime: Date, duration: number): number;
|
|
40
|
+
buildSuitePath(test: TestCase): string;
|
|
41
|
+
extractTagsFromTitle(title: string): string[];
|
|
42
|
+
extractScreenshotBase64(testResult: TestResult): string | undefined;
|
|
43
|
+
extractFailureDetails(testResult: TestResult): Partial<CtrfTest>;
|
|
44
|
+
countSuites(suite: Suite): number;
|
|
45
|
+
writeReportToFile(data: CtrfReport): void;
|
|
46
|
+
}
|
|
47
|
+
export default GenerateCtrfReport;
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const path_1 = __importDefault(require("path"));
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
class GenerateCtrfReport {
|
|
9
|
+
constructor(config) {
|
|
10
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
11
|
+
this.reporterName = 'playwright-ctrf-json-reporter';
|
|
12
|
+
this.defaultOutputFile = 'ctrf-report.json';
|
|
13
|
+
this.defaultOutputDir = '.';
|
|
14
|
+
this.reporterConfigOptions = {
|
|
15
|
+
outputFile: (_a = config === null || config === void 0 ? void 0 : config.outputFile) !== null && _a !== void 0 ? _a : this.defaultOutputFile,
|
|
16
|
+
outputDir: (_b = config === null || config === void 0 ? void 0 : config.outputDir) !== null && _b !== void 0 ? _b : this.defaultOutputDir,
|
|
17
|
+
minimal: (_c = config === null || config === void 0 ? void 0 : config.minimal) !== null && _c !== void 0 ? _c : false,
|
|
18
|
+
screenshot: (_d = config === null || config === void 0 ? void 0 : config.screenshot) !== null && _d !== void 0 ? _d : false,
|
|
19
|
+
testType: (_e = config === null || config === void 0 ? void 0 : config.testType) !== null && _e !== void 0 ? _e : 'e2e',
|
|
20
|
+
appName: (_f = config === null || config === void 0 ? void 0 : config.appName) !== null && _f !== void 0 ? _f : undefined,
|
|
21
|
+
appVersion: (_g = config === null || config === void 0 ? void 0 : config.appVersion) !== null && _g !== void 0 ? _g : undefined,
|
|
22
|
+
osPlatform: (_h = config === null || config === void 0 ? void 0 : config.osPlatform) !== null && _h !== void 0 ? _h : undefined,
|
|
23
|
+
osRelease: (_j = config === null || config === void 0 ? void 0 : config.osRelease) !== null && _j !== void 0 ? _j : undefined,
|
|
24
|
+
osVersion: (_k = config === null || config === void 0 ? void 0 : config.osVersion) !== null && _k !== void 0 ? _k : undefined,
|
|
25
|
+
buildName: (_l = config === null || config === void 0 ? void 0 : config.buildName) !== null && _l !== void 0 ? _l : undefined,
|
|
26
|
+
buildNumber: (_m = config === null || config === void 0 ? void 0 : config.buildNumber) !== null && _m !== void 0 ? _m : undefined,
|
|
27
|
+
};
|
|
28
|
+
this.ctrfReport = {
|
|
29
|
+
results: {
|
|
30
|
+
tool: {
|
|
31
|
+
name: 'playwright',
|
|
32
|
+
},
|
|
33
|
+
summary: {
|
|
34
|
+
tests: 0,
|
|
35
|
+
passed: 0,
|
|
36
|
+
failed: 0,
|
|
37
|
+
pending: 0,
|
|
38
|
+
skipped: 0,
|
|
39
|
+
other: 0,
|
|
40
|
+
start: 0,
|
|
41
|
+
stop: 0,
|
|
42
|
+
},
|
|
43
|
+
tests: [],
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
this.ctrfEnvironment = {};
|
|
47
|
+
}
|
|
48
|
+
onBegin(_config, suite) {
|
|
49
|
+
var _a, _b, _c;
|
|
50
|
+
this.suite = suite;
|
|
51
|
+
this.startTime = Date.now();
|
|
52
|
+
this.ctrfReport.results.summary.start = this.startTime;
|
|
53
|
+
if (!fs_1.default.existsSync((_a = this.reporterConfigOptions.outputDir) !== null && _a !== void 0 ? _a : this.defaultOutputDir)) {
|
|
54
|
+
fs_1.default.mkdirSync((_b = this.reporterConfigOptions.outputDir) !== null && _b !== void 0 ? _b : this.defaultOutputDir, { recursive: true });
|
|
55
|
+
}
|
|
56
|
+
this.setEnvironmentDetails(this.reporterConfigOptions);
|
|
57
|
+
if (this.hasEnvironmentDetails(this.ctrfEnvironment)) {
|
|
58
|
+
this.ctrfReport.results.environment = this.ctrfEnvironment;
|
|
59
|
+
}
|
|
60
|
+
this.setFilename((_c = this.reporterConfigOptions.outputFile) !== null && _c !== void 0 ? _c : this.defaultOutputFile);
|
|
61
|
+
}
|
|
62
|
+
onEnd() {
|
|
63
|
+
this.ctrfReport.results.summary.stop = Date.now();
|
|
64
|
+
if (this.suite !== undefined) {
|
|
65
|
+
this.processSuite(this.suite);
|
|
66
|
+
this.ctrfReport.results.summary.suites = this.countSuites(this.suite);
|
|
67
|
+
}
|
|
68
|
+
this.writeReportToFile(this.ctrfReport);
|
|
69
|
+
}
|
|
70
|
+
processSuite(suite) {
|
|
71
|
+
for (const test of suite.tests) {
|
|
72
|
+
this.processTest(test);
|
|
73
|
+
}
|
|
74
|
+
for (const childSuite of suite.suites) {
|
|
75
|
+
this.processSuite(childSuite);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
processTest(testCase) {
|
|
79
|
+
const latestResult = testCase.results[testCase.results.length - 1];
|
|
80
|
+
this.updateCtrfTestResultsFromTestResult(testCase, latestResult, this.ctrfReport);
|
|
81
|
+
this.updateSummaryFromTestResult(latestResult, this.ctrfReport);
|
|
82
|
+
}
|
|
83
|
+
setFilename(filename) {
|
|
84
|
+
if (filename.endsWith('.json')) {
|
|
85
|
+
this.reporterConfigOptions.outputFile = filename;
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
this.reporterConfigOptions.outputFile = `${filename}.json`;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
updateCtrfTestResultsFromTestResult(testCase, testResult, ctrfReport) {
|
|
92
|
+
var _a, _b, _c, _d, _e;
|
|
93
|
+
const test = {
|
|
94
|
+
name: testCase.title,
|
|
95
|
+
status: this.mapPlaywrightStatusToCtrf(testResult.status),
|
|
96
|
+
duration: testResult.duration,
|
|
97
|
+
};
|
|
98
|
+
if (this.reporterConfigOptions.minimal === false) {
|
|
99
|
+
test.start = this.updateStart(testResult.startTime);
|
|
100
|
+
test.stop = this.calculateStopTime(testResult.startTime, testResult.duration);
|
|
101
|
+
test.message = this.extractFailureDetails(testResult).message;
|
|
102
|
+
test.trace = this.extractFailureDetails(testResult).trace;
|
|
103
|
+
test.rawStatus = testResult.status;
|
|
104
|
+
test.tags = this.extractTagsFromTitle(testCase.title);
|
|
105
|
+
test.type = (_a = this.reporterConfigOptions.testType) !== null && _a !== void 0 ? _a : 'e2e';
|
|
106
|
+
test.filePath = testCase.location.file;
|
|
107
|
+
test.retry = testResult.retry;
|
|
108
|
+
test.flake = testResult.status === 'passed' && testResult.retry > 0;
|
|
109
|
+
if (this.reporterConfigOptions.screenshot === true) {
|
|
110
|
+
test.screenshot = this.extractScreenshotBase64(testResult);
|
|
111
|
+
}
|
|
112
|
+
test.suite = this.buildSuitePath(testCase);
|
|
113
|
+
if (((_b = this.extractMetadata(testResult)) === null || _b === void 0 ? void 0 : _b.name) !== undefined ||
|
|
114
|
+
((_c = this.extractMetadata(testResult)) === null || _c === void 0 ? void 0 : _c.version) !== undefined)
|
|
115
|
+
test.browser = `${(_d = this.extractMetadata(testResult)) === null || _d === void 0 ? void 0 : _d.name} ${(_e = this.extractMetadata(testResult)) === null || _e === void 0 ? void 0 : _e.version}`;
|
|
116
|
+
}
|
|
117
|
+
ctrfReport.results.tests.push(test);
|
|
118
|
+
}
|
|
119
|
+
updateSummaryFromTestResult(testResult, ctrfReport) {
|
|
120
|
+
ctrfReport.results.summary.tests++;
|
|
121
|
+
const ctrfStatus = this.mapPlaywrightStatusToCtrf(testResult.status);
|
|
122
|
+
if (ctrfStatus in ctrfReport.results.summary) {
|
|
123
|
+
ctrfReport.results.summary[ctrfStatus]++;
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
ctrfReport.results.summary.other++;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
mapPlaywrightStatusToCtrf(testStatus) {
|
|
130
|
+
switch (testStatus) {
|
|
131
|
+
case 'passed':
|
|
132
|
+
return 'passed';
|
|
133
|
+
case 'failed':
|
|
134
|
+
case 'timedOut':
|
|
135
|
+
case 'interrupted':
|
|
136
|
+
return 'failed';
|
|
137
|
+
case 'skipped':
|
|
138
|
+
return 'skipped';
|
|
139
|
+
case 'pending':
|
|
140
|
+
return 'pending';
|
|
141
|
+
default:
|
|
142
|
+
return 'other';
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
setEnvironmentDetails(reporterConfigOptions) {
|
|
146
|
+
if (reporterConfigOptions.appName !== undefined) {
|
|
147
|
+
this.ctrfEnvironment.appName = reporterConfigOptions.appName;
|
|
148
|
+
}
|
|
149
|
+
if (reporterConfigOptions.appVersion !== undefined) {
|
|
150
|
+
this.ctrfEnvironment.appVersion = reporterConfigOptions.appVersion;
|
|
151
|
+
}
|
|
152
|
+
if (reporterConfigOptions.osPlatform !== undefined) {
|
|
153
|
+
this.ctrfEnvironment.osPlatform = reporterConfigOptions.osPlatform;
|
|
154
|
+
}
|
|
155
|
+
if (reporterConfigOptions.osRelease !== undefined) {
|
|
156
|
+
this.ctrfEnvironment.osRelease = reporterConfigOptions.osRelease;
|
|
157
|
+
}
|
|
158
|
+
if (reporterConfigOptions.osVersion !== undefined) {
|
|
159
|
+
this.ctrfEnvironment.osVersion = reporterConfigOptions.osVersion;
|
|
160
|
+
}
|
|
161
|
+
if (reporterConfigOptions.buildName !== undefined) {
|
|
162
|
+
this.ctrfEnvironment.buildName = reporterConfigOptions.buildName;
|
|
163
|
+
}
|
|
164
|
+
if (reporterConfigOptions.buildNumber !== undefined) {
|
|
165
|
+
this.ctrfEnvironment.buildNumber = reporterConfigOptions.buildNumber;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
hasEnvironmentDetails(environment) {
|
|
169
|
+
return Object.keys(environment).length > 0;
|
|
170
|
+
}
|
|
171
|
+
extractMetadata(testResult) {
|
|
172
|
+
const metadataAttachment = testResult.attachments.find((attachment) => attachment.name === 'metadata.json');
|
|
173
|
+
if ((metadataAttachment === null || metadataAttachment === void 0 ? void 0 : metadataAttachment.body) !== null &&
|
|
174
|
+
(metadataAttachment === null || metadataAttachment === void 0 ? void 0 : metadataAttachment.body) !== undefined) {
|
|
175
|
+
try {
|
|
176
|
+
const metadataRaw = metadataAttachment.body.toString('utf-8');
|
|
177
|
+
return JSON.parse(metadataRaw);
|
|
178
|
+
}
|
|
179
|
+
catch (e) {
|
|
180
|
+
if (e instanceof Error) {
|
|
181
|
+
console.error(`Error parsing browser metadata: ${e.message}`);
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
console.error('An unknown error occurred in parsing browser metadata');
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
updateStart(startTime) {
|
|
191
|
+
const date = new Date(startTime);
|
|
192
|
+
const unixEpochTime = Math.floor(date.getTime() / 1000);
|
|
193
|
+
return unixEpochTime;
|
|
194
|
+
}
|
|
195
|
+
calculateStopTime(startTime, duration) {
|
|
196
|
+
const startDate = new Date(startTime);
|
|
197
|
+
const stopDate = new Date(startDate.getTime() + duration);
|
|
198
|
+
return Math.floor(stopDate.getTime() / 1000);
|
|
199
|
+
}
|
|
200
|
+
buildSuitePath(test) {
|
|
201
|
+
const pathComponents = [];
|
|
202
|
+
let currentSuite = test.parent;
|
|
203
|
+
while (currentSuite !== undefined) {
|
|
204
|
+
if (currentSuite.title !== '') {
|
|
205
|
+
pathComponents.unshift(currentSuite.title);
|
|
206
|
+
}
|
|
207
|
+
currentSuite = currentSuite.parent;
|
|
208
|
+
}
|
|
209
|
+
return pathComponents.join(' > ');
|
|
210
|
+
}
|
|
211
|
+
extractTagsFromTitle(title) {
|
|
212
|
+
const tagPattern = /@\w+/g;
|
|
213
|
+
const tags = title.match(tagPattern);
|
|
214
|
+
return tags !== null && tags !== void 0 ? tags : [];
|
|
215
|
+
}
|
|
216
|
+
extractScreenshotBase64(testResult) {
|
|
217
|
+
var _a;
|
|
218
|
+
const screenshotAttachment = testResult.attachments.find((attachment) => attachment.name === 'screenshot' &&
|
|
219
|
+
(attachment.contentType === 'image/jpeg' ||
|
|
220
|
+
attachment.contentType === 'image/png'));
|
|
221
|
+
return (_a = screenshotAttachment === null || screenshotAttachment === void 0 ? void 0 : screenshotAttachment.body) === null || _a === void 0 ? void 0 : _a.toString('base64');
|
|
222
|
+
}
|
|
223
|
+
extractFailureDetails(testResult) {
|
|
224
|
+
if (testResult.status === 'failed' && testResult.error !== undefined) {
|
|
225
|
+
const failureDetails = {};
|
|
226
|
+
if (testResult.error.message !== undefined) {
|
|
227
|
+
failureDetails.message = testResult.error.message;
|
|
228
|
+
}
|
|
229
|
+
if (testResult.error.stack !== undefined) {
|
|
230
|
+
failureDetails.trace = testResult.error.stack;
|
|
231
|
+
}
|
|
232
|
+
return failureDetails;
|
|
233
|
+
}
|
|
234
|
+
return {};
|
|
235
|
+
}
|
|
236
|
+
countSuites(suite) {
|
|
237
|
+
let count = 0;
|
|
238
|
+
suite.suites.forEach((childSuite) => {
|
|
239
|
+
count += this.countSuites(childSuite);
|
|
240
|
+
});
|
|
241
|
+
return count;
|
|
242
|
+
}
|
|
243
|
+
writeReportToFile(data) {
|
|
244
|
+
var _a, _b;
|
|
245
|
+
const filePath = path_1.default.join((_a = this.reporterConfigOptions.outputDir) !== null && _a !== void 0 ? _a : this.defaultOutputDir, (_b = this.reporterConfigOptions.outputFile) !== null && _b !== void 0 ? _b : this.defaultOutputFile);
|
|
246
|
+
const str = JSON.stringify(data, null, 2);
|
|
247
|
+
try {
|
|
248
|
+
fs_1.default.writeFileSync(filePath, str + '\n');
|
|
249
|
+
console.log(`${this.reporterName}: successfully written ctrf json to %s`, this.reporterConfigOptions.outputFile);
|
|
250
|
+
}
|
|
251
|
+
catch (error) {
|
|
252
|
+
console.error(`Error writing ctrf json report:, ${String(error)}`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
exports.default = GenerateCtrfReport;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { FullResult, Reporter, TestCase, TestResult } from "@playwright/test/reporter";
|
|
2
|
+
declare class MyReporter implements Reporter {
|
|
3
|
+
private ctrfReport;
|
|
4
|
+
constructor();
|
|
5
|
+
onTestEnd(test: TestCase, result: TestResult): void;
|
|
6
|
+
onEnd(result: FullResult): void;
|
|
7
|
+
private updateCtrfResultsFromAfterSpecResults;
|
|
8
|
+
private writeToFile;
|
|
9
|
+
}
|
|
10
|
+
export default MyReporter;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
class MyReporter {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.ctrfReport = {
|
|
7
|
+
results: {
|
|
8
|
+
tool: {
|
|
9
|
+
name: "playwright",
|
|
10
|
+
},
|
|
11
|
+
totals: {
|
|
12
|
+
suites: 0,
|
|
13
|
+
tests: 0,
|
|
14
|
+
passed: 0,
|
|
15
|
+
failed: 0,
|
|
16
|
+
pending: 0,
|
|
17
|
+
skipped: 0,
|
|
18
|
+
timedOut: 0,
|
|
19
|
+
interrupted: 0,
|
|
20
|
+
other: 0,
|
|
21
|
+
},
|
|
22
|
+
tests: [],
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
// onBegin(config: FullConfig, suite: Suite) {
|
|
27
|
+
// console.log(
|
|
28
|
+
// `Starting the run with my custom reporter ${
|
|
29
|
+
// suite.allTests().length
|
|
30
|
+
// } tests`
|
|
31
|
+
// );
|
|
32
|
+
// }
|
|
33
|
+
// onTestBegin(test: TestCase, result: TestResult) {
|
|
34
|
+
// console.log(`Starting test ${test.title}`);
|
|
35
|
+
// }
|
|
36
|
+
onTestEnd(test, result) {
|
|
37
|
+
this.updateCtrfResultsFromAfterSpecResults(test, result, this.ctrfReport);
|
|
38
|
+
// console.log(`Finished test ${test.title}: ${result.status}`);
|
|
39
|
+
}
|
|
40
|
+
onEnd(result) {
|
|
41
|
+
this.writeToFile("playwright-ctrf-json-report.json", this.ctrfReport);
|
|
42
|
+
// console.log(`Finished the run: ${result.status}`);
|
|
43
|
+
}
|
|
44
|
+
updateCtrfResultsFromAfterSpecResults(testCase, testResult, ctrfReport) {
|
|
45
|
+
ctrfReport.results.tests.push({
|
|
46
|
+
name: testCase.title,
|
|
47
|
+
status: testResult.status,
|
|
48
|
+
duration: testResult.duration,
|
|
49
|
+
});
|
|
50
|
+
ctrfReport.results.totals.tests++;
|
|
51
|
+
switch (testResult.status) {
|
|
52
|
+
case "passed":
|
|
53
|
+
ctrfReport.results.totals.passed++;
|
|
54
|
+
break;
|
|
55
|
+
case "failed":
|
|
56
|
+
ctrfReport.results.totals.failed++;
|
|
57
|
+
break;
|
|
58
|
+
case "skipped":
|
|
59
|
+
ctrfReport.results.totals.skipped++;
|
|
60
|
+
break;
|
|
61
|
+
case "interrupted":
|
|
62
|
+
ctrfReport.results.totals.interrupted++;
|
|
63
|
+
break;
|
|
64
|
+
case "timedOut":
|
|
65
|
+
ctrfReport.results.totals.timedOut++;
|
|
66
|
+
break;
|
|
67
|
+
default:
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
writeToFile(filename, data) {
|
|
72
|
+
const str = JSON.stringify(data, null, 2);
|
|
73
|
+
try {
|
|
74
|
+
fs.writeFileSync(filename, str + "\n");
|
|
75
|
+
console.log("playwright-ctrf-json-report: successfully written ctrf json report to %s", filename);
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
console.error("Error writing ctrf json report:", error);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
exports.default = MyReporter;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './generate-report';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
var generate_report_1 = require("./generate-report");
|
|
8
|
+
Object.defineProperty(exports, "default", { enumerable: true, get: function () { return __importDefault(generate_report_1).default; } });
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "playwright-ctrf-json-reporter",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "tsc",
|
|
8
|
+
"test": "jest",
|
|
9
|
+
"lint": "eslint . --ext .ts --fix",
|
|
10
|
+
"lint-check": "eslint . --ext .ts",
|
|
11
|
+
"format": "prettier --write .",
|
|
12
|
+
"format-check": "prettier --check ."
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist/",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"author": "",
|
|
19
|
+
"license": "ISC",
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@playwright/test": "^1.39.0",
|
|
22
|
+
"@types/jest": "^29.5.6",
|
|
23
|
+
"@types/node": "^20.8.7",
|
|
24
|
+
"@typescript-eslint/eslint-plugin": "^6.12.0",
|
|
25
|
+
"eslint": "^8.54.0",
|
|
26
|
+
"eslint-config-prettier": "^9.0.0",
|
|
27
|
+
"eslint-config-standard-with-typescript": "^40.0.0",
|
|
28
|
+
"eslint-plugin-import": "^2.29.0",
|
|
29
|
+
"eslint-plugin-n": "^16.3.1",
|
|
30
|
+
"eslint-plugin-prettier": "^5.0.1",
|
|
31
|
+
"eslint-plugin-promise": "^6.1.1",
|
|
32
|
+
"jest": "^29.7.0",
|
|
33
|
+
"prettier": "^3.1.0",
|
|
34
|
+
"ts-jest": "^29.1.1",
|
|
35
|
+
"typescript": "^5.3.2"
|
|
36
|
+
}
|
|
37
|
+
}
|