playwright-repl 0.26.0 → 0.27.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/README.md CHANGED
@@ -21,7 +21,7 @@ pw> screenshot
21
21
  ```bash
22
22
  npm install -g playwright-repl
23
23
 
24
- # Install Chromium (needed for standalone mode)
24
+ # Install Chromium
25
25
  npx playwright install chromium
26
26
  ```
27
27
 
@@ -56,33 +56,31 @@ The skill defines `allowed-tools: Bash(pw-cli:*)` so the agent can run browser c
56
56
 
57
57
  | Mode | Flag | How it works |
58
58
  |------|------|--------------|
59
- | **Standalone** | *(default)* | Launches Chromium with Dramaturg extension — keyword + JS |
60
- | **Bridge** | `--bridge` | Connects to your real Chrome via Dramaturg extension — cookies and logins intact |
59
+ | **Launch** | *(default)* | Launches Chromium directly — keyword + JS, full Playwright API |
60
+ | **Connect** | `--connect [port]` | Connects to existing Chrome via CDP — cookies and logins intact |
61
61
 
62
- ### Standalone
62
+ ### Launch (default)
63
63
 
64
- Launches Chromium with the Dramaturg extension pre-installed. Headed by default — use `--headless` for CI/scripting. Supports both keyword commands and JavaScript.
64
+ Launches Chromium with full Playwright API. Headed by default — use `--headless` for CI/scripting. Supports both keyword commands and JavaScript.
65
65
 
66
66
  ```bash
67
- playwright-repl # headed (default)
68
- playwright-repl --headless # headless for CI/scripting
67
+ playwright-repl # headed (default)
68
+ playwright-repl --headless # headless for CI/scripting
69
+ playwright-repl --command "snapshot" # run one command and exit
69
70
  ```
70
71
 
71
- ### Bridge
72
+ ### Connect
72
73
 
73
- The bridge mode turns your terminal into a remote console for the Chrome extension. Commands run inside your real Chrome with your existing cookies and logins.
74
+ Connect to an existing Chrome instance via CDP. Your cookies, logins, and extensions are available.
74
75
 
75
76
  ```bash
76
- playwright-repl --bridge # start bridge server, wait for extension
77
- playwright-repl --bridge --replay script.pw # replay a script via bridge
78
- playwright-repl --bridge --replay examples/ # replay all .pw files
79
- playwright-repl --bridge --bridge-port 9877 # custom port (default 9876)
80
- playwright-repl --bridge --command "snapshot" # run one command and exit
81
- ```
82
-
83
- The extension connects automatically — no need to open the side panel.
77
+ # Start Chrome with debugging enabled:
78
+ # chrome --remote-debugging-port=9222
84
79
 
85
- > Requires the [Dramaturg Chrome extension](../extension/README.md).
80
+ playwright-repl --connect # connect to Chrome on port 9222
81
+ playwright-repl --connect 9333 # custom port
82
+ playwright-repl --connect --replay examples/ # replay scripts against existing Chrome
83
+ ```
86
84
 
87
85
  ### HTTP Mode
88
86
 
@@ -90,8 +88,7 @@ Add `--http` to any mode to start an HTTP server alongside the REPL. This lets o
90
88
 
91
89
  ```bash
92
90
  # Terminal 1: Start REPL with HTTP server
93
- playwright-repl --http # standalone + HTTP (port 9223)
94
- playwright-repl --bridge --http # bridge + HTTP
91
+ playwright-repl --http # launch + HTTP (port 9223)
95
92
  playwright-repl --http --http-port 9224 # custom port
96
93
 
97
94
  # Terminal 2: Send commands via HTTP (fast — reuses existing session)
@@ -152,7 +149,7 @@ playwright-repl --replay session.pw --step
152
149
  playwright-repl --record my-test.pw
153
150
 
154
151
  # Run a single command and exit
155
- playwright-repl --bridge --command "snapshot"
152
+ playwright-repl --command "snapshot"
156
153
 
