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 +26 -31
- package/dist/index.d.ts +2 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -3
- package/dist/playwright-repl.d.ts +1 -1
- package/dist/playwright-repl.js +5 -12
- package/dist/pw-cli.d.ts +0 -1
- package/dist/pw-cli.d.ts.map +1 -1
- package/dist/pw-cli.js +76 -6
- package/dist/recorder.d.ts +4 -91
- package/dist/recorder.d.ts.map +1 -1
- package/dist/recorder.js +4 -203
- package/dist/repl.d.ts +10 -8
- package/dist/repl.d.ts.map +1 -1
- package/dist/repl.js +277 -287
- package/package.json +3 -3
- package/dist/engine.d.ts +0 -66
- package/dist/engine.d.ts.map +0 -1
- package/dist/engine.js +0 -398
- package/dist/engine.js.map +0 -1
- package/dist/http-client.d.ts +0 -29
- package/dist/http-client.js +0 -173
- package/dist/index.js.map +0 -1
- package/dist/playwright-repl.js.map +0 -1
- package/dist/recorder.js.map +0 -1
- package/dist/repl.js.map +0 -1
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
|
|
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
|
-
| **
|
|
60
|
-
| **
|
|
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
|
-
###
|
|
62
|
+
### Launch (default)
|
|
63
63
|
|
|
64
|
-
Launches Chromium with
|
|
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
|
|
68
|
-
playwright-repl --headless
|
|
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
|
-
###
|
|
72
|
+
### Connect
|
|
72
73
|
|
|
73
|
-
|
|
74
|
+
Connect to an existing Chrome instance via CDP. Your cookies, logins, and extensions are available.
|
|
74
75
|
|
|
75
76
|
```bash
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
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 #
|
|
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 --
|
|
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 |
|
|
164
|
-
|
|
165
|
-
| **Keyword** — `click "Sign in"`, `goto https://...` |
|
|
166
|
-
| **
|
|
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
|
-
|
|
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
|
-
| `--
|
|
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\|
|
|
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
|
-
|
|
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 {
|
|
8
|
+
* import { parseInput, SessionRecorder } from 'playwright-repl';
|
|
9
9
|
*/
|
|
10
10
|
export { parseInput, ALIASES, ALL_COMMANDS, buildCompletionItems } from '@playwright-repl/core';
|
|
11
|
-
export {
|
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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;
|
|
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 {
|
|
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 --
|
|
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
|
package/dist/playwright-repl.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Usage:
|
|
6
6
|
* playwright-repl [options]
|
|
7
|
-
* playwright-repl --
|
|
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', '
|
|
18
|
-
string: ['session', 'browser', 'profile', 'config', 'replay', 'record', 'connect', 'port', 'cdp-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 --
|
|
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
|
-
|
|
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
|
package/dist/pw-cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pw-cli.d.ts","sourceRoot":"","sources":["../src/pw-cli.ts"],"names":[],"mappings":";AAEA
|
|
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', '
|
|
15
|
-
string: ['http-port', '
|
|
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) => {
|
package/dist/recorder.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
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
|
|
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
|
package/dist/recorder.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../src/recorder.ts"],"names":[],"mappings":"AAAA
|
|
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
|
-
*
|
|
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
|
-
|
|
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';
|