playwright-ctrf-json-reporter 0.0.23 → 0.0.25-next-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 +4 -3
- package/dist/autoconfig.d.ts +5 -0
- package/dist/autoconfig.js +19 -0
- package/dist/ctrf-runtime/facade.d.ts +14 -0
- package/dist/ctrf-runtime/facade.js +28 -0
- package/dist/ctrf-runtime/runtime.d.ts +25 -0
- package/dist/ctrf-runtime/runtime.js +53 -0
- package/dist/generate-report.d.ts +2 -1
- package/dist/generate-report.js +77 -69
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -8
- package/dist/playwright-runtime.d.ts +32 -0
- package/dist/playwright-runtime.js +82 -0
- package/dist/runtime.d.ts +28 -0
- package/dist/runtime.js +165 -0
- package/package.json +38 -20
package/README.md
CHANGED
|
@@ -21,11 +21,12 @@ A Playwright JSON test reporter to create test reports that follow the CTRF stan
|
|
|
21
21
|
</a>
|
|
22
22
|
</div>
|
|
23
23
|
</div>
|
|
24
|
-
|
|
25
24
|
<p style="font-size: 14px; margin: 1rem 0;">
|
|
26
|
-
|
|
25
|
+
|
|
27
26
|
Contributions are very welcome! <br/>
|
|
28
|
-
Explore more <a href="https://www.ctrf.io/integrations">integrations</a>
|
|
27
|
+
Explore more <a href="https://www.ctrf.io/integrations">integrations</a> <br/>
|
|
28
|
+
We’d love your feedback, share it anonymously <a href="https://app.formbricks.com/s/cmefs524mhlh1tl01gkpvefrb">here</a>.
|
|
29
|
+
|
|
29
30
|
</p>
|
|
30
31
|
</div>
|
|
31
32
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-configuration for Playwright CTRF runtime
|
|
3
|
+
* This file automatically sets up the Playwright runtime when imported
|
|
4
|
+
*/
|
|
5
|
+
import { test } from '@playwright/test';
|
|
6
|
+
import { setGlobalCtrfRuntime } from 'ctrf';
|
|
7
|
+
import { CtrfPlaywrightRuntime } from './playwright-runtime';
|
|
8
|
+
/**
|
|
9
|
+
* Check if we're in a Playwright test context and auto-register the runtime
|
|
10
|
+
*/
|
|
11
|
+
try {
|
|
12
|
+
// This will throw if not in a test context
|
|
13
|
+
test.info();
|
|
14
|
+
// If we get here, we're in a test context
|
|
15
|
+
setGlobalCtrfRuntime(new CtrfPlaywrightRuntime());
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
// Not in test context yet - the runtime will be set up when tests start running
|
|
19
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CTRF framework-agnostic facade functions
|
|
3
|
+
* These functions call into the global runtime that's set by framework-specific packages
|
|
4
|
+
* Simplified API focusing on test.extra enrichment
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Test-related CTRF functions
|
|
8
|
+
*/
|
|
9
|
+
export declare const test: {
|
|
10
|
+
/**
|
|
11
|
+
* Add extra metadata to a test
|
|
12
|
+
*/
|
|
13
|
+
addExtra: (key: string, value: unknown) => Promise<void>;
|
|
14
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CTRF framework-agnostic facade functions
|
|
3
|
+
* These functions call into the global runtime that's set by framework-specific packages
|
|
4
|
+
* Simplified API focusing on test.extra enrichment
|
|
5
|
+
*/
|
|
6
|
+
import { getGlobalCtrfRuntimeWithAutoconfig } from './runtime';
|
|
7
|
+
const callRuntimeMethod = (method, ...args) => {
|
|
8
|
+
const runtime = getGlobalCtrfRuntimeWithAutoconfig();
|
|
9
|
+
if (runtime && typeof runtime.then !== 'function') {
|
|
10
|
+
// @ts-ignore
|
|
11
|
+
return runtime[method](...args);
|
|
12
|
+
}
|
|
13
|
+
return runtime.then((ctrfRuntime) => {
|
|
14
|
+
// @ts-ignore
|
|
15
|
+
return ctrfRuntime[method](...args);
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Test-related CTRF functions
|
|
20
|
+
*/
|
|
21
|
+
export const test = {
|
|
22
|
+
/**
|
|
23
|
+
* Add extra metadata to a test
|
|
24
|
+
*/
|
|
25
|
+
addExtra: (key, value) => {
|
|
26
|
+
return callRuntimeMethod('addExtra', key, value);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CTRF runtime system (framework-agnostic)
|
|
3
|
+
* Similar to Allure's global runtime pattern
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Interface that framework-specific runtimes must implement
|
|
7
|
+
*/
|
|
8
|
+
export interface CtrfRuntime {
|
|
9
|
+
addExtra(key: string, value: unknown): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Set the global CTRF runtime (called by framework-specific packages)
|
|
13
|
+
*/
|
|
14
|
+
export declare const setGlobalCtrfRuntime: (runtime: CtrfRuntime) => void;
|
|
15
|
+
/**
|
|
16
|
+
* Get the current global CTRF runtime
|
|
17
|
+
*/
|
|
18
|
+
export declare const getGlobalCtrfRuntime: () => CtrfRuntime;
|
|
19
|
+
/**
|
|
20
|
+
* Get the global CTRF runtime with auto-configuration attempt
|
|
21
|
+
* This tries to auto-detect and configure framework-specific runtimes
|
|
22
|
+
* Copied from Allure's pattern exactly
|
|
23
|
+
*/
|
|
24
|
+
export declare const getGlobalCtrfRuntimeWithAutoconfig: () => CtrfRuntime | Promise<CtrfRuntime>;
|
|
25
|
+
export { test } from './facade';
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CTRF runtime system (framework-agnostic)
|
|
3
|
+
* Similar to Allure's global runtime pattern
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* No-op runtime that logs warnings when no framework runtime is available
|
|
7
|
+
*/
|
|
8
|
+
class NoopCtrfRuntime {
|
|
9
|
+
warn(method) {
|
|
10
|
+
console.warn(`CTRF: ${method} called but no framework runtime is available. Make sure you have configured a CTRF reporter for your test framework.`);
|
|
11
|
+
}
|
|
12
|
+
async addExtra() {
|
|
13
|
+
this.warn('addExtra');
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
const CTRF_RUNTIME_KEY = 'ctrfRuntime';
|
|
17
|
+
const noopRuntime = new NoopCtrfRuntime();
|
|
18
|
+
/**
|
|
19
|
+
* Set the global CTRF runtime (called by framework-specific packages)
|
|
20
|
+
*/
|
|
21
|
+
export const setGlobalCtrfRuntime = (runtime) => {
|
|
22
|
+
;
|
|
23
|
+
globalThis[CTRF_RUNTIME_KEY] = runtime;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Get the current global CTRF runtime
|
|
27
|
+
*/
|
|
28
|
+
export const getGlobalCtrfRuntime = () => {
|
|
29
|
+
const runtime = globalThis?.[CTRF_RUNTIME_KEY];
|
|
30
|
+
return runtime ?? noopRuntime;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Get the global CTRF runtime with auto-configuration attempt
|
|
34
|
+
* This tries to auto-detect and configure framework-specific runtimes
|
|
35
|
+
* Copied from Allure's pattern exactly
|
|
36
|
+
*/
|
|
37
|
+
export const getGlobalCtrfRuntimeWithAutoconfig = () => {
|
|
38
|
+
const runtime = getGlobalCtrfRuntime();
|
|
39
|
+
if (runtime !== noopRuntime) {
|
|
40
|
+
return runtime;
|
|
41
|
+
}
|
|
42
|
+
// protection from bundlers tree-shaking visiting (webpack, rollup)
|
|
43
|
+
const pwAutoconfigModuleName = 'playwright-ctrf-json-reporter/autoconfig';
|
|
44
|
+
return import(pwAutoconfigModuleName)
|
|
45
|
+
.then(() => {
|
|
46
|
+
return getGlobalCtrfRuntime();
|
|
47
|
+
})
|
|
48
|
+
.catch(() => {
|
|
49
|
+
return noopRuntime;
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
// Export facade functions (framework-agnostic API)
|
|
53
|
+
export { test } from './facade';
|
|
@@ -29,9 +29,11 @@ declare class GenerateCtrfReport implements Reporter {
|
|
|
29
29
|
readonly defaultOutputDir = "ctrf";
|
|
30
30
|
private suite;
|
|
31
31
|
private startTime;
|
|
32
|
+
private testMetadata;
|
|
32
33
|
constructor(config?: Partial<ReporterConfigOptions>);
|
|
33
34
|
onBegin(_config: FullConfig, suite: Suite): void;
|
|
34
35
|
onEnd(): void;
|
|
36
|
+
onTestEnd(test: TestCase, result: TestResult): void;
|
|
35
37
|
printsToStdio(): boolean;
|
|
36
38
|
processSuite(suite: Suite): void;
|
|
37
39
|
processTest(testCase: TestCase): void;
|
|
@@ -45,7 +47,6 @@ declare class GenerateCtrfReport implements Reporter {
|
|
|
45
47
|
updateStart(startTime: Date): number;
|
|
46
48
|
calculateStopTime(startTime: Date, duration: number): number;
|
|
47
49
|
buildSuitePath(test: TestCase): string;
|
|
48
|
-
extractTagsFromTitle(title: string): string[];
|
|
49
50
|
extractScreenshotBase64(testResult: TestResult): string | undefined;
|
|
50
51
|
extractFailureDetails(testResult: TestResult): Partial<CtrfTest>;
|
|
51
52
|
countSuites(suite: Suite): number;
|
package/dist/generate-report.js
CHANGED
|
@@ -1,41 +1,43 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const path_1 = __importDefault(require("path"));
|
|
7
|
-
const fs_1 = __importDefault(require("fs"));
|
|
8
|
-
const crypto_1 = __importDefault(require("crypto"));
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import crypto from 'crypto';
|
|
4
|
+
import { extractCtrfMetadata, ensurePlaywrightRuntime } from './playwright-runtime';
|
|
9
5
|
class GenerateCtrfReport {
|
|
6
|
+
ctrfReport;
|
|
7
|
+
ctrfEnvironment;
|
|
8
|
+
reporterConfigOptions;
|
|
9
|
+
reporterName = 'playwright-ctrf-json-reporter';
|
|
10
|
+
defaultOutputFile = 'ctrf-report.json';
|
|
11
|
+
defaultOutputDir = 'ctrf';
|
|
12
|
+
suite;
|
|
13
|
+
startTime;
|
|
14
|
+
testMetadata = new Map();
|
|
10
15
|
constructor(config) {
|
|
11
|
-
|
|
12
|
-
this.reporterName = 'playwright-ctrf-json-reporter';
|
|
13
|
-
this.defaultOutputFile = 'ctrf-report.json';
|
|
14
|
-
this.defaultOutputDir = 'ctrf';
|
|
16
|
+
ensurePlaywrightRuntime();
|
|
15
17
|
this.reporterConfigOptions = {
|
|
16
|
-
outputFile:
|
|
17
|
-
outputDir:
|
|
18
|
-
minimal:
|
|
19
|
-
screenshot:
|
|
20
|
-
annotations:
|
|
21
|
-
testType:
|
|
22
|
-
appName:
|
|
23
|
-
appVersion:
|
|
24
|
-
osPlatform:
|
|
25
|
-
osRelease:
|
|
26
|
-
osVersion:
|
|
27
|
-
buildName:
|
|
28
|
-
buildNumber:
|
|
29
|
-
buildUrl:
|
|
30
|
-
repositoryName:
|
|
31
|
-
repositoryUrl:
|
|
32
|
-
branchName:
|
|
33
|
-
testEnvironment:
|
|
18
|
+
outputFile: config?.outputFile ?? this.defaultOutputFile,
|
|
19
|
+
outputDir: config?.outputDir ?? this.defaultOutputDir,
|
|
20
|
+
minimal: config?.minimal ?? false,
|
|
21
|
+
screenshot: config?.screenshot ?? false,
|
|
22
|
+
annotations: config?.annotations ?? false,
|
|
23
|
+
testType: config?.testType ?? 'e2e',
|
|
24
|
+
appName: config?.appName ?? undefined,
|
|
25
|
+
appVersion: config?.appVersion ?? undefined,
|
|
26
|
+
osPlatform: config?.osPlatform ?? undefined,
|
|
27
|
+
osRelease: config?.osRelease ?? undefined,
|
|
28
|
+
osVersion: config?.osVersion ?? undefined,
|
|
29
|
+
buildName: config?.buildName ?? undefined,
|
|
30
|
+
buildNumber: config?.buildNumber ?? undefined,
|
|
31
|
+
buildUrl: config?.buildUrl ?? undefined,
|
|
32
|
+
repositoryName: config?.repositoryName ?? undefined,
|
|
33
|
+
repositoryUrl: config?.repositoryUrl ?? undefined,
|
|
34
|
+
branchName: config?.branchName ?? undefined,
|
|
35
|
+
testEnvironment: config?.testEnvironment ?? undefined,
|
|
34
36
|
};
|
|
35
37
|
this.ctrfReport = {
|
|
36
38
|
reportFormat: 'CTRF',
|
|
37
39
|
specVersion: '0.0.0',
|
|
38
|
-
reportId:
|
|
40
|
+
reportId: crypto.randomUUID(),
|
|
39
41
|
timestamp: new Date().toISOString(),
|
|
40
42
|
generatedBy: 'playwright-ctrf-json-reporter',
|
|
41
43
|
results: {
|
|
@@ -58,18 +60,17 @@ class GenerateCtrfReport {
|
|
|
58
60
|
this.ctrfEnvironment = {};
|
|
59
61
|
}
|
|
60
62
|
onBegin(_config, suite) {
|
|
61
|
-
var _a, _b, _c;
|
|
62
63
|
this.suite = suite;
|
|
63
64
|
this.startTime = Date.now();
|
|
64
65
|
this.ctrfReport.results.summary.start = this.startTime;
|
|
65
|
-
if (!
|
|
66
|
-
|
|
66
|
+
if (!fs.existsSync(this.reporterConfigOptions.outputDir ?? this.defaultOutputDir)) {
|
|
67
|
+
fs.mkdirSync(this.reporterConfigOptions.outputDir ?? this.defaultOutputDir, { recursive: true });
|
|
67
68
|
}
|
|
68
69
|
this.setEnvironmentDetails(this.reporterConfigOptions);
|
|
69
70
|
if (this.hasEnvironmentDetails(this.ctrfEnvironment)) {
|
|
70
71
|
this.ctrfReport.results.environment = this.ctrfEnvironment;
|
|
71
72
|
}
|
|
72
|
-
this.setFilename(
|
|
73
|
+
this.setFilename(this.reporterConfigOptions.outputFile ?? this.defaultOutputFile);
|
|
73
74
|
}
|
|
74
75
|
onEnd() {
|
|
75
76
|
this.ctrfReport.results.summary.stop = Date.now();
|
|
@@ -81,6 +82,11 @@ class GenerateCtrfReport {
|
|
|
81
82
|
}
|
|
82
83
|
this.writeReportToFile(this.ctrfReport);
|
|
83
84
|
}
|
|
85
|
+
onTestEnd(test, result) {
|
|
86
|
+
// Extract CTRF metadata from test attachments
|
|
87
|
+
const metadata = extractCtrfMetadata(result.attachments);
|
|
88
|
+
this.testMetadata.set(test.id, metadata);
|
|
89
|
+
}
|
|
84
90
|
printsToStdio() {
|
|
85
91
|
return false;
|
|
86
92
|
}
|
|
@@ -111,10 +117,13 @@ class GenerateCtrfReport {
|
|
|
111
117
|
}
|
|
112
118
|
}
|
|
113
119
|
updateCtrfTestResultsFromTestResult(testCase, testResult, ctrfReport) {
|
|
114
|
-
|
|
120
|
+
// Get stored metadata for this test
|
|
121
|
+
const metadata = this.testMetadata.get(testCase.id);
|
|
115
122
|
const test = {
|
|
116
|
-
name: testCase.title,
|
|
117
|
-
status:
|
|
123
|
+
name: testCase.title, // Use the original test title
|
|
124
|
+
status: testResult.status === testCase.expectedStatus
|
|
125
|
+
? 'passed'
|
|
126
|
+
: this.mapPlaywrightStatusToCtrf(testResult.status),
|
|
118
127
|
duration: testResult.duration,
|
|
119
128
|
};
|
|
120
129
|
if (this.reporterConfigOptions.minimal === false) {
|
|
@@ -124,8 +133,8 @@ class GenerateCtrfReport {
|
|
|
124
133
|
test.trace = this.extractFailureDetails(testResult).trace;
|
|
125
134
|
test.snippet = this.extractFailureDetails(testResult).snippet;
|
|
126
135
|
test.rawStatus = testResult.status;
|
|
127
|
-
test.tags =
|
|
128
|
-
test.type =
|
|
136
|
+
test.tags = testCase.tags ?? [];
|
|
137
|
+
test.type = this.reporterConfigOptions.testType ?? 'e2e';
|
|
129
138
|
test.filePath = testCase.location.file;
|
|
130
139
|
test.retries = testResult.retry;
|
|
131
140
|
test.flaky = testResult.status === 'passed' && testResult.retry > 0;
|
|
@@ -139,14 +148,24 @@ class GenerateCtrfReport {
|
|
|
139
148
|
test.screenshot = this.extractScreenshotBase64(testResult);
|
|
140
149
|
}
|
|
141
150
|
test.suite = this.buildSuitePath(testCase);
|
|
142
|
-
if (
|
|
143
|
-
|
|
144
|
-
test.browser = `${
|
|
151
|
+
if (this.extractMetadata(testResult)?.name !== undefined ||
|
|
152
|
+
this.extractMetadata(testResult)?.version !== undefined)
|
|
153
|
+
test.browser = `${this.extractMetadata(testResult)?.name} ${this.extractMetadata(testResult)?.version}`;
|
|
145
154
|
test.attachments = this.filterValidAttachments(testResult.attachments);
|
|
146
|
-
test.stdout = testResult.stdout.map((item) =>
|
|
147
|
-
test.stderr = testResult.stderr.map((item) =>
|
|
155
|
+
test.stdout = testResult.stdout.map((item) => item && typeof item.toString === 'function' ? item.toString() : String(item));
|
|
156
|
+
test.stderr = testResult.stderr.map((item) => item && typeof item.toString === 'function' ? item.toString() : String(item));
|
|
157
|
+
// Initialize extra object
|
|
158
|
+
let extraData = {};
|
|
148
159
|
if (this.reporterConfigOptions.annotations !== undefined) {
|
|
149
|
-
|
|
160
|
+
extraData.annotations = testCase.annotations;
|
|
161
|
+
}
|
|
162
|
+
// Merge CTRF metadata extra data
|
|
163
|
+
if (metadata?.extra) {
|
|
164
|
+
extraData = { ...extraData, ...metadata.extra };
|
|
165
|
+
}
|
|
166
|
+
// Only set extra if there's data
|
|
167
|
+
if (Object.keys(extraData).length > 0) {
|
|
168
|
+
test.extra = extraData;
|
|
150
169
|
}
|
|
151
170
|
if (testCase.results.length > 1) {
|
|
152
171
|
const retryResults = testCase.results.slice(0, -1);
|
|
@@ -235,8 +254,8 @@ class GenerateCtrfReport {
|
|
|
235
254
|
}
|
|
236
255
|
extractMetadata(testResult) {
|
|
237
256
|
const metadataAttachment = testResult.attachments.find((attachment) => attachment.name === 'metadata.json');
|
|
238
|
-
if (
|
|
239
|
-
|
|
257
|
+
if (metadataAttachment?.body !== null &&
|
|
258
|
+
metadataAttachment?.body !== undefined) {
|
|
240
259
|
try {
|
|
241
260
|
const metadataRaw = metadataAttachment.body.toString('utf-8');
|
|
242
261
|
return JSON.parse(metadataRaw);
|
|
@@ -273,17 +292,11 @@ class GenerateCtrfReport {
|
|
|
273
292
|
}
|
|
274
293
|
return pathComponents.join(' > ');
|
|
275
294
|
}
|
|
276
|
-
extractTagsFromTitle(title) {
|
|
277
|
-
const tagPattern = /@\w+/g;
|
|
278
|
-
const tags = title.match(tagPattern);
|
|
279
|
-
return tags !== null && tags !== void 0 ? tags : [];
|
|
280
|
-
}
|
|
281
295
|
extractScreenshotBase64(testResult) {
|
|
282
|
-
var _a;
|
|
283
296
|
const screenshotAttachment = testResult.attachments.find((attachment) => attachment.name === 'screenshot' &&
|
|
284
297
|
(attachment.contentType === 'image/jpeg' ||
|
|
285
298
|
attachment.contentType === 'image/png'));
|
|
286
|
-
return
|
|
299
|
+
return screenshotAttachment?.body?.toString('base64');
|
|
287
300
|
}
|
|
288
301
|
extractFailureDetails(testResult) {
|
|
289
302
|
if ((testResult.status === 'failed' ||
|
|
@@ -312,11 +325,10 @@ class GenerateCtrfReport {
|
|
|
312
325
|
return count;
|
|
313
326
|
}
|
|
314
327
|
writeReportToFile(data) {
|
|
315
|
-
|
|
316
|
-
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);
|
|
328
|
+
const filePath = path.join(this.reporterConfigOptions.outputDir ?? this.defaultOutputDir, this.reporterConfigOptions.outputFile ?? this.defaultOutputFile);
|
|
317
329
|
const str = JSON.stringify(data, null, 2);
|
|
318
330
|
try {
|
|
319
|
-
|
|
331
|
+
fs.writeFileSync(filePath, str + '\n');
|
|
320
332
|
console.log(`${this.reporterName}: successfully written ctrf json to %s/%s`, this.reporterConfigOptions.outputDir, this.reporterConfigOptions.outputFile);
|
|
321
333
|
}
|
|
322
334
|
catch (error) {
|
|
@@ -324,7 +336,6 @@ class GenerateCtrfReport {
|
|
|
324
336
|
}
|
|
325
337
|
}
|
|
326
338
|
processStep(test, step) {
|
|
327
|
-
var _a;
|
|
328
339
|
if (step.category === 'test.step') {
|
|
329
340
|
const stepStatus = step.error === undefined
|
|
330
341
|
? this.mapPlaywrightStatusToCtrf('passed')
|
|
@@ -333,7 +344,7 @@ class GenerateCtrfReport {
|
|
|
333
344
|
name: step.title,
|
|
334
345
|
status: stepStatus,
|
|
335
346
|
};
|
|
336
|
-
|
|
347
|
+
test.steps?.push(currentStep);
|
|
337
348
|
}
|
|
338
349
|
const childSteps = step.steps;
|
|
339
350
|
if (childSteps.length > 0) {
|
|
@@ -345,14 +356,11 @@ class GenerateCtrfReport {
|
|
|
345
356
|
filterValidAttachments(attachments) {
|
|
346
357
|
return attachments
|
|
347
358
|
.filter((attachment) => attachment.path !== undefined)
|
|
348
|
-
.map((attachment) => {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
path: (_a = attachment.path) !== null && _a !== void 0 ? _a : '',
|
|
354
|
-
});
|
|
355
|
-
});
|
|
359
|
+
.map((attachment) => ({
|
|
360
|
+
name: attachment.name,
|
|
361
|
+
contentType: attachment.contentType,
|
|
362
|
+
path: attachment.path ?? '',
|
|
363
|
+
}));
|
|
356
364
|
}
|
|
357
365
|
}
|
|
358
|
-
|
|
366
|
+
export default GenerateCtrfReport;
|
package/dist/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { default } from './generate-report';
|
|
1
|
+
export { default } from './generate-report.js';
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1 @@
|
|
|
1
|
-
|
|
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; } });
|
|
1
|
+
export { default } from './generate-report.js';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Playwright-specific CTRF runtime
|
|
3
|
+
* This implements the CtrfRuntime interface using Playwright test attachments
|
|
4
|
+
*/
|
|
5
|
+
import type { CtrfRuntime } from 'ctrf';
|
|
6
|
+
/**
|
|
7
|
+
* Playwright-specific CTRF runtime implementation
|
|
8
|
+
*/
|
|
9
|
+
export declare class CtrfPlaywrightRuntime implements CtrfRuntime {
|
|
10
|
+
private sendMessage;
|
|
11
|
+
addExtra(key: string, value: unknown): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Extracted metadata from test attachments
|
|
15
|
+
*/
|
|
16
|
+
export interface ExtractedCtrfMetadata {
|
|
17
|
+
extra?: Record<string, unknown>;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Extracts CTRF metadata from Playwright test attachments
|
|
21
|
+
* This is used by the reporter to get metadata from test attachments
|
|
22
|
+
*/
|
|
23
|
+
export declare function extractCtrfMetadata(attachments: Array<{
|
|
24
|
+
name: string;
|
|
25
|
+
contentType: string;
|
|
26
|
+
body?: any;
|
|
27
|
+
}>): ExtractedCtrfMetadata;
|
|
28
|
+
/**
|
|
29
|
+
* Force initialize the Playwright runtime
|
|
30
|
+
* This can be called by the reporter to ensure runtime is available
|
|
31
|
+
*/
|
|
32
|
+
export declare function ensurePlaywrightRuntime(): void;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Playwright-specific CTRF runtime
|
|
3
|
+
* This implements the CtrfRuntime interface using Playwright test attachments
|
|
4
|
+
*/
|
|
5
|
+
import { test } from '@playwright/test';
|
|
6
|
+
import { setGlobalCtrfRuntime } from 'ctrf';
|
|
7
|
+
/**
|
|
8
|
+
* CTRF metadata content type for Playwright attachments
|
|
9
|
+
*/
|
|
10
|
+
const CTRF_RUNTIME_MESSAGE_CONTENT_TYPE = 'application/x-ctrf-runtime-message';
|
|
11
|
+
/**
|
|
12
|
+
* Playwright-specific CTRF runtime implementation
|
|
13
|
+
*/
|
|
14
|
+
export class CtrfPlaywrightRuntime {
|
|
15
|
+
async sendMessage(message) {
|
|
16
|
+
try {
|
|
17
|
+
const testInfo = test.info();
|
|
18
|
+
await testInfo.attach(`CTRF Runtime Message (${message.type})`, {
|
|
19
|
+
contentType: CTRF_RUNTIME_MESSAGE_CONTENT_TYPE,
|
|
20
|
+
body: JSON.stringify(message)
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
console.warn('CTRF: Failed to send runtime message. Make sure this is called within a Playwright test context.');
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
async addExtra(key, value) {
|
|
28
|
+
await this.sendMessage({
|
|
29
|
+
type: 'test_metadata',
|
|
30
|
+
data: { extra: { [key]: value } }
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Auto-configure the Playwright CTRF runtime
|
|
36
|
+
* This is called when this module is imported
|
|
37
|
+
*/
|
|
38
|
+
function initializePlaywrightRuntime() {
|
|
39
|
+
// Always set up the Playwright runtime when this module is loaded
|
|
40
|
+
// This ensures the runtime is available before any test code runs
|
|
41
|
+
setGlobalCtrfRuntime(new CtrfPlaywrightRuntime());
|
|
42
|
+
}
|
|
43
|
+
// Auto-initialize as soon as this module is loaded
|
|
44
|
+
initializePlaywrightRuntime();
|
|
45
|
+
/**
|
|
46
|
+
* Extracts CTRF metadata from Playwright test attachments
|
|
47
|
+
* This is used by the reporter to get metadata from test attachments
|
|
48
|
+
*/
|
|
49
|
+
export function extractCtrfMetadata(attachments) {
|
|
50
|
+
const result = {
|
|
51
|
+
extra: {}
|
|
52
|
+
};
|
|
53
|
+
for (const attachment of attachments) {
|
|
54
|
+
try {
|
|
55
|
+
if (attachment.contentType === CTRF_RUNTIME_MESSAGE_CONTENT_TYPE && attachment.body) {
|
|
56
|
+
const message = JSON.parse(attachment.body.toString('utf-8'));
|
|
57
|
+
if (message.type === 'test_metadata' && message.data.extra) {
|
|
58
|
+
// Simple merge for metadata
|
|
59
|
+
for (const [key, value] of Object.entries(message.data.extra)) {
|
|
60
|
+
result.extra[key] = value;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
// Silently ignore parsing errors for non-CTRF attachments
|
|
67
|
+
console.warn(`Failed to parse CTRF runtime message from attachment ${attachment.name}:`, error);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// Clean up empty extra object
|
|
71
|
+
if (result.extra && Object.keys(result.extra).length === 0) {
|
|
72
|
+
delete result.extra;
|
|
73
|
+
}
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Force initialize the Playwright runtime
|
|
78
|
+
* This can be called by the reporter to ensure runtime is available
|
|
79
|
+
*/
|
|
80
|
+
export function ensurePlaywrightRuntime() {
|
|
81
|
+
setGlobalCtrfRuntime(new CtrfPlaywrightRuntime());
|
|
82
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extracted metadata from test attachments
|
|
3
|
+
*/
|
|
4
|
+
export interface ExtractedCtrfMetadata {
|
|
5
|
+
extra?: Record<string, unknown>;
|
|
6
|
+
displayName?: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Facade functions that call the global runtime
|
|
10
|
+
*/
|
|
11
|
+
export declare const displayName: (name: string) => Promise<void>;
|
|
12
|
+
export declare const addExtra: (key: string, value: unknown) => Promise<void>;
|
|
13
|
+
export declare const tags: (...tagList: string[]) => Promise<void>;
|
|
14
|
+
export declare const priority: (level: string) => Promise<void>;
|
|
15
|
+
export declare const severity: (level: string) => Promise<void>;
|
|
16
|
+
export declare const link: (url: string, name?: string) => Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Convenience method for setting multiple tags at once
|
|
19
|
+
*/
|
|
20
|
+
export declare const tag: (tagName: string) => Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Extracts CTRF metadata from Playwright test attachments
|
|
23
|
+
*/
|
|
24
|
+
export declare function extractCtrfMetadata(attachments: Array<{
|
|
25
|
+
name: string;
|
|
26
|
+
contentType: string;
|
|
27
|
+
body?: any;
|
|
28
|
+
}>): ExtractedCtrfMetadata;
|
package/dist/runtime.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CTRF metadata content type for Playwright attachments
|
|
3
|
+
*/
|
|
4
|
+
const CTRF_RUNTIME_MESSAGE_CONTENT_TYPE = 'application/x-ctrf-runtime-message';
|
|
5
|
+
/**
|
|
6
|
+
* Gets the current test info from Playwright's global context
|
|
7
|
+
*/
|
|
8
|
+
function getCurrentTestInfo() {
|
|
9
|
+
// In Playwright tests, 'test' is available globally
|
|
10
|
+
try {
|
|
11
|
+
// Access Playwright's global test object
|
|
12
|
+
const globalTest = globalThis.test;
|
|
13
|
+
if (globalTest && typeof globalTest.info === 'function') {
|
|
14
|
+
return globalTest.info();
|
|
15
|
+
}
|
|
16
|
+
// Fallback: try to get from playwright/test module
|
|
17
|
+
// This will work when the runtime is loaded in a test context
|
|
18
|
+
const { test } = require('@playwright/test');
|
|
19
|
+
return test.info();
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
throw new Error('CTRF runtime must be used within a Playwright test context. Make sure to import this module only in test files.');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Global CTRF runtime for Playwright using message-based communication
|
|
27
|
+
*/
|
|
28
|
+
class CtrfPlaywrightRuntime {
|
|
29
|
+
async sendMessage(message) {
|
|
30
|
+
const testInfo = getCurrentTestInfo();
|
|
31
|
+
await testInfo.attach(`CTRF Runtime Message (${message.type})`, {
|
|
32
|
+
contentType: CTRF_RUNTIME_MESSAGE_CONTENT_TYPE,
|
|
33
|
+
body: JSON.stringify(message)
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
async displayName(name) {
|
|
37
|
+
await this.sendMessage({
|
|
38
|
+
type: 'display_name',
|
|
39
|
+
data: { name }
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
async addExtra(key, value) {
|
|
43
|
+
await this.sendMessage({
|
|
44
|
+
type: 'test_metadata',
|
|
45
|
+
data: { extra: { [key]: value } }
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
async tags(...tags) {
|
|
49
|
+
await this.sendMessage({
|
|
50
|
+
type: 'test_metadata',
|
|
51
|
+
data: { extra: { tags } }
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
async priority(level) {
|
|
55
|
+
await this.sendMessage({
|
|
56
|
+
type: 'test_metadata',
|
|
57
|
+
data: { extra: { priority: level } }
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
async severity(level) {
|
|
61
|
+
await this.sendMessage({
|
|
62
|
+
type: 'test_metadata',
|
|
63
|
+
data: { extra: { severity: level } }
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
async link(url, name) {
|
|
67
|
+
const linkData = name ? { url, name } : { url };
|
|
68
|
+
await this.sendMessage({
|
|
69
|
+
type: 'test_metadata',
|
|
70
|
+
data: { extra: { links: [linkData] } }
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Global CTRF runtime instance
|
|
76
|
+
*/
|
|
77
|
+
const globalCtrfRuntime = new CtrfPlaywrightRuntime();
|
|
78
|
+
/**
|
|
79
|
+
* Facade functions that call the global runtime
|
|
80
|
+
*/
|
|
81
|
+
export const displayName = (name) => {
|
|
82
|
+
return globalCtrfRuntime.displayName(name);
|
|
83
|
+
};
|
|
84
|
+
export const addExtra = (key, value) => {
|
|
85
|
+
return globalCtrfRuntime.addExtra(key, value);
|
|
86
|
+
};
|
|
87
|
+
export const tags = (...tagList) => {
|
|
88
|
+
return globalCtrfRuntime.tags(...tagList);
|
|
89
|
+
};
|
|
90
|
+
export const priority = (level) => {
|
|
91
|
+
return globalCtrfRuntime.priority(level);
|
|
92
|
+
};
|
|
93
|
+
export const severity = (level) => {
|
|
94
|
+
return globalCtrfRuntime.severity(level);
|
|
95
|
+
};
|
|
96
|
+
export const link = (url, name) => {
|
|
97
|
+
return globalCtrfRuntime.link(url, name);
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* Convenience method for setting multiple tags at once
|
|
101
|
+
*/
|
|
102
|
+
export const tag = (tagName) => {
|
|
103
|
+
return tags(tagName);
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* Extracts CTRF metadata from Playwright test attachments
|
|
107
|
+
*/
|
|
108
|
+
export function extractCtrfMetadata(attachments) {
|
|
109
|
+
const result = {
|
|
110
|
+
extra: {}
|
|
111
|
+
};
|
|
112
|
+
for (const attachment of attachments) {
|
|
113
|
+
try {
|
|
114
|
+
if (attachment.contentType === CTRF_RUNTIME_MESSAGE_CONTENT_TYPE && attachment.body) {
|
|
115
|
+
const message = JSON.parse(attachment.body.toString('utf-8'));
|
|
116
|
+
switch (message.type) {
|
|
117
|
+
case 'display_name':
|
|
118
|
+
result.displayName = message.data.name;
|
|
119
|
+
break;
|
|
120
|
+
case 'test_metadata':
|
|
121
|
+
if (message.data.extra) {
|
|
122
|
+
// Handle different metadata types
|
|
123
|
+
for (const [key, value] of Object.entries(message.data.extra)) {
|
|
124
|
+
if (key === 'links' && Array.isArray(value)) {
|
|
125
|
+
// Accumulate links
|
|
126
|
+
if (!result.extra.links) {
|
|
127
|
+
result.extra.links = [];
|
|
128
|
+
}
|
|
129
|
+
if (Array.isArray(result.extra.links)) {
|
|
130
|
+
result.extra.links.push(...value);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
else if (key === 'tags' && Array.isArray(value)) {
|
|
134
|
+
// Merge tags
|
|
135
|
+
if (!result.extra.tags) {
|
|
136
|
+
result.extra.tags = [];
|
|
137
|
+
}
|
|
138
|
+
if (Array.isArray(result.extra.tags)) {
|
|
139
|
+
result.extra.tags.push(...value);
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
result.extra.tags = value;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
// Simple merge for other metadata
|
|
147
|
+
result.extra[key] = value;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
// Silently ignore parsing errors for non-CTRF attachments
|
|
157
|
+
console.warn(`Failed to parse CTRF runtime message from attachment ${attachment.name}:`, error);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// Clean up empty extra object
|
|
161
|
+
if (result.extra && Object.keys(result.extra).length === 0) {
|
|
162
|
+
delete result.extra;
|
|
163
|
+
}
|
|
164
|
+
return result;
|
|
165
|
+
}
|
package/package.json
CHANGED
|
@@ -1,15 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "playwright-ctrf-json-reporter",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.25-next-0",
|
|
4
4
|
"description": "A Playwright JSON test reporter to create test results reports",
|
|
5
|
+
"type": "module",
|
|
5
6
|
"main": "dist/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=20.19.0"
|
|
16
|
+
},
|
|
6
17
|
"scripts": {
|
|
7
|
-
"build": "tsc",
|
|
8
|
-
"
|
|
18
|
+
"build": "tsc -p .",
|
|
19
|
+
"build:check": "tsc -p . -noEmit",
|
|
20
|
+
"build:watch": "tsc -p . --watch",
|
|
21
|
+
"clean": "rm -rf dist && rm -rf coverage && rm -rf ctrf",
|
|
22
|
+
"test": "vitest run",
|
|
23
|
+
"test:coverage": "vitest run --coverage",
|
|
9
24
|
"lint": "eslint . --ext .ts --fix",
|
|
10
|
-
"lint
|
|
25
|
+
"lint:check": "eslint . --ext .ts",
|
|
11
26
|
"format": "prettier --write .",
|
|
12
|
-
"format
|
|
27
|
+
"format:check": "prettier --check .",
|
|
28
|
+
"docs": "typedoc",
|
|
29
|
+
"docs:watch": "typedoc --watch",
|
|
30
|
+
"all": "npm run build:check && npm run test:coverage && npm run lint && npm run format && npm run build"
|
|
13
31
|
},
|
|
14
32
|
"repository": "github:ctrf-io/playwright-ctrf-json-report",
|
|
15
33
|
"homepage": "https://ctrf.io",
|
|
@@ -20,20 +38,20 @@
|
|
|
20
38
|
"author": "Matthew Thomas",
|
|
21
39
|
"license": "MIT",
|
|
22
40
|
"devDependencies": {
|
|
23
|
-
"@
|
|
24
|
-
"@
|
|
25
|
-
"@
|
|
26
|
-
"@
|
|
27
|
-
"
|
|
28
|
-
"eslint
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"eslint
|
|
34
|
-
"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"
|
|
41
|
+
"@d2t/vitest-ctrf-json-reporter": "^1.3.0",
|
|
42
|
+
"@eslint/js": "^9.32.0",
|
|
43
|
+
"@playwright/test": "^1.55.0",
|
|
44
|
+
"@types/node": "^24.7.2",
|
|
45
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
46
|
+
"eslint": "^9.32.0",
|
|
47
|
+
"prettier": "^3.5.3",
|
|
48
|
+
"typedoc": "^0.28.9",
|
|
49
|
+
"typedoc-plugin-markdown": "^4.8.0",
|
|
50
|
+
"typescript": "^5.8.3",
|
|
51
|
+
"typescript-eslint": "^8.38.0",
|
|
52
|
+
"vitest": "^3.2.4"
|
|
53
|
+
},
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"ctrf": "0.0.16-next.1"
|
|
38
56
|
}
|
|
39
57
|
}
|