fast-replay 0.1.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 +21 -0
- package/README.md +126 -0
- package/dist/api.d.ts +65 -0
- package/dist/api.js +78 -0
- package/dist/api.js.map +1 -0
- package/dist/browser.d.ts +67 -0
- package/dist/browser.js +77 -0
- package/dist/browser.js.map +1 -0
- package/dist/cli/format.d.ts +12 -0
- package/dist/cli/format.js +55 -0
- package/dist/cli/format.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +199 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/compiler/compile.d.ts +18 -0
- package/dist/compiler/compile.js +226 -0
- package/dist/compiler/compile.js.map +1 -0
- package/dist/compiler/normalize.d.ts +16 -0
- package/dist/compiler/normalize.js +75 -0
- package/dist/compiler/normalize.js.map +1 -0
- package/dist/compiler/waits.d.ts +31 -0
- package/dist/compiler/waits.js +69 -0
- package/dist/compiler/waits.js.map +1 -0
- package/dist/ir/io.d.ts +47 -0
- package/dist/ir/io.js +122 -0
- package/dist/ir/io.js.map +1 -0
- package/dist/ir/schema.d.ts +236 -0
- package/dist/ir/schema.js +153 -0
- package/dist/ir/schema.js.map +1 -0
- package/dist/mcp/index.d.ts +2 -0
- package/dist/mcp/index.js +22 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/server.d.ts +8 -0
- package/dist/mcp/server.js +268 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/noise.d.ts +20 -0
- package/dist/noise.js +70 -0
- package/dist/noise.js.map +1 -0
- package/dist/recorder/agent/capture.d.ts +15 -0
- package/dist/recorder/agent/capture.js +310 -0
- package/dist/recorder/agent/capture.js.map +1 -0
- package/dist/recorder/agent/config.d.ts +25 -0
- package/dist/recorder/agent/config.js +23 -0
- package/dist/recorder/agent/config.js.map +1 -0
- package/dist/recorder/agent/dom-reaction.d.ts +13 -0
- package/dist/recorder/agent/dom-reaction.js +166 -0
- package/dist/recorder/agent/dom-reaction.js.map +1 -0
- package/dist/recorder/agent/main.d.ts +1 -0
- package/dist/recorder/agent/main.js +14 -0
- package/dist/recorder/agent/main.js.map +1 -0
- package/dist/recorder/agent/page-agent.d.ts +12 -0
- package/dist/recorder/agent/page-agent.js +40 -0
- package/dist/recorder/agent/page-agent.js.map +1 -0
- package/dist/recorder/agent/reveal-tracker.d.ts +26 -0
- package/dist/recorder/agent/reveal-tracker.js +41 -0
- package/dist/recorder/agent/reveal-tracker.js.map +1 -0
- package/dist/recorder/agent/roles.d.ts +11 -0
- package/dist/recorder/agent/roles.js +148 -0
- package/dist/recorder/agent/roles.js.map +1 -0
- package/dist/recorder/agent/selectors.d.ts +26 -0
- package/dist/recorder/agent/selectors.js +256 -0
- package/dist/recorder/agent/selectors.js.map +1 -0
- package/dist/recorder/agent/text.d.ts +19 -0
- package/dist/recorder/agent/text.js +82 -0
- package/dist/recorder/agent/text.js.map +1 -0
- package/dist/recorder/agent/transport.d.ts +14 -0
- package/dist/recorder/agent/transport.js +43 -0
- package/dist/recorder/agent/transport.js.map +1 -0
- package/dist/recorder/agent/visibility.d.ts +8 -0
- package/dist/recorder/agent/visibility.js +20 -0
- package/dist/recorder/agent/visibility.js.map +1 -0
- package/dist/recorder/agent-bundle.generated.d.ts +2 -0
- package/dist/recorder/agent-bundle.generated.js +6 -0
- package/dist/recorder/agent-bundle.generated.js.map +1 -0
- package/dist/recorder/attach.d.ts +52 -0
- package/dist/recorder/attach.js +159 -0
- package/dist/recorder/attach.js.map +1 -0
- package/dist/recorder/instrument.d.ts +17 -0
- package/dist/recorder/instrument.js +24 -0
- package/dist/recorder/instrument.js.map +1 -0
- package/dist/recorder/launch.d.ts +38 -0
- package/dist/recorder/launch.js +72 -0
- package/dist/recorder/launch.js.map +1 -0
- package/dist/recorder/reaction.d.ts +13 -0
- package/dist/recorder/reaction.js +65 -0
- package/dist/recorder/reaction.js.map +1 -0
- package/dist/recorder/types.d.ts +62 -0
- package/dist/recorder/types.js +2 -0
- package/dist/recorder/types.js.map +1 -0
- package/dist/replayer/artifacts.d.ts +25 -0
- package/dist/replayer/artifacts.js +58 -0
- package/dist/replayer/artifacts.js.map +1 -0
- package/dist/replayer/invariants.d.ts +26 -0
- package/dist/replayer/invariants.js +109 -0
- package/dist/replayer/invariants.js.map +1 -0
- package/dist/replayer/resolve.d.ts +35 -0
- package/dist/replayer/resolve.js +50 -0
- package/dist/replayer/resolve.js.map +1 -0
- package/dist/replayer/run.d.ts +104 -0
- package/dist/replayer/run.js +467 -0
- package/dist/replayer/run.js.map +1 -0
- package/dist/replayer/values.d.ts +13 -0
- package/dist/replayer/values.js +76 -0
- package/dist/replayer/values.js.map +1 -0
- package/dist/replayer/waits.d.ts +25 -0
- package/dist/replayer/waits.js +90 -0
- package/dist/replayer/waits.js.map +1 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +10 -0
- package/dist/version.js.map +1 -0
- package/package.json +77 -0
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
import { exec } from 'node:child_process';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { mkdir } from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { promisify } from 'node:util';
|
|
6
|
+
import { openBrowser } from '../browser.js';
|
|
7
|
+
import { reproPaths } from '../ir/io.js';
|
|
8
|
+
import { collectReactions } from '../recorder/reaction.js';
|
|
9
|
+
import { writeFailureArtifacts } from './artifacts.js';
|
|
10
|
+
import { checkBugRecurred, checkInvariants, hasBugSignature, hasFixCriterion, } from './invariants.js';
|
|
11
|
+
import { DEFAULT_RESOLVE_TIMEOUTS, resolveTarget, TargetResolutionError, } from './resolve.js';
|
|
12
|
+
import { createExpander } from './values.js';
|
|
13
|
+
import { waitForReaction } from './waits.js';
|
|
14
|
+
const execAsync = promisify(exec);
|
|
15
|
+
export async function runRepro(repro, options = {}) {
|
|
16
|
+
const root = options.root ?? process.cwd();
|
|
17
|
+
const baseUrl = options.baseUrl ?? repro.baseUrl;
|
|
18
|
+
const paths = reproPaths(repro.name, root);
|
|
19
|
+
const expectFixed = options.expectFixed ?? false;
|
|
20
|
+
// One expander per run: a named placeholder resolves identically across every
|
|
21
|
+
// step, selector and wait here, and differently on the next run.
|
|
22
|
+
const expand = createExpander();
|
|
23
|
+
const notes = [];
|
|
24
|
+
const startedAt = Date.now();
|
|
25
|
+
if (options.setupCommand) {
|
|
26
|
+
await execAsync(options.setupCommand);
|
|
27
|
+
}
|
|
28
|
+
const opened = await openBrowser({
|
|
29
|
+
headless: !options.headed,
|
|
30
|
+
viewport: repro.viewport,
|
|
31
|
+
storageStatePath: options.profileDir ? null : storageStatePath(repro, root),
|
|
32
|
+
profileDir: options.profileDir ?? null,
|
|
33
|
+
// A persistent profile owns its own process, so it cannot share one.
|
|
34
|
+
browser: options.profileDir ? null : (options.browser ?? null),
|
|
35
|
+
});
|
|
36
|
+
try {
|
|
37
|
+
const { context, page } = opened;
|
|
38
|
+
const reactions = collectReactions(context);
|
|
39
|
+
await page.goto(new URL(repro.startPath, baseUrl).toString(), {
|
|
40
|
+
waitUntil: 'domcontentloaded',
|
|
41
|
+
});
|
|
42
|
+
const timings = [];
|
|
43
|
+
let stepStart = Date.now();
|
|
44
|
+
for (let i = 0; i < repro.steps.length; i++) {
|
|
45
|
+
const step = repro.steps[i];
|
|
46
|
+
if (!step)
|
|
47
|
+
continue;
|
|
48
|
+
const previousStepStart = stepStart;
|
|
49
|
+
stepStart = Date.now();
|
|
50
|
+
const semantic = step.target?.semantic ?? describeTargetless(step);
|
|
51
|
+
let candidateIndex = -1;
|
|
52
|
+
try {
|
|
53
|
+
candidateIndex = await performStep(page, step, baseUrl, options, expand);
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
return await fail({
|
|
57
|
+
paths,
|
|
58
|
+
page,
|
|
59
|
+
repro,
|
|
60
|
+
reactions,
|
|
61
|
+
timings,
|
|
62
|
+
startedAt,
|
|
63
|
+
since: previousStepStart,
|
|
64
|
+
expectFixed,
|
|
65
|
+
notes,
|
|
66
|
+
}, {
|
|
67
|
+
stepId: step.id,
|
|
68
|
+
stepIndex: i,
|
|
69
|
+
semantic,
|
|
70
|
+
expected: expectationOf(step),
|
|
71
|
+
kind: 'infrastructure',
|
|
72
|
+
observed: err instanceof TargetResolutionError
|
|
73
|
+
? `no candidate selector matched. Tried:\n${err.attempts
|
|
74
|
+
.map((a) => ` ${a.selector} (${a.error})`)
|
|
75
|
+
.join('\n')}`
|
|
76
|
+
: err.message,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
const actMs = Date.now() - stepStart;
|
|
80
|
+
const outcome = await waitForReaction({ page, baseUrl, network: reactions.network, since: stepStart }, {
|
|
81
|
+
...step.waitAfter,
|
|
82
|
+
timeoutMs: Math.round(step.waitAfter.timeoutMs * (options.timeoutScale ?? 1)),
|
|
83
|
+
// A value made unique with {{random:name}} changes the accessible
|
|
84
|
+
// names derived from it, so the waits must expand the same way.
|
|
85
|
+
domAppeared: expand.expandAll(step.waitAfter.domAppeared),
|
|
86
|
+
domGone: expand.expandAll(step.waitAfter.domGone),
|
|
87
|
+
});
|
|
88
|
+
if (!outcome.ok && expectFixed) {
|
|
89
|
+
// The reaction changed. That is the expected consequence of a fix, not
|
|
90
|
+
// a failure — but it is worth telling the reader about.
|
|
91
|
+
notes.push(`${step.id} (${semantic}): recorded reaction no longer occurs — ${outcome.unmet.join('; ')}`);
|
|
92
|
+
}
|
|
93
|
+
else if (!outcome.ok) {
|
|
94
|
+
return await fail({
|
|
95
|
+
paths,
|
|
96
|
+
page,
|
|
97
|
+
repro,
|
|
98
|
+
reactions,
|
|
99
|
+
timings,
|
|
100
|
+
startedAt,
|
|
101
|
+
since: stepStart,
|
|
102
|
+
expectFixed,
|
|
103
|
+
notes,
|
|
104
|
+
}, {
|
|
105
|
+
stepId: step.id,
|
|
106
|
+
stepIndex: i,
|
|
107
|
+
semantic,
|
|
108
|
+
kind: 'assertion',
|
|
109
|
+
expected: expectationOf(step),
|
|
110
|
+
observed: `the action ran, but these recorded signals never arrived within ${step.waitAfter.timeoutMs}ms:\n${outcome.unmet
|
|
111
|
+
.map((u) => ` ${u}`)
|
|
112
|
+
.join('\n')}`,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
timings.push({
|
|
116
|
+
id: step.id,
|
|
117
|
+
action: step.action,
|
|
118
|
+
semantic,
|
|
119
|
+
actMs,
|
|
120
|
+
waitMs: outcome.durationMs,
|
|
121
|
+
totalMs: Date.now() - stepStart,
|
|
122
|
+
candidateIndex,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
// The final-state assertion. Auto-derived from the last step, but the whole
|
|
126
|
+
// point of a readable IR is that a human or an agent can widen it by hand —
|
|
127
|
+
// so it has to actually be evaluated. It previously was not: the fields were
|
|
128
|
+
// written by the compiler, validated by the schema, documented as the
|
|
129
|
+
// assertion seam, and silently ignored. A hand-edited assertion returned
|
|
130
|
+
// green without ever being checked, which is worse than not having it.
|
|
131
|
+
// An absence-bug has no positive signature: the recorded finalState says
|
|
132
|
+
// what vanished, never what wrongly failed to appear, so it stays satisfied
|
|
133
|
+
// after a fix and plain `run` reports the bug still present. When the author
|
|
134
|
+
// has said what "fixed" looks like, the inverse of that is the sharper
|
|
135
|
+
// question — and the only one that can distinguish the two states.
|
|
136
|
+
if (!expectFixed && hasFixCriterion(repro)) {
|
|
137
|
+
const expected = repro.assertion.expectedWhenFixed;
|
|
138
|
+
const outcome = await waitForReaction({ page, baseUrl, network: reactions.network, since: stepStart }, {
|
|
139
|
+
...(expected.domAppeared?.length
|
|
140
|
+
? { domAppeared: expand.expandAll(expected.domAppeared) }
|
|
141
|
+
: {}),
|
|
142
|
+
...(expected.domGone?.length ? { domGone: expand.expandAll(expected.domGone) } : {}),
|
|
143
|
+
timeoutMs: FINAL_STATE_TIMEOUT_MS,
|
|
144
|
+
});
|
|
145
|
+
if (outcome.ok) {
|
|
146
|
+
const last = repro.steps[repro.steps.length - 1];
|
|
147
|
+
return await fail({ paths, page, repro, reactions, timings, startedAt, since: stepStart, expectFixed, notes }, {
|
|
148
|
+
stepId: last?.id ?? 'assertion',
|
|
149
|
+
stepIndex: repro.steps.length - 1,
|
|
150
|
+
semantic: 'bug no longer reproduces',
|
|
151
|
+
kind: 'assertion',
|
|
152
|
+
expected: `the bug to still be present (${describeState(expected)} should NOT hold)`,
|
|
153
|
+
observed: 'the fixed-state criterion is satisfied, so this repro no longer reproduces the bug',
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const finalOutcome = await waitForFinalState({ page, baseUrl, network: reactions.network, since: stepStart }, repro, expectFixed, expand);
|
|
158
|
+
if (finalOutcome && !finalOutcome.ok) {
|
|
159
|
+
const last = repro.steps[repro.steps.length - 1];
|
|
160
|
+
return await fail({ paths, page, repro, reactions, timings, startedAt, since: stepStart, expectFixed, notes }, {
|
|
161
|
+
stepId: last?.id ?? 'assertion',
|
|
162
|
+
stepIndex: repro.steps.length - 1,
|
|
163
|
+
semantic: 'final state assertion',
|
|
164
|
+
kind: 'assertion',
|
|
165
|
+
expected: describeFinalState(repro),
|
|
166
|
+
observed: `these never arrived within ${FINAL_STATE_TIMEOUT_MS}ms:\n ${finalOutcome.unmet.join('\n ')}`,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
const finalScreenshot = options.captureFinalScreenshot
|
|
170
|
+
? await captureEndState(page, paths)
|
|
171
|
+
: null;
|
|
172
|
+
if (expectFixed) {
|
|
173
|
+
// Refuse to certify a fix we had no way to check.
|
|
174
|
+
//
|
|
175
|
+
// Under this polarity a changed step reaction is downgraded to a note —
|
|
176
|
+
// correctly, since a fix is supposed to change behaviour. But that left
|
|
177
|
+
// nothing able to fail for a bug with no console error and no failed
|
|
178
|
+
// request: a missing element, a wrong number, a broken layout all
|
|
179
|
+
// reported FIXED while the page was visibly still broken. Reporting
|
|
180
|
+
// success after checking nothing is the worst thing this tool can do,
|
|
181
|
+
// so it now says so instead.
|
|
182
|
+
if (!hasFixCriterion(repro) && !hasBugSignature(repro)) {
|
|
183
|
+
const last = repro.steps[repro.steps.length - 1];
|
|
184
|
+
return await fail({ paths, page, repro, reactions, timings, startedAt, since: stepStart, expectFixed, notes }, {
|
|
185
|
+
stepId: last?.id ?? 'assertion',
|
|
186
|
+
stepIndex: repro.steps.length - 1,
|
|
187
|
+
semantic: 'fix criterion',
|
|
188
|
+
kind: 'assertion',
|
|
189
|
+
expected: 'something that distinguishes fixed from broken',
|
|
190
|
+
observed: 'This repro records no console error and no failed request, so there is nothing ' +
|
|
191
|
+
'--expect-fixed can check. It would pass whether or not you fixed anything.\n' +
|
|
192
|
+
' Add what must be TRUE once fixed, e.g.:\n' +
|
|
193
|
+
' "assertion": { "expectedWhenFixed": { "domAppeared": ["text=Total spend"] } }\n' +
|
|
194
|
+
' Use `repro run` (without --expect-fixed) to confirm the bug still reproduces.',
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
if (hasFixCriterion(repro)) {
|
|
198
|
+
const expected = repro.assertion.expectedWhenFixed;
|
|
199
|
+
const outcome = await waitForReaction({ page, baseUrl, network: reactions.network, since: stepStart }, {
|
|
200
|
+
...(expected.domAppeared?.length
|
|
201
|
+
? { domAppeared: expand.expandAll(expected.domAppeared) }
|
|
202
|
+
: {}),
|
|
203
|
+
...(expected.domGone?.length ? { domGone: expand.expandAll(expected.domGone) } : {}),
|
|
204
|
+
timeoutMs: FINAL_STATE_TIMEOUT_MS,
|
|
205
|
+
});
|
|
206
|
+
if (!outcome.ok) {
|
|
207
|
+
const last = repro.steps[repro.steps.length - 1];
|
|
208
|
+
return await fail({ paths, page, repro, reactions, timings, startedAt, since: stepStart, expectFixed, notes }, {
|
|
209
|
+
stepId: last?.id ?? 'assertion',
|
|
210
|
+
stepIndex: repro.steps.length - 1,
|
|
211
|
+
semantic: 'fix criterion',
|
|
212
|
+
kind: 'assertion',
|
|
213
|
+
expected: describeState(expected),
|
|
214
|
+
observed: `still not satisfied after ${FINAL_STATE_TIMEOUT_MS}ms:\n ${outcome.unmet.join('\n ')}`,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// Bug-recurrence is a fallback signal, used only when the author has not
|
|
220
|
+
// said what "fixed" means. Console errors are a poor signature on a real
|
|
221
|
+
// SPA — an app that always logs a failed i18n fetch would make every
|
|
222
|
+
// --expect-fixed run report the bug present forever, and no hand-written
|
|
223
|
+
// criterion could override it. If one is supplied, it decides.
|
|
224
|
+
const recurred = expectFixed && !hasFixCriterion(repro)
|
|
225
|
+
? checkBugRecurred(repro, reactions.network, reactions.console, baseUrl)
|
|
226
|
+
: [];
|
|
227
|
+
if (expectFixed && hasFixCriterion(repro)) {
|
|
228
|
+
const alsoRecurred = checkBugRecurred(repro, reactions.network, reactions.console, baseUrl);
|
|
229
|
+
for (const detail of alsoRecurred)
|
|
230
|
+
notes.push(`${detail} (not fatal: expectedWhenFixed governs)`);
|
|
231
|
+
}
|
|
232
|
+
if (recurred.length) {
|
|
233
|
+
const last = repro.steps[repro.steps.length - 1];
|
|
234
|
+
return await fail({ paths, page, repro, reactions, timings, startedAt, since: stepStart, expectFixed, notes }, {
|
|
235
|
+
stepId: last?.id ?? 'assertion',
|
|
236
|
+
stepIndex: repro.steps.length - 1,
|
|
237
|
+
semantic: 'bug recurrence check',
|
|
238
|
+
kind: 'assertion',
|
|
239
|
+
expected: 'the recorded bug not to happen again',
|
|
240
|
+
observed: recurred.join('\n '),
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
const violations = checkInvariants(repro, reactions.network, reactions.console, baseUrl);
|
|
244
|
+
if (violations.length) {
|
|
245
|
+
const last = repro.steps[repro.steps.length - 1];
|
|
246
|
+
return await fail({
|
|
247
|
+
paths,
|
|
248
|
+
page,
|
|
249
|
+
repro,
|
|
250
|
+
reactions,
|
|
251
|
+
timings,
|
|
252
|
+
startedAt,
|
|
253
|
+
since: stepStart,
|
|
254
|
+
expectFixed,
|
|
255
|
+
notes,
|
|
256
|
+
}, {
|
|
257
|
+
stepId: last?.id ?? 'assertion',
|
|
258
|
+
stepIndex: repro.steps.length - 1,
|
|
259
|
+
semantic: 'final assertion',
|
|
260
|
+
kind: 'assertion',
|
|
261
|
+
expected: violations
|
|
262
|
+
.map((v) => `${v.invariant} to hold`)
|
|
263
|
+
.filter((v, i, a) => a.indexOf(v) === i)
|
|
264
|
+
.join(', '),
|
|
265
|
+
observed: violations.map((v) => `${v.invariant}: ${v.detail}`).join('\n '),
|
|
266
|
+
}, violations);
|
|
267
|
+
}
|
|
268
|
+
const lastStepDiverged = notes.length > 0 && notes.some((n) => n.startsWith(`${repro.steps[repro.steps.length - 1]?.id} `));
|
|
269
|
+
if (expectFixed &&
|
|
270
|
+
repro.steps.length >= 3 &&
|
|
271
|
+
// A single-shot repro diverges in exactly one step — the last one, which
|
|
272
|
+
// carries the assertion. Requiring most steps to diverge missed it every
|
|
273
|
+
// time, which is the only case that mattered.
|
|
274
|
+
(lastStepDiverged || notes.length >= Math.ceil(repro.steps.length / 2))) {
|
|
275
|
+
// Most of the flow behaved nothing like the recording. That is what a
|
|
276
|
+
// single-shot repro looks like on its second run — the state it depended
|
|
277
|
+
// on already exists, so the bug cannot recur and the pass means nothing.
|
|
278
|
+
notes.push(`WARNING: ${notes.length} of ${repro.steps.length} steps did not reproduce their recorded reaction. ` +
|
|
279
|
+
`This repro may be single-shot: if the flow mutates server state, replays after the first ` +
|
|
280
|
+
`cannot exercise the same path and will pass regardless of any fix. ` +
|
|
281
|
+
`Use --setup to reset state, or make inputs unique with {{random}}.`);
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
name: repro.name,
|
|
285
|
+
passed: true,
|
|
286
|
+
durationMs: Date.now() - startedAt,
|
|
287
|
+
totalSteps: repro.steps.length,
|
|
288
|
+
expectFixed,
|
|
289
|
+
notes,
|
|
290
|
+
finalScreenshot,
|
|
291
|
+
timings,
|
|
292
|
+
failure: null,
|
|
293
|
+
invariantViolations: [],
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
finally {
|
|
297
|
+
await opened.close();
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
function storageStatePath(repro, root) {
|
|
301
|
+
if (!repro.storageStatePath)
|
|
302
|
+
return null;
|
|
303
|
+
const abs = path.resolve(root, repro.storageStatePath);
|
|
304
|
+
// A deleted state file should degrade to a clean session, not crash the run.
|
|
305
|
+
return existsSync(abs) ? abs : null;
|
|
306
|
+
}
|
|
307
|
+
async function fail(ctx, failure, violations = []) {
|
|
308
|
+
const artifacts = await writeFailureArtifacts(ctx.page, ctx.paths, {
|
|
309
|
+
...failure,
|
|
310
|
+
networkSince: ctx.reactions.network.filter((n) => n.startedAt >= ctx.since),
|
|
311
|
+
consoleErrors: ctx.reactions.console,
|
|
312
|
+
});
|
|
313
|
+
return {
|
|
314
|
+
name: ctx.repro.name,
|
|
315
|
+
passed: false,
|
|
316
|
+
durationMs: Date.now() - ctx.startedAt,
|
|
317
|
+
totalSteps: ctx.repro.steps.length,
|
|
318
|
+
expectFixed: ctx.expectFixed,
|
|
319
|
+
notes: ctx.notes,
|
|
320
|
+
finalScreenshot: artifacts.screenshot,
|
|
321
|
+
timings: ctx.timings,
|
|
322
|
+
failure: { ...failure, artifacts },
|
|
323
|
+
invariantViolations: violations,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
/** Ceiling for the final-state assertion, independent of any step's budget. */
|
|
327
|
+
const FINAL_STATE_TIMEOUT_MS = 5_000;
|
|
328
|
+
/**
|
|
329
|
+
* Evaluate `assertion.finalState` after the last step.
|
|
330
|
+
*
|
|
331
|
+
* Skipped entirely under --expect-fixed: the recorded final state describes the
|
|
332
|
+
* BUG, and demanding it after a fix would fail every correct build.
|
|
333
|
+
*/
|
|
334
|
+
async function waitForFinalState(ctx, repro, expectFixed, expand) {
|
|
335
|
+
if (expectFixed)
|
|
336
|
+
return null;
|
|
337
|
+
const domAppeared = expand.expandAll(repro.assertion.finalState.domAppeared);
|
|
338
|
+
const domGone = expand.expandAll(repro.assertion.finalState.domGone);
|
|
339
|
+
if (!domAppeared?.length && !domGone?.length)
|
|
340
|
+
return null;
|
|
341
|
+
const outcome = await waitForReaction(ctx, {
|
|
342
|
+
...(domAppeared?.length ? { domAppeared } : {}),
|
|
343
|
+
...(domGone?.length ? { domGone } : {}),
|
|
344
|
+
timeoutMs: FINAL_STATE_TIMEOUT_MS,
|
|
345
|
+
});
|
|
346
|
+
return { ok: outcome.ok, unmet: outcome.unmet };
|
|
347
|
+
}
|
|
348
|
+
function describeFinalState(repro) {
|
|
349
|
+
return describeState(repro.assertion.finalState);
|
|
350
|
+
}
|
|
351
|
+
function describeState(state) {
|
|
352
|
+
return [
|
|
353
|
+
...(state.domAppeared ?? []).map((s) => `${s} to be present`),
|
|
354
|
+
...(state.domGone ?? []).map((s) => `${s} to be gone`),
|
|
355
|
+
].join(', ');
|
|
356
|
+
}
|
|
357
|
+
/** End-of-run screenshot for a passing run; failures capture their own. */
|
|
358
|
+
async function captureEndState(page, paths) {
|
|
359
|
+
const file = path.join(paths.artifactsDir, 'final.png');
|
|
360
|
+
try {
|
|
361
|
+
await mkdir(paths.artifactsDir, { recursive: true });
|
|
362
|
+
await page.screenshot({ path: file, fullPage: true, animations: 'disabled', caret: 'hide' });
|
|
363
|
+
return file;
|
|
364
|
+
}
|
|
365
|
+
catch {
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
function describeTargetless(step) {
|
|
370
|
+
if (step.action === 'goto')
|
|
371
|
+
return `navigate to ${step.value ?? '(unknown)'}`;
|
|
372
|
+
if (step.action === 'scroll')
|
|
373
|
+
return `scroll the page to ${step.value ?? '(unknown)'}`;
|
|
374
|
+
if (step.action === 'press')
|
|
375
|
+
return `press ${step.value ?? '(unknown)'}`;
|
|
376
|
+
return step.action;
|
|
377
|
+
}
|
|
378
|
+
function expectationOf(step) {
|
|
379
|
+
const parts = [];
|
|
380
|
+
for (const n of step.waitAfter.network ?? [])
|
|
381
|
+
parts.push(`${n.method} ${n.urlPattern} to settle`);
|
|
382
|
+
for (const s of step.waitAfter.domAppeared ?? [])
|
|
383
|
+
parts.push(`${s} to appear`);
|
|
384
|
+
for (const s of step.waitAfter.domGone ?? [])
|
|
385
|
+
parts.push(`${s} to disappear`);
|
|
386
|
+
if (step.waitAfter.networkIdle)
|
|
387
|
+
parts.push('the network to go idle');
|
|
388
|
+
return parts.length ? parts.join(', ') : 'the step to complete';
|
|
389
|
+
}
|
|
390
|
+
/** Performs one step; returns which candidate selector worked (-1 when targetless). */
|
|
391
|
+
async function performStep(page, step, baseUrl, options, expand) {
|
|
392
|
+
const timeouts = options.resolveTimeouts ?? DEFAULT_RESOLVE_TIMEOUTS;
|
|
393
|
+
if (step.action === 'goto') {
|
|
394
|
+
await page.goto(rebase(step.value, baseUrl), { waitUntil: 'domcontentloaded' });
|
|
395
|
+
return -1;
|
|
396
|
+
}
|
|
397
|
+
if (step.action === 'scroll' && !step.target) {
|
|
398
|
+
const { x, y } = parsePosition(step.value);
|
|
399
|
+
await page.evaluate(([px, py]) => window.scrollTo(px, py), [x, y]);
|
|
400
|
+
return -1;
|
|
401
|
+
}
|
|
402
|
+
if (step.action === 'press' && !step.target) {
|
|
403
|
+
await page.keyboard.press(step.value ?? 'Enter');
|
|
404
|
+
return -1;
|
|
405
|
+
}
|
|
406
|
+
if (!step.target)
|
|
407
|
+
throw new Error(`Step ${step.id} (${step.action}) has no target to act on.`);
|
|
408
|
+
const target = {
|
|
409
|
+
...step.target,
|
|
410
|
+
candidates: expand.expandAll(step.target.candidates) ?? step.target.candidates,
|
|
411
|
+
};
|
|
412
|
+
const resolved = await resolveTarget(page, target, timeouts, options.onStepFailure);
|
|
413
|
+
const { locator } = resolved;
|
|
414
|
+
switch (step.action) {
|
|
415
|
+
case 'click':
|
|
416
|
+
await locator.click();
|
|
417
|
+
break;
|
|
418
|
+
case 'dblclick':
|
|
419
|
+
await locator.dblclick();
|
|
420
|
+
break;
|
|
421
|
+
case 'hover':
|
|
422
|
+
await locator.hover();
|
|
423
|
+
break;
|
|
424
|
+
case 'fill':
|
|
425
|
+
await locator.fill(expand.expand(step.value) ?? '');
|
|
426
|
+
break;
|
|
427
|
+
case 'select':
|
|
428
|
+
await locator.selectOption(expand.expand(step.value) ?? '');
|
|
429
|
+
break;
|
|
430
|
+
case 'press':
|
|
431
|
+
await locator.press(step.value ?? 'Enter');
|
|
432
|
+
break;
|
|
433
|
+
case 'scroll': {
|
|
434
|
+
const { x, y } = parsePosition(step.value);
|
|
435
|
+
await locator.evaluate((el, [px, py]) => {
|
|
436
|
+
el.scrollLeft = px;
|
|
437
|
+
el.scrollTop = py;
|
|
438
|
+
}, [x, y]);
|
|
439
|
+
break;
|
|
440
|
+
}
|
|
441
|
+
default:
|
|
442
|
+
throw new Error(`Unsupported action "${step.action}" in step ${step.id}.`);
|
|
443
|
+
}
|
|
444
|
+
return resolved.candidateIndex;
|
|
445
|
+
}
|
|
446
|
+
/** Re-point a recorded absolute URL at the base URL replay is actually using. */
|
|
447
|
+
function rebase(recorded, baseUrl) {
|
|
448
|
+
if (!recorded)
|
|
449
|
+
return baseUrl;
|
|
450
|
+
try {
|
|
451
|
+
const u = new URL(recorded);
|
|
452
|
+
return new URL(`${u.pathname}${u.search}${u.hash}`, baseUrl).toString();
|
|
453
|
+
}
|
|
454
|
+
catch {
|
|
455
|
+
return new URL(recorded, baseUrl).toString();
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
function parsePosition(value) {
|
|
459
|
+
try {
|
|
460
|
+
const parsed = JSON.parse(value ?? '{}');
|
|
461
|
+
return { x: parsed.x ?? 0, y: parsed.y ?? 0 };
|
|
462
|
+
}
|
|
463
|
+
catch {
|
|
464
|
+
return { x: 0, y: 0 };
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
//# sourceMappingURL=run.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/replayer/run.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare const PLACEHOLDER_NAMES: string[];
|
|
2
|
+
export interface Expander {
|
|
3
|
+
/** Null and plain strings pass through untouched. */
|
|
4
|
+
expand<T extends string | null | undefined>(value: T): T;
|
|
5
|
+
expandAll(values: string[] | undefined): string[] | undefined;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* One expander per replay. Named placeholders are memoized on it, so the same
|
|
9
|
+
* name resolves identically across every step, selector and wait in that run —
|
|
10
|
+
* and differently on the next run, which is the whole point.
|
|
11
|
+
*/
|
|
12
|
+
export declare function createExpander(): Expander;
|
|
13
|
+
export declare function hasPlaceholder(value: string | null | undefined): boolean;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
/**
|
|
3
|
+
* Placeholder expansion for recorded values, selectors and waits.
|
|
4
|
+
*
|
|
5
|
+
* Some repros are single-shot as recorded: the flow mutates server state, so
|
|
6
|
+
* the second replay finds the work already done and the bug cannot recur. A
|
|
7
|
+
* developer looping on `--expect-fixed` then gets green from run 2 onward
|
|
8
|
+
* whether or not they fixed anything — a silent false pass, the worst outcome
|
|
9
|
+
* this tool can produce.
|
|
10
|
+
*
|
|
11
|
+
* Client-side session seeding cannot help; the mutation is on the server. What
|
|
12
|
+
* does help is making the input unique per run:
|
|
13
|
+
*
|
|
14
|
+
* "value": "merchant:walmart-{{random}}"
|
|
15
|
+
*
|
|
16
|
+
* But a changed input invalidates everything downstream that embedded the old
|
|
17
|
+
* one. Renaming a sensor to `Boiler-x7f2` breaks a recorded wait on
|
|
18
|
+
* `role=button[name="Delete Boiler inlet"]`, because the accessible name was
|
|
19
|
+
* derived from the value. So placeholders can be **named**, and a name expands
|
|
20
|
+
* to the same string everywhere within a single run:
|
|
21
|
+
*
|
|
22
|
+
* "value": "Boiler-{{random:sensor}}"
|
|
23
|
+
* "domAppeared": ["role=button[name=\"Delete Boiler-{{random:sensor}}\"]"]
|
|
24
|
+
*
|
|
25
|
+
* Anonymous `{{random}}` still yields a fresh value at every occurrence, which
|
|
26
|
+
* is what you want when nothing downstream refers to it.
|
|
27
|
+
*/
|
|
28
|
+
const GENERATORS = {
|
|
29
|
+
uuid: () => randomUUID(),
|
|
30
|
+
random: () => Math.random().toString(36).slice(2, 10),
|
|
31
|
+
now: () => String(Date.now()),
|
|
32
|
+
isodate: () => new Date().toISOString(),
|
|
33
|
+
};
|
|
34
|
+
export const PLACEHOLDER_NAMES = Object.keys(GENERATORS);
|
|
35
|
+
const PLACEHOLDER_RE = /\{\{(\w+)(?::(\w+))?\}\}/g;
|
|
36
|
+
/**
|
|
37
|
+
* One expander per replay. Named placeholders are memoized on it, so the same
|
|
38
|
+
* name resolves identically across every step, selector and wait in that run —
|
|
39
|
+
* and differently on the next run, which is the whole point.
|
|
40
|
+
*/
|
|
41
|
+
export function createExpander() {
|
|
42
|
+
const memo = new Map();
|
|
43
|
+
function expandString(value) {
|
|
44
|
+
return value.replace(PLACEHOLDER_RE, (match, kind, name) => {
|
|
45
|
+
const make = GENERATORS[kind];
|
|
46
|
+
// Unknown placeholders are left alone rather than blanked, so a typo is
|
|
47
|
+
// visible in the failure message instead of silently emptying a field.
|
|
48
|
+
if (!make)
|
|
49
|
+
return match;
|
|
50
|
+
if (!name)
|
|
51
|
+
return make();
|
|
52
|
+
const key = `${kind}:${name}`;
|
|
53
|
+
let existing = memo.get(key);
|
|
54
|
+
if (existing === undefined) {
|
|
55
|
+
existing = make();
|
|
56
|
+
memo.set(key, existing);
|
|
57
|
+
}
|
|
58
|
+
return existing;
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
expand(value) {
|
|
63
|
+
return (typeof value === 'string' ? expandString(value) : value);
|
|
64
|
+
},
|
|
65
|
+
expandAll(values) {
|
|
66
|
+
return values?.map(expandString);
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
export function hasPlaceholder(value) {
|
|
71
|
+
if (!value)
|
|
72
|
+
return false;
|
|
73
|
+
PLACEHOLDER_RE.lastIndex = 0;
|
|
74
|
+
return PLACEHOLDER_RE.test(value);
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=values.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"values.js","sourceRoot":"","sources":["../../src/replayer/values.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,MAAM,UAAU,GAAiC;IAC/C,IAAI,EAAE,GAAG,EAAE,CAAC,UAAU,EAAE;IACxB,MAAM,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC;IACrD,GAAG,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7B,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;CACxC,CAAC;AAEF,MAAM,CAAC,MAAM,iBAAiB,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;AAEzD,MAAM,cAAc,GAAG,2BAA2B,CAAC;AAQnD;;;;GAIG;AACH,MAAM,UAAU,cAAc;IAC5B,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC;IAEvC,SAAS,YAAY,CAAC,KAAa;QACjC,OAAO,KAAK,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC,KAAK,EAAE,IAAY,EAAE,IAAa,EAAE,EAAE;YAC1E,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;YAC9B,wEAAwE;YACxE,uEAAuE;YACvE,IAAI,CAAC,IAAI;gBAAE,OAAO,KAAK,CAAC;YACxB,IAAI,CAAC,IAAI;gBAAE,OAAO,IAAI,EAAE,CAAC;YACzB,MAAM,GAAG,GAAG,GAAG,IAAI,IAAI,IAAI,EAAE,CAAC;YAC9B,IAAI,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC7B,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;gBAC3B,QAAQ,GAAG,IAAI,EAAE,CAAC;gBAClB,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;YAC1B,CAAC;YACD,OAAO,QAAQ,CAAC;QAClB,CAAC,CAAC,CAAC;IACL,CAAC;IAED,OAAO;QACL,MAAM,CAAC,KAAK;YACV,OAAO,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAiB,CAAC;QACnF,CAAC;QACD,SAAS,CAAC,MAAM;YACd,OAAO,MAAM,EAAE,GAAG,CAAC,YAAY,CAAC,CAAC;QACnC,CAAC;KACF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAgC;IAC7D,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC;IACzB,cAAc,CAAC,SAAS,GAAG,CAAC,CAAC;IAC7B,OAAO,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AACpC,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Page } from 'playwright';
|
|
2
|
+
import type { WaitAfter } from '../ir/schema.js';
|
|
3
|
+
import type { RawNetworkEvent } from '../recorder/types.js';
|
|
4
|
+
export interface WaitContext {
|
|
5
|
+
page: Page;
|
|
6
|
+
baseUrl: string;
|
|
7
|
+
/** Live network buffer, shared with the runner. */
|
|
8
|
+
network: RawNetworkEvent[];
|
|
9
|
+
/** Everything from this timestamp on counts toward this step's signals. */
|
|
10
|
+
since: number;
|
|
11
|
+
}
|
|
12
|
+
export interface WaitOutcome {
|
|
13
|
+
ok: boolean;
|
|
14
|
+
/** Human-readable descriptions of signals that never arrived. */
|
|
15
|
+
unmet: string[];
|
|
16
|
+
/** Signals that did arrive, for the success-path timing table. */
|
|
17
|
+
met: string[];
|
|
18
|
+
durationMs: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Wait for every recorded signal at once, with the step's timeoutMs as a single
|
|
22
|
+
* ceiling over all of them. Never a fixed sleep — replay proceeds the moment
|
|
23
|
+
* the environment actually reacts, which is where the speed comes from.
|
|
24
|
+
*/
|
|
25
|
+
export declare function waitForReaction(ctx: WaitContext, waitAfter: WaitAfter): Promise<WaitOutcome>;
|