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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mukhriddin Shakhriyorov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# fast-replay
|
|
2
|
+
|
|
3
|
+
Record a browser bug once. Verify the fix in seconds, with no model in the loop.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
$ repro run sensor-delete-crash
|
|
7
|
+
|
|
8
|
+
STEP ACTION ACT WAIT TOTAL WHAT
|
|
9
|
+
s1 fill 26ms 492ms 518ms New sensor name textbox in the section labelled "Sensors"
|
|
10
|
+
s5 click 7ms 25ms 32ms Delete Sensor 2 button in the row containing "Sensor 2"
|
|
11
|
+
s10 click 23ms 1.79s 1.81s Generate report button
|
|
12
|
+
|
|
13
|
+
✓ PASS sensor-delete-crash — 10 steps in 2.84s
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Repros are disposable. They live in `.repros/`, and you delete them when the bug is fixed.
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install -D fast-replay
|
|
22
|
+
npx playwright install chromium
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Node >= 20.
|
|
26
|
+
|
|
27
|
+
## Use
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
repro record checkout-crash --url http://localhost:3000 # click the bug once
|
|
31
|
+
repro run checkout-crash # bug still reproduces?
|
|
32
|
+
repro run checkout-crash --expect-fixed # did my fix work?
|
|
33
|
+
repro list
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Stop recording with **Ctrl/Cmd + Shift + X**, or just close the browser.
|
|
37
|
+
|
|
38
|
+
Exit `0` on pass, `1` on fail. On failure you get the failing step, what it does in plain language, expected vs observed, and `.repros/<name>/artifacts/` with a screenshot, console tail and network log.
|
|
39
|
+
|
|
40
|
+
| Flag | |
|
|
41
|
+
|---|---|
|
|
42
|
+
| `--expect-fixed` | pass when the bug no longer happens |
|
|
43
|
+
| `--headed` | visible browser; some apps refuse headless |
|
|
44
|
+
| `--storage-state <file>` / `--profile <dir>` | get past a login |
|
|
45
|
+
| `--setup <cmd>` | reset state before replaying |
|
|
46
|
+
| `--timeout-scale <n>` | multiply recorded waits, for a slower machine |
|
|
47
|
+
|
|
48
|
+
## From a coding agent
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
{ "mcpServers": { "replay": { "command": "npx", "args": ["repro-mcp"] } } }
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`repro_run` returns the verdict, the failing step, console, network and **the page as an inline image** — in one call. Works with Claude Code, Codex, Gemini CLI, Cursor.
|
|
55
|
+
|
|
56
|
+
## What makes it fast
|
|
57
|
+
|
|
58
|
+
Replay never sleeps. Every wait is a signal the recording actually observed — a request settling, an element appearing or vanishing — so a step proceeds the instant the app reacts. Timeouts are derived from what was measured, not guessed.
|
|
59
|
+
|
|
60
|
+
The recording is JSON, not generated code. It is meant to be read and hand-edited.
|
|
61
|
+
|
|
62
|
+
```jsonc
|
|
63
|
+
{
|
|
64
|
+
"id": "s5",
|
|
65
|
+
"action": "click",
|
|
66
|
+
"target": {
|
|
67
|
+
"candidates": ["role=button[name=\"Delete Sensor 2\"]", "[data-testid=\"sensor-row-2\"] > button"],
|
|
68
|
+
"semantic": "Delete Sensor 2 button in the row containing \"Sensor 2\""
|
|
69
|
+
},
|
|
70
|
+
"waitAfter": {
|
|
71
|
+
"network": [{ "urlPattern": "/api/sensors/*", "method": "DELETE" }],
|
|
72
|
+
"domGone": ["[data-testid=\"sensor-row-2\"]"],
|
|
73
|
+
"timeoutMs": 3000
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Selectors are a ladder: test id → `name` → ARIA role + accessible name → labelled ancestor → stable CSS path → text. Build-generated class names are skipped, and a path unique only by sibling position ranks below the text anchor.
|
|
79
|
+
|
|
80
|
+
## Results
|
|
81
|
+
|
|
82
|
+
Measured on one machine, same flow both ways.
|
|
83
|
+
|
|
84
|
+
| | fast-replay | Playwright MCP |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| Per verification | **2.7 s** | ~24–32 s |
|
|
87
|
+
| Tool calls | **1** | 3–14 |
|
|
88
|
+
| Model calls | **0** | 1 per action |
|
|
89
|
+
| Context added | ~120 tokens | ~700–2 000 |
|
|
90
|
+
|
|
91
|
+
10-step flow: 20/20 consecutive replays, slowest 2.77 s.
|
|
92
|
+
|
|
93
|
+
The sharper claim is not speed. In a real app it caught a **736–779 ms** window where a total failed to render — a state that heals before a snapshot returns, so a model-in-the-loop tool structurally cannot see it. Two independent first passes with Playwright MCP concluded "no bug" and were wrong.
|
|
94
|
+
|
|
95
|
+
## Expectations
|
|
96
|
+
|
|
97
|
+
Honest limits, from six benchmark rounds against two production codebases (React Native Web; React + Vite + Radix):
|
|
98
|
+
|
|
99
|
+
- **If you already have a Playwright suite, keep using it.** Authoring a spec costs about what recording costs, and a spec is legible where an IR is not. This earns its keep for throwaway repros and agent loops, not as a test framework.
|
|
100
|
+
- **Recording is a human clicking once.** Driving it programmatically means writing Playwright locators, which defeats the point. `repro auto` from a bug description is not built.
|
|
101
|
+
- **A bug with no console error or failed request needs one hand-written line** — `assertion.expectedWhenFixed` — or `--expect-fixed` refuses to answer rather than return a green that checked nothing.
|
|
102
|
+
- **A flow that mutates server state is single-shot.** Use `--setup` to reset, or `{{random:name}}` placeholders to make inputs unique.
|
|
103
|
+
- Top frame only. No iframes. Drag-and-drop and file upload are untested.
|
|
104
|
+
|
|
105
|
+
## Programmatic
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
import { record, run, list } from 'fast-replay';
|
|
109
|
+
|
|
110
|
+
const result = await run({ name: 'my-bug', expectFixed: true });
|
|
111
|
+
if (!result.passed) console.log(result.failure.semantic, result.failure.artifacts.screenshot);
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
The CLI and MCP server are both thin wrappers over these.
|
|
115
|
+
|
|
116
|
+
## Develop
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
npm test # 110 tests, unit + real-browser integration
|
|
120
|
+
npm run stress # records once, replays 20x, fails on a single flake
|
|
121
|
+
npm run demo # examples/demo-app
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
`src/recorder/agent/` runs inside the browser and is bundled by esbuild into `agent-bundle.generated.ts`. `npm run build:agent` regenerates it; a test fails if it goes stale.
|
|
125
|
+
|
|
126
|
+
MIT.
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { Page } from 'playwright';
|
|
2
|
+
import { type ReproSummary } from './ir/io.js';
|
|
3
|
+
import { STOP_HOTKEY } from './recorder/launch.js';
|
|
4
|
+
import { type RunOptions, type RunResult } from './replayer/run.js';
|
|
5
|
+
import type { Repro } from './ir/schema.js';
|
|
6
|
+
export interface RecordOptions {
|
|
7
|
+
name: string;
|
|
8
|
+
baseUrl: string;
|
|
9
|
+
startPath?: string;
|
|
10
|
+
viewport?: {
|
|
11
|
+
width: number;
|
|
12
|
+
height: number;
|
|
13
|
+
};
|
|
14
|
+
root?: string;
|
|
15
|
+
/** Seed cookies/localStorage/IndexedDB from an existing Playwright state file. */
|
|
16
|
+
storageStatePath?: string | null;
|
|
17
|
+
/** Use a persistent Chromium profile instead of a fresh context. */
|
|
18
|
+
profileDir?: string | null;
|
|
19
|
+
onReady?: () => void;
|
|
20
|
+
headless?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Drive the browser programmatically instead of waiting for a human. The
|
|
23
|
+
* capture pipeline is identical either way — the seam Phase 1's `repro auto`
|
|
24
|
+
* hands to an LLM browser agent.
|
|
25
|
+
*/
|
|
26
|
+
drive?: (page: Page) => Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
export interface RecordResult {
|
|
29
|
+
repro: Repro;
|
|
30
|
+
irPath: string;
|
|
31
|
+
stopReason: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The driver failed part-way, but the steps captured before it did were written
|
|
35
|
+
* to disk anyway. Carries the path so the caller can inspect or resume.
|
|
36
|
+
*/
|
|
37
|
+
export declare class PartialRecordingError extends Error {
|
|
38
|
+
readonly cause: Error;
|
|
39
|
+
readonly irPath: string;
|
|
40
|
+
readonly repro: Repro;
|
|
41
|
+
constructor(cause: Error, irPath: string, repro: Repro);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Drive a real browser, capture the flow, compile it to IR on disk.
|
|
45
|
+
*
|
|
46
|
+
* This — not the CLI — is the product's entry point. `repro record` is a thin
|
|
47
|
+
* wrapper, and the Phase 1 MCP server will wrap this same function so an agent
|
|
48
|
+
* verifies a fix in one call with no per-step round trips.
|
|
49
|
+
*/
|
|
50
|
+
export declare function record(options: RecordOptions): Promise<RecordResult>;
|
|
51
|
+
export interface RunReproOptions extends RunOptions {
|
|
52
|
+
name: string;
|
|
53
|
+
}
|
|
54
|
+
/** Replay a recorded repro. Reads and validates the IR, then drives the browser. */
|
|
55
|
+
export declare function run(options: RunReproOptions): Promise<RunResult>;
|
|
56
|
+
/** Every repro in `.repros/`, newest first. Never throws on a corrupt IR. */
|
|
57
|
+
export declare function list(root?: string): Promise<ReproSummary[]>;
|
|
58
|
+
export { createReplayServer, createServer } from './mcp/server.js';
|
|
59
|
+
export { BrowserPool } from './browser.js';
|
|
60
|
+
export { STOP_HOTKEY };
|
|
61
|
+
export { deleteRepro, readRepro, reproPaths } from './ir/io.js';
|
|
62
|
+
export { compile } from './compiler/compile.js';
|
|
63
|
+
export * from './ir/schema.js';
|
|
64
|
+
export type { RunResult, StepTiming, RunFailure } from './replayer/run.js';
|
|
65
|
+
export type { ReproSummary } from './ir/io.js';
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { compile } from './compiler/compile.js';
|
|
3
|
+
import { listRepros, readRepro, reproPaths, writeFileAtomic, writeLastResult, writeRepro, } from './ir/io.js';
|
|
4
|
+
import { launchRecording, STOP_HOTKEY } from './recorder/launch.js';
|
|
5
|
+
import { runRepro } from './replayer/run.js';
|
|
6
|
+
/**
|
|
7
|
+
* The driver failed part-way, but the steps captured before it did were written
|
|
8
|
+
* to disk anyway. Carries the path so the caller can inspect or resume.
|
|
9
|
+
*/
|
|
10
|
+
export class PartialRecordingError extends Error {
|
|
11
|
+
cause;
|
|
12
|
+
irPath;
|
|
13
|
+
repro;
|
|
14
|
+
constructor(cause, irPath, repro) {
|
|
15
|
+
super(`Recording stopped early after ${repro.steps.length} step(s): ${cause.message}\n` +
|
|
16
|
+
`The partial repro was still written to ${irPath}`);
|
|
17
|
+
this.cause = cause;
|
|
18
|
+
this.irPath = irPath;
|
|
19
|
+
this.repro = repro;
|
|
20
|
+
this.name = 'PartialRecordingError';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Drive a real browser, capture the flow, compile it to IR on disk.
|
|
25
|
+
*
|
|
26
|
+
* This — not the CLI — is the product's entry point. `repro record` is a thin
|
|
27
|
+
* wrapper, and the Phase 1 MCP server will wrap this same function so an agent
|
|
28
|
+
* verifies a fix in one call with no per-step round trips.
|
|
29
|
+
*/
|
|
30
|
+
export async function record(options) {
|
|
31
|
+
const root = options.root ?? process.cwd();
|
|
32
|
+
const paths = reproPaths(options.name, root);
|
|
33
|
+
const { trace, storageState, stopReason, driveError } = await launchRecording({
|
|
34
|
+
baseUrl: options.baseUrl,
|
|
35
|
+
startPath: options.startPath,
|
|
36
|
+
viewport: options.viewport,
|
|
37
|
+
storageStatePath: options.storageStatePath ?? null,
|
|
38
|
+
profileDir: options.profileDir ?? null,
|
|
39
|
+
onReady: options.onReady,
|
|
40
|
+
headless: options.headless,
|
|
41
|
+
drive: options.drive,
|
|
42
|
+
});
|
|
43
|
+
await writeFileAtomic(paths.storageState, storageState);
|
|
44
|
+
const repro = compile(trace, {
|
|
45
|
+
name: options.name,
|
|
46
|
+
storageStatePath: path.relative(root, paths.storageState),
|
|
47
|
+
});
|
|
48
|
+
// Written before any error is raised: a driver that failed on step 12 still
|
|
49
|
+
// captured eleven real steps, and throwing them away wastes the whole run.
|
|
50
|
+
await writeRepro(repro, paths);
|
|
51
|
+
if (driveError)
|
|
52
|
+
throw new PartialRecordingError(driveError, paths.ir, repro);
|
|
53
|
+
return { repro, irPath: paths.ir, stopReason };
|
|
54
|
+
}
|
|
55
|
+
/** Replay a recorded repro. Reads and validates the IR, then drives the browser. */
|
|
56
|
+
export async function run(options) {
|
|
57
|
+
const root = options.root ?? process.cwd();
|
|
58
|
+
const repro = await readRepro(options.name, root);
|
|
59
|
+
const result = await runRepro(repro, options);
|
|
60
|
+
await writeLastResult(reproPaths(options.name, root), {
|
|
61
|
+
status: result.passed ? 'pass' : 'fail',
|
|
62
|
+
at: new Date().toISOString(),
|
|
63
|
+
durationMs: result.durationMs,
|
|
64
|
+
...(result.failure ? { failedStepId: result.failure.stepId } : {}),
|
|
65
|
+
});
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
/** Every repro in `.repros/`, newest first. Never throws on a corrupt IR. */
|
|
69
|
+
export async function list(root = process.cwd()) {
|
|
70
|
+
return listRepros(root);
|
|
71
|
+
}
|
|
72
|
+
export { createReplayServer, createServer } from './mcp/server.js';
|
|
73
|
+
export { BrowserPool } from './browser.js';
|
|
74
|
+
export { STOP_HOTKEY };
|
|
75
|
+
export { deleteRepro, readRepro, reproPaths } from './ir/io.js';
|
|
76
|
+
export { compile } from './compiler/compile.js';
|
|
77
|
+
export * from './ir/schema.js';
|
|
78
|
+
//# sourceMappingURL=api.js.map
|
package/dist/api.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { type Browser, type BrowserContext, type Page } from 'playwright';
|
|
2
|
+
/**
|
|
3
|
+
* Opening a browser is shared by recording and replay, and both need the same
|
|
4
|
+
* two ways of getting an authenticated session.
|
|
5
|
+
*
|
|
6
|
+
* Session seeding is the single thing that decides whether this tool works on a
|
|
7
|
+
* real app, because every target worth verifying is behind a login:
|
|
8
|
+
*
|
|
9
|
+
* - `storageStatePath` — a portable JSON snapshot. Isolated and deterministic:
|
|
10
|
+
* every run starts from an identical session. Now includes IndexedDB, without
|
|
11
|
+
* which offline-first apps (Onyx, Dexie, Firebase Auth) cannot be seeded at
|
|
12
|
+
* all — their tokens never touch cookies or localStorage.
|
|
13
|
+
*
|
|
14
|
+
* - `profileDir` — a real Chromium profile directory. Everything persists:
|
|
15
|
+
* IndexedDB, service workers, caches. It also sidesteps non-idempotent signup,
|
|
16
|
+
* because you never sign up twice. The cost is isolation: state accumulates
|
|
17
|
+
* across runs, so the tenth replay does not start where the first one did.
|
|
18
|
+
*
|
|
19
|
+
* Prefer storageState for repeatable verification; reach for a profile when the
|
|
20
|
+
* app's auth cannot be captured any other way, or to skip a slow cold boot.
|
|
21
|
+
*/
|
|
22
|
+
export interface OpenBrowserOptions {
|
|
23
|
+
headless: boolean;
|
|
24
|
+
viewport: {
|
|
25
|
+
width: number;
|
|
26
|
+
height: number;
|
|
27
|
+
};
|
|
28
|
+
storageStatePath?: string | null;
|
|
29
|
+
profileDir?: string | null;
|
|
30
|
+
/**
|
|
31
|
+
* Reuse an already-running browser instead of launching one.
|
|
32
|
+
*
|
|
33
|
+
* Every run otherwise pays a browser launch *and* a cold page load, because a
|
|
34
|
+
* newly launched Chromium has an empty V8 code cache. Keeping the process
|
|
35
|
+
* alive across runs leaves that cache warm, which on a large single-page app
|
|
36
|
+
* is worth far more than the launch itself. Each run still gets its own
|
|
37
|
+
* context, so isolation between replays is unchanged.
|
|
38
|
+
*/
|
|
39
|
+
browser?: Browser | null;
|
|
40
|
+
}
|
|
41
|
+
export interface OpenedBrowser {
|
|
42
|
+
context: BrowserContext;
|
|
43
|
+
page: Page;
|
|
44
|
+
/** True when running against a persistent profile. */
|
|
45
|
+
persistent: boolean;
|
|
46
|
+
close(): Promise<void>;
|
|
47
|
+
}
|
|
48
|
+
export declare function openBrowser(options: OpenBrowserOptions): Promise<OpenedBrowser>;
|
|
49
|
+
/**
|
|
50
|
+
* Keeps one browser per headless mode alive across runs.
|
|
51
|
+
*
|
|
52
|
+
* Owned by long-lived callers — the MCP server, a stress loop — and closed with
|
|
53
|
+
* `dispose`. A one-shot CLI invocation has nothing to amortise and does not use
|
|
54
|
+
* this.
|
|
55
|
+
*/
|
|
56
|
+
export declare class BrowserPool {
|
|
57
|
+
private readonly browsers;
|
|
58
|
+
acquire(headless: boolean): Promise<Browser>;
|
|
59
|
+
dispose(): Promise<void>;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Snapshot the session, including IndexedDB.
|
|
63
|
+
*
|
|
64
|
+
* Playwright omits IndexedDB by default, which silently produces a state file
|
|
65
|
+
* that looks fine and restores nothing for any app keeping its auth there.
|
|
66
|
+
*/
|
|
67
|
+
export declare function captureStorageState(context: BrowserContext): Promise<string>;
|
package/dist/browser.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { chromium } from 'playwright';
|
|
2
|
+
export async function openBrowser(options) {
|
|
3
|
+
const { headless, viewport } = options;
|
|
4
|
+
if (options.profileDir) {
|
|
5
|
+
const context = await chromium.launchPersistentContext(options.profileDir, {
|
|
6
|
+
headless,
|
|
7
|
+
viewport,
|
|
8
|
+
});
|
|
9
|
+
// A persistent context always opens with one page; reuse it so we do not
|
|
10
|
+
// leave a stray about:blank tab behind.
|
|
11
|
+
const page = context.pages()[0] ?? (await context.newPage());
|
|
12
|
+
return {
|
|
13
|
+
context,
|
|
14
|
+
page,
|
|
15
|
+
persistent: true,
|
|
16
|
+
close: async () => {
|
|
17
|
+
await context.close().catch(() => { });
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
const borrowed = Boolean(options.browser);
|
|
22
|
+
const browser = options.browser ?? (await chromium.launch({ headless }));
|
|
23
|
+
const context = await browser.newContext({
|
|
24
|
+
viewport,
|
|
25
|
+
...(options.storageStatePath ? { storageState: options.storageStatePath } : {}),
|
|
26
|
+
});
|
|
27
|
+
const page = await context.newPage();
|
|
28
|
+
return {
|
|
29
|
+
context,
|
|
30
|
+
page,
|
|
31
|
+
persistent: false,
|
|
32
|
+
close: async () => {
|
|
33
|
+
await context.close().catch(() => { });
|
|
34
|
+
// A borrowed browser belongs to the caller and outlives this run.
|
|
35
|
+
if (!borrowed)
|
|
36
|
+
await browser.close().catch(() => { });
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Keeps one browser per headless mode alive across runs.
|
|
42
|
+
*
|
|
43
|
+
* Owned by long-lived callers — the MCP server, a stress loop — and closed with
|
|
44
|
+
* `dispose`. A one-shot CLI invocation has nothing to amortise and does not use
|
|
45
|
+
* this.
|
|
46
|
+
*/
|
|
47
|
+
export class BrowserPool {
|
|
48
|
+
browsers = new Map();
|
|
49
|
+
async acquire(headless) {
|
|
50
|
+
let existing = this.browsers.get(headless);
|
|
51
|
+
if (existing) {
|
|
52
|
+
const browser = await existing;
|
|
53
|
+
if (browser.isConnected())
|
|
54
|
+
return browser;
|
|
55
|
+
this.browsers.delete(headless);
|
|
56
|
+
existing = undefined;
|
|
57
|
+
}
|
|
58
|
+
const launched = chromium.launch({ headless });
|
|
59
|
+
this.browsers.set(headless, launched);
|
|
60
|
+
return launched;
|
|
61
|
+
}
|
|
62
|
+
async dispose() {
|
|
63
|
+
const all = Array.from(this.browsers.values());
|
|
64
|
+
this.browsers.clear();
|
|
65
|
+
await Promise.all(all.map(async (p) => (await p).close().catch(() => { })));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Snapshot the session, including IndexedDB.
|
|
70
|
+
*
|
|
71
|
+
* Playwright omits IndexedDB by default, which silently produces a state file
|
|
72
|
+
* that looks fine and restores nothing for any app keeping its auth there.
|
|
73
|
+
*/
|
|
74
|
+
export async function captureStorageState(context) {
|
|
75
|
+
return JSON.stringify(await context.storageState({ indexedDB: true }));
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=browser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const bold: (s: string) => string;
|
|
2
|
+
export declare const dim: (s: string) => string;
|
|
3
|
+
export declare const red: (s: string) => string;
|
|
4
|
+
export declare const green: (s: string) => string;
|
|
5
|
+
export declare const yellow: (s: string) => string;
|
|
6
|
+
export declare const cyan: (s: string) => string;
|
|
7
|
+
export declare function ms(value: number): string;
|
|
8
|
+
/** Compact relative age, e.g. "3m", "2h", "5d". */
|
|
9
|
+
export declare function age(iso: string | null): string;
|
|
10
|
+
export declare function truncate(s: string, max: number): string;
|
|
11
|
+
/** Left-aligned columns sized to their content. */
|
|
12
|
+
export declare function table(rows: string[][], gap?: number): string;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const useColor = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
2
|
+
const ESC = '\u001b[';
|
|
3
|
+
const wrap = (code) => (s) => (useColor ? `${ESC}${code}m${s}${ESC}0m` : s);
|
|
4
|
+
export const bold = wrap(1);
|
|
5
|
+
export const dim = wrap(2);
|
|
6
|
+
export const red = wrap(31);
|
|
7
|
+
export const green = wrap(32);
|
|
8
|
+
export const yellow = wrap(33);
|
|
9
|
+
export const cyan = wrap(36);
|
|
10
|
+
export function ms(value) {
|
|
11
|
+
return value >= 1000 ? `${(value / 1000).toFixed(2)}s` : `${Math.round(value)}ms`;
|
|
12
|
+
}
|
|
13
|
+
/** Compact relative age, e.g. "3m", "2h", "5d". */
|
|
14
|
+
export function age(iso) {
|
|
15
|
+
if (!iso)
|
|
16
|
+
return '?';
|
|
17
|
+
const then = Date.parse(iso);
|
|
18
|
+
if (Number.isNaN(then))
|
|
19
|
+
return '?';
|
|
20
|
+
const secs = Math.max(0, (Date.now() - then) / 1000);
|
|
21
|
+
if (secs < 60)
|
|
22
|
+
return `${Math.floor(secs)}s`;
|
|
23
|
+
if (secs < 3600)
|
|
24
|
+
return `${Math.floor(secs / 60)}m`;
|
|
25
|
+
if (secs < 86400)
|
|
26
|
+
return `${Math.floor(secs / 3600)}h`;
|
|
27
|
+
return `${Math.floor(secs / 86400)}d`;
|
|
28
|
+
}
|
|
29
|
+
export function truncate(s, max) {
|
|
30
|
+
return s.length <= max ? s : `${s.slice(0, max - 1)}…`;
|
|
31
|
+
}
|
|
32
|
+
/** Length ignoring ANSI escapes, so colored cells still align. */
|
|
33
|
+
function visibleLength(s) {
|
|
34
|
+
// eslint-disable-next-line no-control-regex
|
|
35
|
+
return s.replace(/\u001b\[\d+m/g, '').length;
|
|
36
|
+
}
|
|
37
|
+
/** Left-aligned columns sized to their content. */
|
|
38
|
+
export function table(rows, gap = 2) {
|
|
39
|
+
if (!rows.length)
|
|
40
|
+
return '';
|
|
41
|
+
const widths = [];
|
|
42
|
+
for (const row of rows) {
|
|
43
|
+
row.forEach((cell, i) => {
|
|
44
|
+
widths[i] = Math.max(widths[i] ?? 0, visibleLength(cell));
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return rows
|
|
48
|
+
.map((row) => row
|
|
49
|
+
.map((cell, i) => i === row.length - 1
|
|
50
|
+
? cell
|
|
51
|
+
: cell + ' '.repeat((widths[i] ?? 0) - visibleLength(cell) + gap))
|
|
52
|
+
.join(''))
|
|
53
|
+
.join('\n');
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/cli/format.ts"],"names":[],"mappings":"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"}
|