playwright-ctrf-json-reporter 0.0.26 → 0.0.28-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
@@ -122,6 +122,7 @@ reporter: [
122
122
  repositoryName: "ctrf-json", // Optional: Specify the repository name.
123
123
  repositoryUrl: "https://gh.io", // Optional: Specify the repository url.
124
124
  branchName: "main", // Optional: Specify the branch name.
125
+ commit: "abc123", // Optional: Specify the commit id.
125
126
  testEnvironment: "staging" // Optional: Specify the test environment (e.g. staging, production).
126
127
  }]
127
128
  ],
@@ -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,66 @@
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
+ * - Shallow merge: keys from later calls overwrite earlier ones
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
+ * - Multiple calls accumulate; later keys overwrite earlier ones (shallow merge)
55
+ * - Safe to call from helper functions - binds to the active test automatically
56
+ * - No-op outside test context (e.g., in global setup)
57
+ *
58
+ * @example
59
+ * ctrf.extra({ owner: 'platform-team' })
60
+ * ctrf.extra({ executionId: uuid(), retryable: true })
61
+ */
62
+ export declare function extra(data: Record<string, unknown>): Promise<void>;
63
+ /** CTRF runtime API namespace */
64
+ export declare const ctrf: {
65
+ readonly extra: typeof extra;
66
+ };
@@ -0,0 +1,113 @@
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
+ * - Shallow merge: keys from later calls overwrite earlier ones
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.extra = exports.CTRF_RUNTIME_MESSAGE_CONTENT_TYPE = void 0;
43
+ const test_1 = require("@playwright/test");
44
+ /**
45
+ * Content type used to identify CTRF runtime messages in attachments.
46
+ * The reporter will look for attachments with this content type.
47
+ */
48
+ exports.CTRF_RUNTIME_MESSAGE_CONTENT_TYPE = 'application/vnd.ctrf.message+json';
49
+ /**
50
+ * Silent fallback when no test is active - prevents errors in setup/teardown code.
51
+ */
52
+ const nullTransport = {
53
+ send: () => __awaiter(void 0, void 0, void 0, function* () {
54
+ /* intentionally empty */
55
+ }),
56
+ };
57
+ /**
58
+ * Attaches metadata payloads to the current Playwright test as hidden attachments.
59
+ * The reporter extracts these by content type during report generation.
60
+ */
61
+ function createPlaywrightTransport() {
62
+ let sequence = 0;
63
+ return {
64
+ send(payload) {
65
+ return __awaiter(this, void 0, void 0, function* () {
66
+ const tag = `__ctrf_${++sequence}_${Date.now()}`;
67
+ const data = JSON.stringify(payload);
68
+ yield test_1.test.info().attach(tag, {
69
+ contentType: exports.CTRF_RUNTIME_MESSAGE_CONTENT_TYPE,
70
+ body: Buffer.from(data),
71
+ });
72
+ });
73
+ },
74
+ };
75
+ }
76
+ const activeTransport = createPlaywrightTransport();
77
+ /**
78
+ * Resolves the active transport, or a null transport if outside a test.
79
+ */
80
+ const resolveTransport = () => {
81
+ try {
82
+ if (test_1.test.info())
83
+ return activeTransport;
84
+ }
85
+ catch (_a) {
86
+ // Outside test context
87
+ }
88
+ return nullTransport;
89
+ };
90
+ /* ═══════════════════════════════════════════════════════════════════════════
91
+ * Public API
92
+ * ═══════════════════════════════════════════════════════════════════════════ */
93
+ /**
94
+ * Attach custom metadata to the current test.
95
+ *
96
+ * @param data - Key-value pairs to include in the CTRF report's `extra` field
97
+ * @returns Promise (can be ignored; completes when attachment is written)
98
+ *
99
+ * @remarks
100
+ * - Multiple calls accumulate; later keys overwrite earlier ones (shallow merge)
101
+ * - Safe to call from helper functions - binds to the active test automatically
102
+ * - No-op outside test context (e.g., in global setup)
103
+ *
104
+ * @example
105
+ * ctrf.extra({ owner: 'platform-team' })
106
+ * ctrf.extra({ executionId: uuid(), retryable: true })
107
+ */
108
+ function extra(data) {
109
+ return resolveTransport().send({ type: 'metadata', data });
110
+ }
111
+ exports.extra = extra;
112
+ /** CTRF runtime API namespace */
113
+ exports.ctrf = { extra };
@@ -18,6 +18,7 @@ interface ReporterConfigOptions {
18
18
  repositoryName?: string | undefined;
19
19
  repositoryUrl?: string | undefined;
20
20
  branchName?: string | undefined;
21
+ commit?: string | undefined;
21
22
  testEnvironment?: string | undefined;
22
23
  }