157
154
  # Pipe commands
158
155
  echo -e "goto https://example.com\nsnapshot" | playwright-repl
@@ -160,20 +157,19 @@ echo -e "goto https://example.com\nsnapshot" | playwright-repl
160
157
 
161
158
  ## Input Modes
162
159
 
163
- | Mode | Standalone | Bridge |
164
- |------|:---:|:---:|
165
- | **Keyword** — `click "Sign in"`, `goto https://...` | Yes | Yes |
166
- | **Playwright API / JS** — `await page.title()`, `1 + 1` | Yes | Yes |
160
+ | Mode | Description |
161
+ |------|-------------|
162
+ | **Keyword** — `click "Sign in"`, `goto https://...` | Playwright commands in natural syntax |
163
+ | **JavaScript** — `await page.title()`, `1 + 1` | Full Playwright API |
167
164
 
168
- Both modes auto-detect keyword commands and JavaScript expressions. For DOM access use `await page.evaluate(() => document.title)`. For keyword commands, see [Command Reference](#command-reference).
165
+ The REPL auto-detects keyword commands and JavaScript expressions. For DOM access use `await page.evaluate(() => document.title)`. For keyword commands, see [Command Reference](#command-reference).
169
166
 
170
167
  ## CLI Options
171
168
 
172
169
  | Option | Description |
173
170
  |--------|-------------|
174
171
  | `--headless` | Run browser in headless mode (default: headed) |
175
- | `--bridge` | Connect to existing Chrome via WebSocket bridge |
176
- | `--bridge-port <port>` | Bridge server port (default: `9876`) |
172
+ | `--connect [port]` | Connect to existing Chrome via CDP (default: `9222`) |
177
173
  | `--http` | Start HTTP server for external command access (port `9223`) |
178
174
  | `--http-port <port>` | HTTP server port (default: `9223`) |
179
175
  | `--command <cmd>` | Run a single command, print output, and exit |
@@ -345,7 +341,7 @@ pw> click "Submit" --frame "#my-iframe" # click inside an iframe
345
341
  |---------|-------|-------------|
346
342
  | `snapshot` | `s` | Accessibility tree with element refs |
347
343
  | `screenshot` | `ss` | Take a screenshot (saved to file) |
348
- | `highlight <text\|selector>` | `hl` | Highlight matching elements on page |
344
+ | `highlight <text\|role\|css>` | `hl` | Highlight matching elements on page |
349
345
  | `eval <expr>` | `e` | Evaluate JavaScript in browser context |
350
346
  | `console` | `con` | Browser console messages |
351
347
  | `network` | `net` | Network requests log |
@@ -417,8 +413,7 @@ pw> click "Submit" --frame "#my-iframe" # click inside an iframe
417
413
  | `video-stop` | Stop recording and save video |
418
414
  | `video-chapter <title>` | Add chapter marker to recording |
419
415
 
420
- In **standalone mode**, video uses Playwright's screencast API and saves to `~/pw-videos/`.
421
- In **bridge mode**, video uses Chrome's tab capture and saves to `Downloads/pw-videos/`.
416
+ Video uses Playwright's screencast API and saves to `~/pw-videos/`.
422
417
 
423
418
  ### Browser Control
424
419
 
package/dist/index.d.ts CHANGED
@@ -5,11 +5,10 @@
5
5
  * npx playwright-repl [options]
6
6
  *
7
7
  * Usage as library:
8
- * import { Engine, parseInput, SessionRecorder } from 'playwright-repl';
8
+ * import { parseInput, SessionRecorder } from 'playwright-repl';
9
9
  */
10
10
  export { parseInput, ALIASES, ALL_COMMANDS, buildCompletionItems } from '@playwright-repl/core';
11
- export { Engine } from './engine.js';
12
- export type { EngineOpts, EngineResult, ParsedArgs } from './engine.js';
11
+ export type { EngineResult, ParsedArgs } from '@playwright-repl/core';
13
12
  export { SessionRecorder, SessionPlayer } from './recorder.js';
14
13
  export { startRepl } from './repl.js';
15
14
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAGhG,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGxE,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAChG,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAGtE,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC"}
package/dist/index.js CHANGED
@@ -5,12 +5,10 @@
5
5
  * npx playwright-repl [options]
6
6
  *
7
7
  * Usage as library:
8
- * import { Engine, parseInput, SessionRecorder } from 'playwright-repl';
8
+ * import { parseInput, SessionRecorder } from 'playwright-repl';
9
9
  */
10
10
  // Re-export core utilities
11
11
  export { parseInput, ALIASES, ALL_COMMANDS, buildCompletionItems } from '@playwright-repl/core';
12
- // Engine (moved from core to cli)
13
- export { Engine } from './engine.js';
14
12
  // CLI-specific
15
13
  export { SessionRecorder, SessionPlayer } from './recorder.js';
16
14
  export { startRepl } from './repl.js';
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * Usage:
6
6
  * playwright-repl [options]
7
- * playwright-repl --bridge --command "snapshot"
7
+ * playwright-repl --command "snapshot"
8
8
  * playwright-repl --replay session.pw
9
9
  * playwright-repl --replay session.pw --step
10
10
  * playwright-repl --replay file1.pw file2.pw
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * Usage:
6
6
  * playwright-repl [options]
7
- * playwright-repl --bridge --command "snapshot"
7
+ * playwright-repl --command "snapshot"
8
8
  * playwright-repl --replay session.pw
9
9
  * playwright-repl --replay session.pw --step
10
10
  * playwright-repl --replay file1.pw file2.pw
@@ -14,8 +14,8 @@
14
14
  import { minimist } from '@playwright-repl/core';
15
15
  import { startRepl } from './repl.js';
16
16
  const args = minimist(process.argv.slice(2), {
17
- boolean: ['headed', 'headless', 'persistent', 'help', 'step', 'silent', 'spawn', 'bridge', 'engine', 'include-snapshot', 'verbose', 'http', 'interactive'],
18
- string: ['session', 'browser', 'profile', 'config', 'replay', 'record', 'connect', 'port', 'cdp-port', 'bridge-port', 'command', 'http-port'],
17
+ boolean: ['headed', 'headless', 'persistent', 'help', 'step', 'silent', 'spawn', 'relay', 'include-snapshot', 'verbose', 'http', 'interactive'],
18
+ string: ['session', 'browser', 'profile', 'config', 'replay', 'record', 'connect', 'port', 'cdp-port', 'command', 'http-port'],
19
19
  alias: { s: 'session', h: 'help', b: 'browser', q: 'silent' },
20
20
  default: { session: 'default' },
21
21
  });
@@ -38,9 +38,6 @@ Options:
38
38
  --persistent Use persistent browser profile
39
39
  --profile <dir> Persistent profile directory
40
40
  --connect [port] Connect to existing Chrome via CDP (default: 9222)
41
- --bridge Connect to extension via WebSocket bridge (no CDP required)
42
- --bridge-port <port> WebSocket bridge port (default: 9876)
43
- --engine Use standalone engine (no extension, keyword commands only)
44
41
  --cdp-port <number> Chrome CDP port (default: 9222)
45
42
  --include-snapshot Include snapshot in update command responses
46
43
  --verbose Show raw response headers (### Result, ### Snapshot, etc.)
@@ -74,13 +71,11 @@ Examples:
74
71
  playwright-repl --headed # start with visible browser
75
72
  playwright-repl --connect # connect to Chrome on port 9222
76
73
  playwright-repl --connect 9333 # connect to Chrome on custom port
77
- playwright-repl --bridge # connect to extension via WebSocket bridge
78
- playwright-repl --bridge --bridge-port 9877 # custom bridge port
79
74
  playwright-repl --replay login.pw # replay a session
80
75
  playwright-repl --replay login.pw --step # step through replay
81
76
  playwright-repl --replay tests/ # replay all .pw files in folder
82
77
  playwright-repl --replay a.pw b.pw # replay multiple files
83
- playwright-repl --bridge --command "snapshot" # run one command and exit
78
+ playwright-repl --command "snapshot" # run one command and exit
84
79
  echo "open https://example.com" | playwright-repl # pipe commands
85
80
  `);
86
81
  process.exit(0);
@@ -109,12 +104,10 @@ startRepl({
109
104
  silent: args.silent || !!commandArg,
110
105
  record: args.record,
111
106
  step: args.step,
112
- bridge: args.bridge,
113
- engine: args.engine,
107
+ relay: args.relay,
114
108
  http: args.http,
115
109
  httpPort: args['http-port'] ? parseInt(args['http-port'], 10) : undefined,
116
110
  interactive: args.interactive,
117
- bridgePort: args['bridge-port'] ? parseInt(args['bridge-port'], 10) : undefined,
118
111
  includeSnapshot: args['include-snapshot'],
119
112
  verbose: args.verbose,
120
113
  }).catch((err) => {
package/dist/pw-cli.d.ts CHANGED
@@ -6,7 +6,6 @@
6
6
  * pw-cli "snapshot" # → --http --command "snapshot"
7
7
  * pw-cli "click e5" # → --http --command "click e5"
8
8
  * pw-cli # → starts interactive REPL (same as playwright-repl)
9
- * pw-cli --bridge # → passes flags through to playwright-repl
10
9
  */
11
10
  export {};
12
11
  //# sourceMappingURL=pw-cli.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pw-cli.d.ts","sourceRoot":"","sources":["../src/pw-cli.ts"],"names":[],"mappings":";AAEA;;;;;;;;GAQG"}
1
+ {"version":3,"file":"pw-cli.d.ts","sourceRoot":"","sources":["../src/pw-cli.ts"],"names":[],"mappings":";AAEA;;;;;;;GAOG"}
package/dist/pw-cli.js CHANGED
@@ -6,13 +6,15 @@
6
6
  * pw-cli "snapshot" # → --http --command "snapshot"
7
7
  * pw-cli "click e5" # → --http --command "click e5"
8
8
  * pw-cli # → starts interactive REPL (same as playwright-repl)
9
- * pw-cli --bridge # → passes flags through to playwright-repl
10
9
  */
11
10
  import { startRepl } from './repl.js';
12
11
  import { minimist } from '@playwright-repl/core';
12
+ import { SessionPlayer } from './recorder.js';
13
+ import fs from 'node:fs';
14
+ import http from 'node:http';
13
15
  const args = minimist(process.argv.slice(2), {
14
- boolean: ['headed', 'headless', 'bridge', 'http', 'interactive', 'help'],
15
- string: ['http-port', 'bridge-port', 'command'],
16
+ boolean: ['headed', 'headless', 'http', 'interactive', 'help'],
17
+ string: ['http-port', 'command', 'replay', 'variable', 'load'],
16
18
  alias: { h: 'help' },
17
19
  });
18
20
  if (args.help) {
@@ -23,7 +25,6 @@ Usage:
23
25
  pw-cli "snapshot" # send command via HTTP (fast)
24
26
  pw-cli "click e5" # send command via HTTP
25
27
  pw-cli # start interactive REPL
26
- pw-cli --bridge # start bridge mode REPL
27
28
  pw-cli --http # start REPL with HTTP server
28
29
 
29
30
  Examples:
@@ -33,6 +34,77 @@ Examples:
33
34
  `);
34
35
  process.exit(0);
35
36
  }
37
+ // --load: load .js file as a global function definition
38
+ if (args.load) {
39
+ const httpPort = args['http-port'] ? parseInt(args['http-port'], 10) : 9223;
40
+ const filename = args.load;
41
+ if (!fs.existsSync(filename)) {
42
+ console.error(`File not found: ${filename}`);
43
+ process.exit(1);
44
+ }
45
+ const script = fs.readFileSync(filename, 'utf-8');
46
+ const result = await new Promise((resolve, reject) => {
47
+ const body = JSON.stringify({ command: script });
48
+ const req = http.request({ hostname: '127.0.0.1', port: httpPort, path: '/run', method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) } }, res => {
49
+ let data = '';
50
+ res.on('data', (chunk) => data += chunk);
51
+ res.on('end', () => { try {
52
+ resolve(JSON.parse(data));
53
+ }
54
+ catch {
55
+ reject(new Error('Invalid response'));
56
+ } });
57
+ });
58
+ req.on('error', reject);
59
+ req.write(body);
60
+ req.end();
61
+ }).catch((e) => ({ text: e.message, isError: true }));
62
+ if (result.isError) {
63
+ console.error(result.text);
64
+ process.exit(1);
65
+ }
66
+ console.log(`✓ Loaded ${filename}`);
67
+ process.exit(0);
68
+ }
69
+ // --replay: load .pw file, substitute variables, send commands via HTTP
70
+ if (args.replay) {
71
+ const httpPort = args['http-port'] ? parseInt(args['http-port'], 10) : 9223;
72
+ // Parse --variable args (string or string[])
73
+ const variables = {};
74
+ const varArgs = args.variable ? (Array.isArray(args.variable) ? args.variable : [args.variable]) : [];
75
+ for (const v of varArgs) {
76
+ const [key, ...rest] = v.split('=');
77
+ if (key && rest.length > 0)
78
+ variables[key] = rest.join('=');
79
+ }
80
+ const commands = SessionPlayer.load(args.replay, Object.keys(variables).length > 0 ? variables : undefined);
81
+ let failed = false;
82
+ for (const cmd of commands) {
83
+ const result = await new Promise((resolve, reject) => {
84
+ const body = JSON.stringify({ command: cmd });
85
+ const req = http.request({ hostname: '127.0.0.1', port: httpPort, path: '/run', method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) } }, res => {
86
+ let data = '';
87
+ res.on('data', (chunk) => data += chunk);
88
+ res.on('end', () => { try {
89
+ resolve(JSON.parse(data));
90
+ }
91
+ catch {
92
+ reject(new Error('Invalid response'));
93
+ } });
94
+ });
95
+ req.on('error', reject);
96
+ req.write(body);
97
+ req.end();
98
+ }).catch((e) => ({ text: e.message, isError: true }));
99
+ const mark = result.isError ? '✗' : '✓';
100
+ console.log(`${mark} ${cmd}${result.isError && result.text ? ` — ${result.text}` : ''}`);
101
+ if (result.isError) {
102
+ failed = true;
103
+ break;
104
+ }
105
+ }
106
+ process.exit(failed ? 1 : 0);
107
+ }
36
108
  // If positional args given, treat as --http --command
