@heal-dev/heal-playwright-tracer 1.0.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.
Files changed (125) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +245 -0
  3. package/dist/application/babel-playwright-tracer-plugin/index.d.ts +16 -0
  4. package/dist/application/babel-playwright-tracer-plugin/index.js +111 -0
  5. package/dist/application/heal-config/index.d.ts +8 -0
  6. package/dist/application/heal-config/index.js +22 -0
  7. package/dist/application/heal-config/registry.d.ts +37 -0
  8. package/dist/application/heal-config/registry.js +64 -0
  9. package/dist/application/heal-config/types.d.ts +73 -0
  10. package/dist/application/heal-config/types.js +7 -0
  11. package/dist/application/playwright-fixture/index.d.ts +14 -0
  12. package/dist/application/playwright-fixture/index.js +234 -0
  13. package/dist/application/trace-event-recorder-runtime/index.d.ts +15 -0
  14. package/dist/application/trace-event-recorder-runtime/index.js +68 -0
  15. package/dist/domain/code-hook-injector/service/meta-fields/enclosing-scope-labeler.d.ts +12 -0
  16. package/dist/domain/code-hook-injector/service/meta-fields/enclosing-scope-labeler.js +53 -0
  17. package/dist/domain/code-hook-injector/service/meta-fields/leading-comment-extractor.d.ts +14 -0
  18. package/dist/domain/code-hook-injector/service/meta-fields/leading-comment-extractor.js +20 -0
  19. package/dist/domain/code-hook-injector/service/meta-fields/relative-file-path.d.ts +6 -0
  20. package/dist/domain/code-hook-injector/service/meta-fields/relative-file-path.js +57 -0
  21. package/dist/domain/code-hook-injector/service/meta-fields/source-snippet-extractor.d.ts +12 -0
  22. package/dist/domain/code-hook-injector/service/meta-fields/source-snippet-extractor.js +28 -0
  23. package/dist/domain/code-hook-injector/service/playwright-import-rewriter.d.ts +10 -0
  24. package/dist/domain/code-hook-injector/service/playwright-import-rewriter.js +21 -0
  25. package/dist/domain/code-hook-injector/service/statement-analysis/cjs-artifact-detector.d.ts +14 -0
  26. package/dist/domain/code-hook-injector/service/statement-analysis/cjs-artifact-detector.js +30 -0
  27. package/dist/domain/code-hook-injector/service/statement-analysis/for-head-declaration-detector.d.ts +10 -0
  28. package/dist/domain/code-hook-injector/service/statement-analysis/for-head-declaration-detector.js +18 -0
  29. package/dist/domain/code-hook-injector/service/statement-analysis/leaf-statement-classifier.d.ts +15 -0
  30. package/dist/domain/code-hook-injector/service/statement-analysis/leaf-statement-classifier.js +66 -0
  31. package/dist/domain/code-hook-injector/service/statement-analysis/non-wrappable-statement.d.ts +11 -0
  32. package/dist/domain/code-hook-injector/service/statement-analysis/non-wrappable-statement.js +20 -0
  33. package/dist/domain/code-hook-injector/service/trace-hook/enter-meta-literal.d.ts +20 -0
  34. package/dist/domain/code-hook-injector/service/trace-hook/enter-meta-literal.js +34 -0
  35. package/dist/domain/code-hook-injector/service/trace-hook/global-trace-call.d.ts +10 -0
  36. package/dist/domain/code-hook-injector/service/trace-hook/global-trace-call.js +16 -0
  37. package/dist/domain/code-hook-injector/service/trace-hook/try-finally-wrapper.d.ts +18 -0
  38. package/dist/domain/code-hook-injector/service/trace-hook/try-finally-wrapper.js +44 -0
  39. package/dist/domain/code-hook-injector/service/trace-hook/variable-declaration-hoister.d.ts +20 -0
  40. package/dist/domain/code-hook-injector/service/trace-hook/variable-declaration-hoister.js +27 -0
  41. package/dist/domain/code-hook-injector/service/traced-file-matcher.d.ts +10 -0
  42. package/dist/domain/code-hook-injector/service/traced-file-matcher.js +26 -0
  43. package/dist/domain/trace-event-recorder/model/enter-meta.d.ts +24 -0
  44. package/dist/domain/trace-event-recorder/model/enter-meta.js +7 -0
  45. package/dist/domain/trace-event-recorder/model/global-names.d.ts +8 -0
  46. package/dist/domain/trace-event-recorder/model/global-names.js +20 -0
  47. package/dist/domain/trace-event-recorder/model/serialized-error.d.ts +16 -0
  48. package/dist/domain/trace-event-recorder/model/serialized-error.js +7 -0
  49. package/dist/domain/trace-event-recorder/model/statement-trace-schema.d.ts +171 -0
  50. package/dist/domain/trace-event-recorder/model/statement-trace-schema.js +33 -0
  51. package/dist/domain/trace-event-recorder/model/trace-schema.d.ts +114 -0
  52. package/dist/domain/trace-event-recorder/model/trace-schema.js +16 -0
  53. package/dist/domain/trace-event-recorder/port/clock.d.ts +9 -0
  54. package/dist/domain/trace-event-recorder/port/clock.js +7 -0
  55. package/dist/domain/trace-event-recorder/port/heal-trace-exporter.d.ts +11 -0
  56. package/dist/domain/trace-event-recorder/port/heal-trace-exporter.js +7 -0
  57. package/dist/domain/trace-event-recorder/port/system-info-provider.d.ts +18 -0
  58. package/dist/domain/trace-event-recorder/port/system-info-provider.js +7 -0
  59. package/dist/domain/trace-event-recorder/port/trace-event-consumer.d.ts +11 -0
  60. package/dist/domain/trace-event-recorder/port/trace-event-consumer.js +7 -0
  61. package/dist/domain/trace-event-recorder/service/active-enter-stack.d.ts +15 -0
  62. package/dist/domain/trace-event-recorder/service/active-enter-stack.js +34 -0
  63. package/dist/domain/trace-event-recorder/service/event-builders/enter-event-builder.d.ts +8 -0
  64. package/dist/domain/trace-event-recorder/service/event-builders/enter-event-builder.js +37 -0
  65. package/dist/domain/trace-event-recorder/service/event-builders/meta-event-builder.d.ts +7 -0
  66. package/dist/domain/trace-event-recorder/service/event-builders/meta-event-builder.js +19 -0
  67. package/dist/domain/trace-event-recorder/service/event-builders/ok-event-builder.d.ts +7 -0
  68. package/dist/domain/trace-event-recorder/service/event-builders/ok-event-builder.js +27 -0
  69. package/dist/domain/trace-event-recorder/service/event-builders/throw-event-builder.d.ts +7 -0
  70. package/dist/domain/trace-event-recorder/service/event-builders/throw-event-builder.js +23 -0
  71. package/dist/domain/trace-event-recorder/service/exporters/composite-heal-trace-exporter.d.ts +12 -0
  72. package/dist/domain/trace-event-recorder/service/exporters/composite-heal-trace-exporter.js +32 -0
  73. package/dist/domain/trace-event-recorder/service/index.d.ts +10 -0
  74. package/dist/domain/trace-event-recorder/service/index.js +15 -0
  75. package/dist/domain/trace-event-recorder/service/projectors/index.d.ts +6 -0
  76. package/dist/domain/trace-event-recorder/service/projectors/index.js +10 -0
  77. package/dist/domain/trace-event-recorder/service/projectors/statement-projector.d.ts +26 -0
  78. package/dist/domain/trace-event-recorder/service/projectors/statement-projector.js +183 -0
  79. package/dist/domain/trace-event-recorder/service/serializers/error-serializer.d.ts +8 -0
  80. package/dist/domain/trace-event-recorder/service/serializers/error-serializer.js +49 -0
  81. package/dist/domain/trace-event-recorder/service/serializers/variable-snapshot-serializer.d.ts +7 -0
  82. package/dist/domain/trace-event-recorder/service/serializers/variable-snapshot-serializer.js +102 -0
  83. package/dist/domain/trace-event-recorder/service/trace-event-recorder-state.d.ts +19 -0
  84. package/dist/domain/trace-event-recorder/service/trace-event-recorder-state.js +7 -0
  85. package/dist/domain/trace-event-recorder/service/trace-event-recorder.d.ts +56 -0
  86. package/dist/domain/trace-event-recorder/service/trace-event-recorder.js +80 -0
  87. package/dist/index.d.ts +11 -0
  88. package/dist/index.js +43 -0
  89. package/dist/infrastructure/ndjson-exporter-adapter/index.d.ts +6 -0
  90. package/dist/infrastructure/ndjson-exporter-adapter/index.js +10 -0
  91. package/dist/infrastructure/ndjson-exporter-adapter/ndjson-exporter.d.ts +13 -0
  92. package/dist/infrastructure/ndjson-exporter-adapter/ndjson-exporter.js +77 -0
  93. package/dist/infrastructure/perf-hooks-clock-adapter/index.d.ts +6 -0
  94. package/dist/infrastructure/perf-hooks-clock-adapter/index.js +10 -0
  95. package/dist/infrastructure/perf-hooks-clock-adapter/perf-hooks-clock.d.ts +11 -0
  96. package/dist/infrastructure/perf-hooks-clock-adapter/perf-hooks-clock.js +22 -0
  97. package/dist/infrastructure/playwright-locator-screenshot-adapter/assertion-wrapper.d.ts +6 -0
  98. package/dist/infrastructure/playwright-locator-screenshot-adapter/assertion-wrapper.js +109 -0
  99. package/dist/infrastructure/playwright-locator-screenshot-adapter/index.d.ts +9 -0
  100. package/dist/infrastructure/playwright-locator-screenshot-adapter/index.js +21 -0
  101. package/dist/infrastructure/playwright-locator-screenshot-adapter/locator-patch.d.ts +11 -0
  102. package/dist/infrastructure/playwright-locator-screenshot-adapter/locator-patch.js +79 -0
  103. package/dist/infrastructure/playwright-locator-screenshot-adapter/overlay-helpers.d.ts +15 -0
  104. package/dist/infrastructure/playwright-locator-screenshot-adapter/overlay-helpers.js +33 -0
  105. package/dist/infrastructure/playwright-locator-screenshot-adapter/screenshot-capture-session.d.ts +26 -0
  106. package/dist/infrastructure/playwright-locator-screenshot-adapter/screenshot-capture-session.js +125 -0
  107. package/dist/infrastructure/playwright-step-tracking-adapter/index.d.ts +7 -0
  108. package/dist/infrastructure/playwright-step-tracking-adapter/index.js +10 -0
  109. package/dist/infrastructure/playwright-step-tracking-adapter/playwright-step-tracking-adapter.d.ts +14 -0
  110. package/dist/infrastructure/playwright-step-tracking-adapter/playwright-step-tracking-adapter.js +51 -0
  111. package/dist/infrastructure/playwright-test-context-adapter/heal-tag-prefix.d.ts +25 -0
  112. package/dist/infrastructure/playwright-test-context-adapter/heal-tag-prefix.js +28 -0
  113. package/dist/infrastructure/playwright-test-context-adapter/index.d.ts +8 -0
  114. package/dist/infrastructure/playwright-test-context-adapter/index.js +12 -0
  115. package/dist/infrastructure/playwright-test-context-adapter/playwright-test-context-adapter.d.ts +19 -0
  116. package/dist/infrastructure/playwright-test-context-adapter/playwright-test-context-adapter.js +43 -0
  117. package/dist/infrastructure/stdout-capture-adapter/index.d.ts +7 -0
  118. package/dist/infrastructure/stdout-capture-adapter/index.js +10 -0
  119. package/dist/infrastructure/stdout-capture-adapter/stdout-capture-session.d.ts +20 -0
  120. package/dist/infrastructure/stdout-capture-adapter/stdout-capture-session.js +47 -0
  121. package/dist/infrastructure/system-info-adapter/index.d.ts +6 -0
  122. package/dist/infrastructure/system-info-adapter/index.js +10 -0
  123. package/dist/infrastructure/system-info-adapter/system-info-adapter.d.ts +12 -0
  124. package/dist/infrastructure/system-info-adapter/system-info-adapter.js +83 -0
  125. package/package.json +95 -0
