playwright-repl 0.24.0 → 0.25.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 +80 -0
- package/dist/engine.js +0 -1
- package/dist/index.js +0 -1
- package/dist/playwright-repl.d.ts +1 -0
- package/dist/playwright-repl.d.ts.map +1 -1
- package/dist/playwright-repl.js +16 -4
- package/dist/pw-cli.d.ts +12 -0
- package/dist/pw-cli.d.ts.map +1 -0
- package/dist/pw-cli.js +63 -0
- package/dist/recorder.js +0 -1
- package/dist/repl.d.ts +4 -0
- package/dist/repl.d.ts.map +1 -1
- package/dist/repl.js +194 -2
- package/package.json +5 -4
- package/dist/http-client.d.ts.map +0 -1
- package/dist/http-client.js.map +0 -1
package/README.md
CHANGED
|
@@ -25,6 +25,33 @@ npm install -g playwright-repl
|
|
|
25
25
|
npx playwright install chromium
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
+
## AI Skills
|
|
29
|
+
|
|
30
|
+
playwright-repl includes an AI skill file that teaches agents (Claude Code, Cursor, etc.) how to use `pw-cli` via Bash — no MCP needed.
|
|
31
|
+
|
|
32
|
+
### Claude Code
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# Project-level (this project only)
|
|
36
|
+
mkdir -p .claude/skills/playwright-repl
|
|
37
|
+
cp node_modules/playwright-repl/skills/SKILL.md .claude/skills/playwright-repl/SKILL.md
|
|
38
|
+
|
|
39
|
+
# Or global (all projects)
|
|
40
|
+
mkdir -p ~/.claude/skills/playwright-repl
|
|
41
|
+
cp node_modules/playwright-repl/skills/SKILL.md ~/.claude/skills/playwright-repl/SKILL.md
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Then invoke with `/playwright-repl` or let Claude auto-detect it from the skill description.
|
|
45
|
+
|
|
46
|
+
### Cursor
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
mkdir -p .cursor/skills/playwright-repl
|
|
50
|
+
cp node_modules/playwright-repl/skills/SKILL.md .cursor/skills/playwright-repl/SKILL.md
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The skill defines `allowed-tools: Bash(pw-cli:*)` so the agent can run browser commands directly.
|
|
54
|
+
|
|
28
55
|
## Connection Modes
|
|
29
56
|
|
|
30
57
|
| Mode | Flag | How it works |
|
|
@@ -50,12 +77,59 @@ playwright-repl --bridge # start bridge server, wait for ex
|
|
|
50
77
|
playwright-repl --bridge --replay script.pw # replay a script via bridge
|
|
51
78
|
playwright-repl --bridge --replay examples/ # replay all .pw files
|
|
52
79
|
playwright-repl --bridge --bridge-port 9877 # custom port (default 9876)
|
|
80
|
+
playwright-repl --bridge --command "snapshot" # run one command and exit
|
|
53
81
|
```
|
|
54
82
|
|
|
55
83
|
The extension connects automatically — no need to open the side panel.
|
|
56
84
|
|
|
57
85
|
> Requires the [Dramaturg Chrome extension](../extension/README.md).
|
|
58
86
|
|
|
87
|
+
### HTTP Mode
|
|
88
|
+
|
|
89
|
+
Add `--http` to any mode to start an HTTP server alongside the REPL. This lets other processes send commands without starting a new browser session — ideal for AI tools, scripts, and CI.
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# Terminal 1: Start REPL with HTTP server
|
|
93
|
+
playwright-repl --http # standalone + HTTP (port 9223)
|
|
94
|
+
playwright-repl --bridge --http # bridge + HTTP
|
|
95
|
+
playwright-repl --http --http-port 9224 # custom port
|
|
96
|
+
|
|
97
|
+
# Terminal 2: Send commands via HTTP (fast — reuses existing session)
|
|
98
|
+
playwright-repl --http --command "snapshot"
|
|
99
|
+
playwright-repl --http --command "click e5"
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The HTTP server exposes:
|
|
103
|
+
- `GET /health` — check if server is alive
|
|
104
|
+
- `POST /run` — run a command: `{"command": "snapshot"}`
|
|
105
|
+
- `POST /run-script` — run a multi-line script: `{"script": "...", "language": "pw"}`
|
|
106
|
+
|
|
107
|
+
Works with `curl` too:
|
|
108
|
+
```bash
|
|
109
|
+
curl -X POST http://localhost:9223/run -d '{"command":"snapshot"}'
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## pw-cli — Quick Command Shorthand
|
|
113
|
+
|
|
114
|
+
`pw-cli` is a shorthand for `playwright-repl --http --command`. When given arguments, it sends the command via HTTP to a running session (REPL, MCP, or VS Code):
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
pw-cli "snapshot" # → playwright-repl --http --command "snapshot"
|
|
118
|
+
pw-cli "click e5" # → playwright-repl --http --command "click e5"
|
|
119
|
+
pw-cli "await page.title()" # JavaScript works too
|
|
120
|
+
pw-cli # no args → starts interactive REPL
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Requires a running `--http` server (or MCP server) on port 9223. Start one first:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
# Option 1: REPL with HTTP
|
|
127
|
+
playwright-repl --http
|
|
128
|
+
|
|
129
|
+
# Option 2: MCP server (HTTP starts automatically)
|
|
130
|
+
# (launched by Claude Desktop / Claude Code)
|
|
131
|
+
```
|
|
132
|
+
|
|
59
133
|
## Usage
|
|
60
134
|
|
|
61
135
|
```bash
|
|
@@ -77,6 +151,9 @@ playwright-repl --replay session.pw --step
|
|
|
77
151
|
# Start REPL with recording enabled
|
|
78
152
|
playwright-repl --record my-test.pw
|
|
79
153
|
|
|
154
|
+
# Run a single command and exit
|
|
155
|
+
playwright-repl --bridge --command "snapshot"
|
|
156
|
+
|
|
80
157
|
# Pipe commands
|
|
81
158
|
echo -e "goto https://example.com\nsnapshot" | playwright-repl
|
|
82
159
|
```
|
|
@@ -97,6 +174,9 @@ Both modes auto-detect keyword commands and JavaScript expressions. For DOM acce
|
|
|
97
174
|
| `--headless` | Run browser in headless mode (default: headed) |
|
|
98
175
|
| `--bridge` | Connect to existing Chrome via WebSocket bridge |
|
|
99
176
|
| `--bridge-port <port>` | Bridge server port (default: `9876`) |
|
|
177
|
+
| `--http` | Start HTTP server for external command access (port `9223`) |
|
|
178
|
+
| `--http-port <port>` | HTTP server port (default: `9223`) |
|
|
179
|
+
| `--command <cmd>` | Run a single command, print output, and exit |
|
|
100
180
|
| `--config <file>` | Path to config file |
|
|
101
181
|
| `--replay <files...>` | Replay `.pw` or `.js` file(s) or folder(s) |
|
|
102
182
|
| `--record <file>` | Start REPL with recording to file |
|
package/dist/engine.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"playwright-repl.d.ts","sourceRoot":"","sources":["../src/playwright-repl.ts"],"names":[],"mappings":";AAEA
|
|
1
|
+
{"version":3,"file":"playwright-repl.d.ts","sourceRoot":"","sources":["../src/playwright-repl.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;GAWG"}
|
package/dist/playwright-repl.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Usage:
|
|
6
6
|
* playwright-repl [options]
|
|
7
|
+
* playwright-repl --bridge --command "snapshot"
|
|
7
8
|
* playwright-repl --replay session.pw
|
|
8
9
|
* playwright-repl --replay session.pw --step
|
|
9
10
|
* playwright-repl --replay file1.pw file2.pw
|
|
@@ -13,8 +14,8 @@
|
|
|
13
14
|
import { minimist } from '@playwright-repl/core';
|
|
14
15
|
import { startRepl } from './repl.js';
|
|
15
16
|
const args = minimist(process.argv.slice(2), {
|
|
16
|
-
boolean: ['headed', 'headless', 'persistent', 'help', 'step', 'silent', 'spawn', 'bridge', 'engine', 'include-snapshot', 'verbose'],
|
|
17
|
-
string: ['session', 'browser', 'profile', 'config', 'replay', 'record', 'connect', 'port', 'cdp-port', 'bridge-port'],
|
|
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'],
|
|
18
19
|
alias: { s: 'session', h: 'help', b: 'browser', q: 'silent' },
|
|
19
20
|
default: { session: 'default' },
|
|
20
21
|
});
|
|
@@ -43,6 +44,12 @@ Options:
|
|
|
43
44
|
--cdp-port <number> Chrome CDP port (default: 9222)
|
|
44
45
|
--include-snapshot Include snapshot in update command responses
|
|
45
46
|
--verbose Show raw response headers (### Result, ### Snapshot, etc.)
|
|
47
|
+
--http Start HTTP server for external command access (port 9223).
|
|
48
|
+
Runs as a console (no interactive prompt) — pair with --interactive
|
|
49
|
+
to also get a readline REPL.
|
|
50
|
+
--http-port <port> HTTP server port (default: 9223)
|
|
51
|
+
--interactive Force interactive readline prompt even when --http is set
|
|
52
|
+
--command <cmd> Run a single command, print output, and exit
|
|
46
53
|
--config <file> Path to config file
|
|
47
54
|
--replay <files...> Replay .pw file(s) or folder(s)
|
|
48
55
|
--record <file> Start REPL with recording to file
|
|
@@ -73,6 +80,7 @@ Examples:
|
|
|
73
80
|
playwright-repl --replay login.pw --step # step through replay
|
|
74
81
|
playwright-repl --replay tests/ # replay all .pw files in folder
|
|
75
82
|
playwright-repl --replay a.pw b.pw # replay multiple files
|
|
83
|
+
playwright-repl --bridge --command "snapshot" # run one command and exit
|
|
76
84
|
echo "open https://example.com" | playwright-repl # pipe commands
|
|
77
85
|
`);
|
|
78
86
|
process.exit(0);
|
|
@@ -84,6 +92,7 @@ if (args.replay) {
|
|
|
84
92
|
for (const a of args._)
|
|
85
93
|
replayFiles.push(String(a));
|
|
86
94
|
}
|
|
95
|
+
const commandArg = args.command;
|
|
87
96
|
startRepl({
|
|
88
97
|
session: args.session,
|
|
89
98
|
headed: args.headless ? false : args.headed ? true : undefined,
|
|
@@ -96,11 +105,15 @@ startRepl({
|
|
|
96
105
|
cdpPort: args['cdp-port'] ? parseInt(args['cdp-port'], 10) : undefined,
|
|
97
106
|
config: args.config,
|
|
98
107
|
replay: replayFiles.length > 0 ? replayFiles : undefined,
|
|
108
|
+
command: commandArg,
|
|
109
|
+
silent: args.silent || !!commandArg,
|
|
99
110
|
record: args.record,
|
|
100
111
|
step: args.step,
|
|
101
|
-
silent: args.silent,
|
|
102
112
|
bridge: args.bridge,
|
|
103
113
|
engine: args.engine,
|
|
114
|
+
http: args.http,
|
|
115
|
+
httpPort: args['http-port'] ? parseInt(args['http-port'], 10) : undefined,
|
|
116
|
+
interactive: args.interactive,
|
|
104
117
|
bridgePort: args['bridge-port'] ? parseInt(args['bridge-port'], 10) : undefined,
|
|
105
118
|
includeSnapshot: args['include-snapshot'],
|
|
106
119
|
verbose: args.verbose,
|
|
@@ -108,4 +121,3 @@ startRepl({
|
|
|
108
121
|
console.error(`Fatal: ${err.message}`);
|
|
109
122
|
process.exit(1);
|
|
110
123
|
});
|
|
111
|
-
//# sourceMappingURL=playwright-repl.js.map
|
package/dist/pw-cli.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* pw-cli — shorthand for playwright-repl --http --command.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* pw-cli "snapshot" # → --http --command "snapshot"
|
|
7
|
+
* pw-cli "click e5" # → --http --command "click e5"
|
|
8
|
+
* pw-cli # → starts interactive REPL (same as playwright-repl)
|
|
9
|
+
* pw-cli --bridge # → passes flags through to playwright-repl
|
|
10
|
+
*/
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=pw-cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pw-cli.d.ts","sourceRoot":"","sources":["../src/pw-cli.ts"],"names":[],"mappings":";AAEA;;;;;;;;GAQG"}
|
package/dist/pw-cli.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* pw-cli — shorthand for playwright-repl --http --command.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* pw-cli "snapshot" # → --http --command "snapshot"
|
|
7
|
+
* pw-cli "click e5" # → --http --command "click e5"
|
|
8
|
+
* pw-cli # → starts interactive REPL (same as playwright-repl)
|
|
9
|
+
* pw-cli --bridge # → passes flags through to playwright-repl
|
|
10
|
+
*/
|
|
11
|
+
import { startRepl } from './repl.js';
|
|
12
|
+
import { minimist } from '@playwright-repl/core';
|
|
13
|
+
const args = minimist(process.argv.slice(2), {
|
|
14
|
+
boolean: ['headed', 'headless', 'bridge', 'http', 'interactive', 'help'],
|
|
15
|
+
string: ['http-port', 'bridge-port', 'command'],
|
|
16
|
+
alias: { h: 'help' },
|
|
17
|
+
});
|
|
18
|
+
if (args.help) {
|
|
19
|
+
console.log(`
|
|
20
|
+
pw-cli — shorthand for playwright-repl
|
|
21
|
+
|
|
22
|
+
Usage:
|
|
23
|
+
pw-cli "snapshot" # send command via HTTP (fast)
|
|
24
|
+
pw-cli "click e5" # send command via HTTP
|
|
25
|
+
pw-cli # start interactive REPL
|
|
26
|
+
pw-cli --bridge # start bridge mode REPL
|
|
27
|
+
pw-cli --http # start REPL with HTTP server
|
|
28
|
+
|
|
29
|
+
Examples:
|
|
30
|
+
pw-cli "goto https://example.com"
|
|
31
|
+
pw-cli "await page.title()"
|
|
32
|
+
pw-cli "screenshot"
|
|
33
|
+
`);
|
|
34
|
+
process.exit(0);
|
|
35
|
+
}
|
|
36
|
+
// If positional args given, treat as --http --command
|
|
37
|
+
const positional = args._;
|
|
38
|
+
if (positional.length > 0) {
|
|
39
|
+
const command = positional.join(' ');
|
|
40
|
+
startRepl({
|
|
41
|
+
command,
|
|
42
|
+
http: true,
|
|
43
|
+
httpPort: args['http-port'] ? parseInt(args['http-port'], 10) : undefined,
|
|
44
|
+
silent: true,
|
|
45
|
+
}).catch((err) => {
|
|
46
|
+
console.error(err.message);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
// No args — start interactive REPL, pass through flags
|
|
52
|
+
startRepl({
|
|
53
|
+
bridge: args.bridge,
|
|
54
|
+
http: args.http,
|
|
55
|
+
httpPort: args['http-port'] ? parseInt(args['http-port'], 10) : undefined,
|
|
56
|
+
bridgePort: args['bridge-port'] ? parseInt(args['bridge-port'], 10) : undefined,
|
|
57
|
+
interactive: args.interactive,
|
|
58
|
+
headed: args.headless ? false : args.headed ? true : undefined,
|
|
59
|
+
}).catch((err) => {
|
|
60
|
+
console.error(`Fatal: ${err.message}`);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
});
|
|
63
|
+
}
|
package/dist/recorder.js
CHANGED
package/dist/repl.d.ts
CHANGED
|
@@ -13,9 +13,13 @@ export interface ReplOpts extends EngineOpts {
|
|
|
13
13
|
record?: string;
|
|
14
14
|
step?: boolean;
|
|
15
15
|
silent?: boolean;
|
|
16
|
+
command?: string;
|
|
16
17
|
bridge?: boolean;
|
|
17
18
|
bridgePort?: number;
|
|
18
19
|
engine?: boolean;
|
|
20
|
+
http?: boolean;
|
|
21
|
+
httpPort?: number;
|
|
22
|
+
interactive?: boolean;
|
|
19
23
|
includeSnapshot?: boolean;
|
|
20
24
|
verbose?: boolean;
|
|
21
25
|
}
|
package/dist/repl.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repl.d.ts","sourceRoot":"","sources":["../src/repl.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,QAAQ,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"repl.d.ts","sourceRoot":"","sources":["../src/repl.ts"],"names":[],"mappings":"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"}
|
package/dist/repl.js
CHANGED
|
@@ -7,6 +7,7 @@ import readline from 'node:readline';
|
|
|
7
7
|
import path from 'node:path';
|
|
8
8
|
import fs from 'node:fs';
|
|
9
9
|
import os from 'node:os';
|
|
10
|
+
import http from 'node:http';
|
|
10
11
|
import { replVersion, parseInput, ALIASES, ALL_COMMANDS, buildCompletionItems, c, prettyJson, BridgeServer, COMMANDS, CATEGORIES, JS_CATEGORIES, filterResponse as filterResponseBase, resolveArgs, handleLocalCommand, } from '@playwright-repl/core';
|
|
11
12
|
import { Engine } from './engine.js';
|
|
12
13
|
import { SessionManager } from './recorder.js';
|
|
@@ -858,11 +859,146 @@ async function startBridgeLoop(opts, srv) {
|
|
|
858
859
|
rl.close();
|
|
859
860
|
});
|
|
860
861
|
}
|
|
862
|
+
// ─── HTTP server for --http mode ─────────────────────────────────────────────
|
|
863
|
+
const DEFAULT_HTTP_PORT = 9223;
|
|
864
|
+
function httpTs() {
|
|
865
|
+
const d = new Date();
|
|
866
|
+
const pad = (n) => n.toString().padStart(2, '0');
|
|
867
|
+
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
|
868
|
+
}
|
|
869
|
+
function startHttpServer(port, runner, log) {
|
|
870
|
+
return new Promise((resolve, reject) => {
|
|
871
|
+
const server = http.createServer(async (req, res) => {
|
|
872
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
873
|
+
if (req.method === 'GET' && req.url === '/health') {
|
|
874
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
875
|
+
res.end(JSON.stringify({ status: 'ok' }));
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
if (req.method === 'POST' && req.url === '/run') {
|
|
879
|
+
const t0 = Date.now();
|
|
880
|
+
let command = '';
|
|
881
|
+
try {
|
|
882
|
+
const body = await readBody(req);
|
|
883
|
+
({ command } = JSON.parse(body));
|
|
884
|
+
log(`${c.dim}[${httpTs()}] →${c.reset} ${command}`);
|
|
885
|
+
const result = await runner.run(command);
|
|
886
|
+
const ms = Date.now() - t0;
|
|
887
|
+
const mark = result.isError ? `${c.red}✗${c.reset}` : `${c.green}✓${c.reset}`;
|
|
888
|
+
log(`${c.dim}[${httpTs()}]${c.reset} ${mark} ${command} ${c.dim}(${ms}ms)${c.reset}`);
|
|
889
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
890
|
+
res.end(JSON.stringify(result));
|
|
891
|
+
}
|
|
892
|
+
catch (e) {
|
|
893
|
+
log(`${c.dim}[${httpTs()}]${c.reset} ${c.red}✗${c.reset} ${command} ${c.dim}— ${e.message}${c.reset}`);
|
|
894
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
895
|
+
res.end(JSON.stringify({ text: e.message, isError: true }));
|
|
896
|
+
}
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
if (req.method === 'POST' && req.url === '/run-script') {
|
|
900
|
+
const t0 = Date.now();
|
|
901
|
+
let language = 'javascript';
|
|
902
|
+
try {
|
|
903
|
+
const body = await readBody(req);
|
|
904
|
+
const parsed = JSON.parse(body);
|
|
905
|
+
const script = parsed.script;
|
|
906
|
+
language = parsed.language || 'javascript';
|
|
907
|
+
const preview = String(script).split('\n')[0].slice(0, 60);
|
|
908
|
+
log(`${c.dim}[${httpTs()}] → [${language}]${c.reset} ${preview}${String(script).length > preview.length ? '…' : ''}`);
|
|
909
|
+
const result = runner.runScript
|
|
910
|
+
? await runner.runScript(script, language)
|
|
911
|
+
: await runner.run(script);
|
|
912
|
+
const ms = Date.now() - t0;
|
|
913
|
+
const mark = result.isError ? `${c.red}✗${c.reset}` : `${c.green}✓${c.reset}`;
|
|
914
|
+
log(`${c.dim}[${httpTs()}]${c.reset} ${mark} [${language}] ${c.dim}(${ms}ms)${c.reset}`);
|
|
915
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
916
|
+
res.end(JSON.stringify(result));
|
|
917
|
+
}
|
|
918
|
+
catch (e) {
|
|
919
|
+
log(`${c.dim}[${httpTs()}]${c.reset} ${c.red}✗${c.reset} [${language}] ${c.dim}— ${e.message}${c.reset}`);
|
|
920
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
921
|
+
res.end(JSON.stringify({ text: e.message, isError: true }));
|
|
922
|
+
}
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
res.writeHead(404);
|
|
926
|
+
res.end('Not found');
|
|
927
|
+
});
|
|
928
|
+
server.listen(port, () => {
|
|
929
|
+
log(`${c.green}✓${c.reset} HTTP server on port ${port}`);
|
|
930
|
+
resolve(server);
|
|
931
|
+
});
|
|
932
|
+
server.on('error', reject);
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
function readBody(req) {
|
|
936
|
+
return new Promise((resolve, reject) => {
|
|
937
|
+
let data = '';
|
|
938
|
+
req.on('data', (chunk) => data += chunk);
|
|
939
|
+
req.on('end', () => resolve(data));
|
|
940
|
+
req.on('error', reject);
|
|
941
|
+
});
|
|
942
|
+
}
|
|
943
|
+
/** Block until SIGINT/SIGTERM. Used by --http console mode (no readline loop). */
|
|
944
|
+
function waitForShutdown(log) {
|
|
945
|
+
return new Promise((resolve) => {
|
|
946
|
+
const onSignal = (sig) => {
|
|
947
|
+
log(`${c.dim}Received ${sig}, shutting down...${c.reset}`);
|
|
948
|
+
resolve();
|
|
949
|
+
};
|
|
950
|
+
process.once('SIGINT', () => onSignal('SIGINT'));
|
|
951
|
+
process.once('SIGTERM', () => onSignal('SIGTERM'));
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
/** Try to send --command via HTTP to an already-running --http server. */
|
|
955
|
+
async function tryHttpCommand(command, port) {
|
|
956
|
+
try {
|
|
957
|
+
const result = await new Promise((resolve, reject) => {
|
|
958
|
+
const body = JSON.stringify({ command });
|
|
959
|
+
const req = http.request({ hostname: '127.0.0.1', port, path: '/run', method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) } }, res => {
|
|
960
|
+
let data = '';
|
|
961
|
+
res.on('data', (chunk) => data += chunk);
|
|
962
|
+
res.on('end', () => { try {
|
|
963
|
+
resolve(JSON.parse(data));
|
|
964
|
+
}
|
|
965
|
+
catch {
|
|
966
|
+
reject(new Error('Invalid response'));
|
|
967
|
+
} });
|
|
968
|
+
});
|
|
969
|
+
req.on('error', reject);
|
|
970
|
+
req.write(body);
|
|
971
|
+
req.end();
|
|
972
|
+
});
|
|
973
|
+
const text = (result.text ?? '').replace(/^\s*\n+|\n+\s*$/g, '');
|
|
974
|
+
if (text)
|
|
975
|
+
process.stdout.write(text + '\n');
|
|
976
|
+
process.exit(result.isError ? 1 : 0);
|
|
977
|
+
}
|
|
978
|
+
catch (e) {
|
|
979
|
+
const code = e.code;
|
|
980
|
+
if (code === 'ECONNREFUSED' || code === 'ENOTFOUND')
|
|
981
|
+
return false; // Server not running, fall back
|
|
982
|
+
// Server is running but request errored — surface the real error, don't pretend the server is down
|
|
983
|
+
console.error(`HTTP request failed: ${e.message}`);
|
|
984
|
+
process.exit(1);
|
|
985
|
+
}
|
|
986
|
+
return false; // unreachable
|
|
987
|
+
}
|
|
861
988
|
// ─── REPL ────────────────────────────────────────────────────────────────────
|
|
862
989
|
export async function startRepl(opts = {}) {
|
|
863
990
|
const silent = opts.silent || false;
|
|
864
991
|
const log = (...args) => { if (!silent)
|
|
865
992
|
console.log(...args); };
|
|
993
|
+
// ─── --command --http: send via HTTP to running server ──────────
|
|
994
|
+
if (opts.command && opts.http) {
|
|
995
|
+
const httpPort = opts.httpPort ?? DEFAULT_HTTP_PORT;
|
|
996
|
+
const sent = await tryHttpCommand(opts.command, httpPort);
|
|
997
|
+
if (sent)
|
|
998
|
+
return;
|
|
999
|
+
console.error(`Could not connect to HTTP server on port ${httpPort}. Is playwright-repl --http running?`);
|
|
1000
|
+
process.exit(1);
|
|
1001
|
+
}
|
|
866
1002
|
log(`${c.bold}${c.magenta}🎭 Playwright REPL${c.reset} ${c.dim}v${replVersion}${c.reset}`);
|
|
867
1003
|
// ─── Standalone mode (new: serviceWorker.evaluate) ─────────────
|
|
868
1004
|
if (!opts.bridge && !opts.connect && !opts.engine) {
|
|
@@ -876,6 +1012,28 @@ export async function startRepl(opts = {}) {
|
|
|
876
1012
|
// Default to headed for evaluate mode (interactive REPL with extension)
|
|
877
1013
|
await conn.start(extPath, { headed: opts.headed ?? true, chromium });
|
|
878
1014
|
log(`${c.green}✓${c.reset} Browser ready (with extension)`);
|
|
1015
|
+
if (opts.command) {
|
|
1016
|
+
const result = await conn.run(opts.command);
|
|
1017
|
+
process.stdout.write((result.text ?? '') + '\n');
|
|
1018
|
+
await conn.close();
|
|
1019
|
+
process.exit(result.isError ? 1 : 0);
|
|
1020
|
+
}
|
|
1021
|
+
// Start HTTP server if --http
|
|
1022
|
+
if (opts.http) {
|
|
1023
|
+
try {
|
|
1024
|
+
await startHttpServer(opts.httpPort ?? DEFAULT_HTTP_PORT, conn, log);
|
|
1025
|
+
}
|
|
1026
|
+
catch (e) {
|
|
1027
|
+
log(`${c.yellow}⚠${c.reset} HTTP server failed: ${e.message}`);
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
// --http runs as a console (no readline) unless --interactive is also set
|
|
1031
|
+
if (opts.http && !opts.interactive && !(opts.replay && opts.replay.length > 0)) {
|
|
1032
|
+
log(`${c.dim}Console mode — send commands via HTTP. Ctrl+C to exit.${c.reset}\n`);
|
|
1033
|
+
await waitForShutdown(log);
|
|
1034
|
+
await conn.close();
|
|
1035
|
+
process.exit(0);
|
|
1036
|
+
}
|
|
879
1037
|
log(`${c.dim}Type .help for commands, JavaScript supported${c.reset}\n`);
|
|
880
1038
|
if (opts.replay && opts.replay.length > 0) {
|
|
881
1039
|
await runBridgeReplayMode(opts, conn);
|
|
@@ -895,11 +1053,33 @@ export async function startRepl(opts = {}) {
|
|
|
895
1053
|
if (opts.bridge) {
|
|
896
1054
|
const port = opts.bridgePort ?? 9876;
|
|
897
1055
|
const srv = new BridgeServer();
|
|
898
|
-
await srv.start(port);
|
|
1056
|
+
await srv.start(port, { silent: !!opts.command });
|
|
899
1057
|
log(`Bridge server listening on ws://localhost:${port}`);
|
|
1058
|
+
if (opts.command) {
|
|
1059
|
+
await srv.waitForConnection(30000);
|
|
1060
|
+
const result = await srv.run(opts.command);
|
|
1061
|
+
process.stdout.write((result.text ?? '') + '\n');
|
|
1062
|
+
srv.close();
|
|
1063
|
+
process.exit(result.isError ? 1 : 0);
|
|
1064
|
+
}
|
|
1065
|
+
// Start HTTP server if --http
|
|
1066
|
+
if (opts.http) {
|
|
1067
|
+
try {
|
|
1068
|
+
await startHttpServer(opts.httpPort ?? DEFAULT_HTTP_PORT, srv, log);
|
|
1069
|
+
}
|
|
1070
|
+
catch (e) {
|
|
1071
|
+
log(`${c.yellow}⚠${c.reset} HTTP server failed: ${e.message}`);
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
900
1074
|
if (opts.replay && opts.replay.length > 0) {
|
|
901
1075
|
await runBridgeReplayMode(opts, srv);
|
|
902
1076
|
}
|
|
1077
|
+
else if (opts.http && !opts.interactive) {
|
|
1078
|
+
log(`${c.dim}Console mode — send commands via HTTP. Ctrl+C to exit.${c.reset}\n`);
|
|
1079
|
+
await waitForShutdown(log);
|
|
1080
|
+
srv.close();
|
|
1081
|
+
process.exit(0);
|
|
1082
|
+
}
|
|
903
1083
|
else {
|
|
904
1084
|
log('Waiting for extension to connect...');
|
|
905
1085
|
await startBridgeLoop(opts, srv);
|
|
@@ -917,6 +1097,19 @@ export async function startRepl(opts = {}) {
|
|
|
917
1097
|
console.error(`${c.red}✗${c.reset} Failed to start: ${err.message}`);
|
|
918
1098
|
process.exit(1);
|
|
919
1099
|
}
|
|
1100
|
+
if (opts.command) {
|
|
1101
|
+
const parsed = parseInput(opts.command);
|
|
1102
|
+
if (!parsed) {
|
|
1103
|
+
process.stderr.write('Invalid command\n');
|
|
1104
|
+
conn.close();
|
|
1105
|
+
process.exit(1);
|
|
1106
|
+
return; // unreachable, but satisfies TS control-flow
|
|
1107
|
+
}
|
|
1108
|
+
const result = await conn.run(parsed);
|
|
1109
|
+
process.stdout.write((result.text ?? '') + '\n');
|
|
1110
|
+
conn.close();
|
|
1111
|
+
process.exit(result.isError ? 1 : 0);
|
|
1112
|
+
}
|
|
920
1113
|
// ─── Session + readline ──────────────────────────────────────────
|
|
921
1114
|
const session = new SessionManager();
|
|
922
1115
|
const historyDir = path.join(os.homedir(), '.playwright-repl');
|
|
@@ -949,4 +1142,3 @@ export async function startRepl(opts = {}) {
|
|
|
949
1142
|
startCommandLoop(ctx);
|
|
950
1143
|
}
|
|
951
1144
|
}
|
|
952
|
-
//# sourceMappingURL=repl.js.map
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "playwright-repl",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
4
4
|
"description": "Interactive REPL for Playwright — keyword commands, recording, and replay",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"playwright-repl": "./dist/playwright-repl.js"
|
|
7
|
+
"playwright-repl": "./dist/playwright-repl.js",
|
|
8
|
+
"pw-cli": "./dist/pw-cli.js"
|
|
8
9
|
},
|
|
9
10
|
"main": "./dist/index.js",
|
|
10
11
|
"exports": {
|
|
@@ -35,8 +36,8 @@
|
|
|
35
36
|
"dependencies": {
|
|
36
37
|
"playwright": "^1.59.1",
|
|
37
38
|
"playwright-core": "^1.59.1",
|
|
38
|
-
"@playwright-repl/browser-extension": "0.
|
|
39
|
-
"@playwright-repl/core": "0.
|
|
39
|
+
"@playwright-repl/browser-extension": "0.25.0",
|
|
40
|
+
"@playwright-repl/core": "0.25.0"
|
|
40
41
|
},
|
|
41
42
|
"devDependencies": {
|
|
42
43
|
"vitest": "^4.0.18"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"http-client.d.ts","sourceRoot":"","sources":["../src/http-client.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAI1D,qBAAa,aAAa;IACtB,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,UAAU,CAAS;gBAEf,GAAG,CAAC,EAAE,MAAM;IAIxB,IAAI,SAAS,IAAI,OAAO,CAA4B;IAE9C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA0BtB,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,YAAY,CAAC;IAUjF,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,GAAE,IAAI,GAAG,YAAmB,GAAG,OAAO,CAAC,YAAY,CAAC;IAQtF,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAW5B,SAAS,CAAC,GAAG,EAAE,MAAM,IAAI,GAAG,IAAI;IAChC,YAAY,CAAC,GAAG,EAAE,MAAM,IAAI,GAAG,IAAI;IACnC,OAAO,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,GAAG,IAAI;YAE9C,QAAQ;YAoCR,IAAI;IAIlB,OAAO,CAAC,OAAO;CAwDlB"}
|
package/dist/http-client.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"http-client.js","sourceRoot":"","sources":["../src/http-client.ts"],"names":[],"mappings":"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"}
|