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.
Files changed (111) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +126 -0
  3. package/dist/api.d.ts +65 -0
  4. package/dist/api.js +78 -0
  5. package/dist/api.js.map +1 -0
  6. package/dist/browser.d.ts +67 -0
  7. package/dist/browser.js +77 -0
  8. package/dist/browser.js.map +1 -0
  9. package/dist/cli/format.d.ts +12 -0
  10. package/dist/cli/format.js +55 -0
  11. package/dist/cli/format.js.map +1 -0
  12. package/dist/cli/index.d.ts +2 -0
  13. package/dist/cli/index.js +199 -0
  14. package/dist/cli/index.js.map +1 -0
  15. package/dist/compiler/compile.d.ts +18 -0
  16. package/dist/compiler/compile.js +226 -0
  17. package/dist/compiler/compile.js.map +1 -0
  18. package/dist/compiler/normalize.d.ts +16 -0
  19. package/dist/compiler/normalize.js +75 -0
  20. package/dist/compiler/normalize.js.map +1 -0
  21. package/dist/compiler/waits.d.ts +31 -0
  22. package/dist/compiler/waits.js +69 -0
  23. package/dist/compiler/waits.js.map +1 -0
  24. package/dist/ir/io.d.ts +47 -0
  25. package/dist/ir/io.js +122 -0
  26. package/dist/ir/io.js.map +1 -0
  27. package/dist/ir/schema.d.ts +236 -0
  28. package/dist/ir/schema.js +153 -0
  29. package/dist/ir/schema.js.map +1 -0
  30. package/dist/mcp/index.d.ts +2 -0
  31. package/dist/mcp/index.js +22 -0
  32. package/dist/mcp/index.js.map +1 -0
  33. package/dist/mcp/server.d.ts +8 -0
  34. package/dist/mcp/server.js +268 -0
  35. package/dist/mcp/server.js.map +1 -0
  36. package/dist/noise.d.ts +20 -0
  37. package/dist/noise.js +70 -0
  38. package/dist/noise.js.map +1 -0
  39. package/dist/recorder/agent/capture.d.ts +15 -0
  40. package/dist/recorder/agent/capture.js +310 -0
  41. package/dist/recorder/agent/capture.js.map +1 -0
  42. package/dist/recorder/agent/config.d.ts +25 -0
  43. package/dist/recorder/agent/config.js +23 -0
  44. package/dist/recorder/agent/config.js.map +1 -0
  45. package/dist/recorder/agent/dom-reaction.d.ts +13 -0
  46. package/dist/recorder/agent/dom-reaction.js +166 -0
  47. package/dist/recorder/agent/dom-reaction.js.map +1 -0
  48. package/dist/recorder/agent/main.d.ts +1 -0
  49. package/dist/recorder/agent/main.js +14 -0
  50. package/dist/recorder/agent/main.js.map +1 -0
  51. package/dist/recorder/agent/page-agent.d.ts +12 -0
  52. package/dist/recorder/agent/page-agent.js +40 -0
  53. package/dist/recorder/agent/page-agent.js.map +1 -0
  54. package/dist/recorder/agent/reveal-tracker.d.ts +26 -0
  55. package/dist/recorder/agent/reveal-tracker.js +41 -0
  56. package/dist/recorder/agent/reveal-tracker.js.map +1 -0
  57. package/dist/recorder/agent/roles.d.ts +11 -0
  58. package/dist/recorder/agent/roles.js +148 -0
  59. package/dist/recorder/agent/roles.js.map +1 -0
  60. package/dist/recorder/agent/selectors.d.ts +26 -0
  61. package/dist/recorder/agent/selectors.js +256 -0
  62. package/dist/recorder/agent/selectors.js.map +1 -0
  63. package/dist/recorder/agent/text.d.ts +19 -0
  64. package/dist/recorder/agent/text.js +82 -0
  65. package/dist/recorder/agent/text.js.map +1 -0
  66. package/dist/recorder/agent/transport.d.ts +14 -0
  67. package/dist/recorder/agent/transport.js +43 -0
  68. package/dist/recorder/agent/transport.js.map +1 -0
  69. package/dist/recorder/agent/visibility.d.ts +8 -0
  70. package/dist/recorder/agent/visibility.js +20 -0
  71. package/dist/recorder/agent/visibility.js.map +1 -0
  72. package/dist/recorder/agent-bundle.generated.d.ts +2 -0
  73. package/dist/recorder/agent-bundle.generated.js +6 -0
  74. package/dist/recorder/agent-bundle.generated.js.map +1 -0
  75. package/dist/recorder/attach.d.ts +52 -0
  76. package/dist/recorder/attach.js +159 -0
  77. package/dist/recorder/attach.js.map +1 -0
  78. package/dist/recorder/instrument.d.ts +17 -0
  79. package/dist/recorder/instrument.js +24 -0
  80. package/dist/recorder/instrument.js.map +1 -0
  81. package/dist/recorder/launch.d.ts +38 -0
  82. package/dist/recorder/launch.js +72 -0
  83. package/dist/recorder/launch.js.map +1 -0
  84. package/dist/recorder/reaction.d.ts +13 -0
  85. package/dist/recorder/reaction.js +65 -0
  86. package/dist/recorder/reaction.js.map +1 -0
  87. package/dist/recorder/types.d.ts +62 -0
  88. package/dist/recorder/types.js +2 -0
  89. package/dist/recorder/types.js.map +1 -0
  90. package/dist/replayer/artifacts.d.ts +25 -0
  91. package/dist/replayer/artifacts.js +58 -0
  92. package/dist/replayer/artifacts.js.map +1 -0
  93. package/dist/replayer/invariants.d.ts +26 -0
  94. package/dist/replayer/invariants.js +109 -0
  95. package/dist/replayer/invariants.js.map +1 -0
  96. package/dist/replayer/resolve.d.ts +35 -0
  97. package/dist/replayer/resolve.js +50 -0
  98. package/dist/replayer/resolve.js.map +1 -0
  99. package/dist/replayer/run.d.ts +104 -0
  100. package/dist/replayer/run.js +467 -0
  101. package/dist/replayer/run.js.map +1 -0
  102. package/dist/replayer/values.d.ts +13 -0
  103. package/dist/replayer/values.js +76 -0
  104. package/dist/replayer/values.js.map +1 -0
  105. package/dist/replayer/waits.d.ts +25 -0
  106. package/dist/replayer/waits.js +90 -0
  107. package/dist/replayer/waits.js.map +1 -0
  108. package/dist/version.d.ts +1 -0
  109. package/dist/version.js +10 -0
  110. package/dist/version.js.map +1 -0
  111. package/package.json +77 -0
