playwright-ctrf-json-reporter 0.0.24 → 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/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 -0
- package/dist/generate-report.js +74 -63
- 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 +37 -19
|
@@ -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;
|
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,9 +117,10 @@ 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,
|
|
123
|
+
name: testCase.title, // Use the original test title
|
|
117
124
|
status: testResult.status === testCase.expectedStatus
|
|
118
125
|
? 'passed'
|
|
119
126
|
: this.mapPlaywrightStatusToCtrf(testResult.status),
|
|
@@ -126,8 +133,8 @@ class GenerateCtrfReport {
|
|
|
126
133
|
test.trace = this.extractFailureDetails(testResult).trace;
|
|
127
134
|
test.snippet = this.extractFailureDetails(testResult).snippet;
|
|
128
135
|
test.rawStatus = testResult.status;
|
|
129
|
-
test.tags =
|
|
130
|
-
test.type =
|
|
136
|
+
test.tags = testCase.tags ?? [];
|
|
137
|
+
test.type = this.reporterConfigOptions.testType ?? 'e2e';
|
|
131
138
|
test.filePath = testCase.location.file;
|
|
132
139
|
test.retries = testResult.retry;
|
|
133
140
|
test.flaky = testResult.status === 'passed' && testResult.retry > 0;
|
|
@@ -141,14 +148,24 @@ class GenerateCtrfReport {
|
|
|
141
148
|
test.screenshot = this.extractScreenshotBase64(testResult);
|
|
142
149
|
}
|
|
143
150
|
test.suite = this.buildSuitePath(testCase);
|
|
144
|
-
if (
|
|
145
|
-
|
|
146
|
-
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}`;
|
|
147
154
|
test.attachments = this.filterValidAttachments(testResult.attachments);
|
|
148
|
-
test.stdout = testResult.stdout.map((item) =>
|
|
149
|
-
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 = {};
|
|
150
159
|
if (this.reporterConfigOptions.annotations !== undefined) {
|
|
151
|
-
|
|
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;
|
|
152
169
|
}
|
|
153
170
|
if (testCase.results.length > 1) {
|
|
154
171
|
const retryResults = testCase.results.slice(0, -1);
|
|
@@ -237,8 +254,8 @@ class GenerateCtrfReport {
|
|
|
237
254
|
}
|
|
238
255
|
extractMetadata(testResult) {
|
|
239
256
|
const metadataAttachment = testResult.attachments.find((attachment) => attachment.name === 'metadata.json');
|
|
240
|
-
if (
|
|
241
|
-
|
|
257
|
+
if (metadataAttachment?.body !== null &&
|
|
258
|
+
metadataAttachment?.body !== undefined) {
|
|
242
259
|
try {
|
|
243
260
|
const metadataRaw = metadataAttachment.body.toString('utf-8');
|
|
244
261
|
return JSON.parse(metadataRaw);
|
|
@@ -276,11 +293,10 @@ class GenerateCtrfReport {
|
|
|
276
293
|
return pathComponents.join(' > ');
|
|
277
294
|
}
|
|
278
295
|
extractScreenshotBase64(testResult) {
|
|
279
|
-
var _a;
|
|
280
296
|
const screenshotAttachment = testResult.attachments.find((attachment) => attachment.name === 'screenshot' &&
|
|
281
297
|
(attachment.contentType === 'image/jpeg' ||
|
|
282
298
|
attachment.contentType === 'image/png'));
|
|
283
|
-
return
|
|
299
|
+
return screenshotAttachment?.body?.toString('base64');
|
|
284
300
|
}
|
|
285
301
|
extractFailureDetails(testResult) {
|
|
286
302
|
if ((testResult.status === 'failed' ||
|
|
@@ -309,11 +325,10 @@ class GenerateCtrfReport {
|
|
|
309
325
|
return count;
|
|
310
326
|
}
|
|
311
327
|
writeReportToFile(data) {
|
|
312
|
-
|
|
313
|
-
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);
|
|
314
329
|
const str = JSON.stringify(data, null, 2);
|
|
315
330
|
try {
|
|
316
|
-
|
|
331
|
+
fs.writeFileSync(filePath, str + '\n');
|
|
317
332
|
console.log(`${this.reporterName}: successfully written ctrf json to %s/%s`, this.reporterConfigOptions.outputDir, this.reporterConfigOptions.outputFile);
|
|
318
333
|
}
|
|
319
334
|
catch (error) {
|
|
@@ -321,7 +336,6 @@ class GenerateCtrfReport {
|
|
|
321
336
|
}
|
|
322
337
|
}
|
|
323
338
|
processStep(test, step) {
|
|
324
|
-
var _a;
|
|
325
339
|
if (step.category === 'test.step') {
|
|
326
340
|
const stepStatus = step.error === undefined
|
|
327
341
|
? this.mapPlaywrightStatusToCtrf('passed')
|
|
@@ -330,7 +344,7 @@ class GenerateCtrfReport {
|
|
|
330
344
|
name: step.title,
|
|
331
345
|
status: stepStatus,
|
|
332
346
|
};
|
|
333
|
-
|
|
347
|
+
test.steps?.push(currentStep);
|
|
334
348
|
}
|
|
335
349
|
const childSteps = step.steps;
|
|
336
350
|
if (childSteps.length > 0) {
|
|
@@ -342,14 +356,11 @@ class GenerateCtrfReport {
|
|
|
342
356
|
filterValidAttachments(attachments) {
|
|
343
357
|
return attachments
|
|
344
358
|
.filter((attachment) => attachment.path !== undefined)
|
|
345
|
-
.map((attachment) => {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
path: (_a = attachment.path) !== null && _a !== void 0 ? _a : '',
|
|
351
|
-
});
|
|
352
|
-
});
|
|
359
|
+
.map((attachment) => ({
|
|
360
|
+
name: attachment.name,
|
|
361
|
+
contentType: attachment.contentType,
|
|
362
|
+
path: attachment.path ?? '',
|
|
363
|
+
}));
|
|
353
364
|
}
|
|
354
365
|
}
|
|
355
|
-
|
|
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": {
|
|
41
|
+
"@d2t/vitest-ctrf-json-reporter": "^1.3.0",
|
|
42
|
+
"@eslint/js": "^9.32.0",
|
|
23
43
|
"@playwright/test": "^1.55.0",
|
|
24
|
-
"@types/
|
|
25
|
-
"@
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"eslint
|
|
32
|
-
"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"
|
|
36
|
-
"ts-jest": "^29.1.1",
|
|
37
|
-
"typescript": "^5.3.2"
|
|
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
|
}
|