@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,234 @@
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 __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
19
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
20
+ }) : function(o, v) {
21
+ o["default"] = v;
22
+ });
23
+ var __importStar = (this && this.__importStar) || (function () {
24
+ var ownKeys = function(o) {
25
+ ownKeys = Object.getOwnPropertyNames || function (o) {
26
+ var ar = [];
27
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
28
+ return ar;
29
+ };
30
+ return ownKeys(o);
31
+ };
32
+ return function (mod) {
33
+ if (mod && mod.__esModule) return mod;
34
+ var result = {};
35
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
36
+ __setModuleDefault(result, mod);
37
+ return result;
38
+ };
39
+ })();
40
+ Object.defineProperty(exports, "__esModule", { value: true });
41
+ exports.reset = exports.expect = exports.test = void 0;
42
+ // Composition root for the heal-playwright-tracer auto-fixture.
43
+ //
44
+ // Each feature owns its own setup/cleanup; this file is the shopping
45
+ // list. Reading top-to-bottom tells you everything the tracer does to
46
+ // a test and in what order.
47
+ //
48
+ // Built-in features wired here (always-on, OSS):
49
+ // - runtime (side-effect import installs globalThis.__heal_enter/__heal_ok/__heal_throw)
50
+ // - step-tracking → new PlaywrightStepTrackingAdapter(hooks).patch(test)
51
+ // - test-context → testContextAdapter.capture(testInfo)
52
+ // - locator-screenshots → startLocatorScreenshotCapture(page, outDir)
53
+ // - test-stdout-capture → new StdoutCaptureSession()
54
+ // - NDJSON exporter → default trace output to `heal-data/heal-traces.ndjson`
55
+ //
56
+ // User-extensible features (wired via `configureTracer(...)` from the
57
+ // host `playwright.config.ts`):
58
+ // - exporters → each factory returns a `HealTraceExporter` composed
59
+ // into a tee alongside the default NDJSON exporter
60
+ // - lifecycles → each entry exposes `setup(ctx)` + `teardown()`;
61
+ // setups run in declaration order at test start,
62
+ // teardowns in reverse order in `finally` (LIFO)
63
+ // - onTestTeardown(fn) → runtime registration, drained before
64
+ // lifecycle teardowns so SDKs still see any globals
65
+ // a lifecycle installed
66
+ //
67
+ // Output shape (per test): `heal-data/heal-traces.ndjson` — one
68
+ // HealTraceRecord per line. See
69
+ // `../../domain/trace-event-recorder/model/statement-trace-schema.ts`
70
+ // for the contract.
71
+ //
72
+ // Env toggles:
73
+ // HEAL_TRACE_NDJSON default on; set to `0`/`false`/`off` to disable.
74
+ //
75
+ // Any backend integration (HTTP shipping, APM bindings,
76
+ // telemetry-session setup, …) lives in user code and plugs in via
77
+ // `configureTracer`. The fixture knows nothing about any specific
78
+ // backend.
79
+ const fs = __importStar(require("fs"));
80
+ const path = __importStar(require("path"));
81
+ const test_1 = require("@playwright/test");
82
+ // Side-effect: installs `globalThis.__heal_enter/__heal_ok/__heal_throw`.
83
+ const trace_event_recorder_runtime_1 = require("../trace-event-recorder-runtime");
84
+ Object.defineProperty(exports, "reset", { enumerable: true, get: function () { return trace_event_recorder_runtime_1.reset; } });
85
+ const playwright_locator_screenshot_adapter_1 = require("../../infrastructure/playwright-locator-screenshot-adapter");
86
+ const projectors_1 = require("../../domain/trace-event-recorder/service/projectors");
87
+ const ndjson_exporter_adapter_1 = require("../../infrastructure/ndjson-exporter-adapter");
88
+ const service_1 = require("../../domain/trace-event-recorder/service");
89
+ const playwright_step_tracking_adapter_1 = require("../../infrastructure/playwright-step-tracking-adapter");
90
+ const playwright_test_context_adapter_1 = require("../../infrastructure/playwright-test-context-adapter");
91
+ const playwright_locator_screenshot_adapter_2 = require("../../infrastructure/playwright-locator-screenshot-adapter");
92
+ const stdout_capture_adapter_1 = require("../../infrastructure/stdout-capture-adapter");
93
+ const heal_config_1 = require("../heal-config");
94
+ // Wrap `expect` so any assertion made against a Locator gets a
95
+ // highlight screenshot stamped onto the active statement, the same
96
+ // way locator actions do. Non-locator assertions fall through.
97
+ const expect = (0, playwright_locator_screenshot_adapter_1.wrapExpect)(test_1.expect);
98
+ exports.expect = expect;
99
+ // All artifacts this package produces live under this subdirectory of
100
+ // `testInfo.outputDir`, so they stay segregated from Playwright's own
101
+ // output (screenshot/trace/video attachments).
102
+ const HEAL_DATA_SUBDIR = 'heal-data';
103
+ const NDJSON_FILENAME = 'heal-traces.ndjson';
104
+ function envFlag(name, defaultOn) {
105
+ const raw = process.env[name];
106
+ if (raw == null || raw === '')
107
+ return defaultOn;
108
+ const v = raw.toLowerCase();
109
+ if (v === '0' || v === 'false' || v === 'off' || v === 'no')
110
+ return false;
111
+ return true;
112
+ }
113
+ function buildHealTraceExporter(healDataDir, ctx) {
114
+ const legs = [];
115
+ if (envFlag('HEAL_TRACE_NDJSON', true)) {
116
+ legs.push(new ndjson_exporter_adapter_1.NdjsonExporter(path.join(healDataDir, NDJSON_FILENAME)));
117
+ }
118
+ const { exporters = [] } = (0, heal_config_1.getTracerConfig)();
119
+ for (const factory of exporters) {
120
+ try {
121
+ legs.push(factory(ctx));
122
+ }
123
+ catch (err) {
124
+ console.error('[heal-playwright-tracer] exporter factory failed:', err);
125
+ }
126
+ }
127
+ if (legs.length === 0) {
128
+ // Neither leg active — return a no-op exporter so the projector
129
+ // still runs without doing anything user-visible.
130
+ return { write() { }, async close() { } };
131
+ }
132
+ return legs.length === 1 ? legs[0] : new service_1.CompositeHealTraceExporter(legs);
133
+ }
134
+ // Composition-root singletons — one per process.
135
+ const testContextAdapter = new playwright_test_context_adapter_1.PlaywrightTestContextAdapter({ setContext: trace_event_recorder_runtime_1.setContext });
136
+ const stepTrackingAdapter = new playwright_step_tracking_adapter_1.PlaywrightStepTrackingAdapter({ pushStep: trace_event_recorder_runtime_1.pushStep, popStep: trace_event_recorder_runtime_1.popStep });
137
+ exports.test = test_1.test.extend({
138
+ _traceAuto: [
139
+ async ({ page }, use, testInfo) => {
140
+ const captured = testContextAdapter.capture(testInfo);
141
+ const healDataDir = path.join(testInfo.outputDir, HEAL_DATA_SUBDIR);
142
+ fs.mkdirSync(healDataDir, { recursive: true });
143
+ const tracerCtx = {
144
+ testInfo,
145
+ healDataDir,
146
+ transport: {
147
+ testId: captured.testId,
148
+ attempt: captured.attempt,
149
+ rootDir: testInfo.outputDir,
150
+ },
151
+ };
152
+ // Fresh output pipeline per test: build a HealTraceExporter (default
153
+ // NDJSON leg + any user-configured exporters), wrap it in a
154
+ // projector, install on the recorder, then reset() — which
155
+ // clears projector state and emits the test-header record via
156
+ // the buildMetaEvent call inside the recorder.
157
+ const output = buildHealTraceExporter(healDataDir, tracerCtx);
158
+ const projector = new projectors_1.StatementProjector(output);
159
+ (0, trace_event_recorder_runtime_1.setExporter)(projector);
160
+ (0, trace_event_recorder_runtime_1.reset)();
161
+ // Defensive: clear any teardown hooks that leaked from a
162
+ // previous test that crashed before drain ran.
163
+ (0, heal_config_1.resetTeardownHooks)();
164
+ const stopScreenshots = (0, playwright_locator_screenshot_adapter_2.startLocatorScreenshotCapture)(page, healDataDir, trace_event_recorder_runtime_1.setCurrentStatementScreenshot);
165
+ const stdoutSession = new stdout_capture_adapter_1.StdoutCaptureSession();
166
+ // Instantiate user-configured lifecycles for this test. Each
167
+ // factory runs fresh per test so any closure state the factory
168
+ // declares is isolated between tests. Setup failures are
169
+ // isolated: a lifecycle whose `setup` throws is NOT pushed onto
170
+ // `activeLifecycles`, so its `teardown` will not run. Later
171
+ // lifecycles still get a chance to set up.
172
+ const activeLifecycles = [];
173
+ const { lifecycles = [] } = (0, heal_config_1.getTracerConfig)();
174
+ for (const factory of lifecycles) {
175
+ try {
176
+ const lc = factory();
177
+ await lc.setup(tracerCtx);
178
+ activeLifecycles.push(lc);
179
+ }
180
+ catch (err) {
181
+ console.error('[heal-playwright-tracer] lifecycle setup failed:', err);
182
+ }
183
+ }
184
+ try {
185
+ await use();
186
+ }
187
+ finally {
188
+ // Run any teardown hooks registered during the test. Runs
189
+ // BEFORE lifecycle teardowns so hooks that log via an SDK
190
+ // still see the per-test globals a lifecycle installed, and
191
+ // BEFORE stopping stdout capture so SDK teardown output lands
192
+ // in the ndjson's `test-result.stdout/stderr`.
193
+ await (0, heal_config_1.drainTeardownHooks)();
194
+ // Teardown in reverse order (LIFO): the last lifecycle to set
195
+ // up is the first to tear down, matching the mental model of
196
+ // nested `using` blocks.
197
+ for (let i = activeLifecycles.length - 1; i >= 0; i--) {
198
+ try {
199
+ await activeLifecycles[i].teardown();
200
+ }
201
+ catch (err) {
202
+ console.error('[heal-playwright-tracer] lifecycle teardown failed:', err);
203
+ }
204
+ }
205
+ const capturedStdout = stdoutSession.stop();
206
+ stopScreenshots();
207
+ // Emit the final test-result record and close the output
208
+ // exporter chain (flushes NDJSON fd, awaits in-flight
209
+ // user-exporter I/O). The projector has its own `finalized`
210
+ // guard that silently swallows any stray events fired after
211
+ // this point, so we do NOT swap the recorder's exporter back
212
+ // to a no-op — it stays pointed at the (now-finalized)
213
+ // projector until the next test installs a fresh one.
214
+ //
215
+ // Playwright-native artifacts (trace.zip, videos) are NOT
216
+ // shipped from here — their attachments are populated by
217
+ // Playwright in a later phase. Users who need them can
218
+ // register a Playwright reporter in their `playwright.config`.
219
+ await projector.finalize({
220
+ status: testInfo.status ?? 'passed',
221
+ duration: testInfo.duration,
222
+ stdout: capturedStdout.stdout.length ? capturedStdout.stdout : undefined,
223
+ stderr: capturedStdout.stderr.length ? capturedStdout.stderr : undefined,
224
+ });
225
+ }
226
+ },
227
+ { auto: true },
228
+ ],
229
+ });
230
+ // Thread test.step titles onto the runtime step stack. Must run AFTER
231
+ // base.extend because Playwright's extend creates a fresh `.step` on
232
+ // the new test object that doesn't inherit from base — patching `base`
233
+ // first would never reach the extended test.
234
+ stepTrackingAdapter.patch(exports.test);
@@ -0,0 +1,15 @@
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 { SCHEMA_VERSION } from '../../domain/trace-event-recorder/service';
7
+ import type { TraceEventConsumer } from '../../domain/trace-event-recorder/port/trace-event-consumer';
8
+ export declare const reset: () => void;
9
+ export declare const setContext: (ctx: Record<string, unknown> | null) => void;
10
+ export declare const setPage: (page: unknown | null) => void;
11
+ export declare const pushStep: (name: string) => void;
12
+ export declare const popStep: () => void;
13
+ export declare const setCurrentStatementScreenshot: (filename: string) => void;
14
+ export declare const setExporter: (newExporter: TraceEventConsumer) => void;
15
+ export { SCHEMA_VERSION };
@@ -0,0 +1,68 @@
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.SCHEMA_VERSION = exports.setExporter = exports.setCurrentStatementScreenshot = exports.popStep = exports.pushStep = exports.setPage = exports.setContext = exports.reset = void 0;
9
+ // Composition root for the process-wide trace-event-recorder singleton.
10
+ //
11
+ // `domain/.../factory.ts` exports the `TraceEventRecorder` class —
12
+ // a pure service that requires exporter + clock + staticContext. This
13
+ // file is the thin wrapper that:
14
+ //
15
+ // 1. Wires the default Clock (PerfHooksClock) and the default
16
+ // SystemInfoProvider (SystemInfoAdapter — reads pid/os/git).
17
+ // 2. Builds one trace-event-recorder wired to `BOOTSTRAP_EXPORTER`
18
+ // (a no-op that silently swallows any events fired before the
19
+ // fixture swaps in a real projector via `setExporter(...)` at
20
+ // the start of every test).
21
+ // 3. Installs `globalThis.__heal_enter/__heal_ok/__heal_throw` so the
22
+ // Babel-inserted calls find them at runtime.
23
+ // 4. Re-exports the public API (`reset`, `setContext`, `setPage`,
24
+ // `pushStep`, `popStep`, `setCurrentStatementScreenshot`,
25
+ // `setExporter`) for the fixture and infrastructure adapters to
26
+ // import. The singleton shape is the integration surface between
27
+ // the always-on domain recorder and the per-test fixture.
28
+ //
29
+ // Tests do NOT import this file — they import the domain factory
30
+ // directly and inject stubs + a fake clock.
31
+ const service_1 = require("../../domain/trace-event-recorder/service");
32
+ Object.defineProperty(exports, "SCHEMA_VERSION", { enumerable: true, get: function () { return service_1.SCHEMA_VERSION; } });
33
+ const perf_hooks_clock_adapter_1 = require("../../infrastructure/perf-hooks-clock-adapter");
34
+ const system_info_adapter_1 = require("../../infrastructure/system-info-adapter");
35
+ const global_names_1 = require("../../domain/trace-event-recorder/model/global-names");
36
+ // See the header comment: the recorder is constructed at module load
37
+ // and MUST have an exporter from that moment so any pre-fixture
38
+ // `__heal_enter` call doesn't crash. The fixture replaces this with
39
+ // the real per-test projector via `setExporter(...)`.
40
+ const BOOTSTRAP_EXPORTER = {
41
+ write() { },
42
+ clear() { },
43
+ };
44
+ const systemInfo = new system_info_adapter_1.SystemInfoAdapter().getStaticContext();
45
+ const staticContext = {
46
+ schemaVersion: service_1.SCHEMA_VERSION,
47
+ ...systemInfo,
48
+ };
49
+ const traceEventRecorder = new service_1.TraceEventRecorder({
50
+ exporter: BOOTSTRAP_EXPORTER,
51
+ clock: new perf_hooks_clock_adapter_1.PerfHooksClock(),
52
+ staticContext,
53
+ });
54
+ // Global-name contract lives in `domain/.../model/global-names.ts`,
55
+ // shared with the Babel plugin that emits calls to these names. The
56
+ // factory's internal method names (`__enter`, `__ok`, `__throw`) are
57
+ // unchanged — they never leak into user-facing code.
58
+ const g = globalThis;
59
+ g[global_names_1.HEAL_ENTER] = traceEventRecorder.__enter;
60
+ g[global_names_1.HEAL_OK] = traceEventRecorder.__ok;
61
+ g[global_names_1.HEAL_THROW] = traceEventRecorder.__throw;
62
+ exports.reset = traceEventRecorder.reset;
63
+ exports.setContext = traceEventRecorder.setContext;
64
+ exports.setPage = traceEventRecorder.setPage;
65
+ exports.pushStep = traceEventRecorder.pushStep;
66
+ exports.popStep = traceEventRecorder.popStep;
67
+ exports.setCurrentStatementScreenshot = traceEventRecorder.setCurrentStatementScreenshot;
68
+ exports.setExporter = traceEventRecorder.setExporter;
@@ -0,0 +1,12 @@
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 * as BabelTypes from '@babel/types';
7
+ import type { NodePath } from '@babel/traverse';
8
+ type Types = typeof BabelTypes;
9
+ export declare const TEST_API_NAMES: RegExp;
10
+ export type EnclosingScopeLabeler = (nodePath: NodePath) => string;
11
+ export declare function createEnclosingScopeLabeler(t: Types): EnclosingScopeLabeler;
12
+ export {};
@@ -0,0 +1,53 @@
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.TEST_API_NAMES = void 0;
9
+ exports.createEnclosingScopeLabeler = createEnclosingScopeLabeler;
10
+ exports.TEST_API_NAMES = /^(test|it|describe|step|beforeEach|afterEach|beforeAll|afterAll)$/;
11
+ function createEnclosingScopeLabeler(t) {
12
+ return function labelEnclosingScope(nodePath) {
13
+ let p = nodePath.parentPath;
14
+ while (p) {
15
+ if (p.isFunction()) {
16
+ // ObjectMethod and ClassMethod ARE function nodes (not wrapped
17
+ // by one), so we check them on the current path itself, not on
18
+ // its parent. Must come BEFORE the named-function branch since
19
+ // those methods can also have a `.key` we want to surface.
20
+ if (p.isObjectMethod() || p.isClassMethod()) {
21
+ const key = p.node.key;
22
+ if (key && t.isIdentifier(key))
23
+ return key.name;
24
+ }
25
+ const fnNode = p.node;
26
+ if ('id' in fnNode && fnNode.id && fnNode.id.name)
27
+ return fnNode.id.name;
28
+ const parent = p.parentPath;
29
+ if (parent && parent.isCallExpression()) {
30
+ const callee = parent.node.callee;
31
+ let calleeName = '';
32
+ if (t.isIdentifier(callee))
33
+ calleeName = callee.name;
34
+ else if (t.isMemberExpression(callee) && t.isIdentifier(callee.property))
35
+ calleeName = callee.property.name;
36
+ if (exports.TEST_API_NAMES.test(calleeName)) {
37
+ const titleNode = parent.node.arguments[0];
38
+ if (titleNode && t.isStringLiteral(titleNode)) {
39
+ return `${calleeName}: ${titleNode.value}`;
40
+ }
41
+ return `${calleeName}()`;
42
+ }
43
+ }
44
+ if (parent && parent.isVariableDeclarator() && t.isIdentifier(parent.node.id)) {
45
+ return parent.node.id.name;
46
+ }
47
+ return '<anonymous>';
48
+ }
49
+ p = p.parentPath;
50
+ }
51
+ return '<module>';
52
+ };
53
+ }
@@ -0,0 +1,14 @@
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
+ interface BabelComment {
7
+ type: 'CommentLine' | 'CommentBlock';
8
+ value: string;
9
+ }
10
+ interface NodeLike {
11
+ leadingComments?: BabelComment[] | null;
12
+ }
13
+ export declare function extractLeadingComment(node: NodeLike | null | undefined): string | null;
14
+ export {};
@@ -0,0 +1,20 @@
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.extractLeadingComment = extractLeadingComment;
9
+ function extractLeadingComment(node) {
10
+ const comments = node?.leadingComments;
11
+ if (!comments || comments.length === 0)
12
+ return null;
13
+ return comments.map((c) => stripOneSpace(c.value)).join('\n');
14
+ }
15
+ // Strip at most one leading and one trailing space. Not `.trim()` —
16
+ // that would destroy intentional indentation in JSDoc-style blocks
17
+ // where inner lines are `' * foo'`.
18
+ function stripOneSpace(value) {
19
+ return value.replace(/^ /, '').replace(/ $/, '');
20
+ }
@@ -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 declare function relFile(cwd: string, absFile: string | undefined | null): string;
@@ -0,0 +1,57 @@
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 __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
19
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
20
+ }) : function(o, v) {
21
+ o["default"] = v;
22
+ });
23
+ var __importStar = (this && this.__importStar) || (function () {
24
+ var ownKeys = function(o) {
25
+ ownKeys = Object.getOwnPropertyNames || function (o) {
26
+ var ar = [];
27
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
28
+ return ar;
29
+ };
30
+ return ownKeys(o);
31
+ };
32
+ return function (mod) {
33
+ if (mod && mod.__esModule) return mod;
34
+ var result = {};
35
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
36
+ __setModuleDefault(result, mod);
37
+ return result;
38
+ };
39
+ })();
40
+ Object.defineProperty(exports, "__esModule", { value: true });
41
+ exports.relFile = relFile;
42
+ // Computes the `meta.file` field: a repo-relative path when the
43
+ // absolute filename lives under the instrumenter's `rootDir` (or
44
+ // process.cwd() by default), or the absolute path otherwise.
45
+ //
46
+ // Relative paths make traces portable across machines; falling back
47
+ // to absolute when the file is outside the root avoids generating
48
+ // broken `..`-prefixed paths that can't be resolved on the consumer
49
+ // side. Missing filenames collapse to `<anonymous>` so downstream
50
+ // code never has to null-check.
51
+ const path = __importStar(require("path"));
52
+ function relFile(cwd, absFile) {
53
+ if (!absFile)
54
+ return '<anonymous>';
55
+ const rel = path.relative(cwd, absFile);
56
+ return rel && !rel.startsWith('..') ? rel : absFile;
57
+ }
@@ -0,0 +1,12 @@
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 const DEFAULT_MAX_SOURCE_LEN = 200;
7
+ interface NodeLike {
8
+ start?: number | null;
9
+ end?: number | null;
10
+ }
11
+ export declare function extractSource(code: string | undefined | null, node: NodeLike | null | undefined, maxLen?: number): string;
12
+ export {};
@@ -0,0 +1,28 @@
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.DEFAULT_MAX_SOURCE_LEN = void 0;
9
+ exports.extractSource = extractSource;
10
+ // Extracts the raw source text for a node, collapsed and length-capped.
11
+ //
12
+ // Produces the `meta.source` field on every __enter event. Slicing
13
+ // the original file between `node.start` and `node.end` is cheap and
14
+ // avoids re-printing the AST; collapsing whitespace keeps multi-line
15
+ // statements readable on a single trace line; the 200-char cap keeps
16
+ // long expressions from blowing up the trace buffer.
17
+ exports.DEFAULT_MAX_SOURCE_LEN = 200;
18
+ function extractSource(code, node, maxLen = exports.DEFAULT_MAX_SOURCE_LEN) {
19
+ const src = code || '';
20
+ if (!node || node.start == null || node.end == null)
21
+ return '';
22
+ let snippet = src.slice(node.start, node.end);
23
+ snippet = snippet.replace(/\s+/g, ' ').trim();
24
+ if (snippet.length > maxLen) {
25
+ snippet = snippet.slice(0, maxLen - 1) + '…';
26
+ }
27
+ return snippet;
28
+ }
@@ -0,0 +1,10 @@
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 * as BabelTypes from '@babel/types';
7
+ type Types = typeof BabelTypes;
8
+ export type PlaywrightImportRewriter = (program: BabelTypes.Program) => void;
9
+ export declare function createPlaywrightImportRewriter(t: Types): PlaywrightImportRewriter;
10
+ export {};
@@ -0,0 +1,21 @@
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.createPlaywrightImportRewriter = createPlaywrightImportRewriter;
9
+ const PLAYWRIGHT_IMPORT_SOURCE = '@playwright/test';
10
+ const REPLACEMENT_SOURCE = '@heal-dev/heal-playwright-tracer';
11
+ function createPlaywrightImportRewriter(t) {
12
+ return function rewritePlaywrightImports(program) {
13
+ for (const node of program.body) {
14
+ if (t.isImportDeclaration(node) &&
15
+ node.source &&
16
+ node.source.value === PLAYWRIGHT_IMPORT_SOURCE) {
17
+ node.source = t.stringLiteral(REPLACEMENT_SOURCE);
18
+ }
19
+ }
20
+ };
21
+ }
@@ -0,0 +1,14 @@
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 * as BabelTypes from '@babel/types';
7
+ type Types = typeof BabelTypes;
8
+ type Node = BabelTypes.Node;
9
+ export interface CjsArtifactDetector {
10
+ isRequireLike: (node: Node | null | undefined) => boolean;
11
+ isGeneratedModuleStatement: (node: Node) => boolean;
12
+ }
13
+ export declare function createCjsArtifactDetector(t: Types): CjsArtifactDetector;
14
+ export {};
@@ -0,0 +1,30 @@
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.createCjsArtifactDetector = createCjsArtifactDetector;
9
+ function createCjsArtifactDetector(t) {
10
+ const isRequireLike = (node) => {
11
+ if (!node || !t.isCallExpression(node))
12
+ return false;
13
+ const callee = node.callee;
14
+ if (!t.isIdentifier(callee))
15
+ return false;
16
+ if (callee.name === 'require')
17
+ return true;
18
+ if (/^_interop/.test(callee.name) && node.arguments.length > 0) {
19
+ return isRequireLike(node.arguments[0]);
20
+ }
21
+ return false;
22
+ };
23
+ const isGeneratedModuleStatement = (node) => {
24
+ if (t.isVariableDeclaration(node)) {
25
+ return node.declarations.every((d) => isRequireLike(d.init));
26
+ }
27
+ return false;
28
+ };
29
+ return { isRequireLike, isGeneratedModuleStatement };
30
+ }
@@ -0,0 +1,10 @@
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 * as BabelTypes from '@babel/types';
7
+ type Types = typeof BabelTypes;
8
+ export type ForHeadDeclarationDetector = (declaration: BabelTypes.VariableDeclaration, parent: BabelTypes.Node) => boolean;
9
+ export declare function createForHeadDeclarationDetector(t: Types): ForHeadDeclarationDetector;
10
+ export {};
@@ -0,0 +1,18 @@
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.createForHeadDeclarationDetector = createForHeadDeclarationDetector;
9
+ function createForHeadDeclarationDetector(t) {
10
+ return function isForHeadDeclaration(declaration, parent) {
11
+ if (t.isForStatement(parent) && parent.init === declaration)
12
+ return true;
13
+ if ((t.isForInStatement(parent) || t.isForOfStatement(parent)) && parent.left === declaration) {
14
+ return true;
15
+ }
16
+ return false;
17
+ };
18
+ }