23
24
  declare class GenerateCtrfReport implements Reporter {
@@ -42,6 +43,24 @@ declare class GenerateCtrfReport implements Reporter {
42
43
  setEnvironmentDetails(reporterConfigOptions: ReporterConfigOptions): void;
43
44
  hasEnvironmentDetails(environment: CtrfEnvironment): boolean;
44
45
  extractMetadata(testResult: TestResult): any;
46
+ /**
47
+ * Extract and merge runtime data from CTRF message attachments.
48
+ *
49
+ * Runtime messages are internal transport artifacts used to send data from
50
+ * test code to the reporter via test.info().attach(). They are identified
51
+ * by the CTRF_RUNTIME_MESSAGE_CONTENT_TYPE content type.
52
+ *
53
+ * ## Merge Semantics (shallow merge)
54
+ *
55
+ * Messages are processed in attachment order and merged using Object.assign:
56
+ * - Later messages overwrite earlier keys with the same name
57
+ * - Non-overlapping keys are preserved
58
+ * - Arrays and objects are replaced wholesale, not deep-merged
59
+ *
60
+ * @param testResult - The test result containing attachments
61
+ * @returns Merged runtime data or null if no CTRF messages found
62
+ */
63
+ extractRuntimeData(testResult: TestResult): Record<string, unknown> | null;
45
64
  updateStart(startTime: Date): number;
46
65
  calculateStopTime(startTime: Date, duration: number): number;
47
66
  buildSuitePath(test: TestCase): string;
@@ -50,6 +69,16 @@ declare class GenerateCtrfReport implements Reporter {
50
69
  countSuites(suite: Suite): number;
51
70
  writeReportToFile(data: CtrfReport): void;
52
71
  processStep(test: CtrfTest, step: TestStep): void;
72
+ /**
73
+ * Filter attachments to include in CTRF output.
74
+ *
75
+ * Excludes:
76
+ * - Attachments without a file path (body-only attachments)
77
+ * - CTRF runtime message attachments (internal transport artifacts)
78
+ *
79
+ * Runtime messages exist only to transmit data from test → reporter and
80
+ * are not user-facing attachments.
81
+ */
53
82
  filterValidAttachments(attachments: TestResult['attachments']): CtrfAttachment[];
54
83
  }
55
84
  export default GenerateCtrfReport;
@@ -6,9 +6,10 @@ 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
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
12
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u;
12
13
  this.reporterName = 'playwright-ctrf-json-reporter';
13
14
  this.defaultOutputFile = 'ctrf-report.json';
14
15
  this.defaultOutputDir = 'ctrf';
@@ -30,7 +31,8 @@ class GenerateCtrfReport {
30
31
  repositoryName: (_q = config === null || config === void 0 ? void 0 : config.repositoryName) !== null && _q !== void 0 ? _q : undefined,
31
32
  repositoryUrl: (_r = config === null || config === void 0 ? void 0 : config.repositoryUrl) !== null && _r !== void 0 ? _r : undefined,
32
33
  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,
34
+ commit: (_t = config === null || config === void 0 ? void 0 : config.commit) !== null && _t !== void 0 ? _t : undefined,
35
+ testEnvironment: (_u = config === null || config === void 0 ? void 0 : config.testEnvironment) !== null && _u !== void 0 ? _u : undefined,
34
36
  };
35
37
  this.ctrfReport = {
36
38
  reportFormat: 'CTRF',
@@ -148,9 +150,15 @@ class GenerateCtrfReport {
148
150
  test.attachments = this.filterValidAttachments(testResult.attachments);
149
151
  test.stdout = testResult.stdout.map((item) => Buffer.isBuffer(item) ? item.toString() : String(item));
150
152
  test.stderr = testResult.stderr.map((item) => Buffer.isBuffer(item) ? item.toString() : String(item));
153
+ // Initialize extra with annotations if configured
151
154
  if (this.reporterConfigOptions.annotations !== undefined) {
152
155
  test.extra = { annotations: testCase.annotations };
153
156
  }
157
+ // Merge runtime data from ctrf.extra() calls
158
+ const runtimeData = this.extractRuntimeData(testResult);
159
+ if (runtimeData !== null) {
160
+ test.extra = Object.assign(Object.assign({}, test.extra), runtimeData);
161
+ }
154
162
  if (testCase.results.length > 1) {
155
163
  const retryResults = testCase.results.slice(0, -1);
156
164
  test.retryAttempts = [];
@@ -228,6 +236,9 @@ class GenerateCtrfReport {
228
236
  if (reporterConfigOptions.branchName !== undefined) {
229
237
  this.ctrfEnvironment.branchName = reporterConfigOptions.branchName;
230
238
  }
239
+ if (reporterConfigOptions.commit !== undefined) {
240
+ this.ctrfEnvironment.commit = reporterConfigOptions.commit;
241
+ }
231
242
  if (reporterConfigOptions.testEnvironment !== undefined) {
232
243
  this.ctrfEnvironment.testEnvironment =
233
244
  reporterConfigOptions.testEnvironment;
@@ -255,6 +266,49 @@ class GenerateCtrfReport {
255
266
  }
256
267
  return null;
257
268
  }
269
+ /**
270
+ * Extract and merge runtime data from CTRF message attachments.
271
+ *
272
+ * Runtime messages are internal transport artifacts used to send data from
273
+ * test code to the reporter via test.info().attach(). They are identified
274
+ * by the CTRF_RUNTIME_MESSAGE_CONTENT_TYPE content type.
275
+ *
276
+ * ## Merge Semantics (shallow merge)
277
+ *
278
+ * Messages are processed in attachment order and merged using Object.assign:
279
+ * - Later messages overwrite earlier keys with the same name
280
+ * - Non-overlapping keys are preserved
281
+ * - Arrays and objects are replaced wholesale, not deep-merged
282
+ *
283
+ * @param testResult - The test result containing attachments
284
+ * @returns Merged runtime data or null if no CTRF messages found
285
+ */
286
+ extractRuntimeData(testResult) {
287
+ const runtimeData = {};
288
+ let hasData = false;
289
+ for (const attachment of testResult.attachments) {
290
+ if (attachment.contentType !== adapter_1.CTRF_RUNTIME_MESSAGE_CONTENT_TYPE) {
291
+ continue;
292
+ }
293
+ if (attachment.body === undefined) {
294
+ continue;
295
+ }
296
+ try {
297
+ const message = JSON.parse(attachment.body.toString('utf-8'));
298
+ if (message.type === 'metadata' && message.data !== null) {
299
+ Object.assign(runtimeData, message.data);
300
+ hasData = true;
301
+ }
302
+ }
303
+ catch (e) {
304
+ // Silently ignore malformed messages
305
+ if (e instanceof Error) {
306
+ console.error(`Error parsing CTRF runtime message: ${e.message}`);
307
+ }
308
+ }
309
+ }
310
+ return hasData ? runtimeData : null;
311
+ }
258
312
  updateStart(startTime) {
259
313
  const date = new Date(startTime);
260
314
  const unixEpochTime = Math.floor(date.getTime() / 1000);
@@ -340,9 +394,29 @@ class GenerateCtrfReport {
340
394
  });
341
395
  }
342
396
  }
397
+ /**
398
+ * Filter attachments to include in CTRF output.
399
+ *
400
+ * Excludes:
401
+ * - Attachments without a file path (body-only attachments)
402
+ * - CTRF runtime message attachments (internal transport artifacts)
403
+ *
404
+ * Runtime messages exist only to transmit data from test → reporter and
405
+ * are not user-facing attachments.
406
+ */
343
407
  filterValidAttachments(attachments) {
344
408
  return attachments
345
- .filter((attachment) => attachment.path !== undefined)
409
+ .filter((attachment) => {
410
+ // Exclude attachments without a path
411
+ if (attachment.path === undefined) {
412
+ return false;
413
+ }
414
+ // Exclude CTRF runtime messages (internal transport)
415
+ if (attachment.contentType === adapter_1.CTRF_RUNTIME_MESSAGE_CONTENT_TYPE) {
416
+ return false;
417
+ }
418
+ return true;
419
+ })
346
420
  .map((attachment) => {
347
421
  var _a;
348
422
  return ({
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,6 +1,6 @@
1
1
  {
2
2
  "name": "playwright-ctrf-json-reporter",
3
- "version": "0.0.26",
3
+ "version": "0.0.28-next-0",
4
4
  "description": "A Playwright JSON test reporter to create test results reports",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -17,8 +17,7 @@
17
17
  "url": "git+https://github.com/ctrf-io/playwright-ctrf-json-reporter.git"
18
18
  },
19
19
  "publishConfig": {
20
- "access": "public",
21
- "provenance": true
20
+ "access": "public"
22
21
  },
23
22
  "homepage": "https://ctrf.io",
24
23
  "files": [