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,65 @@
1
+ const MAX_CONSOLE_TEXT = 500;
2
+ export function collectReactions(context) {
3
+ const network = [];
4
+ const console_ = [];
5
+ const inFlight = new Map();
6
+ const onRequest = (request) => {
7
+ const entry = {
8
+ kind: 'network',
9
+ method: request.method(),
10
+ url: request.url(),
11
+ startedAt: Date.now(),
12
+ settledAt: null,
13
+ status: null,
14
+ failed: false,
15
+ };
16
+ inFlight.set(request, entry);
17
+ network.push(entry);
18
+ };
19
+ const settle = (request, status, failed) => {
20
+ const entry = inFlight.get(request);
21
+ if (!entry)
22
+ return;
23
+ entry.settledAt = Date.now();
24
+ entry.status = status;
25
+ entry.failed = failed;
26
+ inFlight.delete(request);
27
+ };
28
+ const onResponse = (response) => {
29
+ const status = response.status();
30
+ settle(response.request(), status, status >= 400);
31
+ };
32
+ const onRequestFailed = (request) => {
33
+ // Navigations the user aborts are noise, not evidence of a bug.
34
+ const aborted = request.failure()?.errorText === 'net::ERR_ABORTED';
35
+ settle(request, null, !aborted);
36
+ };
37
+ const onConsole = (msg) => {
38
+ if (msg.type() !== 'error')
39
+ return;
40
+ console_.push({ kind: 'console', text: msg.text().slice(0, MAX_CONSOLE_TEXT), t: Date.now() });
41
+ };
42
+ // Context-level uncaught page exceptions arrive as WebError, not Error.
43
+ const onWebError = (webError) => {
44
+ const err = webError.error();
45
+ const text = `${err.name}: ${err.message}`;
46
+ console_.push({ kind: 'console', text: text.slice(0, MAX_CONSOLE_TEXT), t: Date.now() });
47
+ };
48
+ context.on('request', onRequest);
49
+ context.on('response', onResponse);
50
+ context.on('requestfailed', onRequestFailed);
51
+ context.on('console', onConsole);
52
+ context.on('weberror', onWebError);
53
+ return {
54
+ network,
55
+ console: console_,
56
+ detach() {
57
+ context.off('request', onRequest);
58
+ context.off('response', onResponse);
59
+ context.off('requestfailed', onRequestFailed);
60
+ context.off('console', onConsole);
61
+ context.off('weberror', onWebError);
62
+ },
63
+ };
64
+ }
65
+ //# sourceMappingURL=reaction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reaction.js","sourceRoot":"","sources":["../../src/recorder/reaction.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ import type { Action, Author } from '../ir/schema.js';
2
+ /** Selector + semantic info resolved in the page, while the element is still live. */
3
+ export interface CapturedTarget {
4
+ candidates: string[];
5
+ semantic: string;
6
+ }
7
+ /** A user action, as captured in the page. */
8
+ export interface RawActionEvent {
9
+ kind: 'action';
10
+ action: Action;
11
+ value: string | null;
12
+ target: CapturedTarget | null;
13
+ /** Date.now() at capture time. */
14
+ t: number;
15
+ author: Author;
16
+ }
17
+ /** A batch of DOM changes summarized as selectors that appeared or disappeared. */
18
+ export interface RawDomEvent {
19
+ kind: 'dom';
20
+ appeared: string[];
21
+ gone: string[];
22
+ t: number;
23
+ }
24
+ export type PageEvent = RawActionEvent | RawDomEvent;
25
+ export interface RawNavigationEvent {
26
+ kind: 'navigation';
27
+ url: string;
28
+ t: number;
29
+ }
30
+ export interface RawNetworkEvent {
31
+ kind: 'network';
32
+ method: string;
33
+ url: string;
34
+ /** When the request was issued. */
35
+ startedAt: number;
36
+ /** When it settled (response or failure). Null while still in flight. */
37
+ settledAt: number | null;
38
+ status: number | null;
39
+ failed: boolean;
40
+ }
41
+ export interface RawConsoleEvent {
42
+ kind: 'console';
43
+ text: string;
44
+ t: number;
45
+ }
46
+ export type RawEvent = RawActionEvent | RawDomEvent | RawNavigationEvent | RawNetworkEvent | RawConsoleEvent;
47
+ /** Everything a recording session collected, in capture order per channel. */
48
+ export interface RecordingTrace {
49
+ actions: RawActionEvent[];
50
+ dom: RawDomEvent[];
51
+ navigations: RawNavigationEvent[];
52
+ network: RawNetworkEvent[];
53
+ console: RawConsoleEvent[];
54
+ startedAt: number;
55
+ endedAt: number;
56
+ baseUrl: string;
57
+ startPath: string;
58
+ viewport: {
59
+ width: number;
60
+ height: number;
61
+ };
62
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/recorder/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,25 @@
1
+ import type { Page } from 'playwright';
2
+ import { type ReproPaths } from '../ir/io.js';
3
+ import type { RawConsoleEvent, RawNetworkEvent } from '../recorder/types.js';
4
+ export interface FailureContext {
5
+ stepId: string;
6
+ stepIndex: number;
7
+ semantic: string;
8
+ expected: string;
9
+ observed: string;
10
+ /** Only network activity since the previous step, so the log stays readable. */
11
+ networkSince: RawNetworkEvent[];
12
+ consoleErrors: RawConsoleEvent[];
13
+ }
14
+ export interface WrittenArtifacts {
15
+ dir: string;
16
+ screenshot: string | null;
17
+ consoleLog: string;
18
+ networkLog: string;
19
+ summary: string;
20
+ }
21
+ /**
22
+ * Everything a human (or an agent reading the MCP response) needs to understand
23
+ * a failure without re-running anything.
24
+ */
25
+ export declare function writeFailureArtifacts(page: Page | null, paths: ReproPaths, failure: FailureContext): Promise<WrittenArtifacts>;
@@ -0,0 +1,58 @@
1
+ import { mkdir, rm } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { writeFileAtomic } from '../ir/io.js';
4
+ const CONSOLE_TAIL = 50;
5
+ /**
6
+ * Everything a human (or an agent reading the MCP response) needs to understand
7
+ * a failure without re-running anything.
8
+ */
9
+ export async function writeFailureArtifacts(page, paths, failure) {
10
+ // Stale artifacts from an earlier run are worse than none.
11
+ await rm(paths.artifactsDir, { recursive: true, force: true });
12
+ await mkdir(paths.artifactsDir, { recursive: true });
13
+ const screenshot = path.join(paths.artifactsDir, 'screenshot.png');
14
+ let screenshotPath = null;
15
+ if (page && !page.isClosed()) {
16
+ try {
17
+ await page.screenshot({ path: screenshot, fullPage: true });
18
+ screenshotPath = screenshot;
19
+ }
20
+ catch {
21
+ // A crashed or navigating page cannot be captured; the rest still helps.
22
+ }
23
+ }
24
+ const consoleLog = path.join(paths.artifactsDir, 'console.log');
25
+ const tail = failure.consoleErrors.slice(-CONSOLE_TAIL);
26
+ await writeFileAtomic(consoleLog, tail.length
27
+ ? `${tail.map((c) => `[${new Date(c.t).toISOString()}] ${c.text}`).join('\n')}\n`
28
+ : 'No console errors captured.\n');
29
+ const networkLog = path.join(paths.artifactsDir, 'network.log');
30
+ await writeFileAtomic(networkLog, failure.networkSince.length
31
+ ? `${failure.networkSince
32
+ .map((n) => {
33
+ const status = n.failed ? `FAILED(${n.status ?? 'aborted'})` : (n.status ?? 'pending');
34
+ const ms = n.settledAt ? `${n.settledAt - n.startedAt}ms` : 'in-flight';
35
+ return `${n.method} ${n.url} -> ${status} (${ms})`;
36
+ })
37
+ .join('\n')}\n`
38
+ : 'No network activity since the previous step.\n');
39
+ const summary = path.join(paths.artifactsDir, 'failure.json');
40
+ await writeFileAtomic(summary, `${JSON.stringify({
41
+ stepId: failure.stepId,
42
+ stepIndex: failure.stepIndex,
43
+ semantic: failure.semantic,
44
+ expected: failure.expected,
45
+ observed: failure.observed,
46
+ screenshot: screenshotPath,
47
+ consoleLog,
48
+ networkLog,
49
+ }, null, 2)}\n`);
50
+ return {
51
+ dir: paths.artifactsDir,
52
+ screenshot: screenshotPath,
53
+ consoleLog,
54
+ networkLog,
55
+ summary,
56
+ };
57
+ }
58
+ //# sourceMappingURL=artifacts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"artifacts.js","sourceRoot":"","sources":["../../src/replayer/artifacts.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import type { NetworkWait, Repro } from '../ir/schema.js';
2
+ import type { RawConsoleEvent, RawNetworkEvent } from '../recorder/types.js';
3
+ /**
4
+ * Every endpoint the recording actually exercised. Failed-request checking is
5
+ * scoped to these: a third-party analytics beacon 404-ing is noise, and letting
6
+ * it fail a repro would make the tool untrustworthy within a day.
7
+ */
8
+ export declare function recordedPatterns(repro: Repro): NetworkWait[];
9
+ export interface InvariantViolation {
10
+ invariant: 'noConsoleErrors' | 'noFailedRequests';
11
+ detail: string;
12
+ }
13
+ /**
14
+ * Did the specific bug this repro captured happen again?
15
+ *
16
+ * Deliberately narrow: it looks for the exact evidence recorded at authoring
17
+ * time, not for "any console error". An app that always logs a benign warning
18
+ * would otherwise never pass --expect-fixed, and a tool that cries wolf gets
19
+ * turned off.
20
+ */
21
+ /** Does this repro record anything that could tell us the bug came back? */
22
+ export declare function hasBugSignature(repro: Repro): boolean;
23
+ /** Has the author stated what must be true once it is fixed? */
24
+ export declare function hasFixCriterion(repro: Repro): boolean;
25
+ export declare function checkBugRecurred(repro: Repro, network: RawNetworkEvent[], consoleErrors: RawConsoleEvent[], baseUrl: string): string[];
26
+ export declare function checkInvariants(repro: Repro, network: RawNetworkEvent[], consoleErrors: RawConsoleEvent[], baseUrl: string): InvariantViolation[];
@@ -0,0 +1,109 @@
1
+ import { matchesUrlPattern } from '../compiler/normalize.js';
2
+ import { isAmbientConsoleError, isIncidentalRequest } from '../noise.js';
3
+ /**
4
+ * Every endpoint the recording actually exercised. Failed-request checking is
5
+ * scoped to these: a third-party analytics beacon 404-ing is noise, and letting
6
+ * it fail a repro would make the tool untrustworthy within a day.
7
+ */
8
+ export function recordedPatterns(repro) {
9
+ const all = [];
10
+ for (const step of repro.steps)
11
+ all.push(...(step.waitAfter.network ?? []));
12
+ all.push(...(repro.assertion.finalState.network ?? []));
13
+ for (const f of repro.assertion.observedAtRecord?.failedRequests ?? []) {
14
+ all.push({ urlPattern: f.urlPattern, method: f.method });
15
+ }
16
+ const seen = new Set();
17
+ return all.filter((n) => {
18
+ const key = `${n.method} ${n.urlPattern}`;
19
+ if (seen.has(key))
20
+ return false;
21
+ seen.add(key);
22
+ return true;
23
+ });
24
+ }
25
+ /**
26
+ * Did the specific bug this repro captured happen again?
27
+ *
28
+ * Deliberately narrow: it looks for the exact evidence recorded at authoring
29
+ * time, not for "any console error". An app that always logs a benign warning
30
+ * would otherwise never pass --expect-fixed, and a tool that cries wolf gets
31
+ * turned off.
32
+ */
33
+ /** Does this repro record anything that could tell us the bug came back? */
34
+ export function hasBugSignature(repro) {
35
+ const observed = repro.assertion.observedAtRecord;
36
+ return Boolean(observed && (observed.consoleErrors.length || observed.failedRequests.length));
37
+ }
38
+ /** Has the author stated what must be true once it is fixed? */
39
+ export function hasFixCriterion(repro) {
40
+ const expected = repro.assertion.expectedWhenFixed;
41
+ return Boolean(expected && (expected.domAppeared?.length || expected.domGone?.length));
42
+ }
43
+ export function checkBugRecurred(repro, network, consoleErrors, baseUrl) {
44
+ const observed = repro.assertion.observedAtRecord;
45
+ if (!observed)
46
+ return [];
47
+ const recurred = [];
48
+ const normalize = (s) => s.replace(/\s+/g, ' ').trim();
49
+ const baseline = observed.ambientConsoleErrors ?? [];
50
+ const live = consoleErrors
51
+ .filter((c) => !isAmbientConsoleError(c.text))
52
+ .filter((c) => !baseline.some((b) => c.text.startsWith(b.slice(0, 60))))
53
+ .map((c) => normalize(c.text));
54
+ for (const recorded of observed.consoleErrors) {
55
+ // Compare on a prefix: stack frames and ids drift between runs, the
56
+ // message that identifies the bug does not.
57
+ const needle = normalize(recorded).slice(0, 80);
58
+ if (needle && live.some((l) => l.includes(needle))) {
59
+ recurred.push(`console error recurred: ${recorded}`);
60
+ }
61
+ }
62
+ for (const recorded of observed.failedRequests) {
63
+ const again = network.some((n) => n.failed &&
64
+ n.method.toUpperCase() === recorded.method.toUpperCase() &&
65
+ matchesUrlPattern(n.url, recorded.urlPattern, baseUrl));
66
+ if (again) {
67
+ recurred.push(`request failed again: ${recorded.method} ${recorded.urlPattern} -> ${recorded.status ?? 'aborted'}`);
68
+ }
69
+ }
70
+ return recurred;
71
+ }
72
+ export function checkInvariants(repro, network, consoleErrors, baseUrl) {
73
+ const violations = [];
74
+ if (repro.assertion.invariants.noConsoleErrors) {
75
+ // Same filter the compiler used. Without it the invariant is inferred from
76
+ // filtered output but enforced against raw output, so ambient CORS and
77
+ // connection errors fail every replay of a perfectly healthy app.
78
+ const baseline = repro.assertion.observedAtRecord?.ambientConsoleErrors ?? [];
79
+ const seenAtRecord = (text) => baseline.some((b) => b === text || text.startsWith(b.slice(0, 60)));
80
+ for (const err of consoleErrors) {
81
+ if (isAmbientConsoleError(err.text) || seenAtRecord(err.text))
82
+ continue;
83
+ violations.push({ invariant: 'noConsoleErrors', detail: err.text });
84
+ }
85
+ }
86
+ if (repro.assertion.invariants.noFailedRequests) {
87
+ const patterns = recordedPatterns(repro);
88
+ for (const n of network) {
89
+ // An aborted request is a cancellation — a navigation, an unmount, a
90
+ // user moving on — not a server failure. Counting it made a same-host
91
+ // telemetry beacon fail every replay, and host rules cannot catch that
92
+ // because it is served from the app's own API origin.
93
+ if (!n.failed || n.status === null || n.status < 400)
94
+ continue;
95
+ if (isIncidentalRequest(n.url))
96
+ continue;
97
+ const inScope = patterns.some((p) => p.method.toUpperCase() === n.method.toUpperCase() &&
98
+ matchesUrlPattern(n.url, p.urlPattern, baseUrl));
99
+ if (!inScope)
100
+ continue;
101
+ violations.push({
102
+ invariant: 'noFailedRequests',
103
+ detail: `${n.method} ${n.url} -> ${n.status ?? 'aborted'}`,
104
+ });
105
+ }
106
+ }
107
+ return violations;
108
+ }
109
+ //# sourceMappingURL=invariants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invariants.js","sourceRoot":"","sources":["../../src/replayer/invariants.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ import type { Locator, Page } from 'playwright';
2
+ import type { Target } from '../ir/schema.js';
3
+ export interface ResolveTimeouts {
4
+ /** The recorded best guess deserves a real chance before we move on. */
5
+ first: number;
6
+ /** Fallbacks are cheap probes; a long timeout here just burns wall-clock. */
7
+ subsequent: number;
8
+ }
9
+ export declare const DEFAULT_RESOLVE_TIMEOUTS: ResolveTimeouts;
10
+ export interface Resolved {
11
+ locator: Locator;
12
+ selector: string;
13
+ /** Index into target.candidates, so callers can report degraded resolution. */
14
+ candidateIndex: number;
15
+ }
16
+ export declare class TargetResolutionError extends Error {
17
+ readonly target: Target;
18
+ readonly attempts: {
19
+ selector: string;
20
+ error: string;
21
+ }[];
22
+ constructor(target: Target, attempts: {
23
+ selector: string;
24
+ error: string;
25
+ }[]);
26
+ }
27
+ /**
28
+ * Try each recorded selector in priority order. Phase 0 deliberately does NOT
29
+ * heal: exhausting the candidates is a hard failure, reported with the semantic
30
+ * description so a human knows what the step meant.
31
+ *
32
+ * `onExhausted` is the seam Phase 1's LLM re-grounding plugs into — it may
33
+ * return a replacement selector, and the caller patches the IR.
34
+ */
35
+ export declare function resolveTarget(page: Page, target: Target, timeouts?: ResolveTimeouts, onExhausted?: (target: Target, page: Page) => Promise<string | null>): Promise<Resolved>;
@@ -0,0 +1,50 @@
1
+ export const DEFAULT_RESOLVE_TIMEOUTS = { first: 800, subsequent: 400 };
2
+ export class TargetResolutionError extends Error {
3
+ target;
4
+ attempts;
5
+ constructor(target, attempts) {
6
+ super(`Could not find ${target.semantic}`);
7
+ this.target = target;
8
+ this.attempts = attempts;
9
+ this.name = 'TargetResolutionError';
10
+ }
11
+ }
12
+ /**
13
+ * Try each recorded selector in priority order. Phase 0 deliberately does NOT
14
+ * heal: exhausting the candidates is a hard failure, reported with the semantic
15
+ * description so a human knows what the step meant.
16
+ *
17
+ * `onExhausted` is the seam Phase 1's LLM re-grounding plugs into — it may
18
+ * return a replacement selector, and the caller patches the IR.
19
+ */
20
+ export async function resolveTarget(page, target, timeouts = DEFAULT_RESOLVE_TIMEOUTS, onExhausted) {
21
+ const attempts = [];
22
+ for (let i = 0; i < target.candidates.length; i++) {
23
+ const selector = target.candidates[i];
24
+ if (!selector)
25
+ continue;
26
+ const timeout = i === 0 ? timeouts.first : timeouts.subsequent;
27
+ try {
28
+ const locator = page.locator(selector).first();
29
+ await locator.waitFor({ state: 'visible', timeout });
30
+ return { locator, selector, candidateIndex: i };
31
+ }
32
+ catch (err) {
33
+ attempts.push({ selector, error: firstLine(err) });
34
+ }
35
+ }
36
+ if (onExhausted) {
37
+ const healed = await onExhausted(target, page);
38
+ if (healed) {
39
+ const locator = page.locator(healed).first();
40
+ await locator.waitFor({ state: 'visible', timeout: timeouts.first });
41
+ return { locator, selector: healed, candidateIndex: -1 };
42
+ }
43
+ }
44
+ throw new TargetResolutionError(target, attempts);
45
+ }
46
+ function firstLine(err) {
47
+ const msg = err instanceof Error ? err.message : String(err);
48
+ return msg.split('\n')[0] ?? msg;
49
+ }
50
+ //# sourceMappingURL=resolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../src/replayer/resolve.ts"],"names":[],"mappings":"AAUA,MAAM,CAAC,MAAM,wBAAwB,GAAoB,EAAE,KAAK,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC;AASzF,MAAM,OAAO,qBAAsB,SAAQ,KAAK;IAEnC;IACA;IAFX,YACW,MAAc,EACd,QAA+C;QAExD,KAAK,CAAC,kBAAkB,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;QAHlC,WAAM,GAAN,MAAM,CAAQ;QACd,aAAQ,GAAR,QAAQ,CAAuC;QAGxD,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;IACtC,CAAC;CACF;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,IAAU,EACV,MAAc,EACd,WAA4B,wBAAwB,EACpD,WAAoE;IAEpE,MAAM,QAAQ,GAA0C,EAAE,CAAC;IAE3D,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAClD,MAAM,QAAQ,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QACtC,IAAI,CAAC,QAAQ;YAAE,SAAS;QACxB,MAAM,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC;QAC/D,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,CAAC;YAC/C,MAAM,OAAO,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC;YACrD,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAC,EAAE,CAAC;QAClD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACrD,CAAC;IACH,CAAC;IAED,IAAI,WAAW,EAAE,CAAC;QAChB,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAC/C,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,CAAC;YAC7C,MAAM,OAAO,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;YACrE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC,CAAC,EAAE,CAAC;QAC3D,CAAC;IACH,CAAC;IAED,MAAM,IAAI,qBAAqB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;AACpD,CAAC;AAED,SAAS,SAAS,CAAC,GAAY;IAC7B,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC7D,OAAO,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC;AACnC,CAAC"}
@@ -0,0 +1,104 @@
1
+ import type { Browser, Page } from 'playwright';
2
+ import type { Repro, Target } from '../ir/schema.js';
3
+ import { type WrittenArtifacts } from './artifacts.js';
4
+ import { type InvariantViolation } from './invariants.js';
5
+ import { type ResolveTimeouts } from './resolve.js';
6
+ export interface StepTiming {
7
+ id: string;
8
+ action: string;
9
+ semantic: string;
10
+ /** Time to resolve the target and perform the action. */
11
+ actMs: number;
12
+ /** Time spent waiting for the recorded reaction. */
13
+ waitMs: number;
14
+ totalMs: number;
15
+ /** >0 means the preferred selector failed and a fallback was used. */
16
+ candidateIndex: number;
17
+ }
18
+ export interface RunFailure {
19
+ stepId: string;
20
+ stepIndex: number;
21
+ semantic: string;
22
+ expected: string;
23
+ observed: string;
24
+ /**
25
+ * `assertion` — the app was driven successfully and the verdict is about the
26
+ * bug. `infrastructure` — the harness could not drive the app at all, so it
27
+ * says nothing about whether the bug is fixed. Reporting the second as
28
+ * "NOT FIXED" sends people chasing a bug they already fixed.
29
+ */
30
+ kind: 'assertion' | 'infrastructure';
31
+ artifacts: WrittenArtifacts | null;
32
+ }
33
+ export interface RunResult {
34
+ name: string;
35
+ passed: boolean;
36
+ durationMs: number;
37
+ /** Total steps in the IR — `timings` only covers the ones that ran. */
38
+ totalSteps: number;
39
+ /** Which polarity this run asserted. */
40
+ expectFixed: boolean;
41
+ /** Non-fatal observations, e.g. reactions that changed after a fix. */
42
+ notes: string[];
43
+ /** Path to the end-state screenshot, when one was requested. */
44
+ finalScreenshot: string | null;
45
+ timings: StepTiming[];
46
+ failure: RunFailure | null;
47
+ invariantViolations: InvariantViolation[];
48
+ }
49
+ export interface RunOptions {
50
+ headed?: boolean;
51
+ baseUrl?: string;
52
+ root?: string;
53
+ resolveTimeouts?: ResolveTimeouts;
54
+ /**
55
+ * Capture the end state even when the run passes.
56
+ *
57
+ * The CLI leaves this off to stay fast. The MCP server turns it on: its whole
58
+ * job is to hand the browser's visual state to a model, and "it passed" is a
59
+ * far weaker signal to a reader than seeing the resulting page.
60
+ */
61
+ captureFinalScreenshot?: boolean;
62
+ /**
63
+ * Verification mode: pass when the flow still walks and the recorded bug
64
+ * does NOT happen again.
65
+ *
66
+ * The default polarity asserts the opposite — that the bug still reproduces —
67
+ * which is what you want to confirm a fresh repro is sound, but backwards
68
+ * while you are fixing something. Under --expect-fixed a step's recorded
69
+ * reaction becomes a note rather than a failure: after a real fix the app is
70
+ * *supposed* to react differently.
71
+ */
72
+ expectFixed?: boolean;
73
+ /**
74
+ * Called when every selector candidate for a step failed. Returning a
75
+ * selector lets replay continue — the seam for Phase 1's LLM re-grounding.
76
+ * Phase 0 never supplies one.
77
+ */
78
+ onStepFailure?: (target: Target, page: Page) => Promise<string | null>;
79
+ /** Replay against a persistent Chromium profile (e.g. to reuse a login). */
80
+ profileDir?: string | null;
81
+ /**
82
+ * Multiply every recorded `waitAfter.timeoutMs`.
83
+ *
84
+ * Timeouts are derived from what the recording observed, which is wrong when
85
+ * replay runs somewhere slower than the machine that recorded — a cold dev
86
+ * server, CI, a heavier dataset. Without this the only fix was hand-editing
87
+ * every step in the IR.
88
+ */
89
+ timeoutScale?: number;
90
+ /**
91
+ * Reuse a running browser across runs. Saves the launch and, more usefully,
92
+ * keeps V8's code cache warm so a large app boots far faster on replay two
93
+ * onward. Each run still gets its own context.
94
+ */
95
+ browser?: Browser | null;
96
+ /**
97
+ * Shell command run before the browser opens, to reset state the flow mutates.
98
+ *
99
+ * Deliberately NOT a field in the IR: a repro file is something you download,
100
+ * hand-edit and share, and one that can execute commands is a liability.
101
+ */
102
+ setupCommand?: string | null;
103
+ }
104
+ export declare function runRepro(repro: Repro, options?: RunOptions): Promise<RunResult>;