@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.
- package/LICENSE +661 -0
- package/README.md +245 -0
- package/dist/application/babel-playwright-tracer-plugin/index.d.ts +16 -0
- package/dist/application/babel-playwright-tracer-plugin/index.js +111 -0
- package/dist/application/heal-config/index.d.ts +8 -0
- package/dist/application/heal-config/index.js +22 -0
- package/dist/application/heal-config/registry.d.ts +37 -0
- package/dist/application/heal-config/registry.js +64 -0
- package/dist/application/heal-config/types.d.ts +73 -0
- package/dist/application/heal-config/types.js +7 -0
- package/dist/application/playwright-fixture/index.d.ts +14 -0
- package/dist/application/playwright-fixture/index.js +234 -0
- package/dist/application/trace-event-recorder-runtime/index.d.ts +15 -0
- package/dist/application/trace-event-recorder-runtime/index.js +68 -0
- package/dist/domain/code-hook-injector/service/meta-fields/enclosing-scope-labeler.d.ts +12 -0
- package/dist/domain/code-hook-injector/service/meta-fields/enclosing-scope-labeler.js +53 -0
- package/dist/domain/code-hook-injector/service/meta-fields/leading-comment-extractor.d.ts +14 -0
- package/dist/domain/code-hook-injector/service/meta-fields/leading-comment-extractor.js +20 -0
- package/dist/domain/code-hook-injector/service/meta-fields/relative-file-path.d.ts +6 -0
- package/dist/domain/code-hook-injector/service/meta-fields/relative-file-path.js +57 -0
- package/dist/domain/code-hook-injector/service/meta-fields/source-snippet-extractor.d.ts +12 -0
- package/dist/domain/code-hook-injector/service/meta-fields/source-snippet-extractor.js +28 -0
- package/dist/domain/code-hook-injector/service/playwright-import-rewriter.d.ts +10 -0
- package/dist/domain/code-hook-injector/service/playwright-import-rewriter.js +21 -0
- package/dist/domain/code-hook-injector/service/statement-analysis/cjs-artifact-detector.d.ts +14 -0
- package/dist/domain/code-hook-injector/service/statement-analysis/cjs-artifact-detector.js +30 -0
- package/dist/domain/code-hook-injector/service/statement-analysis/for-head-declaration-detector.d.ts +10 -0
- package/dist/domain/code-hook-injector/service/statement-analysis/for-head-declaration-detector.js +18 -0
- package/dist/domain/code-hook-injector/service/statement-analysis/leaf-statement-classifier.d.ts +15 -0
- package/dist/domain/code-hook-injector/service/statement-analysis/leaf-statement-classifier.js +66 -0
- package/dist/domain/code-hook-injector/service/statement-analysis/non-wrappable-statement.d.ts +11 -0
- package/dist/domain/code-hook-injector/service/statement-analysis/non-wrappable-statement.js +20 -0
- package/dist/domain/code-hook-injector/service/trace-hook/enter-meta-literal.d.ts +20 -0
- package/dist/domain/code-hook-injector/service/trace-hook/enter-meta-literal.js +34 -0
- package/dist/domain/code-hook-injector/service/trace-hook/global-trace-call.d.ts +10 -0
- package/dist/domain/code-hook-injector/service/trace-hook/global-trace-call.js +16 -0
- package/dist/domain/code-hook-injector/service/trace-hook/try-finally-wrapper.d.ts +18 -0
- package/dist/domain/code-hook-injector/service/trace-hook/try-finally-wrapper.js +44 -0
- package/dist/domain/code-hook-injector/service/trace-hook/variable-declaration-hoister.d.ts +20 -0
- package/dist/domain/code-hook-injector/service/trace-hook/variable-declaration-hoister.js +27 -0
- package/dist/domain/code-hook-injector/service/traced-file-matcher.d.ts +10 -0
- package/dist/domain/code-hook-injector/service/traced-file-matcher.js +26 -0
- package/dist/domain/trace-event-recorder/model/enter-meta.d.ts +24 -0
- package/dist/domain/trace-event-recorder/model/enter-meta.js +7 -0
- package/dist/domain/trace-event-recorder/model/global-names.d.ts +8 -0
- package/dist/domain/trace-event-recorder/model/global-names.js +20 -0
- package/dist/domain/trace-event-recorder/model/serialized-error.d.ts +16 -0
- package/dist/domain/trace-event-recorder/model/serialized-error.js +7 -0
- package/dist/domain/trace-event-recorder/model/statement-trace-schema.d.ts +171 -0
- package/dist/domain/trace-event-recorder/model/statement-trace-schema.js +33 -0
- package/dist/domain/trace-event-recorder/model/trace-schema.d.ts +114 -0
- package/dist/domain/trace-event-recorder/model/trace-schema.js +16 -0
- package/dist/domain/trace-event-recorder/port/clock.d.ts +9 -0
- package/dist/domain/trace-event-recorder/port/clock.js +7 -0
- package/dist/domain/trace-event-recorder/port/heal-trace-exporter.d.ts +11 -0
- package/dist/domain/trace-event-recorder/port/heal-trace-exporter.js +7 -0
- package/dist/domain/trace-event-recorder/port/system-info-provider.d.ts +18 -0
- package/dist/domain/trace-event-recorder/port/system-info-provider.js +7 -0
- package/dist/domain/trace-event-recorder/port/trace-event-consumer.d.ts +11 -0
- package/dist/domain/trace-event-recorder/port/trace-event-consumer.js +7 -0
- package/dist/domain/trace-event-recorder/service/active-enter-stack.d.ts +15 -0
- package/dist/domain/trace-event-recorder/service/active-enter-stack.js +34 -0
- package/dist/domain/trace-event-recorder/service/event-builders/enter-event-builder.d.ts +8 -0
- package/dist/domain/trace-event-recorder/service/event-builders/enter-event-builder.js +37 -0
- package/dist/domain/trace-event-recorder/service/event-builders/meta-event-builder.d.ts +7 -0
- package/dist/domain/trace-event-recorder/service/event-builders/meta-event-builder.js +19 -0
- package/dist/domain/trace-event-recorder/service/event-builders/ok-event-builder.d.ts +7 -0
- package/dist/domain/trace-event-recorder/service/event-builders/ok-event-builder.js +27 -0
- package/dist/domain/trace-event-recorder/service/event-builders/throw-event-builder.d.ts +7 -0
- package/dist/domain/trace-event-recorder/service/event-builders/throw-event-builder.js +23 -0
- package/dist/domain/trace-event-recorder/service/exporters/composite-heal-trace-exporter.d.ts +12 -0
- package/dist/domain/trace-event-recorder/service/exporters/composite-heal-trace-exporter.js +32 -0
- package/dist/domain/trace-event-recorder/service/index.d.ts +10 -0
- package/dist/domain/trace-event-recorder/service/index.js +15 -0
- package/dist/domain/trace-event-recorder/service/projectors/index.d.ts +6 -0
- package/dist/domain/trace-event-recorder/service/projectors/index.js +10 -0
- package/dist/domain/trace-event-recorder/service/projectors/statement-projector.d.ts +26 -0
- package/dist/domain/trace-event-recorder/service/projectors/statement-projector.js +183 -0
- package/dist/domain/trace-event-recorder/service/serializers/error-serializer.d.ts +8 -0
- package/dist/domain/trace-event-recorder/service/serializers/error-serializer.js +49 -0
- package/dist/domain/trace-event-recorder/service/serializers/variable-snapshot-serializer.d.ts +7 -0
- package/dist/domain/trace-event-recorder/service/serializers/variable-snapshot-serializer.js +102 -0
- package/dist/domain/trace-event-recorder/service/trace-event-recorder-state.d.ts +19 -0
- package/dist/domain/trace-event-recorder/service/trace-event-recorder-state.js +7 -0
- package/dist/domain/trace-event-recorder/service/trace-event-recorder.d.ts +56 -0
- package/dist/domain/trace-event-recorder/service/trace-event-recorder.js +80 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +43 -0
- package/dist/infrastructure/ndjson-exporter-adapter/index.d.ts +6 -0
- package/dist/infrastructure/ndjson-exporter-adapter/index.js +10 -0
- package/dist/infrastructure/ndjson-exporter-adapter/ndjson-exporter.d.ts +13 -0
- package/dist/infrastructure/ndjson-exporter-adapter/ndjson-exporter.js +77 -0
- package/dist/infrastructure/perf-hooks-clock-adapter/index.d.ts +6 -0
- package/dist/infrastructure/perf-hooks-clock-adapter/index.js +10 -0
- package/dist/infrastructure/perf-hooks-clock-adapter/perf-hooks-clock.d.ts +11 -0
- package/dist/infrastructure/perf-hooks-clock-adapter/perf-hooks-clock.js +22 -0
- package/dist/infrastructure/playwright-locator-screenshot-adapter/assertion-wrapper.d.ts +6 -0
- package/dist/infrastructure/playwright-locator-screenshot-adapter/assertion-wrapper.js +109 -0
- package/dist/infrastructure/playwright-locator-screenshot-adapter/index.d.ts +9 -0
- package/dist/infrastructure/playwright-locator-screenshot-adapter/index.js +21 -0
- package/dist/infrastructure/playwright-locator-screenshot-adapter/locator-patch.d.ts +11 -0
- package/dist/infrastructure/playwright-locator-screenshot-adapter/locator-patch.js +79 -0
- package/dist/infrastructure/playwright-locator-screenshot-adapter/overlay-helpers.d.ts +15 -0
- package/dist/infrastructure/playwright-locator-screenshot-adapter/overlay-helpers.js +33 -0
- package/dist/infrastructure/playwright-locator-screenshot-adapter/screenshot-capture-session.d.ts +26 -0
- package/dist/infrastructure/playwright-locator-screenshot-adapter/screenshot-capture-session.js +125 -0
- package/dist/infrastructure/playwright-step-tracking-adapter/index.d.ts +7 -0
- package/dist/infrastructure/playwright-step-tracking-adapter/index.js +10 -0
- package/dist/infrastructure/playwright-step-tracking-adapter/playwright-step-tracking-adapter.d.ts +14 -0
- package/dist/infrastructure/playwright-step-tracking-adapter/playwright-step-tracking-adapter.js +51 -0
- package/dist/infrastructure/playwright-test-context-adapter/heal-tag-prefix.d.ts +25 -0
- package/dist/infrastructure/playwright-test-context-adapter/heal-tag-prefix.js +28 -0
- package/dist/infrastructure/playwright-test-context-adapter/index.d.ts +8 -0
- package/dist/infrastructure/playwright-test-context-adapter/index.js +12 -0
- package/dist/infrastructure/playwright-test-context-adapter/playwright-test-context-adapter.d.ts +19 -0
- package/dist/infrastructure/playwright-test-context-adapter/playwright-test-context-adapter.js +43 -0
- package/dist/infrastructure/stdout-capture-adapter/index.d.ts +7 -0
- package/dist/infrastructure/stdout-capture-adapter/index.js +10 -0
- package/dist/infrastructure/stdout-capture-adapter/stdout-capture-session.d.ts +20 -0
- package/dist/infrastructure/stdout-capture-adapter/stdout-capture-session.js +47 -0
- package/dist/infrastructure/system-info-adapter/index.d.ts +6 -0
- package/dist/infrastructure/system-info-adapter/index.js +10 -0
- package/dist/infrastructure/system-info-adapter/system-info-adapter.d.ts +12 -0
- package/dist/infrastructure/system-info-adapter/system-info-adapter.js +83 -0
- 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
|
+
}
|
package/dist/domain/trace-event-recorder/service/serializers/variable-snapshot-serializer.d.ts
ADDED
|
@@ -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,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;
|
package/dist/index.d.ts
ADDED
|
@@ -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,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
|
+
}
|