@@ -0,0 +1,183 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright: (c) Myia SAS 2026.
4
+ * This file and its contents are licensed under the AGPLv3 License.
5
+ * Please see the LICENSE file at the root of this repository
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.StatementProjector = void 0;
9
+ const statement_trace_schema_1 = require("../../model/statement-trace-schema");
10
+ class StatementProjector {
11
+ constructor(output) {
12
+ this.output = output;
13
+ // Every live statement, keyed by its enter-event seq. Used for
14
+ // parent lookup on nested enters and for ok/throw stamping.
15
+ this.allBySeq = new Map();
16
+ // Subset: statements whose runtime parent was null. These are
17
+ // the only ones that become their own NDJSON record.
18
+ this.rootsBySeq = new Map();
19
+ this.headerEmitted = false;
20
+ this.finalized = false;
21
+ }
22
+ write(event) {
23
+ if (this.finalized)
24
+ return;
25
+ switch (event.type) {
26
+ case 'meta': {
27
+ if (!this.headerEmitted) {
28
+ this.output.write({
29
+ kind: 'test-header',
30
+ schemaVersion: statement_trace_schema_1.HEAL_TRACE_SCHEMA_VERSION,
31
+ test: buildTestHeader(event),
32
+ });
33
+ this.headerEmitted = true;
34
+ }
35
+ return;
36
+ }
37
+ case 'enter': {
38
+ const live = { stmt: createStatement(event), enter: event };
39
+ this.allBySeq.set(event.seq, live);
40
+ if (event.parentSeq == null) {
41
+ this.rootsBySeq.set(event.seq, live);
42
+ }
43
+ else {
44
+ const parent = this.allBySeq.get(event.parentSeq);
45
+ if (parent)
46
+ parent.stmt.children.push(live.stmt);
47
+ // Orphaned (parent was never seen) — promote to root so
48
+ // the statement is not lost.
49
+ else
50
+ this.rootsBySeq.set(event.seq, live);
51
+ }
52
+ return;
53
+ }
54
+ case 'ok': {
55
+ const live = this.allBySeq.get(event.enterSeq);
56
+ if (!live)
57
+ return;
58
+ live.stmt.duration = event.duration;
59
+ if (event.vars)
60
+ live.stmt.vars = event.vars;
61
+ // Screenshot may have been stamped onto the enter event
62
+ // after the enter was emitted (locator-screenshots
63
+ // feature); pick it up now.
64
+ if (live.enter.screenshot)
65
+ live.stmt.screenshot = live.enter.screenshot;
66
+ this.maybeEmitRoot(event.enterSeq, live);
67
+ return;
68
+ }
69
+ case 'throw': {
70
+ if (event.enterSeq == null)
71
+ return;
72
+ const live = this.allBySeq.get(event.enterSeq);
73
+ if (!live)
74
+ return;
75
+ live.stmt.status = 'threw';
76
+ live.stmt.duration = event.duration;
77
+ live.stmt.error = event.error;
78
+ if (live.enter.screenshot)
79
+ live.stmt.screenshot = live.enter.screenshot;
80
+ this.maybeEmitRoot(event.enterSeq, live);
81
+ return;
82
+ }
83
+ default:
84
+ return;
85
+ }
86
+ }
87
+ // Called by `reset()` at the start of every test. Drops all
88
+ // in-progress statement state so the next test begins with a
89
+ // blank slate. Does NOT close the inner exporter — the fixture
90
+ // owns that lifecycle.
91
+ clear() {
92
+ this.allBySeq.clear();
93
+ this.rootsBySeq.clear();
94
+ this.headerEmitted = false;
95
+ }
96
+ /**
97
+ * Emit the final `test-result` record and close the underlying
98
+ * exporter. Called once at test teardown; the projector is unusable
99
+ * afterwards.
100
+ */
101
+ async finalize(result) {
102
+ if (this.finalized)
103
+ return;
104
+ this.finalized = true;
105
+ this.output.write({ kind: 'test-result', ...result });
106
+ await this.output.close();
107
+ }
108
+ maybeEmitRoot(seq, live) {
109
+ if (!this.rootsBySeq.has(seq))
110
+ return;
111
+ sortChildrenDeep(live.stmt);
112
+ this.output.write({ kind: 'statement', statement: live.stmt });
113
+ // Drop the subtree from the working maps — memory bound is
114
+ // now the maximum in-flight depth rather than the whole test.
115
+ this.dropSubtree(live.stmt);
116
+ this.rootsBySeq.delete(seq);
117
+ }
118
+ dropSubtree(stmt) {
119
+ this.allBySeq.delete(stmt.seq);
120
+ for (const child of stmt.children)
121
+ this.dropSubtree(child);
122
+ }
123
+ }
124
+ exports.StatementProjector = StatementProjector;
125
+ function createStatement(event) {
126
+ const stmt = {
127
+ seq: event.seq,
128
+ file: event.file,
129
+ line: event.startLine,
130
+ endLine: event.endLine,
131
+ kind: event.kind,
132
+ scope: event.scope,
133
+ source: event.source,
134
+ hasAwait: event.hasAwait,
135
+ step: event.step,
136
+ stepPath: event.stepPath,
137
+ status: 'ok',
138
+ duration: 0,
139
+ t: event.t,
140
+ pageUrl: event.pageUrl,
141
+ children: [],
142
+ };
143
+ if (event.screenshot)
144
+ stmt.screenshot = event.screenshot;
145
+ if (event.leadingComment != null)
146
+ stmt.leadingComment = event.leadingComment;
147
+ return stmt;
148
+ }
149
+ function sortChildrenDeep(stmt) {
150
+ if (stmt.children.length === 0)
151
+ return;
152
+ stmt.children.sort((a, b) => a.seq - b.seq);
153
+ for (const child of stmt.children)
154
+ sortChildrenDeep(child);
155
+ }
156
+ function buildTestHeader(meta) {
157
+ const env = {
158
+ nodeVersion: meta.nodeVersion,
159
+ platform: meta.platform,
160
+ arch: meta.arch,
161
+ hostname: meta.hostname,
162
+ isCI: meta.isCI,
163
+ cwd: meta.cwd,
164
+ gitSha: meta.gitSha,
165
+ pid: meta.pid,
166
+ };
167
+ const context = {
168
+ testId: meta.testId ?? '',
169
+ attempt: meta.attempt ?? 1,
170
+ ...(meta.testCaseId !== undefined ? { testCaseId: meta.testCaseId } : {}),
171
+ };
172
+ return {
173
+ title: meta.testTitle ?? '',
174
+ titlePath: meta.titlePath ?? [],
175
+ file: meta.testFile ?? '',
176
+ project: meta.projectName ?? '',
177
+ workerIndex: meta.workerIndex ?? 0,
178
+ retry: meta.retry ?? 0,
179
+ startedAt: meta.wallTime ?? 0,
180
+ env,
181
+ context,
182
+ };
183
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Copyright: (c) Myia SAS 2026.
3
+ * This file and its contents are licensed under the AGPLv3 License.
4
+ * Please see the LICENSE file at the root of this repository
5
+ */
6
+ import type { SerializedError } from '../../model/serialized-error';
7
+ export type { SerializedError };
8
+ export declare function serializeError(err: unknown): SerializedError;
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright: (c) Myia SAS 2026.
4
+ * This file and its contents are licensed under the AGPLv3 License.
5
+ * Please see the LICENSE file at the root of this repository
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.serializeError = serializeError;
9
+ const PLAYWRIGHT_ERROR_CTOR = /^(TimeoutError|PlaywrightError|LocatorError|PlaywrightTestError)$/;
10
+ const PLAYWRIGHT_STACK = /(node_modules\/playwright|node_modules\/@playwright)/;
11
+ function serializeError(err) {
12
+ if (err == null)
13
+ return { message: String(err) };
14
+ if (typeof err !== 'object')
15
+ return { message: String(err) };
16
+ const e = err;
17
+ const ctorName = (e.constructor && e.constructor.name) || '';
18
+ const stackStr = e.stack ? String(e.stack).split('\n').slice(0, 20).join('\n') : undefined;
19
+ const result = {
20
+ name: e.name || ctorName || 'Error',
21
+ message: e.message != null ? String(e.message) : String(err),
22
+ };
23
+ if (stackStr)
24
+ result.stack = stackStr;
25
+ result.isPlaywrightError =
26
+ PLAYWRIGHT_ERROR_CTOR.test(ctorName) || PLAYWRIGHT_STACK.test(stackStr || '');
27
+ const causes = [];
28
+ let cur = e.cause;
29
+ let d = 0;
30
+ while (cur != null && d < 5) {
31
+ if (typeof cur === 'object') {
32
+ const c = cur;
33
+ causes.push({
34
+ name: c.name || (c.constructor && c.constructor.name) || 'Error',
35
+ message: c.message != null ? String(c.message) : String(cur),
36
+ stack: c.stack ? String(c.stack).split('\n').slice(0, 10).join('\n') : undefined,
37
+ });
38
+ cur = c.cause;
39
+ }
40
+ else {
41
+ causes.push({ message: String(cur) });
42
+ break;
43
+ }
44
+ d++;
45
+ }
46
+ if (causes.length)
47
+ result.causes = causes;
48
+ return result;
49
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Copyright: (c) Myia SAS 2026.
3
+ * This file and its contents are licensed under the AGPLv3 License.
4
+ * Please see the LICENSE file at the root of this repository
5
+ */
6
+ export declare function safeValue(v: unknown, depth?: number): unknown;
7
+ export declare function safeVars(vars: unknown): Record<string, unknown> | undefined;
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright: (c) Myia SAS 2026.
4
+ * This file and its contents are licensed under the AGPLv3 License.
5
+ * Please see the LICENSE file at the root of this repository
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.safeValue = safeValue;
9
+ exports.safeVars = safeVars;
10
+ // Turns a `{ name: value }` map of user variables into a trace-safe
11
+ // snapshot that the ok-event-builder attaches to the `meta.vars`
12
+ // field. Called from `__ok` with the bindings the Babel
13
+ // instrumenter passed to it (only for `VariableDeclaration`
14
+ // statements — `const x = compute()` sends `{ x }`).
15
+ //
16
+ // "Trace-safe" means:
17
+ //
18
+ // - **Size-capped**. Strings are truncated at 200 chars, arrays
19
+ // keep the first 10 elements, objects keep the first 10 keys.
20
+ // The trace buffer has to serialize to JSON at teardown and get
21
+ // attached to a Playwright report; shipping a 50 MB mega-object
22
+ // would break both.
23
+ // - **Depth-capped**. Walks stop at depth 2 and replace deeper
24
+ // values with a `[Ctor]` placeholder.
25
+ // - **Plain-object only**. Only object literals (`{}` / `Object.create(null)`)
26
+ // and arrays are walked. Any class instance — Playwright's `Page`, an SDK
27
+ // adapter holding an API key, a `Date`, a `URL`, the user's domain
28
+ // types — short-circuits to a `[Ctor]` placeholder. This is the
29
+ // default-safe rule: walking a class instance can dump private state
30
+ // (e.g. `apiKey`, auth headers) into the trace, which then ships to the
31
+ // report. Plain JSON-shaped data — `await response.json()`, config
32
+ // objects, user records — is what tests actually inspect, and that path
33
+ // is preserved.
34
+ // - **Throwing-getter tolerant**. A property whose getter throws
35
+ // becomes the string `[getter threw]` instead of crashing the
36
+ // whole ok event.
37
+ function safeValue(v, depth = 0) {
38
+ if (v === null || v === undefined)
39
+ return v;
40
+ const type = typeof v;
41
+ if (type === 'number' || type === 'boolean')
42
+ return v;
43
+ if (type === 'string') {
44
+ const s = v;
45
+ return s.length > 200 ? s.slice(0, 197) + '…' : s;
46
+ }
47
+ if (type === 'function')
48
+ return `[Function ${v.name || 'anonymous'}]`;
49
+ if (type === 'symbol' || type === 'bigint')
50
+ return String(v);
51
+ if (type !== 'object')
52
+ return String(v);
53
+ if (v instanceof Error) {
54
+ return { __error: true, name: v.name, message: v.message };
55
+ }
56
+ const obj = v;
57
+ const ctor = (obj.constructor && obj.constructor.name) || '';
58
+ // Anything that isn't a plain object literal or array becomes opaque.
59
+ // Object.create(null) instances have no constructor and are treated as plain.
60
+ const isPlainObject = ctor === '' || ctor === 'Object';
61
+ if (!isPlainObject && !Array.isArray(v)) {
62
+ return `[${ctor}]`;
63
+ }
64
+ if (depth >= 2)
65
+ return `[${ctor || 'Object'}]`;
66
+ if (Array.isArray(v)) {
67
+ const out = v.slice(0, 10).map((x) => safeValue(x, depth + 1));
68
+ if (v.length > 10)
69
+ out.push(`…${v.length - 10} more`);
70
+ return out;
71
+ }
72
+ const out = {};
73
+ let count = 0;
74
+ for (const k of Object.keys(obj)) {
75
+ if (count >= 10) {
76
+ out['…'] = `${Object.keys(obj).length - 10} more keys`;
77
+ break;
78
+ }
79
+ try {
80
+ out[k] = safeValue(obj[k], depth + 1);
81
+ }
82
+ catch (_) {
83
+ out[k] = '[getter threw]';
84
+ }
85
+ count++;
86
+ }
87
+ return out;
88
+ }
89
+ function safeVars(vars) {
90
+ if (!vars || typeof vars !== 'object')
91
+ return undefined;
92
+ const out = {};
93
+ for (const k of Object.keys(vars)) {
94
+ try {
95
+ out[k] = safeValue(vars[k], 0);
96
+ }
97
+ catch (_) {
98
+ out[k] = '[serialize threw]';
99
+ }
100
+ }
101
+ return out;
102
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Copyright: (c) Myia SAS 2026.
3
+ * This file and its contents are licensed under the AGPLv3 License.
4
+ * Please see the LICENSE file at the root of this repository
5
+ */
6
+ import type { TraceEventConsumer } from '../port/trace-event-consumer';
7
+ import type { Clock } from '../port/clock';
8
+ import type { ActiveEnterStack } from './active-enter-stack';
9
+ export interface TraceEventRecorderState {
10
+ exporter: TraceEventConsumer;
11
+ clock: Clock;
12
+ staticContext: Record<string, unknown>;
13
+ dynamicContext: Record<string, unknown> | null;
14
+ currentPage: unknown;
15
+ enterStack: ActiveEnterStack;
16
+ stepStack: string[];
17
+ seq: number;
18
+ startedAt: number;
19
+ }
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright: (c) Myia SAS 2026.
4
+ * This file and its contents are licensed under the AGPLv3 License.
5
+ * Please see the LICENSE file at the root of this repository
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Copyright: (c) Myia SAS 2026.
3
+ * This file and its contents are licensed under the AGPLv3 License.
4
+ * Please see the LICENSE file at the root of this repository
5
+ */
6
+ import type { TraceEventConsumer } from '../port/trace-event-consumer';
7
+ import type { Clock } from '../port/clock';
8
+ import { ActiveEnterStack } from './active-enter-stack';
9
+ import type { TraceEventRecorderState } from './trace-event-recorder-state';
10
+ import type { EnterMeta } from '../model/enter-meta';
11
+ import { SCHEMA_VERSION } from '../model/trace-schema';
12
+ export { SCHEMA_VERSION };
13
+ export type { Clock } from '../port/clock';
14
+ export type { EnterMeta } from '../model/enter-meta';
15
+ export interface CreateTraceEventRecorderOptions {
16
+ exporter: TraceEventConsumer;
17
+ clock: Clock;
18
+ staticContext?: Record<string, unknown>;
19
+ }
20
+ export declare class TraceEventRecorder implements TraceEventRecorderState {
21
+ readonly SCHEMA_VERSION = 1;
22
+ exporter: TraceEventConsumer;
23
+ readonly clock: Clock;
24
+ readonly staticContext: Record<string, unknown>;
25
+ dynamicContext: Record<string, unknown> | null;
26
+ currentPage: unknown;
27
+ readonly enterStack: ActiveEnterStack;
28
+ readonly stepStack: string[];
29
+ seq: number;
30
+ startedAt: number;
31
+ constructor(options: CreateTraceEventRecorderOptions);
32
+ /**
33
+ * Swap the active exporter for subsequent events. Used by the fixture
34
+ * to install a fresh projector+tee at the start of every test —
35
+ * that way each test has an isolated output pipeline without the
36
+ * recorder having to be rebuilt. The caller is responsible for
37
+ * flushing/closing the previous exporter if needed.
38
+ */
39
+ setExporter: (newExporter: TraceEventConsumer) => void;
40
+ reset: () => void;
41
+ setContext: (ctx: Record<string, unknown> | null) => void;
42
+ setPage: (page: unknown | null) => void;
43
+ pushStep: (name: string) => void;
44
+ popStep: () => void;
45
+ /**
46
+ * Stamp a highlight screenshot filename onto whatever enter event
47
+ * is currently on top of the active-enter stack. Called by the
48
+ * locator-screenshots feature right after it writes the PNG to
49
+ * disk. No-op if the stack is empty (screenshot fired outside any
50
+ * instrumented statement, e.g. before the first __enter).
51
+ */
52
+ setCurrentStatementScreenshot: (filename: string) => void;
53
+ __enter: (meta: EnterMeta) => void;
54
+ __ok: (vars?: unknown) => void;
55
+ __throw: (err: unknown) => void;
56
+ }
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright: (c) Myia SAS 2026.
4
+ * This file and its contents are licensed under the AGPLv3 License.
5
+ * Please see the LICENSE file at the root of this repository
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.TraceEventRecorder = exports.SCHEMA_VERSION = void 0;
9
+ const active_enter_stack_1 = require("./active-enter-stack");
10
+ const trace_schema_1 = require("../model/trace-schema");
11
+ Object.defineProperty(exports, "SCHEMA_VERSION", { enumerable: true, get: function () { return trace_schema_1.SCHEMA_VERSION; } });
12
+ const enter_event_builder_1 = require("./event-builders/enter-event-builder");
13
+ const ok_event_builder_1 = require("./event-builders/ok-event-builder");
14
+ const throw_event_builder_1 = require("./event-builders/throw-event-builder");
15
+ const meta_event_builder_1 = require("./event-builders/meta-event-builder");
16
+ class TraceEventRecorder {
17
+ constructor(options) {
18
+ this.SCHEMA_VERSION = trace_schema_1.SCHEMA_VERSION;
19
+ this.dynamicContext = null;
20
+ this.currentPage = null;
21
+ this.enterStack = new active_enter_stack_1.ActiveEnterStack();
22
+ this.stepStack = [];
23
+ this.seq = 0;
24
+ /**
25
+ * Swap the active exporter for subsequent events. Used by the fixture
26
+ * to install a fresh projector+tee at the start of every test —
27
+ * that way each test has an isolated output pipeline without the
28
+ * recorder having to be rebuilt. The caller is responsible for
29
+ * flushing/closing the previous exporter if needed.
30
+ */
31
+ this.setExporter = (newExporter) => {
32
+ this.exporter = newExporter;
33
+ };
34
+ this.reset = () => {
35
+ this.exporter.clear();
36
+ this.enterStack.clear();
37
+ this.seq = 0;
38
+ this.startedAt = this.clock.now();
39
+ (0, meta_event_builder_1.buildMetaEvent)(this);
40
+ };
41
+ this.setContext = (ctx) => {
42
+ this.dynamicContext = ctx ?? null;
43
+ };
44
+ this.setPage = (page) => {
45
+ this.currentPage = page ?? null;
46
+ };
47
+ this.pushStep = (name) => {
48
+ this.stepStack.push(name);
49
+ };
50
+ this.popStep = () => {
51
+ this.stepStack.pop();
52
+ };
53
+ /**
54
+ * Stamp a highlight screenshot filename onto whatever enter event
55
+ * is currently on top of the active-enter stack. Called by the
56
+ * locator-screenshots feature right after it writes the PNG to
57
+ * disk. No-op if the stack is empty (screenshot fired outside any
58
+ * instrumented statement, e.g. before the first __enter).
59
+ */
60
+ this.setCurrentStatementScreenshot = (filename) => {
61
+ const top = this.enterStack.peek();
62
+ if (top)
63
+ top.screenshot = filename;
64
+ };
65
+ this.__enter = (meta) => {
66
+ (0, enter_event_builder_1.buildEnterEvent)(this, meta);
67
+ };
68
+ this.__ok = (vars) => {
69
+ (0, ok_event_builder_1.buildOkEvent)(this, vars);
70
+ };
71
+ this.__throw = (err) => {
72
+ (0, throw_event_builder_1.buildThrowEvent)(this, err);
73
+ };
74
+ this.exporter = options.exporter;
75
+ this.clock = options.clock;
76
+ this.staticContext = options.staticContext ?? {};
77
+ this.startedAt = this.clock.now();
78
+ }
79
+ }
80
+ exports.TraceEventRecorder = TraceEventRecorder;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Copyright: (c) Myia SAS 2026.
3
+ * This file and its contents are licensed under the AGPLv3 License.
4
+ * Please see the LICENSE file at the root of this repository
5
+ */
6
+ export { test, expect, reset } from './application/playwright-fixture';
7
+ export { configureTracer, onTestTeardown } from './application/heal-config';
8
+ export type { HealTracerConfig, HealTracerTestContext, HealTraceExporterFactory, HealTestLifecycle, HealTestLifecycleFactory, } from './application/heal-config';
9
+ export type { HealTraceExporter } from './domain/trace-event-recorder/port/heal-trace-exporter';
10
+ export type { HealTraceRecord } from './domain/trace-event-recorder/model/statement-trace-schema';
11
+ export * from '@playwright/test';
package/dist/index.js ADDED
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright: (c) Myia SAS 2026.
4
+ * This file and its contents are licensed under the AGPLv3 License.
5
+ * Please see the LICENSE file at the root of this repository
6
+ */
7
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
+ if (k2 === undefined) k2 = k;
9
+ var desc = Object.getOwnPropertyDescriptor(m, k);
10
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
+ desc = { enumerable: true, get: function() { return m[k]; } };
12
+ }
13
+ Object.defineProperty(o, k2, desc);
14
+ }) : (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ o[k2] = m[k];
17
+ }));
18
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
19
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
20
+ };
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.onTestTeardown = exports.configureTracer = exports.reset = exports.expect = exports.test = void 0;
23
+ // Package entry — what `import ... from '@heal-dev/heal-playwright-tracer'` hits.
24
+ //
25
+ // The Playwright surface is re-exported so consumers can keep
26
+ // `import { test, expect, devices } from '@heal-dev/heal-playwright-tracer'`
27
+ // as a drop-in replacement for `@playwright/test`.
28
+ var playwright_fixture_1 = require("./application/playwright-fixture");
29
+ Object.defineProperty(exports, "test", { enumerable: true, get: function () { return playwright_fixture_1.test; } });
30
+ Object.defineProperty(exports, "expect", { enumerable: true, get: function () { return playwright_fixture_1.expect; } });
31
+ Object.defineProperty(exports, "reset", { enumerable: true, get: function () { return playwright_fixture_1.reset; } });
32
+ // Extension API. Users call `configureTracer(...)` from their
33
+ // `playwright.config.ts` to plug in exporters and bindings, and can
34
+ // register per-test teardown callbacks at runtime via
35
+ // `onTestTeardown(...)`.
36
+ var heal_config_1 = require("./application/heal-config");
37
+ Object.defineProperty(exports, "configureTracer", { enumerable: true, get: function () { return heal_config_1.configureTracer; } });
38
+ Object.defineProperty(exports, "onTestTeardown", { enumerable: true, get: function () { return heal_config_1.onTestTeardown; } });
39
+ // Re-export every symbol from @playwright/test so code that imports
40
+ // e.g. `devices`, `chromium`, or the `Page`/`Locator` types from our
41
+ // package still gets them. The named exports above take precedence
42
+ // in module-load order.
43
+ __exportStar(require("@playwright/test"), exports);
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Copyright: (c) Myia SAS 2026.
3
+ * This file and its contents are licensed under the AGPLv3 License.
4
+ * Please see the LICENSE file at the root of this repository
5
+ */
6
+ export { NdjsonExporter } from './ndjson-exporter';
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright: (c) Myia SAS 2026.
4
+ * This file and its contents are licensed under the AGPLv3 License.
5
+ * Please see the LICENSE file at the root of this repository
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.NdjsonExporter = void 0;
9
+ var ndjson_exporter_1 = require("./ndjson-exporter");
10
+ Object.defineProperty(exports, "NdjsonExporter", { enumerable: true, get: function () { return ndjson_exporter_1.NdjsonExporter; } });
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Copyright: (c) Myia SAS 2026.
3
+ * This file and its contents are licensed under the AGPLv3 License.
4
+ * Please see the LICENSE file at the root of this repository
5
+ */
6
+ import type { HealTraceExporter, HealTraceRecord } from '../../domain/trace-event-recorder/port/heal-trace-exporter';
7
+ export declare class NdjsonExporter implements HealTraceExporter {
8
+ private readonly fd;
9
+ private closed;
10
+ constructor(filePath: string);
11
+ write(record: HealTraceRecord): void;
12
+ close(): Promise<void>;
13
+ }