37
109
  const positional = args._;
38
110
  if (positional.length > 0) {
@@ -50,10 +122,8 @@ if (positional.length > 0) {
50
122
  else {
51
123
  // No args — start interactive REPL, pass through flags
52
124
  startRepl({
53
- bridge: args.bridge,
54
125
  http: args.http,
55
126
  httpPort: args['http-port'] ? parseInt(args['http-port'], 10) : undefined,
56
- bridgePort: args['bridge-port'] ? parseInt(args['bridge-port'], 10) : undefined,
57
127
  interactive: args.interactive,
58
128
  headed: args.headless ? false : args.headed ? true : undefined,
59
129
  }).catch((err) => {
@@ -1,95 +1,8 @@
1
1
  /**
2
- * Session recorder and player.
2
+ * Session recorder and player — re-exported from @playwright-repl/core.
3
3
  *
4
- * Records REPL commands to .pw files and replays them.
5
- *
6
- * File format (.pw):
7
- * - One command per line (exactly as typed in REPL)
8
- * - Comments start with #
9
- * - Blank lines are ignored
10
- * - First line is a metadata comment with timestamp
11
- *
12
- * Example:
13
- * # Login test
14
- * # recorded 2026-02-09T19:30:00Z
15
- *
16
- * open https://myapp.com
17
- * snapshot
18
- * click e5
19
- * fill e7 admin@test.com
20
- * fill e9 password123
21
- * click e12
22
- * verify-text Welcome back
4
+ * The recording infrastructure (SessionRecorder, SessionPlayer, SessionManager)
5
+ * lives in packages/core so it can be shared by CLI, MCP, and VS Code.
23
6
  */
24
- export declare class SessionRecorder {
25
- commands: string[];
26
- recording: boolean;
27
- filename: string | null;
28
- paused: boolean;
29
- /**
30
- * Start recording commands.
31
- */
32
- start(filename?: string): string;
33
- /**
34
- * Record a command (called after each successful REPL command).
35
- * Skips meta-commands (lines starting with .).
36
- */
37
- record(line: string): void;
38
- /**
39
- * Pause recording (toggle).
40
- */
41
- pause(): boolean;
42
- /**
43
- * Stop recording and save to file.
44
- */
45
- save(): {
46
- filename: string;
47
- count: number;
48
- };
49
- /**
50
- * Discard recording without saving.
51
- */
52
- discard(): void;
53
- get status(): string;
54
- get commandCount(): number;
55
- }
56
- export declare class SessionPlayer {
57
- filename: string;
58
- commands: string[];
59
- index: number;
60
- /**
61
- * Load commands from a .pw file.
62
- */
63
- static load(filename: string): string[];
64
- /**
65
- * Create a player that yields commands one at a time.
66
- * Supports step-through mode where it pauses between commands.
67
- */
68
- constructor(filename: string);
69
- get done(): boolean;
70
- get current(): string | null;
71
- get progress(): string;
72
- next(): string | null;
73
- reset(): void;
74
- }
75
- export declare class SessionManager {
76
- #private;
77
- /** Current mode: 'idle' | 'recording' | 'paused' | 'replaying' */
78
- get mode(): string;
79
- startRecording(filename?: string): string;
80
- save(): {
81
- filename: string;
82
- count: number;
83
- };
84
- togglePause(): boolean;
85
- discard(): void;
86
- /** Called after each successful command — records if active. */
87
- record(line: string): void;
88
- get recordingFilename(): string | null;
89
- get recordedCount(): number;
90
- startReplay(filename: string, step?: boolean): SessionPlayer;
91
- endReplay(): void;
92
- get player(): SessionPlayer | null;
93
- get step(): boolean;
94
- }
7
+ export { SessionRecorder, SessionPlayer, SessionManager } from '@playwright-repl/core';
95
8
  //# sourceMappingURL=recorder.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../src/recorder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAOH,qBAAa,eAAe;IAC1B,QAAQ,EAAE,MAAM,EAAE,CAAM;IACxB,SAAS,UAAS;IAClB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAQ;IAC/B,MAAM,UAAS;IAEf;;OAEG;IACH,KAAK,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM;IAQhC;;;OAGG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAO1B;;OAEG;IACH,KAAK,IAAI,OAAO;IAKhB;;OAEG;IACH,IAAI,IAAI;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE;IA6B3C;;OAEG;IACH,OAAO,IAAI,IAAI;IAOf,IAAI,MAAM,IAAI,MAAM,CAInB;IAED,IAAI,YAAY,IAAI,MAAM,CAEzB;CACF;AAID,qBAAa,aAAa;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,KAAK,SAAK;IAEV;;OAEG;IACH,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE;IAYvC;;;OAGG;gBACS,QAAQ,EAAE,MAAM;IAK5B,IAAI,IAAI,IAAI,OAAO,CAElB;IAED,IAAI,OAAO,IAAI,MAAM,GAAG,IAAI,CAE3B;IAED,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED,IAAI,IAAI,MAAM,GAAG,IAAI;IAKrB,KAAK,IAAI,IAAI;CAGd;AAQD,qBAAa,cAAc;;IAKzB,kEAAkE;IAClE,IAAI,IAAI,IAAI,MAAM,CAGjB;IAID,cAAc,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM;IAKzC,IAAI,IAAI;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE;IAM3C,WAAW,IAAI,OAAO;IAMtB,OAAO,IAAI,IAAI;IAMf,gEAAgE;IAChE,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAI1B,IAAI,iBAAiB,IAAI,MAAM,GAAG,IAAI,CAAoC;IAC1E,IAAI,aAAa,IAAI,MAAM,CAAwC;IAInE,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,UAAQ,GAAG,aAAa;IAO1D,SAAS,IAAI,IAAI;IAKjB,IAAI,MAAM,IAAI,aAAa,GAAG,IAAI,CAAyB;IAC3D,IAAI,IAAI,IAAI,OAAO,CAAuB;CAC3C"}
1
+ {"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../src/recorder.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC"}
package/dist/recorder.js CHANGED
@@ -1,206 +1,7 @@
1
1
  /**
2
- * Session recorder and player.
2
+ * Session recorder and player — re-exported from @playwright-repl/core.
3
3
  *
4
- * Records REPL commands to .pw files and replays them.
5
- *
6
- * File format (.pw):
7
- * - One command per line (exactly as typed in REPL)
8
- * - Comments start with #
9
- * - Blank lines are ignored
10
- * - First line is a metadata comment with timestamp
11
- *
12
- * Example:
13
- * # Login test
14
- * # recorded 2026-02-09T19:30:00Z
15
- *
16
- * open https://myapp.com
17
- * snapshot
18
- * click e5
19
- * fill e7 admin@test.com
20
- * fill e9 password123
21
- * click e12
22
- * verify-text Welcome back
4
+ * The recording infrastructure (SessionRecorder, SessionPlayer, SessionManager)
5
+ * lives in packages/core so it can be shared by CLI, MCP, and VS Code.
23
6
  */
24
- import fs from 'node:fs';
25
- import path from 'node:path';
26
- // ─── Session Recorder ────────────────────────────────────────────────────────
27
- export class SessionRecorder {
28
- commands = [];
29
- recording = false;
30
- filename = null;
31
- paused = false;
32
- /**
33
- * Start recording commands.
34
- */
35
- start(filename) {
36
- this.filename = filename || `session-${new Date().toISOString().replace(/[:.]/g, '-')}.pw`;
37
- this.commands = [];
38
- this.recording = true;
39
- this.paused = false;
40
- return this.filename;
41
- }
42
- /**
43
- * Record a command (called after each successful REPL command).
44
- * Skips meta-commands (lines starting with .).
45
- */
46
- record(line) {
47
- if (!this.recording || this.paused)
48
- return;
49
- const trimmed = line.trim();
50
- if (!trimmed || trimmed.startsWith('.'))
51
- return;
52
- this.commands.push(trimmed);
53
- }
54
- /**
55
- * Pause recording (toggle).
56
- */
57
- pause() {
58
- this.paused = !this.paused;
59
- return this.paused;
60
- }
61
- /**
62
- * Stop recording and save to file.
63
- */
64
- save() {
65
- if (!this.recording)
66
- throw new Error('Not recording');
67
- const header = [
68
- `# Playwright REPL session`,
69
- `# recorded ${new Date().toISOString()}`,
70
- ``,
71
- ];
72
- const content = [...header, ...this.commands, ''].join('\n');
73
- // Ensure directory exists
74
- const dir = path.dirname(this.filename);
75
- if (dir && dir !== '.') {
76
- fs.mkdirSync(dir, { recursive: true });
77
- }
78
- fs.writeFileSync(this.filename, content, 'utf-8');
79
- const result = { filename: this.filename, count: this.commands.length };
80
- this.recording = false;
81
- this.commands = [];
82
- this.filename = null;
83
- this.paused = false;
84
- return result;
85
- }
86
- /**
87
- * Discard recording without saving.
88
- */
89
- discard() {
90
- this.recording = false;
91
- this.commands = [];
92
- this.filename = null;
93
- this.paused = false;
94
- }
95
- get status() {
96
- if (!this.recording)
97
- return 'idle';
98
- if (this.paused)
99
- return 'paused';
100
- return 'recording';
101
- }
102
- get commandCount() {
103
- return this.commands.length;
104
- }
105
- }
106
- // ─── Session Player ──────────────────────────────────────────────────────────
107
- export class SessionPlayer {
108
- filename;
109
- commands;
110
- index = 0;
111
- /**
112
- * Load commands from a .pw file.
113
- */
114
- static load(filename) {
115
- if (!fs.existsSync(filename)) {
116
- throw new Error(`File not found: ${filename}`);
117
- }
118
- const content = fs.readFileSync(filename, 'utf-8');
119
- return content
120
- .split('\n')
121
- .map(line => line.trim())
122
- .filter(line => line && !line.startsWith('#'));
123
- }
124
- /**
125
- * Create a player that yields commands one at a time.
126
- * Supports step-through mode where it pauses between commands.
127
- */
128
- constructor(filename) {
129
- this.filename = filename;
130
- this.commands = SessionPlayer.load(filename);
131
- }
132
- get done() {
133
- return this.index >= this.commands.length;
134
- }
135
- get current() {
136
- return this.commands[this.index] || null;
137
- }
138
- get progress() {
139
- return `[${this.index}/${this.commands.length}]`;
140
- }
141
- next() {
142
- if (this.done)
143
- return null;
144
- return this.commands[this.index++];
145
- }
146
- reset() {
147
- this.index = 0;
148
- }
149
- }
150
- // ─── Session Manager (state machine) ────────────────────────────────────────
151
- //
152
- // States: idle → recording ⇄ paused → idle
153
- // idle → replaying → idle
154
- //
155
- export class SessionManager {
156
- #recorder = new SessionRecorder();
157
- #player = null;
158
- #step = false;
159
- /** Current mode: 'idle' | 'recording' | 'paused' | 'replaying' */
160
- get mode() {
161
- if (this.#player && !this.#player.done)
162
- return 'replaying';
163
- return this.#recorder.status;
164
- }
165
- // ── Recording ──────────────────────────────────────────────────
166
- startRecording(filename) {
167
- if (this.mode !== 'idle')
168
- throw new Error(`Cannot record while ${this.mode}`);
169
- return this.#recorder.start(filename);
170
- }
171
- save() {
172
- if (this.mode !== 'recording' && this.mode !== 'paused')
173
- throw new Error('Not recording');
174
- return this.#recorder.save();
175
- }
176
- togglePause() {
177
- if (this.mode !== 'recording' && this.mode !== 'paused')
178
- throw new Error('Not recording');
179
- return this.#recorder.pause();
180
- }
181
- discard() {
182
- if (this.mode !== 'recording' && this.mode !== 'paused')
183
- throw new Error('Not recording');
184
- this.#recorder.discard();
185
- }
186
- /** Called after each successful command — records if active. */
187
- record(line) {
188
- this.#recorder.record(line);
189
- }
190
- get recordingFilename() { return this.#recorder.filename; }
191
- get recordedCount() { return this.#recorder.commandCount; }
192
- // ── Playback ───────────────────────────────────────────────────
193
- startReplay(filename, step = false) {
194
- if (this.mode !== 'idle')
195
- throw new Error(`Cannot replay while ${this.mode}`);
196
- this.#player = new SessionPlayer(filename);
197
- this.#step = step;
198
- return this.#player;
199
- }
200
- endReplay() {
201
- this.#player = null;
202
- this.#step = false;
203
- }
204
- get player() { return this.#player; }
205
- get step() { return this.#step; }
206
- }
7
+ export { SessionRecorder, SessionPlayer, SessionManager } from '@playwright-repl/core';