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.
@@ -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;
@@ -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,9 +117,10 @@ class GenerateCtrfReport {
111
117
  }
112
118
  }
113
119
  updateCtrfTestResultsFromTestResult(testCase, testResult, ctrfReport) {
114
- var _a, _b, _c, _d, _e, _f;
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 = (_a = testCase.tags) !== null && _a !== void 0 ? _a : [];
130
- test.type = (_b = this.reporterConfigOptions.testType) !== null && _b !== void 0 ? _b : 'e2e';
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 (((_c = this.extractMetadata(testResult)) === null || _c === void 0 ? void 0 : _c.name) !== undefined ||
145
- ((_d = this.extractMetadata(testResult)) === null || _d === void 0 ? void 0 : _d.version) !== undefined)
146
- test.browser = `${(_e = this.extractMetadata(testResult)) === null || _e === void 0 ? void 0 : _e.name} ${(_f = this.extractMetadata(testResult)) === null || _f === void 0 ? void 0 : _f.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}`;
147
154
  test.attachments = this.filterValidAttachments(testResult.attachments);
148
- test.stdout = testResult.stdout.map((item) => Buffer.isBuffer(item) ? item.toString() : String(item));
149
- 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 = {};
150
159
  if (this.reporterConfigOptions.annotations !== undefined) {
151
- 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;
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 ((metadataAttachment === null || metadataAttachment === void 0 ? void 0 : metadataAttachment.body) !== null &&
241
- (metadataAttachment === null || metadataAttachment === void 0 ? void 0 : metadataAttachment.body) !== undefined) {
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 (_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');
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
- var _a, _b;
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
- fs_1.default.writeFileSync(filePath, str + '\n');
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
- (_a = test.steps) === null || _a === void 0 ? void 0 : _a.push(currentStep);
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
- var _a;
347
- return ({
348
- name: attachment.name,
349
- contentType: attachment.contentType,
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
- 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.24",
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": {
41
+ "@d2t/vitest-ctrf-json-reporter": "^1.3.0",
42
+ "@eslint/js": "^9.32.0",
23
43
  "@playwright/test": "^1.55.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"
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
  }