@@ -0,0 +1,199 @@
1
+ #!/usr/bin/env node
2
+ import path from 'node:path';
3
+ import { Command } from 'commander';
4
+ import { list, PartialRecordingError, record, run, STOP_HOTKEY } from '../api.js';
5
+ import { IRValidationError } from '../ir/schema.js';
6
+ import { age, bold, cyan, dim, green, ms, red, table, truncate, yellow } from './format.js';
7
+ import { VERSION } from '../version.js';
8
+ const program = new Command();
9
+ program
10
+ .name('repro')
11
+ .description('Record a bug once, verify the fix in seconds.')
12
+ // Read from the manifest so a build can never misreport which one it is.
13
+ .version(VERSION);
14
+ program
15
+ .command('record')
16
+ .argument('<name>', 'name for this repro')
17
+ .requiredOption('-u, --url <baseUrl>', 'base URL of your dev server, e.g. http://localhost:3000')
18
+ .option('-p, --path <startPath>', 'path to start recording at', '/')
19
+ .option('--viewport <WxH>', 'browser viewport', '1440x900')
20
+ .option('--storage-state <file>', 'seed cookies/localStorage/IndexedDB from a Playwright state file')
21
+ .option('--profile <dir>', 'record against a persistent Chromium profile (reuses a login)')
22
+ .description('launch an instrumented browser and record a bug reproduction')
23
+ .action(async (name, opts) => {
24
+ const viewport = parseViewport(opts.viewport);
25
+ const { repro, irPath, stopReason } = await record({
26
+ name,
27
+ baseUrl: opts.url,
28
+ startPath: opts.path,
29
+ viewport,
30
+ storageStatePath: opts.storageState ?? null,
31
+ profileDir: opts.profile ?? null,
32
+ onReady: () => {
33
+ console.log(`${green('●')} ${bold('Recording')} ${cyan(name)} on ${opts.url}${opts.path}`);
34
+ console.log(dim(` Reproduce the bug, then press ${STOP_HOTKEY} — or just close the browser.`));
35
+ console.log('');
36
+ },
37
+ });
38
+ if (!repro.steps.length) {
39
+ console.log(yellow('No actions captured — nothing was written.'));
40
+ console.log(dim(' If you did interact, check that the app loaded before you started.'));
41
+ process.exitCode = 1;
42
+ return;
43
+ }
44
+ console.log(`${green('✓')} Captured ${bold(String(repro.steps.length))} steps ${dim(`(stopped: ${stopReason})`)}`);
45
+ console.log(` ${dim('→')} ${path.relative(process.cwd(), irPath)}`);
46
+ const { invariants, observedAtRecord } = repro.assertion;
47
+ if (!invariants.noConsoleErrors || !invariants.noFailedRequests) {
48
+ console.log('');
49
+ console.log(yellow(' The bug was observed while recording:'));
50
+ for (const e of observedAtRecord?.consoleErrors ?? []) {
51
+ console.log(dim(` console ${truncate(e, 100)}`));
52
+ }
53
+ for (const f of observedAtRecord?.failedRequests ?? []) {
54
+ console.log(dim(` network ${f.method} ${f.urlPattern} -> ${f.status ?? 'aborted'}`));
55
+ }
56
+ console.log(dim(' Those invariants are off so the repro passes its own replay; the evidence is'));
57
+ console.log(dim(' kept under assertion.observedAtRecord.'));
58
+ }
59
+ });
60
+ program
61
+ .command('run')
62
+ .argument('<name>', 'name of the repro to replay')
63
+ .option('--headed', 'watch the replay in a visible browser', false)
64
+ .option('--expect-fixed', 'pass when the bug no longer happens — use while fixing', false)
65
+ .option('-u, --url <baseUrl>', 'override the recorded base URL')
66
+ .option('--profile <dir>', 'replay against a persistent Chromium profile (reuses a login)')
67
+ .option('--setup <command>', 'shell command to reset state before replaying')
68
+ .option('--timeout-scale <n>', 'multiply every recorded wait; raise on slow machines', '1')
69
+ .option('--resolve-timeout <ms>', 'budget for the first selector candidate; raise it for slow-booting SPAs', '800')
70
+ .description('replay a repro at machine speed and assert the recorded outcome')
71
+ .action(async (name, opts) => {
72
+ const first = Number(opts.resolveTimeout);
73
+ if (!Number.isFinite(first) || first <= 0) {
74
+ throw new Error(`Invalid --resolve-timeout "${opts.resolveTimeout}". Expected milliseconds.`);
75
+ }
76
+ const result = await run({
77
+ name,
78
+ headed: opts.headed,
79
+ baseUrl: opts.url,
80
+ expectFixed: opts.expectFixed,
81
+ profileDir: opts.profile ?? null,
82
+ setupCommand: opts.setup ?? null,
83
+ timeoutScale: Number(opts.timeoutScale) || 1,
84
+ // Fallbacks stay cheap probes: half the primary budget.
85
+ resolveTimeouts: { first, subsequent: Math.max(200, Math.round(first / 2)) },
86
+ });
87
+ result.passed ? reportPass(result) : reportFail(result);
88
+ process.exitCode = result.passed ? 0 : 1;
89
+ });
90
+ program
91
+ .command('list')
92
+ .description('list recorded repros with their last result and age')
93
+ .action(async () => {
94
+ const repros = await list();
95
+ if (!repros.length) {
96
+ console.log(dim('No repros yet. Record one with: repro record <name> --url http://localhost:3000'));
97
+ return;
98
+ }
99
+ const rows = [
100
+ [bold('NAME'), bold('STEPS'), bold('LAST RUN'), bold('AGE')],
101
+ ...repros.map((r) => {
102
+ if (r.error)
103
+ return [cyan(r.name), dim('—'), red('invalid IR'), age(r.createdAt)];
104
+ const last = r.lastResult;
105
+ const status = !last
106
+ ? dim('never run')
107
+ : last.status === 'pass'
108
+ ? green(`pass ${dim(`(${ms(last.durationMs)}, ${age(last.at)} ago)`)}`)
109
+ : red(`fail ${dim(`at ${last.failedStepId ?? '?'}, ${age(last.at)} ago`)}`);
110
+ return [cyan(r.name), String(r.steps ?? '—'), status, age(r.createdAt)];
111
+ }),
112
+ ];
113
+ console.log(table(rows));
114
+ });
115
+ function reportPass(result) {
116
+ const rows = [
117
+ [bold('STEP'), bold('ACTION'), bold('ACT'), bold('WAIT'), bold('TOTAL'), bold('WHAT')],
118
+ ...result.timings.map((t) => [
119
+ t.id,
120
+ t.action,
121
+ ms(t.actMs),
122
+ ms(t.waitMs),
123
+ ms(t.totalMs),
124
+ t.candidateIndex > 0
125
+ ? `${truncate(t.semantic, 52)} ${yellow(`(fallback selector #${t.candidateIndex})`)}`
126
+ : dim(truncate(t.semantic, 52)),
127
+ ]),
128
+ ];
129
+ console.log(table(rows));
130
+ for (const note of result.notes)
131
+ console.log(dim(` note ${note}`));
132
+ console.log('');
133
+ const verdict = result.expectFixed
134
+ ? `${green('✓ FIXED')} ${dim('— the flow completed and the bug did not happen')}`
135
+ : `${green('✓ PASS')} ${dim('— the recorded outcome still occurs')}`;
136
+ console.log(`${verdict} ${bold(result.name)} ${dim(`(${result.timings.length} steps in ${ms(result.durationMs)})`)}`);
137
+ }
138
+ function reportFail(result) {
139
+ const f = result.failure;
140
+ // An infrastructure failure says nothing about the bug. Reporting it as
141
+ // "NOT FIXED" sends people chasing a bug they have already fixed.
142
+ const infra = f?.kind === 'infrastructure';
143
+ const label = infra ? '✗ COULD NOT VERIFY' : result.expectFixed ? '✗ NOT FIXED' : '✗ FAIL';
144
+ console.log(`${red(label)} ${bold(result.name)} ${dim(`after ${ms(result.durationMs)}`)}`);
145
+ if (!f)
146
+ return;
147
+ if (infra) {
148
+ console.log(dim(' The replay could not drive the app, so this is not a verdict on the bug.'));
149
+ console.log(dim(' Fix the step below, or raise --resolve-timeout / --timeout-scale.'));
150
+ }
151
+ console.log('');
152
+ console.log(` ${bold('Step')} ${f.stepId} ${dim(`(step ${f.stepIndex + 1} of ${result.totalSteps})`)}`);
153
+ console.log(` ${bold('Target')} ${f.semantic}`);
154
+ console.log(` ${bold('Expected')} ${f.expected}`);
155
+ console.log(` ${bold('Observed')} ${f.observed}`);
156
+ if (f.artifacts) {
157
+ console.log('');
158
+ console.log(` ${bold('Artifacts')} ${dim(path.relative(process.cwd(), f.artifacts.dir))}`);
159
+ for (const [label, file] of [
160
+ ['screenshot', f.artifacts.screenshot],
161
+ ['console', f.artifacts.consoleLog],
162
+ ['network', f.artifacts.networkLog],
163
+ ]) {
164
+ if (file)
165
+ console.log(dim(` ${label.padEnd(11)}${path.relative(process.cwd(), file)}`));
166
+ }
167
+ }
168
+ }
169
+ function parseViewport(value) {
170
+ const match = /^(\d+)x(\d+)$/.exec(value.trim());
171
+ if (!match)
172
+ throw new Error(`Invalid --viewport "${value}". Expected WxH, e.g. 1440x900.`);
173
+ return { width: Number(match[1]), height: Number(match[2]) };
174
+ }
175
+ async function main() {
176
+ try {
177
+ await program.parseAsync(process.argv);
178
+ }
179
+ catch (err) {
180
+ if (err instanceof PartialRecordingError) {
181
+ // The steps captured before the failure are on disk and worth having.
182
+ console.error(`${yellow('!')} ${err.message}`);
183
+ console.error(dim(` Inspect it, trim the last step, and re-run — or re-record.`));
184
+ process.exitCode = 1;
185
+ return;
186
+ }
187
+ if (err instanceof IRValidationError) {
188
+ // Schema errors are the user's to fix by hand — show every issue at once.
189
+ console.error(red('✗ Invalid repro file'));
190
+ console.error(err.message);
191
+ }
192
+ else {
193
+ console.error(`${red('✗')} ${err.message}`);
194
+ }
195
+ process.exitCode = 1;
196
+ }
197
+ }
198
+ void main();
199
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import { type Assertion, type Repro, type Step } from '../ir/schema.js';
2
+ import type { RecordingTrace } from '../recorder/types.js';
3
+ import { type WaitRules } from './waits.js';
4
+ export interface CompileOptions {
5
+ name: string;
6
+ storageStatePath: string | null;
7
+ createdAt?: string;
8
+ waitRules?: WaitRules;
9
+ }
10
+ export declare function compile(trace: RecordingTrace, options: CompileOptions): Repro;
11
+ /**
12
+ * Phase 0's assertion is whatever the recording ended in. Because a repro
13
+ * captures the BUG, an invariant that the recording itself violated is not a
14
+ * usable check — it would make a fresh repro fail its own replay. Those get
15
+ * switched off here, and the violation is preserved under `observedAtRecord`
16
+ * so `--expect-fixed` can later assert the bug is gone.
17
+ */
18
+ export declare function deriveAssertion(steps: Step[], trace: RecordingTrace): Assertion;
@@ -0,0 +1,226 @@
1
+ import { IR_VERSION, } from '../ir/schema.js';
2
+ import { isAmbientConsoleError, isIncidentalRequest } from '../noise.js';
3
+ import { isSameOrigin, normalizeUrlPattern } from './normalize.js';
4
+ import { buildWaitAfter, DEFAULT_WAIT_RULES } from './waits.js';
5
+ /** A click and a dblclick on the same element this close together are one gesture. */
6
+ const DBLCLICK_MERGE_MS = 600;
7
+ /** A navigation this soon after an action is that action's consequence, not a step. */
8
+ const NAV_ATTRIBUTION_MS = 1_000;
9
+ /**
10
+ * Drop the two clicks that the browser necessarily fires before a dblclick.
11
+ * Replaying click-click-dblclick would triple-fire the handler.
12
+ */
13
+ function mergeDoubleClicks(actions) {
14
+ const out = [];
15
+ for (const action of actions) {
16
+ if (action.action === 'dblclick') {
17
+ const key = action.target?.candidates[0];
18
+ while (out.length) {
19
+ const prev = out[out.length - 1];
20
+ if (!prev)
21
+ break;
22
+ const sameTarget = prev.target?.candidates[0] === key;
23
+ if (prev.action === 'click' && sameTarget && action.t - prev.t <= DBLCLICK_MERGE_MS) {
24
+ out.pop();
25
+ continue;
26
+ }
27
+ break;
28
+ }
29
+ }
30
+ out.push(action);
31
+ }
32
+ return out;
33
+ }
34
+ /**
35
+ * Navigations become steps only when nothing the user did explains them —
36
+ * a typed URL or a back button. A navigation caused by clicking a link is the
37
+ * click's reaction, and replaying both would double-navigate.
38
+ */
39
+ function interleaveNavigations(actions, trace) {
40
+ const merged = actions.map((a) => ({ ...a }));
41
+ for (const nav of trace.navigations) {
42
+ const caused = actions.some((a) => nav.t - a.t >= 0 && nav.t - a.t <= NAV_ATTRIBUTION_MS);
43
+ if (caused)
44
+ continue;
45
+ merged.push({
46
+ kind: 'action',
47
+ action: 'goto',
48
+ value: nav.url,
49
+ target: null,
50
+ t: nav.t,
51
+ author: 'human',
52
+ syntheticUrl: nav.url,
53
+ });
54
+ }
55
+ return merged.sort((a, b) => a.t - b.t);
56
+ }
57
+ /** Collapse runs of scrolls on the same target down to the final position. */
58
+ function collapseScrolls(actions) {
59
+ const out = [];
60
+ for (const action of actions) {
61
+ const prev = out[out.length - 1];
62
+ if (prev &&
63
+ action.action === 'scroll' &&
64
+ prev.action === 'scroll' &&
65
+ prev.target?.candidates[0] === action.target?.candidates[0]) {
66
+ out[out.length - 1] = action;
67
+ continue;
68
+ }
69
+ out.push(action);
70
+ }
71
+ return out;
72
+ }
73
+ export function compile(trace, options) {
74
+ const rules = options.waitRules ?? DEFAULT_WAIT_RULES;
75
+ const merged = collapseScrolls(interleaveNavigations(mergeDoubleClicks(trace.actions), trace));
76
+ const traceEnd = trace.endedAt || Date.now();
77
+ const steps = merged.map((action, index) => {
78
+ const next = merged[index + 1];
79
+ const windowEnd = next ? next.t : traceEnd;
80
+ const step = {
81
+ id: `s${index + 1}`,
82
+ action: action.action,
83
+ value: action.value,
84
+ waitAfter: buildWaitAfter({
85
+ actionAt: action.t,
86
+ windowEnd,
87
+ network: trace.network,
88
+ dom: trace.dom,
89
+ baseUrl: trace.baseUrl,
90
+ }, rules),
91
+ author: action.author,
92
+ };
93
+ if (action.target) {
94
+ step.target = { candidates: action.target.candidates, semantic: action.target.semantic };
95
+ }
96
+ return step;
97
+ });
98
+ dropRerenderChurn(steps);
99
+ return {
100
+ version: IR_VERSION,
101
+ name: options.name,
102
+ createdAt: options.createdAt ?? new Date().toISOString(),
103
+ baseUrl: trace.baseUrl,
104
+ startPath: trace.startPath,
105
+ viewport: trace.viewport,
106
+ storageStatePath: options.storageStatePath,
107
+ steps,
108
+ assertion: deriveAssertion(steps, trace),
109
+ };
110
+ }
111
+ /** An error is the flow's evidence only if it landed in some action's wake. */
112
+ const REACTION_WINDOW_MS = 3_000;
113
+ /**
114
+ * Separate the app's noise from the bug's evidence.
115
+ *
116
+ * Splitting on "before the first action" was not stable: whether a boot error
117
+ * landed in one bucket or the other depended on how quickly the recording
118
+ * started clicking, so the same app and the same flow classified the same two
119
+ * errors differently from run to run — and a recording that happened to start
120
+ * fast treated boot chatter as the bug's signature.
121
+ *
122
+ * Causation is the stable question. Background noise fires whenever it fires;
123
+ * evidence of a bug fires in the wake of an action. Anything outside every
124
+ * action's reaction window is the app talking to itself, whenever it happened.
125
+ *
126
+ * The per-repro `ambient` list therefore holds only THIS app's own chatter.
127
+ * Noise common to every app is handled by the shared filter and never recorded
128
+ * here, so it cannot disable an otherwise dependable invariant.
129
+ */
130
+ function splitConsole(trace) {
131
+ const signature = [];
132
+ const ambient = [];
133
+ for (const entry of trace.console) {
134
+ // Noise every app produces is filtered identically on both sides, so it
135
+ // needs no per-repro baseline and must not weaken the invariant.
136
+ if (isAmbientConsoleError(entry.text))
137
+ continue;
138
+ // With no actions there is no causation to reason about, and nothing to
139
+ // disprove attribution — so the error stands as evidence.
140
+ if (!trace.actions.length) {
141
+ signature.push(entry.text);
142
+ continue;
143
+ }
144
+ const inWake = trace.actions.some((a) => entry.t >= a.t && entry.t <= a.t + REACTION_WINDOW_MS);
145
+ (inWake ? signature : ambient).push(entry.text);
146
+ }
147
+ const ambientSet = new Set(ambient);
148
+ return {
149
+ // An error that also fires unprompted is noise even when it recurs after an
150
+ // action, so ambient always wins the tie.
151
+ signature: Array.from(new Set(signature)).filter((t) => !ambientSet.has(t)),
152
+ ambient: Array.from(ambientSet),
153
+ };
154
+ }
155
+ /**
156
+ * A selector that vanishes on one step and returns on the next is a component
157
+ * re-mounting, not a state change. Replay sees the element present throughout
158
+ * and can satisfy neither half, so both are dropped.
159
+ */
160
+ function dropRerenderChurn(steps) {
161
+ for (let i = 0; i < steps.length - 1; i++) {
162
+ const a = steps[i];
163
+ const b = steps[i + 1];
164
+ if (!a || !b)
165
+ continue;
166
+ const gone = new Set(a.waitAfter.domGone ?? []);
167
+ const churned = (b.waitAfter.domAppeared ?? []).filter((s) => gone.has(s));
168
+ if (!churned.length)
169
+ continue;
170
+ a.waitAfter.domGone = (a.waitAfter.domGone ?? []).filter((s) => !churned.includes(s));
171
+ b.waitAfter.domAppeared = (b.waitAfter.domAppeared ?? []).filter((s) => !churned.includes(s));
172
+ if (!a.waitAfter.domGone.length)
173
+ delete a.waitAfter.domGone;
174
+ if (!b.waitAfter.domAppeared.length)
175
+ delete b.waitAfter.domAppeared;
176
+ }
177
+ }
178
+ /**
179
+ * Phase 0's assertion is whatever the recording ended in. Because a repro
180
+ * captures the BUG, an invariant that the recording itself violated is not a
181
+ * usable check — it would make a fresh repro fail its own replay. Those get
182
+ * switched off here, and the violation is preserved under `observedAtRecord`
183
+ * so `--expect-fixed` can later assert the bug is gone.
184
+ */
185
+ export function deriveAssertion(steps, trace) {
186
+ const last = steps[steps.length - 1];
187
+ const finalState = {};
188
+ if (last) {
189
+ if (last.waitAfter.domAppeared?.length)
190
+ finalState.domAppeared = last.waitAfter.domAppeared;
191
+ if (last.waitAfter.domGone?.length)
192
+ finalState.domGone = last.waitAfter.domGone;
193
+ if (last.waitAfter.network?.length)
194
+ finalState.network = last.waitAfter.network;
195
+ }
196
+ const { signature: consoleErrors, ambient: ambientConsoleErrors } = splitConsole(trace);
197
+ // Third-party failures are not this app's bug and must not disable the check.
198
+ const failedRequests = trace.network
199
+ .filter((n) => n.failed && isSameOrigin(n.url, trace.baseUrl) && !isIncidentalRequest(n.url))
200
+ .map((n) => ({
201
+ urlPattern: normalizeUrlPattern(n.url, trace.baseUrl),
202
+ method: n.method,
203
+ status: n.status,
204
+ }));
205
+ const dedupedFailures = Array.from(new Map(failedRequests.map((f) => [`${f.method} ${f.urlPattern} ${f.status}`, f])).values());
206
+ return {
207
+ mode: 'expect-bug',
208
+ finalState,
209
+ invariants: {
210
+ // Absence during one recording is not evidence of a clean app. An error
211
+ // that fires intermittently at boot would be absent here and present on
212
+ // the next run, silently flipping this invariant from run to run and
213
+ // failing a healthy replay. If the app logs anything at all while
214
+ // booting, the check is not dependable and expectedWhenFixed carries the
215
+ // assertion instead.
216
+ noConsoleErrors: consoleErrors.length === 0 && ambientConsoleErrors.length === 0,
217
+ noFailedRequests: dedupedFailures.length === 0,
218
+ },
219
+ observedAtRecord: {
220
+ consoleErrors,
221
+ ambientConsoleErrors,
222
+ failedRequests: dedupedFailures,
223
+ },
224
+ };
225
+ }
226
+ //# sourceMappingURL=compile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compile.js","sourceRoot":"","sources":["../../src/compiler/compile.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Reduce a URL to a stable pattern. Same-origin URLs become an origin-relative
3
+ * path so a repro survives a different dev-server port; cross-origin URLs keep
4
+ * their origin so third-party traffic stays distinguishable.
5
+ *
6
+ * The query string is dropped: it is dominated by cache-busters and is almost
7
+ * never what identifies an endpoint.
8
+ */
9
+ export declare function normalizeUrlPattern(url: string, baseUrl: string): string;
10
+ export declare function isSameOrigin(url: string, baseUrl: string): boolean;
11
+ /**
12
+ * Does a live URL satisfy a recorded pattern? Compared after normalization so
13
+ * generated ids line up, and also as a glob so a hand-edited IR can widen a
14
+ * pattern with `*` and have it behave the way a reader would expect.
15
+ */
16
+ export declare function matchesUrlPattern(url: string, pattern: string, baseUrl: string): boolean;
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Path segments that are volatile between runs. Recording `/api/sensors/4` and
3
+ * replaying against `/api/sensors/9` must still match, so identifiers collapse
4
+ * to `*` on both sides.
5
+ */
6
+ const VOLATILE_SEGMENT = [
7
+ /^\d+$/, // numeric id
8
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, // uuid
9
+ /^[0-9a-f]{12,}$/i, // hex blob
10
+ /^[A-Za-z0-9_-]{16,}$/, // opaque token / nanoid
11
+ ];
12
+ function normalizeSegment(seg) {
13
+ if (!seg)
14
+ return seg;
15
+ return VOLATILE_SEGMENT.some((re) => re.test(seg)) ? '*' : seg;
16
+ }
17
+ /**
18
+ * Reduce a URL to a stable pattern. Same-origin URLs become an origin-relative
19
+ * path so a repro survives a different dev-server port; cross-origin URLs keep
20
+ * their origin so third-party traffic stays distinguishable.
21
+ *
22
+ * The query string is dropped: it is dominated by cache-busters and is almost
23
+ * never what identifies an endpoint.
24
+ */
25
+ export function normalizeUrlPattern(url, baseUrl) {
26
+ let parsed;
27
+ try {
28
+ parsed = new URL(url);
29
+ }
30
+ catch {
31
+ return url;
32
+ }
33
+ const path = parsed.pathname.split('/').map(normalizeSegment).join('/') || '/';
34
+ let sameOrigin = false;
35
+ try {
36
+ sameOrigin = parsed.origin === new URL(baseUrl).origin;
37
+ }
38
+ catch {
39
+ sameOrigin = false;
40
+ }
41
+ return sameOrigin ? path : `${parsed.origin}${path}`;
42
+ }
43
+ export function isSameOrigin(url, baseUrl) {
44
+ try {
45
+ return new URL(url).origin === new URL(baseUrl).origin;
46
+ }
47
+ catch {
48
+ return false;
49
+ }
50
+ }
51
+ function globToRegExp(pattern) {
52
+ const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\\\*/g, '[^/]+');
53
+ return new RegExp(`^${escaped}$`);
54
+ }
55
+ /**
56
+ * Does a live URL satisfy a recorded pattern? Compared after normalization so
57
+ * generated ids line up, and also as a glob so a hand-edited IR can widen a
58
+ * pattern with `*` and have it behave the way a reader would expect.
59
+ */
60
+ export function matchesUrlPattern(url, pattern, baseUrl) {
61
+ const normalized = normalizeUrlPattern(url, baseUrl);
62
+ if (normalized === pattern)
63
+ return true;
64
+ let rawPath = url;
65
+ try {
66
+ const parsed = new URL(url);
67
+ rawPath = isSameOrigin(url, baseUrl) ? parsed.pathname : `${parsed.origin}${parsed.pathname}`;
68
+ }
69
+ catch {
70
+ /* keep the raw string */
71
+ }
72
+ const re = globToRegExp(pattern);
73
+ return re.test(normalized) || re.test(rawPath);
74
+ }
75
+ //# sourceMappingURL=normalize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalize.js","sourceRoot":"","sources":["../../src/compiler/normalize.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,gBAAgB,GAAG;IACvB,OAAO,EAAE,aAAa;IACtB,iEAAiE,EAAE,OAAO;IAC1E,kBAAkB,EAAE,WAAW;IAC/B,sBAAsB,EAAE,wBAAwB;CACjD,CAAC;AAEF,SAAS,gBAAgB,CAAC,GAAW;IACnC,IAAI,CAAC,GAAG;QAAE,OAAO,GAAG,CAAC;IACrB,OAAO,gBAAgB,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;AACjE,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAAW,EAAE,OAAe;IAC9D,IAAI,MAAW,CAAC;IAChB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IACxB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,CAAC;IACb,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC;IAE/E,IAAI,UAAU,GAAG,KAAK,CAAC;IACvB,IAAI,CAAC;QACH,UAAU,GAAG,MAAM,CAAC,MAAM,KAAK,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC;IACzD,CAAC;IAAC,MAAM,CAAC;QACP,UAAU,GAAG,KAAK,CAAC;IACrB,CAAC;IAED,OAAO,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,GAAG,IAAI,EAAE,CAAC;AACvD,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,GAAW,EAAE,OAAe;IACvD,IAAI,CAAC;QACH,OAAO,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,MAAM,KAAK,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC;IACzD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,SAAS,YAAY,CAAC,OAAe;IACnC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACzF,OAAO,IAAI,MAAM,CAAC,IAAI,OAAO,GAAG,CAAC,CAAC;AACpC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAW,EAAE,OAAe,EAAE,OAAe;IAC7E,MAAM,UAAU,GAAG,mBAAmB,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IACrD,IAAI,UAAU,KAAK,OAAO;QAAE,OAAO,IAAI,CAAC;IAExC,IAAI,OAAO,GAAG,GAAG,CAAC;IAClB,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QAC5B,OAAO,GAAG,YAAY,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAC;IAChG,CAAC;IAAC,MAAM,CAAC;QACP,yBAAyB;IAC3B,CAAC;IAED,MAAM,EAAE,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;IACjC,OAAO,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AACjD,CAAC"}
@@ -0,0 +1,31 @@
1
+ import type { WaitAfter } from '../ir/schema.js';
2
+ import type { RawDomEvent, RawNetworkEvent } from '../recorder/types.js';
3
+ export interface WaitRules {
4
+ /** A request counts as triggered by the action only if it started this soon after it. */
5
+ triggerWindowMs: number;
6
+ /** How long after an action DOM changes are still considered its reaction. */
7
+ domWindowMs: number;
8
+ /** Applied when the action produced no observable reaction at all. */
9
+ emptyTimeoutMs: number;
10
+ minTimeoutMs: number;
11
+ maxTimeoutMs: number;
12
+ /** Multiple of the observed settle time used as the replay ceiling. */
13
+ timeoutSlack: number;
14
+ maxSelectors: number;
15
+ }
16
+ export declare const DEFAULT_WAIT_RULES: WaitRules;
17
+ export interface ReactionWindow {
18
+ actionAt: number;
19
+ windowEnd: number;
20
+ network: RawNetworkEvent[];
21
+ dom: RawDomEvent[];
22
+ baseUrl: string;
23
+ }
24
+ /**
25
+ * Turn one action's observed reaction into the wait the replayer will perform.
26
+ *
27
+ * The whole point is that replay waits on *signals*, never on sleeps: if the
28
+ * recording saw a DELETE settle and a row vanish, replay waits for exactly
29
+ * that, and proceeds the instant it happens.
30
+ */
31
+ export declare function buildWaitAfter(window: ReactionWindow, rules?: WaitRules): WaitAfter;