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 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
- Maintained by <a href="https://github.com/ma11hewthomas">Matthew Thomas</a><br/>
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,5 @@
1
+ /**
2
+ * Auto-configuration for Playwright CTRF runtime
3
+ * This file automatically sets up the Playwright runtime when imported
4
+ */
5
+ export {};
@@ -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;
@@ -1,41 +1,43 @@
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
- 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
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
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: (_a = config === null || config === void 0 ? void 0 : config.outputFile) !== null && _a !== void 0 ? _a : this.defaultOutputFile,
17
- outputDir: (_b = config === null || config === void 0 ? void 0 : config.outputDir) !== null && _b !== void 0 ? _b : this.defaultOutputDir,
18
- minimal: (_c = config === null || config === void 0 ? void 0 : config.minimal) !== null && _c !== void 0 ? _c : false,
19
- screenshot: (_d = config === null || config === void 0 ? void 0 : config.screenshot) !== null && _d !== void 0 ? _d : false,
20
- annotations: (_e = config === null || config === void 0 ? void 0 : config.annotations) !== null && _e !== void 0 ? _e : false,
21
- testType: (_f = config === null || config === void 0 ? void 0 : config.testType) !== null && _f !== void 0 ? _f : 'e2e',
22
- appName: (_g = config === null || config === void 0 ? void 0 : config.appName) !== null && _g !== void 0 ? _g : undefined,
23
- appVersion: (_h = config === null || config === void 0 ? void 0 : config.appVersion) !== null && _h !== void 0 ? _h : undefined,
24
- osPlatform: (_j = config === null || config === void 0 ? void 0 : config.osPlatform) !== null && _j !== void 0 ? _j : undefined,
25
- osRelease: (_k = config === null || config === void 0 ? void 0 : config.osRelease) !== null && _k !== void 0 ? _k : undefined,
26
- osVersion: (_l = config === null || config === void 0 ? void 0 : config.osVersion) !== null && _l !== void 0 ? _l : undefined,
27
- buildName: (_m = config === null || config === void 0 ? void 0 : config.buildName) !== null && _m !== void 0 ? _m : undefined,
28
- buildNumber: (_o = config === null || config === void 0 ? void 0 : config.buildNumber) !== null && _o !== void 0 ? _o : undefined,
29
- buildUrl: (_p = config === null || config === void 0 ? void 0 : config.buildUrl) !== null && _p !== void 0 ? _p : undefined,
30
- repositoryName: (_q = config === null || config === void 0 ? void 0 : config.repositoryName) !== null && _q !== void 0 ? _q : undefined,
31
- repositoryUrl: (_r = config === null || config === void 0 ? void 0 : config.repositoryUrl) !== null && _r !== void 0 ? _r : undefined,
32
- branchName: (_s = config === null || config === void 0 ? void 0 : config.branchName) !== null && _s !== void 0 ? _s : undefined,
33
- testEnvironment: (_t = config === null || config === void 0 ? void 0 : config.testEnvironment) !== null && _t !== void 0 ? _t : undefined,
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: crypto_1.default.randomUUID(),
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 (!fs_1.default.existsSync((_a = this.reporterConfigOptions.outputDir) !== null && _a !== void 0 ? _a : this.defaultOutputDir)) {
66
- fs_1.default.mkdirSync((_b = this.reporterConfigOptions.outputDir) !== null && _b !== void 0 ? _b : this.defaultOutputDir, { recursive: true });
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((_c = this.reporterConfigOptions.outputFile) !== null && _c !== void 0 ? _c : this.defaultOutputFile);
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
- var _a, _b, _c, _d, _e;
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: this.mapPlaywrightStatusToCtrf(testResult.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 = this.extractTagsFromTitle(testCase.title);
128
- test.type = (_a = this.reporterConfigOptions.testType) !== null && _a !== void 0 ? _a : 'e2e';
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 (((_b = this.extractMetadata(testResult)) === null || _b === void 0 ? void 0 : _b.name) !== undefined ||
143
- ((_c = this.extractMetadata(testResult)) === null || _c === void 0 ? void 0 : _c.version) !== undefined)
144
- 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}`;
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) => Buffer.isBuffer(item) ? item.toString() : String(item));
147
- test.stderr = testResult.stderr.map((item) => Buffer.isBuffer(item) ? item.toString() : String(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
- test.extra = { annotations: testCase.annotations };
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 ((metadataAttachment === null || metadataAttachment === void 0 ? void 0 : metadataAttachment.body) !== null &&
239
- (metadataAttachment === null || metadataAttachment === void 0 ? void 0 : metadataAttachment.body) !== undefined) {
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 (_a = screenshotAttachment === null || screenshotAttachment === void 0 ? void 0 : screenshotAttachment.body) === null || _a === void 0 ? void 0 : _a.toString('base64');
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
- var _a, _b;
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
- fs_1.default.writeFileSync(filePath, str + '\n');
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
- (_a = test.steps) === null || _a === void 0 ? void 0 : _a.push(currentStep);
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
- var _a;
350
- return ({
351
- name: attachment.name,
352
- contentType: attachment.contentType,
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
- exports.default = GenerateCtrfReport;
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
- "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; } });
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;
@@ -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.23",
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
- "test": "jest",
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-check": "eslint . --ext .ts",
25
+ "lint:check": "eslint . --ext .ts",
11
26
  "format": "prettier --write .",
12
- "format-check": "prettier --check ."
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
- "@playwright/test": "^1.39.0",
24
- "@types/jest": "^29.5.6",
25
- "@types/node": "^20.8.7",
26
- "@typescript-eslint/eslint-plugin": "^6.12.0",
27
- "eslint": "^8.54.0",
28
- "eslint-config-prettier": "^9.0.0",
29
- "eslint-config-standard-with-typescript": "^40.0.0",
30
- "eslint-plugin-import": "^2.29.0",
31
- "eslint-plugin-n": "^16.3.1",
32
- "eslint-plugin-prettier": "^5.0.1",
33
- "eslint-plugin-promise": "^6.1.1",
34
- "jest": "^29.7.0",
35
- "prettier": "^3.1.0",
36
- "ts-jest": "^29.1.1",
37
- "typescript": "^5.3.2"
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
  }