playwright-ctrf-json-reporter 0.0.27 → 0.0.28-next-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024 Matthew Thomas
3
+ Copyright (c) 2024 Matthew Poulton-White
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -6,29 +6,20 @@ A Playwright JSON test reporter to create test reports that follow the CTRF stan
6
6
 
7
7
  [Common Test Report Format](https://ctrf.io) ensures the generation of uniform JSON test reports, independent of programming languages or test framework in use.
8
8
 
9
- <div align="center">
10
- <div style="padding: 1.5rem; border-radius: 8px; margin: 1rem 0; border: 1px solid #30363d;">
11
- <span style="font-size: 23px;">💚</span>
12
- <h3 style="margin: 1rem 0;">CTRF tooling is open source and free to use</h3>
13
- <p style="font-size: 16px;">You can support the project with a follow and a star</p>
14
-
15
- <div style="margin-top: 1.5rem;">
16
- <a href="https://github.com/ctrf-io/playwright-ctrf-json-reporter">
17
- <img src="https://img.shields.io/github/stars/ctrf-io/playwright-ctrf-json-reporter?style=for-the-badge&color=2ea043" alt="GitHub stars">
18
- </a>
19
- <a href="https://github.com/ctrf-io">
20
- <img src="https://img.shields.io/github/followers/ctrf-io?style=for-the-badge&color=2ea043" alt="GitHub followers">
21
- </a>
22
- </div>
23
- </div>
24
- <p style="font-size: 14px; margin: 1rem 0;">
25
-
26
- Contributions are very welcome! <br/>
27
- Explore more <a href="https://www.ctrf.io/integrations">integrations</a> <br/>
28
- <a href="https://app.formbricks.com/s/cmefs524mhlh1tl01gkpvefrb">Let us know your thoughts</a>.
29
-
30
- </p>
31
- </div>
9
+ ## CTRF Open Standard
10
+
11
+ CTRF is a community-driven open standard for test reporting.
12
+
13
+ By standardizing test results, reports can be validated, merged, compared, and analyzed consistently across languages and frameworks.
14
+
15
+ - **CTRF Specification**: https://github.com/ctrf-io/ctrf
16
+ The official specification defining the format and semantics
17
+ - **Discussions**: https://github.com/orgs/ctrf-io/discussions
18
+ Community forum for questions, ideas, and support
19
+
20
+ > [!NOTE]
21
+ > ⭐ Starring the **CTRF specification repository** (https://github.com/ctrf-io/ctrf)
22
+ > helps support the standard.
32
23
 
33
24
  ## Features
34
25
 
@@ -259,7 +250,3 @@ CTRF is a universal JSON test report schema that addresses the lack of a standar
259
250
  **Language and Framework Agnostic:** It provides a universal reporting schema that works seamlessly with any programming language and testing framework.
260
251
 
261
252
  **Facilitates Better Analysis:** With a standardized format, programatically analyzing test outcomes across multiple platforms becomes more straightforward.
262
-
263
- ## Support Us
264
-
265
- If you find this project useful, consider giving it a GitHub star ⭐ It means a lot to us.
@@ -0,0 +1,2 @@
1
+ export { ctrf, extra, CTRF_RUNTIME_MESSAGE_CONTENT_TYPE } from './runtime';
2
+ export type { CtrfRuntimeMessage, CtrfRuntimeMessageType } from './runtime';
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CTRF_RUNTIME_MESSAGE_CONTENT_TYPE = exports.extra = exports.ctrf = void 0;
4
+ var runtime_1 = require("./runtime");
5
+ Object.defineProperty(exports, "ctrf", { enumerable: true, get: function () { return runtime_1.ctrf; } });
6
+ Object.defineProperty(exports, "extra", { enumerable: true, get: function () { return runtime_1.extra; } });
7
+ Object.defineProperty(exports, "CTRF_RUNTIME_MESSAGE_CONTENT_TYPE", { enumerable: true, get: function () { return runtime_1.CTRF_RUNTIME_MESSAGE_CONTENT_TYPE; } });
@@ -0,0 +1,80 @@
1
+ /**
2
+ * CTRF Runtime API for Playwright
3
+ *
4
+ * Enables enriching CTRF test reports with custom metadata at runtime.
5
+ * Metadata is collected via Playwright's native attachment mechanism and
6
+ * consolidated by the reporter into the test's `extra` field.
7
+ *
8
+ * ## Usage
9
+ *
10
+ * ```ts
11
+ * import { ctrf } from 'playwright-ctrf-json-reporter'
12
+ *
13
+ * test('checkout flow', async ({ page }) => {
14
+ * ctrf.extra({ owner: 'checkout-team', priority: 'P1' })
15
+ * // ... test code
16
+ * ctrf.extra({ customMetric: calculateValue() })
17
+ * })
18
+ * ```
19
+ *
20
+ * ## API
21
+ *
22
+ * - `ctrf.extra(data)` - Attach key-value metadata to the current test
23
+ *
24
+ * ## Behavior
25
+ *
26
+ * - Call multiple times; all data is collected and merged
27
+ * - Works from any function in the call stack during test execution
28
+ * - Silently ignored when called outside test context
29
+ * - Deep merge: arrays concatenate, objects merge recursively, primitives overwrite
30
+ */
31
+ /**
32
+ * Content type used to identify CTRF runtime messages in attachments.
33
+ * The reporter will look for attachments with this content type.
34
+ */
35
+ export declare const CTRF_RUNTIME_MESSAGE_CONTENT_TYPE = "application/vnd.ctrf.message+json";
36
+ /**
37
+ * Runtime message types
38
+ */
39
+ export type CtrfRuntimeMessageType = 'metadata';
40
+ /**
41
+ * A runtime message sent from test code to the reporter
42
+ */
43
+ export interface CtrfRuntimeMessage {
44
+ type: CtrfRuntimeMessageType;
45
+ data: Record<string, unknown>;
46
+ }
47
+ /**
48
+ * Attach custom metadata to the current test.
49
+ *
50
+ * @param data - Key-value pairs to include in the CTRF report's `extra` field
51
+ * @returns Promise (can be ignored; completes when attachment is written)
52
+ *
53
+ * @remarks
54
+ * - **Arrays**: Concatenated across calls (duplicates allowed)
55
+ * - **Objects**: Deep merged (nested keys preserved)
56
+ * - **Primitives**: Later calls overwrite earlier ones
57
+ * - Safe to call from helper functions - binds to the active test automatically
58
+ * - No-op outside test context (e.g., in global setup)
59
+ *
60
+ * @example
61
+ * // Arrays concatenate
62
+ * ctrf.extra({ tags: ['smoke'] })
63
+ * ctrf.extra({ tags: ['e2e'] })
64
+ * // → { tags: ['smoke', 'e2e'] }
65
+ *
66
+ * // Objects merge deeply
67
+ * ctrf.extra({ build: { id: 'abc' } })
68
+ * ctrf.extra({ build: { url: 'https://...' } })
69
+ * // → { build: { id: 'abc', url: 'https://...' } }
70
+ *
71
+ * // Primitives overwrite
72
+ * ctrf.extra({ owner: 'platform-team' })
73
+ * ctrf.extra({ owner: 'checkout-team' })
74
+ * // → { owner: 'checkout-team' }
75
+ */
76
+ export declare function extra(data: Record<string, unknown>): Promise<void>;
77
+ /** CTRF runtime API namespace */
78
+ export declare const ctrf: {
79
+ readonly extra: typeof extra;
80
+ };
@@ -0,0 +1,129 @@
1
+ "use strict";
2
+ /**
3
+ * CTRF Runtime API for Playwright
4
+ *
5
+ * Enables enriching CTRF test reports with custom metadata at runtime.
6
+ * Metadata is collected via Playwright's native attachment mechanism and
7
+ * consolidated by the reporter into the test's `extra` field.
8
+ *
9
+ * ## Usage
10
+ *
11
+ * ```ts
12
+ * import { ctrf } from 'playwright-ctrf-json-reporter'
13
+ *
14
+ * test('checkout flow', async ({ page }) => {
15
+ * ctrf.extra({ owner: 'checkout-team', priority: 'P1' })
16
+ * // ... test code
17
+ * ctrf.extra({ customMetric: calculateValue() })
18
+ * })
19
+ * ```
20
+ *
21
+ * ## API
22
+ *
23
+ * - `ctrf.extra(data)` - Attach key-value metadata to the current test
24
+ *
25
+ * ## Behavior
26
+ *
27
+ * - Call multiple times; all data is collected and merged
28
+ * - Works from any function in the call stack during test execution
29
+ * - Silently ignored when called outside test context
30
+ * - Deep merge: arrays concatenate, objects merge recursively, primitives overwrite
31
+ */
32
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
33
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
34
+ return new (P || (P = Promise))(function (resolve, reject) {
35
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
36
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
37
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
38
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
39
+ });
40
+ };
41
+ Object.defineProperty(exports, "__esModule", { value: true });
42
+ exports.ctrf = exports.CTRF_RUNTIME_MESSAGE_CONTENT_TYPE = void 0;
43
+ exports.extra = extra;
44
+ const test_1 = require("@playwright/test");
45
+ /**
46
+ * Content type used to identify CTRF runtime messages in attachments.
47
+ * The reporter will look for attachments with this content type.
48
+ */
49
+ exports.CTRF_RUNTIME_MESSAGE_CONTENT_TYPE = 'application/vnd.ctrf.message+json';
50
+ /**
51
+ * Silent fallback when no test is active - prevents errors in setup/teardown code.
52
+ */
53
+ const nullTransport = {
54
+ send: () => __awaiter(void 0, void 0, void 0, function* () {
55
+ /* intentionally empty */
56
+ }),
57
+ };
58
+ /**
59
+ * Attaches metadata payloads to the current Playwright test as hidden attachments.
60
+ * The reporter extracts these by content type during report generation.
61
+ */
62
+ function createPlaywrightTransport() {
63
+ let sequence = 0;
64
+ return {
65
+ send(payload) {
66
+ return __awaiter(this, void 0, void 0, function* () {
67
+ const tag = `__ctrf_${++sequence}_${Date.now()}`;
68
+ const data = JSON.stringify(payload);
69
+ yield test_1.test.info().attach(tag, {
70
+ contentType: exports.CTRF_RUNTIME_MESSAGE_CONTENT_TYPE,
71
+ body: Buffer.from(data),
72
+ });
73
+ });
74
+ },
75
+ };
76
+ }
77
+ const activeTransport = createPlaywrightTransport();
78
+ /**
79
+ * Resolves the active transport, or a null transport if outside a test.
80
+ */
81
+ const resolveTransport = () => {
82
+ try {
83
+ test_1.test.info();
84
+ return activeTransport;
85
+ }
86
+ catch (_a) {
87
+ // Outside test context
88
+ }
89
+ return nullTransport;
90
+ };
91
+ /* ═══════════════════════════════════════════════════════════════════════════
92
+ * Public API
93
+ * ═══════════════════════════════════════════════════════════════════════════ */
94
+ /**
95
+ * Attach custom metadata to the current test.
96
+ *
97
+ * @param data - Key-value pairs to include in the CTRF report's `extra` field
98
+ * @returns Promise (can be ignored; completes when attachment is written)
99
+ *
100
+ * @remarks
101
+ * - **Arrays**: Concatenated across calls (duplicates allowed)
102
+ * - **Objects**: Deep merged (nested keys preserved)
103
+ * - **Primitives**: Later calls overwrite earlier ones
104
+ * - Safe to call from helper functions - binds to the active test automatically
105
+ * - No-op outside test context (e.g., in global setup)
106
+ *
107
+ * @example
108
+ * // Arrays concatenate
109
+ * ctrf.extra({ tags: ['smoke'] })
110
+ * ctrf.extra({ tags: ['e2e'] })
111
+ * // → { tags: ['smoke', 'e2e'] }
112
+ *
113
+ * // Objects merge deeply
114
+ * ctrf.extra({ build: { id: 'abc' } })
115
+ * ctrf.extra({ build: { url: 'https://...' } })
116
+ * // → { build: { id: 'abc', url: 'https://...' } }
117
+ *
118
+ * // Primitives overwrite
119
+ * ctrf.extra({ owner: 'platform-team' })
120
+ * ctrf.extra({ owner: 'checkout-team' })
121
+ * // → { owner: 'checkout-team' }
122
+ */
123
+ function extra(data) {
124
+ return __awaiter(this, void 0, void 0, function* () {
125
+ yield resolveTransport().send({ type: 'metadata', data });
126
+ });
127
+ }
128
+ /** CTRF runtime API namespace */
129
+ exports.ctrf = { extra };
@@ -43,6 +43,29 @@ declare class GenerateCtrfReport implements Reporter {
43
43
  setEnvironmentDetails(reporterConfigOptions: ReporterConfigOptions): void;
44
44
  hasEnvironmentDetails(environment: CtrfEnvironment): boolean;
45
45
  extractMetadata(testResult: TestResult): any;
46
+ /**
47
+ * Deep merge with array concatenation.
48
+ * Arrays are concatenated, objects are recursively merged, primitives overwrite.
49
+ */
50
+ private deepMerge;
51
+ /**
52
+ * Extract and merge runtime data from CTRF message attachments.
53
+ *
54
+ * Runtime messages are internal transport artifacts used to send data from
55
+ * test code to the reporter via test.info().attach(). They are identified
56
+ * by the CTRF_RUNTIME_MESSAGE_CONTENT_TYPE content type.
57
+ *
58
+ * ## Merge Semantics (deep merge with array concatenation)
59
+ *
60
+ * Messages are processed in attachment order with intelligent merging:
61
+ * - Arrays: Concatenated across calls (duplicates allowed)
62
+ * - Objects: Deep merged (nested keys preserved)
63
+ * - Primitives: Later values overwrite earlier ones
64
+ *
65
+ * @param testResult - The test result containing attachments
66
+ * @returns Merged runtime data or null if no CTRF messages found
67
+ */
68
+ extractRuntimeData(testResult: TestResult): Record<string, unknown> | null;
46
69
  updateStart(startTime: Date): number;
47
70
  calculateStopTime(startTime: Date, duration: number): number;
48
71
  buildSuitePath(test: TestCase): string;
@@ -51,6 +74,16 @@ declare class GenerateCtrfReport implements Reporter {
51
74
  countSuites(suite: Suite): number;
52
75
  writeReportToFile(data: CtrfReport): void;
53
76
  processStep(test: CtrfTest, step: TestStep): void;
77
+ /**
78
+ * Filter attachments to include in CTRF output.
79
+ *
80
+ * Excludes:
81
+ * - Attachments without a file path (body-only attachments)
82
+ * - CTRF runtime message attachments (internal transport artifacts)
83
+ *
84
+ * Runtime messages exist only to transmit data from test → reporter and
85
+ * are not user-facing attachments.
86
+ */
54
87
  filterValidAttachments(attachments: TestResult['attachments']): CtrfAttachment[];
55
88
  }
56
89
  export default GenerateCtrfReport;
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const path_1 = __importDefault(require("path"));
7
7
  const fs_1 = __importDefault(require("fs"));
8
8
  const crypto_1 = __importDefault(require("crypto"));
9
+ const adapter_1 = require("./adapter");
9
10
  class GenerateCtrfReport {
10
11
  constructor(config) {
11
12
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u;
@@ -152,6 +153,10 @@ class GenerateCtrfReport {
152
153
  if (this.reporterConfigOptions.annotations !== undefined) {
153
154
  test.extra = { annotations: testCase.annotations };
154
155
  }
156
+ const runtimeData = this.extractRuntimeData(testResult);
157
+ if (runtimeData !== null) {
158
+ test.extra = Object.assign(Object.assign({}, test.extra), runtimeData);
159
+ }
155
160
  if (testCase.results.length > 1) {
156
161
  const retryResults = testCase.results.slice(0, -1);
157
162
  test.retryAttempts = [];
@@ -259,6 +264,77 @@ class GenerateCtrfReport {
259
264
  }
260
265
  return null;
261
266
  }
267
+ /**
268
+ * Deep merge with array concatenation.
269
+ * Arrays are concatenated, objects are recursively merged, primitives overwrite.
270
+ */
271
+ deepMerge(target, source) {
272
+ const result = Object.assign({}, target);
273
+ for (const [key, sourceValue] of Object.entries(source)) {
274
+ const targetValue = result[key];
275
+ if (Array.isArray(sourceValue)) {
276
+ result[key] = Array.isArray(targetValue)
277
+ ? [...targetValue, ...sourceValue]
278
+ : [...sourceValue];
279
+ }
280
+ else if (sourceValue !== null &&
281
+ typeof sourceValue === 'object' &&
282
+ !Array.isArray(sourceValue)) {
283
+ result[key] =
284
+ targetValue !== null &&
285
+ typeof targetValue === 'object' &&
286
+ !Array.isArray(targetValue)
287
+ ? this.deepMerge(targetValue, sourceValue)
288
+ : Object.assign({}, sourceValue);
289
+ }
290
+ else {
291
+ result[key] = sourceValue;
292
+ }
293
+ }
294
+ return result;
295
+ }
296
+ /**
297
+ * Extract and merge runtime data from CTRF message attachments.
298
+ *
299
+ * Runtime messages are internal transport artifacts used to send data from
300
+ * test code to the reporter via test.info().attach(). They are identified
301
+ * by the CTRF_RUNTIME_MESSAGE_CONTENT_TYPE content type.
302
+ *
303
+ * ## Merge Semantics (deep merge with array concatenation)
304
+ *
305
+ * Messages are processed in attachment order with intelligent merging:
306
+ * - Arrays: Concatenated across calls (duplicates allowed)
307
+ * - Objects: Deep merged (nested keys preserved)
308
+ * - Primitives: Later values overwrite earlier ones
309
+ *
310
+ * @param testResult - The test result containing attachments
311
+ * @returns Merged runtime data or null if no CTRF messages found
312
+ */
313
+ extractRuntimeData(testResult) {
314
+ const runtimeData = {};
315
+ let hasData = false;
316
+ for (const attachment of testResult.attachments) {
317
+ if (attachment.contentType !== adapter_1.CTRF_RUNTIME_MESSAGE_CONTENT_TYPE) {
318
+ continue;
319
+ }
320
+ if (attachment.body === undefined) {
321
+ continue;
322
+ }
323
+ try {
324
+ const message = JSON.parse(attachment.body.toString('utf-8'));
325
+ if (message.type === 'metadata' && message.data !== null) {
326
+ Object.assign(runtimeData, this.deepMerge(runtimeData, message.data));
327
+ hasData = true;
328
+ }
329
+ }
330
+ catch (e) {
331
+ if (e instanceof Error) {
332
+ console.error(`Error parsing CTRF runtime message: ${e.message}`);
333
+ }
334
+ }
335
+ }
336
+ return hasData ? runtimeData : null;
337
+ }
262
338
  updateStart(startTime) {
263
339
  const date = new Date(startTime);
264
340
  const unixEpochTime = Math.floor(date.getTime() / 1000);
@@ -344,9 +420,27 @@ class GenerateCtrfReport {
344
420
  });
345
421
  }
346
422
  }
423
+ /**
424
+ * Filter attachments to include in CTRF output.
425
+ *
426
+ * Excludes:
427
+ * - Attachments without a file path (body-only attachments)
428
+ * - CTRF runtime message attachments (internal transport artifacts)
429
+ *
430
+ * Runtime messages exist only to transmit data from test → reporter and
431
+ * are not user-facing attachments.
432
+ */
347
433
  filterValidAttachments(attachments) {
348
434
  return attachments
349
- .filter((attachment) => attachment.path !== undefined)
435
+ .filter((attachment) => {
436
+ if (attachment.path === undefined) {
437
+ return false;
438
+ }
439
+ if (attachment.contentType === adapter_1.CTRF_RUNTIME_MESSAGE_CONTENT_TYPE) {
440
+ return false;
441
+ }
442
+ return true;
443
+ })
350
444
  .map((attachment) => {
351
445
  var _a;
352
446
  return ({
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Unit tests for GenerateCtrfReport deep merge functionality
3
+ */
4
+ export {};
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ /**
3
+ * Unit tests for GenerateCtrfReport deep merge functionality
4
+ */
5
+ var __importDefault = (this && this.__importDefault) || function (mod) {
6
+ return (mod && mod.__esModule) ? mod : { "default": mod };
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ const generate_report_1 = __importDefault(require("./generate-report"));
10
+ describe('GenerateCtrfReport', () => {
11
+ describe('deepMerge', () => {
12
+ let reporter;
13
+ beforeEach(() => {
14
+ reporter = new generate_report_1.default();
15
+ });
16
+ it('should concatenate arrays', () => {
17
+ const target = { tags: ['smoke'] };
18
+ const source = { tags: ['e2e'] };
19
+ // @ts-expect-error - accessing private method for testing
20
+ const result = reporter.deepMerge(target, source);
21
+ expect(result).toEqual({ tags: ['smoke', 'e2e'] });
22
+ });
23
+ it('should concatenate arrays from empty target', () => {
24
+ const target = {};
25
+ const source = { tags: ['smoke'] };
26
+ // @ts-expect-error - accessing private method for testing
27
+ const result = reporter.deepMerge(target, source);
28
+ expect(result).toEqual({ tags: ['smoke'] });
29
+ });
30
+ it('should merge objects deeply', () => {
31
+ const target = { build: { id: '123' } };
32
+ const source = { build: { branch: 'main' } };
33
+ // @ts-expect-error - accessing private method for testing
34
+ const result = reporter.deepMerge(target, source);
35
+ expect(result).toEqual({ build: { id: '123', branch: 'main' } });
36
+ });
37
+ it('should merge nested objects deeply', () => {
38
+ const target = { meta: { build: { id: '123' } } };
39
+ const source = { meta: { build: { url: 'https://...' } } };
40
+ // @ts-expect-error - accessing private method for testing
41
+ const result = reporter.deepMerge(target, source);
42
+ expect(result).toEqual({
43
+ meta: { build: { id: '123', url: 'https://...' } },
44
+ });
45
+ });
46
+ it('should overwrite primitives', () => {
47
+ const target = { owner: 'platform-team' };
48
+ const source = { owner: 'checkout-team' };
49
+ // @ts-expect-error - accessing private method for testing
50
+ const result = reporter.deepMerge(target, source);
51
+ expect(result).toEqual({ owner: 'checkout-team' });
52
+ });
53
+ it('should preserve non-overlapping keys', () => {
54
+ const target = { owner: 'platform-team', priority: 'P1' };
55
+ const source = { retries: 3 };
56
+ // @ts-expect-error - accessing private method for testing
57
+ const result = reporter.deepMerge(target, source);
58
+ expect(result).toEqual({
59
+ owner: 'platform-team',
60
+ priority: 'P1',
61
+ retries: 3,
62
+ });
63
+ });
64
+ it('should handle complex nested structures', () => {
65
+ const target = {
66
+ tags: ['smoke'],
67
+ build: { id: '123', metadata: { author: 'alice' } },
68
+ retries: 1,
69
+ };
70
+ const source = {
71
+ tags: ['e2e', 'regression'],
72
+ build: { branch: 'main', metadata: { timestamp: '2026-02-06' } },
73
+ retries: 2,
74
+ owner: 'platform',
75
+ };
76
+ // @ts-expect-error - accessing private method for testing
77
+ const result = reporter.deepMerge(target, source);
78
+ expect(result).toEqual({
79
+ tags: ['smoke', 'e2e', 'regression'],
80
+ build: {
81
+ id: '123',
82
+ branch: 'main',
83
+ metadata: { author: 'alice', timestamp: '2026-02-06' },
84
+ },
85
+ retries: 2,
86
+ owner: 'platform',
87
+ });
88
+ });
89
+ it('should replace object with primitive', () => {
90
+ const target = { config: { timeout: 5000 } };
91
+ const source = { config: 'default' };
92
+ // @ts-expect-error - accessing private method for testing
93
+ const result = reporter.deepMerge(target, source);
94
+ expect(result).toEqual({ config: 'default' });
95
+ });
96
+ it('should replace primitive with object', () => {
97
+ const target = { config: 'default' };
98
+ const source = { config: { timeout: 5000 } };
99
+ // @ts-expect-error - accessing private method for testing
100
+ const result = reporter.deepMerge(target, source);
101
+ expect(result).toEqual({ config: { timeout: 5000 } });
102
+ });
103
+ it('should replace primitive with array', () => {
104
+ const target = { tags: 'smoke' };
105
+ const source = { tags: ['e2e'] };
106
+ // @ts-expect-error - accessing private method for testing
107
+ const result = reporter.deepMerge(target, source);
108
+ expect(result).toEqual({ tags: ['e2e'] });
109
+ });
110
+ it('should handle null values', () => {
111
+ const target = { value: null };
112
+ const source = { value: 'something' };
113
+ // @ts-expect-error - accessing private method for testing
114
+ const result = reporter.deepMerge(target, source);
115
+ expect(result).toEqual({ value: 'something' });
116
+ });
117
+ it('should handle empty objects', () => {
118
+ const target = {};
119
+ const source = { owner: 'platform' };
120
+ // @ts-expect-error - accessing private method for testing
121
+ const result = reporter.deepMerge(target, source);
122
+ expect(result).toEqual({ owner: 'platform' });
123
+ });
124
+ it('should not mutate original objects', () => {
125
+ const target = { tags: ['smoke'], build: { id: '123' } };
126
+ const source = { tags: ['e2e'], build: { branch: 'main' } };
127
+ // @ts-expect-error - accessing private method for testing
128
+ reporter.deepMerge(target, source);
129
+ expect(target).toEqual({ tags: ['smoke'], build: { id: '123' } });
130
+ expect(source).toEqual({ tags: ['e2e'], build: { branch: 'main' } });
131
+ });
132
+ });
133
+ });
package/dist/index.d.ts CHANGED
@@ -1 +1,2 @@
1
1
  export { default } from './generate-report';
2
+ export { ctrf, extra } from './adapter';
package/dist/index.js CHANGED
@@ -3,6 +3,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.default = void 0;
6
+ exports.extra = exports.ctrf = exports.default = void 0;
7
7
  var generate_report_1 = require("./generate-report");
8
8
  Object.defineProperty(exports, "default", { enumerable: true, get: function () { return __importDefault(generate_report_1).default; } });
9
+ var adapter_1 = require("./adapter");
10
+ Object.defineProperty(exports, "ctrf", { enumerable: true, get: function () { return adapter_1.ctrf; } });
11
+ Object.defineProperty(exports, "extra", { enumerable: true, get: function () { return adapter_1.extra; } });
package/package.json CHANGED
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "name": "playwright-ctrf-json-reporter",
3
- "version": "0.0.27",
3
+ "version": "0.0.28-next-1",
4
4
  "description": "A Playwright JSON test reporter to create test results reports",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
7
7
  "build": "tsc",
8
8
  "test": "jest",
9
- "lint": "eslint . --ext .ts --fix",
10
- "lint-check": "eslint . --ext .ts",
9
+ "lint": "eslint . --fix",
10
+ "lint:check": "eslint .",
11
11
  "format": "prettier --write .",
12
- "format-check": "prettier --check .",
13
- "all": "npm run lint && npm run format-check && npm run test && npm run build"
12
+ "format:check": "prettier --check .",
13
+ "all": "npm run lint && npm run format:check && npm run test && npm run build"
14
14
  },
15
15
  "repository": {
16
16
  "type": "git",
@@ -25,23 +25,43 @@
25
25
  "dist/",
26
26
  "README.md"
27
27
  ],
28
- "author": "Matthew Thomas",
28
+ "author": "Matthew Poulton-White",
29
29
  "license": "MIT",
30
+ "keywords": [
31
+ "ctrf",
32
+ "common-test-report-format",
33
+ "test-reporting",
34
+ "test-results",
35
+ "testing",
36
+ "json",
37
+ "ci",
38
+ "playwright",
39
+ "playwright-reporter"
40
+ ],
41
+ "security": {
42
+ "email": "security@ctrf.io",
43
+ "url": "https://github.com/ctrf-io/.github/blob/main/SECURITY.md"
44
+ },
30
45
  "devDependencies": {
31
- "@playwright/test": "^1.55.0",
32
- "@types/jest": "^29.5.6",
33
- "@types/node": "^20.8.7",
34
- "@typescript-eslint/eslint-plugin": "^6.12.0",
35
- "eslint": "^8.54.0",
36
- "eslint-config-prettier": "^9.0.0",
37
- "eslint-config-standard-with-typescript": "^40.0.0",
38
- "eslint-plugin-import": "^2.29.0",
39
- "eslint-plugin-n": "^16.3.1",
40
- "eslint-plugin-prettier": "^5.0.1",
41
- "eslint-plugin-promise": "^6.1.1",
42
- "jest": "^29.7.0",
43
- "prettier": "^3.1.0",
44
- "ts-jest": "^29.1.1",
45
- "typescript": "^5.3.2"
46
+ "@playwright/test": "^1.58.2",
47
+ "@types/jest": "^30.0.0",
48
+ "@types/node": "^25.2.2",
49
+ "@typescript-eslint/eslint-plugin": "^8.55.0",
50
+ "@typescript-eslint/parser": "^8.55.0",
51
+ "eslint": "^9.39.2",
52
+ "eslint-config-prettier": "^10.1.8",
53
+ "eslint-plugin-import": "^2.32.0",
54
+ "eslint-plugin-n": "^17.23.2",
55
+ "eslint-plugin-prettier": "^5.5.5",
56
+ "eslint-plugin-promise": "^7.2.1",
57
+ "jest": "^30.2.0",
58
+ "jest-ctrf-json-reporter": "^0.0.10",
59
+ "prettier": "^3.8.1",
60
+ "ts-jest": "^29.4.6",
61
+ "typescript": "^5.9.3",
62
+ "typescript-eslint": "^8.55.0"
63
+ },
64
+ "dependencies": {
65
+ "ctrf": "^0.2.0-next-0"
46
66
  }
47
67
  }