@playwright-repl/mcp 0.18.1 → 0.20.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 +66 -24
- package/dist/bridge.d.ts +12 -0
- package/dist/bridge.d.ts.map +1 -0
- package/dist/bridge.js +92 -0
- package/dist/bridge.js.map +1 -0
- package/dist/index.js +59 -126
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +15 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +69 -0
- package/dist/logger.js.map +1 -0
- package/dist/standalone.d.ts +12 -0
- package/dist/standalone.d.ts.map +1 -0
- package/dist/standalone.js +87 -0
- package/dist/standalone.js.map +1 -0
- package/dist/types.d.ts +23 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# @playwright-repl/mcp
|
|
2
2
|
|
|
3
|
-
MCP server that lets AI agents (Claude Desktop, Claude Code, or any MCP client)
|
|
3
|
+
MCP server that lets AI agents (Claude Desktop, Claude Code, or any MCP client) automate a browser using playwright-repl keyword commands.
|
|
4
|
+
|
|
5
|
+
Two modes:
|
|
6
|
+
- **Bridge mode** (default) — controls your real Chrome browser through the **Dramaturg** Chrome extension
|
|
7
|
+
- **Standalone mode** (`--standalone`) — launches its own browser via Playwright, no extension needed
|
|
4
8
|
|
|
5
9
|
## Why
|
|
6
10
|
|
|
@@ -31,6 +35,8 @@ It consists of two parts working together: **Dramaturg** (a Chrome extension tha
|
|
|
31
35
|
|
|
32
36
|
## Architecture
|
|
33
37
|
|
|
38
|
+
### Bridge mode (default)
|
|
39
|
+
|
|
34
40
|
```
|
|
35
41
|
Claude Desktop / Claude Code (or any MCP client)
|
|
36
42
|
↕ MCP (stdio)
|
|
@@ -41,6 +47,18 @@ Chrome extension (offscreen document → service worker)
|
|
|
41
47
|
Playwright running in your real Chrome session
|
|
42
48
|
```
|
|
43
49
|
|
|
50
|
+
### Standalone mode (`--standalone`)
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
Claude Desktop / Claude Code (or any MCP client)
|
|
54
|
+
↕ MCP (stdio)
|
|
55
|
+
playwright-repl MCP server
|
|
56
|
+
↕ Engine.run() (in-process)
|
|
57
|
+
Playwright BrowserServerBackend
|
|
58
|
+
↕ CDP
|
|
59
|
+
Browser (launched by Playwright)
|
|
60
|
+
```
|
|
61
|
+
|
|
44
62
|
## Setup
|
|
45
63
|
|
|
46
64
|
### 1. Install the MCP server
|
|
@@ -49,12 +67,23 @@ Playwright running in your real Chrome session
|
|
|
49
67
|
npm install -g @playwright-repl/mcp
|
|
50
68
|
```
|
|
51
69
|
|
|
52
|
-
### 2.
|
|
70
|
+
### 2. Choose a mode
|
|
71
|
+
|
|
72
|
+
#### Bridge mode (default) — use your real Chrome session
|
|
73
|
+
|
|
74
|
+
Install Dramaturg (Chrome extension):
|
|
53
75
|
|
|
54
76
|
Load `packages/extension/dist/` as an unpacked extension in Chrome (`chrome://extensions` → Enable Developer mode → Load unpacked).
|
|
55
77
|
|
|
56
78
|
Or install from the [Chrome Web Store](https://chromewebstore.google.com/detail/dramaturg/ppbkmncnmjkfppilnmplpokdfagobipa).
|
|
57
79
|
|
|
80
|
+
#### Standalone mode — no extension needed
|
|
81
|
+
|
|
82
|
+
Add `--standalone` to the MCP server command. The server launches its own browser via Playwright.
|
|
83
|
+
|
|
84
|
+
- Default: headless. Add `--headed` to show the browser window.
|
|
85
|
+
- Only `.pw` keyword commands are supported (no raw Playwright API or JavaScript expressions). Use `run-code` or `eval` keywords within `.pw` mode for Playwright API / JS.
|
|
86
|
+
|
|
58
87
|
### 3. Configure your MCP client
|
|
59
88
|
|
|
60
89
|
**Claude Desktop** — add to `claude_desktop_config.json`:
|
|
@@ -62,6 +91,8 @@ Or install from the [Chrome Web Store](https://chromewebstore.google.com/detail/
|
|
|
62
91
|
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
63
92
|
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
64
93
|
|
|
94
|
+
Bridge mode:
|
|
95
|
+
|
|
65
96
|
```json
|
|
66
97
|
{
|
|
67
98
|
"mcpServers": {
|
|
@@ -72,17 +103,36 @@ Or install from the [Chrome Web Store](https://chromewebstore.google.com/detail/
|
|
|
72
103
|
}
|
|
73
104
|
```
|
|
74
105
|
|
|
106
|
+
Standalone mode:
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"mcpServers": {
|
|
111
|
+
"playwright-repl": {
|
|
112
|
+
"command": "playwright-repl-mcp",
|
|
113
|
+
"args": ["--standalone", "--headed"]
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
75
119
|
Restart Claude Desktop after saving.
|
|
76
120
|
|
|
77
121
|
**Claude Code** — run once in a terminal:
|
|
78
122
|
|
|
79
123
|
```bash
|
|
124
|
+
# Bridge mode
|
|
80
125
|
claude mcp add playwright-repl playwright-repl-mcp
|
|
126
|
+
|
|
127
|
+
# Standalone mode
|
|
128
|
+
claude mcp add playwright-repl playwright-repl-mcp -- --standalone --headed
|
|
81
129
|
```
|
|
82
130
|
|
|
83
131
|
### 4. Connect
|
|
84
132
|
|
|
85
|
-
The extension connects to the MCP server automatically — no need to open the side panel. Just make sure Chrome is running with the Dramaturg extension installed.
|
|
133
|
+
**Bridge mode:** The extension connects to the MCP server automatically — no need to open the side panel. Just make sure Chrome is running with the Dramaturg extension installed.
|
|
134
|
+
|
|
135
|
+
**Standalone mode:** The browser launches automatically on the first command. No extension needed.
|
|
86
136
|
|
|
87
137
|
## Dramaturg — The Extension
|
|
88
138
|
|
|
@@ -113,10 +163,10 @@ When the extension is connected, the MCP server logs `Extension connected` to st
|
|
|
113
163
|
|
|
114
164
|
## Tool: `run_command`
|
|
115
165
|
|
|
116
|
-
One tool, two input modes:
|
|
117
|
-
|
|
118
166
|
### Keyword commands (`.pw` syntax)
|
|
119
167
|
|
|
168
|
+
Both modes support `.pw` keyword commands:
|
|
169
|
+
|
|
120
170
|
```
|
|
121
171
|
snapshot # accessibility tree — always start here
|
|
122
172
|
goto https://example.com # navigate
|
|
@@ -125,13 +175,14 @@ fill "Email" user@example.com # fill a form field
|
|
|
125
175
|
press Enter # key press
|
|
126
176
|
verify-text Welcome # assert text is visible
|
|
127
177
|
screenshot # capture page (returned as image to AI)
|
|
128
|
-
scroll-down # scroll
|
|
129
178
|
check "Remember me" # check a checkbox
|
|
130
179
|
select "Country" "United States" # select dropdown option
|
|
131
180
|
localstorage-list # list localStorage
|
|
132
181
|
```
|
|
133
182
|
|
|
134
|
-
### Playwright API / JavaScript
|
|
183
|
+
### Playwright API / JavaScript (bridge mode only)
|
|
184
|
+
|
|
185
|
+
In bridge mode, `run_command` also accepts raw Playwright expressions and JavaScript:
|
|
135
186
|
|
|
136
187
|
```
|
|
137
188
|
await page.url()
|
|
@@ -142,13 +193,16 @@ await page.evaluate(() => document.title)
|
|
|
142
193
|
await page.evaluate(() => document.querySelectorAll('a').length)
|
|
143
194
|
```
|
|
144
195
|
|
|
196
|
+
> In standalone mode, use the `run-code` or `eval` keywords to run Playwright API / JavaScript:
|
|
197
|
+
> `run-code await page.url()` or `eval document.title`.
|
|
198
|
+
|
|
145
199
|
## Tool: `run_script`
|
|
146
200
|
|
|
147
|
-
Batch execution for multi-line scripts.
|
|
201
|
+
Batch execution for multi-line scripts.
|
|
148
202
|
|
|
149
203
|
### Keyword script (`language="pw"`)
|
|
150
204
|
|
|
151
|
-
Splits by line, runs each command sequentially, returns ✓/✗ per line
|
|
205
|
+
Splits by line, runs each command sequentially, returns ✓/✗ per line. Lines starting with `#` are skipped. Stops on first error.
|
|
152
206
|
|
|
153
207
|
```
|
|
154
208
|
goto https://demo.playwright.dev/todomvc/
|
|
@@ -157,7 +211,9 @@ press Enter
|
|
|
157
211
|
verify-text "Buy groceries"
|
|
158
212
|
```
|
|
159
213
|
|
|
160
|
-
|
|
214
|
+
> In standalone mode, `run_script` only accepts `.pw` keyword scripts (no `language` parameter needed).
|
|
215
|
+
|
|
216
|
+
### JavaScript (`language="javascript"`) — bridge mode only
|
|
161
217
|
|
|
162
218
|
Runs the entire block as one evaluation — use for Playwright API with assertions:
|
|
163
219
|
|
|
@@ -168,20 +224,6 @@ await page.keyboard.press('Enter');
|
|
|
168
224
|
await expect(page.getByText('Buy groceries')).toBeVisible();
|
|
169
225
|
```
|
|
170
226
|
|
|
171
|
-
## Prompt: `generate-test`
|
|
172
|
-
|
|
173
|
-
A prompt template that guides the AI to generate a passing Playwright test from a described scenario.
|
|
174
|
-
|
|
175
|
-
**Args:**
|
|
176
|
-
- `steps` (required) — describe the test scenario, e.g. "log in with email/password, verify the dashboard loads". Also accepts pasted `.pw` scripts.
|
|
177
|
-
- `url` (optional) — URL to navigate to first.
|
|
178
|
-
|
|
179
|
-
**In Claude Desktop:** click "+" → select `generate-test` → fill in the form → send.
|
|
180
|
-
|
|
181
|
-
**In Claude Code:** `/mcp__playwright-repl__generate-test`
|
|
182
|
-
|
|
183
|
-
The AI navigates the page, takes snapshots, writes Playwright assertions, runs them via `run_script(language="javascript")`, and iterates until all pass.
|
|
184
|
-
|
|
185
227
|
## AI Agents
|
|
186
228
|
|
|
187
229
|
The MCP package includes four ready-to-use AI agents in `packages/mcp/agents/`:
|
package/dist/bridge.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridge runner — connects to Chrome via the Dramaturg extension over WebSocket.
|
|
3
|
+
*/
|
|
4
|
+
import type { RunnerModule, SnapshotCache } from './types.js';
|
|
5
|
+
export declare const descriptions: {
|
|
6
|
+
readonly runCommandInput: "A keyword command ('snapshot', 'goto https://example.com', 'click Submit', 'fill \"Email\" user@example.com') or a Playwright expression ('await page.url()')";
|
|
7
|
+
readonly runCommand: "Run a command in the connected Chrome browser. Supports two input modes:\n\n1. KEYWORD (.pw) — playwright-repl commands:\n snapshot, goto <url>, click <text>, fill <label> <value>, press <key>,\n verify-text <text>, verify-no-text <text>, screenshot,\n check <label>, select <label> <value>, localstorage-list, localstorage-clear\n\n2. PLAYWRIGHT — Playwright API (page.* / crxApp.*):\n await page.url(), await page.title(),\n await page.locator('button').count(),\n await page.evaluate(() => document.title)\n\nUpdate commands (click, fill, goto, press, hover, select, check, uncheck, etc.) automatically include a snapshot of the page after the action. You do NOT need to call snapshot separately after these commands.\n\nUse snapshot only for initial exploration or after read-only commands. Use screenshot to visually verify the current state.\n\nIMPORTANT: Before writing .pw commands, run 'help' to get the full list of available commands. Only use commands that appear in the help output. Do not invent commands.";
|
|
8
|
+
readonly runScript: "Run a multi-line script, returning combined pass/fail results.\nUseful for replaying a known script without per-step round trips.\nPrefer run_command for AI-driven exploration where you need to observe and adapt after each step.\n\nlanguage='pw': each line is a .pw keyword command, run sequentially. Lines starting with # are skipped. Stops on first error.\nlanguage='javascript': the entire script is run as a single JavaScript/Playwright block.\n\nIMPORTANT: Only use commands listed by 'help'. Run run_command('help') first if unsure which commands are available.";
|
|
9
|
+
readonly scriptOnly: false;
|
|
10
|
+
};
|
|
11
|
+
export declare function createBridgeRunner(argv: string[], snapshotCache: SnapshotCache): Promise<RunnerModule>;
|
|
12
|
+
//# sourceMappingURL=bridge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAG9D,eAAO,MAAM,YAAY;;;;;CAgCf,CAAC;AAEX,wBAAsB,kBAAkB,CACpC,IAAI,EAAE,MAAM,EAAE,EACd,aAAa,EAAE,aAAa,GAC7B,OAAO,CAAC,YAAY,CAAC,CA0DvB"}
|
package/dist/bridge.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridge runner — connects to Chrome via the Dramaturg extension over WebSocket.
|
|
3
|
+
*/
|
|
4
|
+
import { BridgeServer, UPDATE_COMMANDS, parseInput } from '@playwright-repl/core';
|
|
5
|
+
import { logEvent } from './logger.js';
|
|
6
|
+
export const descriptions = {
|
|
7
|
+
runCommandInput: `A keyword command ('snapshot', 'goto https://example.com', 'click Submit', \
|
|
8
|
+
'fill "Email" user@example.com') or a Playwright expression ('await page.url()')`,
|
|
9
|
+
runCommand: `Run a command in the connected Chrome browser. Supports two input modes:
|
|
10
|
+
|
|
11
|
+
1. KEYWORD (.pw) — playwright-repl commands:
|
|
12
|
+
snapshot, goto <url>, click <text>, fill <label> <value>, press <key>,
|
|
13
|
+
verify-text <text>, verify-no-text <text>, screenshot,
|
|
14
|
+
check <label>, select <label> <value>, localstorage-list, localstorage-clear
|
|
15
|
+
|
|
16
|
+
2. PLAYWRIGHT — Playwright API (page.* / crxApp.*):
|
|
17
|
+
await page.url(), await page.title(),
|
|
18
|
+
await page.locator('button').count(),
|
|
19
|
+
await page.evaluate(() => document.title)
|
|
20
|
+
|
|
21
|
+
Update commands (click, fill, goto, press, hover, select, check, uncheck, etc.) automatically include a snapshot of the page after the action. You do NOT need to call snapshot separately after these commands.
|
|
22
|
+
|
|
23
|
+
Use snapshot only for initial exploration or after read-only commands. Use screenshot to visually verify the current state.
|
|
24
|
+
|
|
25
|
+
IMPORTANT: Before writing .pw commands, run 'help' to get the full list of available commands. Only use commands that appear in the help output. Do not invent commands.`,
|
|
26
|
+
runScript: `Run a multi-line script, returning combined pass/fail results.
|
|
27
|
+
Useful for replaying a known script without per-step round trips.
|
|
28
|
+
Prefer run_command for AI-driven exploration where you need to observe and adapt after each step.
|
|
29
|
+
|
|
30
|
+
language='pw': each line is a .pw keyword command, run sequentially. Lines starting with # are skipped. Stops on first error.
|
|
31
|
+
language='javascript': the entire script is run as a single JavaScript/Playwright block.
|
|
32
|
+
|
|
33
|
+
IMPORTANT: Only use commands listed by 'help'. Run run_command('help') first if unsure which commands are available.`,
|
|
34
|
+
scriptOnly: false,
|
|
35
|
+
};
|
|
36
|
+
export async function createBridgeRunner(argv, snapshotCache) {
|
|
37
|
+
const portIdx = argv.indexOf('--port');
|
|
38
|
+
const port = portIdx !== -1
|
|
39
|
+
? parseInt(argv[portIdx + 1])
|
|
40
|
+
: (process.env.BRIDGE_PORT ? parseInt(process.env.BRIDGE_PORT) : 9876);
|
|
41
|
+
const srv = new BridgeServer();
|
|
42
|
+
try {
|
|
43
|
+
await srv.start(port);
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
if (err?.code === 'EADDRINUSE') {
|
|
47
|
+
console.error(`Error: port ${port} is already in use. Another playwright-repl bridge or MCP inspector may be running. Stop it and restart Claude Desktop.`);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
throw err;
|
|
51
|
+
}
|
|
52
|
+
console.error(`playwright-repl bridge listening on ws://localhost:${port}`);
|
|
53
|
+
logEvent(`Bridge listening on ws://localhost:${port}`);
|
|
54
|
+
srv.onConnect(() => { console.error('Extension connected'); logEvent('Extension connected'); });
|
|
55
|
+
srv.onDisconnect(() => { console.error('Extension disconnected'); logEvent('Extension disconnected'); });
|
|
56
|
+
return {
|
|
57
|
+
descriptions,
|
|
58
|
+
runner: {
|
|
59
|
+
async runCommand(command) {
|
|
60
|
+
if (!srv.connected) {
|
|
61
|
+
return { text: 'Browser not connected. Open Chrome with the playwright-repl extension — it connects automatically.', isError: true };
|
|
62
|
+
}
|
|
63
|
+
// Determine command name for snapshot logic
|
|
64
|
+
const parsed = parseInput(command);
|
|
65
|
+
const cmdName = parsed?._[0];
|
|
66
|
+
const isUpdate = cmdName !== undefined && UPDATE_COMMANDS.has(cmdName);
|
|
67
|
+
// Request snapshot in the same round-trip for update commands
|
|
68
|
+
const result = await srv.run(command, isUpdate ? { includeSnapshot: true } : undefined);
|
|
69
|
+
if (result.isError)
|
|
70
|
+
return result;
|
|
71
|
+
// Cache snapshot (from explicit snapshot command or appended by handler)
|
|
72
|
+
if (result.text) {
|
|
73
|
+
const snapMatch = result.text.match(/### Snapshot\n([\s\S]+)$/);
|
|
74
|
+
if (snapMatch) {
|
|
75
|
+
snapshotCache.value = { url: '', snapshotString: snapMatch[1].trim() };
|
|
76
|
+
}
|
|
77
|
+
else if (cmdName === 'snapshot') {
|
|
78
|
+
snapshotCache.value = { url: '', snapshotString: result.text.trim() };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return result;
|
|
82
|
+
},
|
|
83
|
+
async runScript(script, language) {
|
|
84
|
+
if (!srv.connected) {
|
|
85
|
+
return { text: 'Browser not connected. Open Chrome with the playwright-repl extension — it connects automatically.', isError: true };
|
|
86
|
+
}
|
|
87
|
+
return srv.runScript(script, language);
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge.js","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -2,71 +2,44 @@
|
|
|
2
2
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
3
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
4
|
import { z } from 'zod';
|
|
5
|
-
import {
|
|
5
|
+
import { COMMANDS, CATEGORIES, refToLocator } from '@playwright-repl/core';
|
|
6
6
|
import pkg from '../package.json' with { type: 'json' };
|
|
7
|
+
import { createBridgeRunner } from './bridge.js';
|
|
8
|
+
import { createStandaloneRunner } from './standalone.js';
|
|
9
|
+
import { logStartup, logEvent, logToolCall, logToolResult, logError, LOG_FILE } from './logger.js';
|
|
7
10
|
const argv = process.argv.slice(2);
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
catch (err) {
|
|
18
|
-
if (err?.code === 'EADDRINUSE') {
|
|
19
|
-
console.error(`Error: port ${port} is already in use. Another playwright-repl bridge or MCP inspector may be running. Stop it and restart Claude Desktop.`);
|
|
20
|
-
process.exit(1);
|
|
21
|
-
}
|
|
22
|
-
throw err;
|
|
23
|
-
}
|
|
24
|
-
console.error(`playwright-repl bridge listening on ws://localhost:${port}`);
|
|
25
|
-
srv.onConnect(() => console.error('Extension connected'));
|
|
26
|
-
srv.onDisconnect(() => console.error('Extension disconnected'));
|
|
27
|
-
const RUN_COMMAND_INPUT_DESCRIPTION = `\
|
|
28
|
-
A keyword command ('snapshot', 'goto https://example.com', 'click Submit', \
|
|
29
|
-
'fill "Email" user@example.com'), a Playwright expression \
|
|
30
|
-
('await page.url()'), or a JavaScript expression ('document.title')`;
|
|
31
|
-
const RUN_COMMAND_DESCRIPTION = `\
|
|
32
|
-
Run a command in the connected Chrome browser. Supports three input modes:
|
|
33
|
-
|
|
34
|
-
1. KEYWORD (.pw) — playwright-repl commands:
|
|
35
|
-
snapshot, goto <url>, click <text>, fill <label> <value>, press <key>,
|
|
36
|
-
verify-text <text>, verify-no-text <text>, screenshot, scroll-down,
|
|
37
|
-
check <label>, select <label> <value>, localstorage-list, localstorage-clear
|
|
38
|
-
|
|
39
|
-
2. PLAYWRIGHT — Playwright API (page.* / crxApp.*):
|
|
40
|
-
await page.url(), await page.title(),
|
|
41
|
-
await page.locator('button').count()
|
|
42
|
-
|
|
43
|
-
3. JAVASCRIPT — any JS expression evaluated in the browser:
|
|
44
|
-
document.title, window.location.href,
|
|
45
|
-
document.querySelectorAll('a').length
|
|
46
|
-
|
|
47
|
-
Use snapshot to understand the page structure before interacting. Use screenshot to visually verify the current state.
|
|
48
|
-
|
|
49
|
-
IMPORTANT: Before writing .pw commands, run 'help' to get the full list of available commands. Only use commands that appear in the help output. Do not invent commands.`;
|
|
11
|
+
const standalone = argv.includes('--standalone');
|
|
12
|
+
const headed = argv.includes('--headed');
|
|
13
|
+
const snapshotCache = { value: null };
|
|
14
|
+
// ─── Create runner ───────────────────────────────────────────────────────────
|
|
15
|
+
const { runner, descriptions } = standalone
|
|
16
|
+
? createStandaloneRunner(headed, snapshotCache)
|
|
17
|
+
: await createBridgeRunner(argv, snapshotCache);
|
|
18
|
+
logStartup(standalone ? 'standalone' : 'bridge', `log → ${LOG_FILE}`);
|
|
19
|
+
// ─── MCP server ──────────────────────────────────────────────────────────────
|
|
50
20
|
const server = new McpServer({ name: 'playwright-repl', version: pkg.version });
|
|
51
21
|
server.registerTool('run_command', {
|
|
52
|
-
description:
|
|
22
|
+
description: descriptions.runCommand,
|
|
53
23
|
inputSchema: {
|
|
54
|
-
command: z.string().describe(
|
|
24
|
+
command: z.string().describe(descriptions.runCommandInput),
|
|
55
25
|
},
|
|
56
26
|
}, async ({ command }) => {
|
|
27
|
+
const start = Date.now();
|
|
28
|
+
logToolCall('run_command', { command });
|
|
57
29
|
const trimmed = command.trim().toLowerCase();
|
|
58
30
|
if (trimmed === 'help') {
|
|
59
31
|
const lines = Object.entries(CATEGORIES)
|
|
60
32
|
.map(([cat, cmds]) => ` ${cat}: ${cmds.join(', ')}`)
|
|
61
33
|
.join('\n');
|
|
34
|
+
logToolResult('run_command', false, 'help', Date.now() - start);
|
|
62
35
|
return { content: [{ type: 'text', text: `Available commands:\n${lines}\n\nType "help <command>" for details.` }] };
|
|
63
36
|
}
|
|
64
37
|
if (trimmed.startsWith('locator ')) {
|
|
65
38
|
const ref = command.trim().slice(8).trim();
|
|
66
|
-
if (!
|
|
39
|
+
if (!snapshotCache.value) {
|
|
67
40
|
return { content: [{ type: 'text', text: 'No snapshot cached. Run "snapshot" first.' }], isError: true };
|
|
68
41
|
}
|
|
69
|
-
const locator = refToLocator(
|
|
42
|
+
const locator = refToLocator(snapshotCache.value.snapshotString, ref);
|
|
70
43
|
if (!locator) {
|
|
71
44
|
return { content: [{ type: 'text', text: `Ref "${ref}" not found in last snapshot. Run "snapshot" to refresh.` }], isError: true };
|
|
72
45
|
}
|
|
@@ -88,95 +61,55 @@ server.registerTool('run_command', {
|
|
|
88
61
|
}
|
|
89
62
|
return { content: [{ type: 'text', text: parts.join('\n') }] };
|
|
90
63
|
}
|
|
91
|
-
|
|
64
|
+
try {
|
|
65
|
+
const result = await runner.runCommand(command);
|
|
66
|
+
logToolResult('run_command', !!result.isError, result.text, Date.now() - start);
|
|
67
|
+
if (result.image) {
|
|
68
|
+
const [header, data] = result.image.split(',');
|
|
69
|
+
const mimeType = (header.match(/data:(.*);base64/) ?? [])[1] ?? 'image/png';
|
|
70
|
+
return { content: [{ type: 'image', data, mimeType }] };
|
|
71
|
+
}
|
|
92
72
|
return {
|
|
93
|
-
content: [{ type: 'text', text:
|
|
94
|
-
isError:
|
|
73
|
+
content: [{ type: 'text', text: result.text || 'Done' }],
|
|
74
|
+
isError: result.isError,
|
|
95
75
|
};
|
|
96
76
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if (trimmed.startsWith('snapshot') && result.text && !result.isError) {
|
|
101
|
-
lastSnapshot = { url: '', snapshotString: result.text.trim() };
|
|
77
|
+
catch (err) {
|
|
78
|
+
logError('run_command', err);
|
|
79
|
+
throw err;
|
|
102
80
|
}
|
|
103
|
-
if (result.image) {
|
|
104
|
-
const [header, data] = result.image.split(',');
|
|
105
|
-
const mimeType = (header.match(/data:(.*);base64/) ?? [])[1] ?? 'image/png';
|
|
106
|
-
return { content: [{ type: 'image', data, mimeType }] };
|
|
107
|
-
}
|
|
108
|
-
return {
|
|
109
|
-
content: [{ type: 'text', text: result.text || 'Done' }],
|
|
110
|
-
isError: result.isError,
|
|
111
|
-
};
|
|
112
81
|
});
|
|
113
|
-
const RUN_SCRIPT_DESCRIPTION = `\
|
|
114
|
-
Run a multi-line script, returning combined pass/fail results.
|
|
115
|
-
Useful for replaying a known script without per-step round trips.
|
|
116
|
-
Prefer run_command for AI-driven exploration where you need to observe and adapt after each step.
|
|
117
|
-
|
|
118
|
-
language='pw': each line is a .pw keyword command, run sequentially. Lines starting with # are skipped. Stops on first error.
|
|
119
|
-
language='javascript': the entire script is run as a single JavaScript/Playwright block.
|
|
120
|
-
|
|
121
|
-
IMPORTANT: Only use commands listed by 'help'. Run run_command('help') first if unsure which commands are available.`;
|
|
122
82
|
server.registerTool('run_script', {
|
|
123
|
-
description:
|
|
124
|
-
inputSchema:
|
|
125
|
-
script: z.string().describe('The script to execute')
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
83
|
+
description: descriptions.runScript,
|
|
84
|
+
inputSchema: descriptions.scriptOnly
|
|
85
|
+
? { script: z.string().describe('The .pw keyword script to execute (one command per line)') }
|
|
86
|
+
: {
|
|
87
|
+
script: z.string().describe('The script to execute'),
|
|
88
|
+
language: z.enum(['pw', 'javascript']).describe("'pw' for keyword commands (one per line), 'javascript' for a JS/Playwright block"),
|
|
89
|
+
},
|
|
90
|
+
}, async (params) => {
|
|
91
|
+
const start = Date.now();
|
|
92
|
+
const script = params.script;
|
|
93
|
+
const language = params.language || 'pw';
|
|
94
|
+
logToolCall('run_script', { language, script });
|
|
95
|
+
try {
|
|
96
|
+
const result = await runner.runScript(script, language);
|
|
97
|
+
logToolResult('run_script', !!result.isError, result.text, Date.now() - start);
|
|
130
98
|
return {
|
|
131
|
-
content: [{ type: 'text', text:
|
|
132
|
-
isError:
|
|
99
|
+
content: [{ type: 'text', text: result.text || 'Done' }],
|
|
100
|
+
isError: result.isError,
|
|
133
101
|
};
|
|
134
102
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
};
|
|
103
|
+
catch (err) {
|
|
104
|
+
logError('run_script', err);
|
|
105
|
+
throw err;
|
|
106
|
+
}
|
|
140
107
|
});
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
0. Run run_command('help') to see all available keyword commands. Only use commands from this list — do not invent commands.
|
|
147
|
-
1. ${url ? `Navigate to ${url} using run_command('goto ${url}').` : 'Navigate to the target URL using run_command.'}
|
|
148
|
-
2. Take a snapshot using run_command('snapshot') to understand the page structure.
|
|
149
|
-
3. Interact with the page as needed (click, fill, press) using run_command.
|
|
150
|
-
4. After each interaction, take another snapshot to verify the state before asserting.
|
|
151
|
-
5. Write assertions using \`expect\`.
|
|
152
|
-
|
|
153
|
-
Example pattern:
|
|
154
|
-
await page.goto('https://example.com');
|
|
155
|
-
await expect(page).toHaveTitle('Example', { exact: true });
|
|
156
|
-
await expect(page.getByRole('heading', { name: 'Welcome' })).toBeVisible();
|
|
157
|
-
await page.getByRole('link', { name: 'Get started', exact: true }).click();
|
|
158
|
-
await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible();
|
|
159
|
-
|
|
160
|
-
Code constraints:
|
|
161
|
-
- Use only \`page\` and \`expect\` — available as globals, do NOT import them
|
|
162
|
-
- Plain \`await\` statements only — no \`import\`, no \`test()\` wrapper, no \`describe()\`
|
|
163
|
-
- Use \`exact: true\` when a locator text might match multiple elements
|
|
164
|
-
|
|
165
|
-
Once you have the code, run it with run_script(language="javascript").
|
|
166
|
-
If any assertion fails, read the error, fix the code, and run again until all pass.
|
|
167
|
-
Show the final passing code.`;
|
|
168
|
-
server.registerPrompt('generate-test', {
|
|
169
|
-
description: 'Generate a passing Playwright test from a described scenario',
|
|
170
|
-
argsSchema: {
|
|
171
|
-
steps: z.string().describe('Describe the test scenario, e.g. "log in with email/password, verify the dashboard loads"'),
|
|
172
|
-
url: z.string().optional().describe('URL to navigate to first (optional)'),
|
|
173
|
-
},
|
|
174
|
-
}, ({ steps, url }) => ({
|
|
175
|
-
messages: [{
|
|
176
|
-
role: 'user',
|
|
177
|
-
content: { type: 'text', text: GENERATE_TEST_PROMPT(steps, url) },
|
|
178
|
-
}],
|
|
179
|
-
}));
|
|
108
|
+
server.server.oninitialized = () => {
|
|
109
|
+
const client = server.server.getClientVersion();
|
|
110
|
+
if (client)
|
|
111
|
+
logEvent(`Client: ${client.name} ${client.version}`);
|
|
112
|
+
};
|
|
180
113
|
const transport = new StdioServerTransport();
|
|
181
114
|
await server.connect(transport);
|
|
182
115
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File-based logger for the MCP server.
|
|
3
|
+
*
|
|
4
|
+
* Writes to ~/.playwright-repl/mcp.log so tool calls are visible
|
|
5
|
+
* regardless of whether the host (Claude Desktop, Claude Code, etc.)
|
|
6
|
+
* captures stderr.
|
|
7
|
+
*/
|
|
8
|
+
declare const LOG_FILE: string;
|
|
9
|
+
export declare function logStartup(mode: string, detail: string): void;
|
|
10
|
+
export declare function logEvent(event: string): void;
|
|
11
|
+
export declare function logToolCall(tool: string, input: Record<string, unknown>): void;
|
|
12
|
+
export declare function logToolResult(tool: string, isError: boolean, text: string | undefined, durationMs: number): void;
|
|
13
|
+
export declare function logError(context: string, err: unknown): void;
|
|
14
|
+
export { LOG_FILE };
|
|
15
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAOH,QAAA,MAAM,QAAQ,QAA2B,CAAC;AAoC1C,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAE7D;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAE5C;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAQ9E;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAIhH;AAED,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,GAAG,IAAI,CAG5D;AAED,OAAO,EAAE,QAAQ,EAAE,CAAC"}
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File-based logger for the MCP server.
|
|
3
|
+
*
|
|
4
|
+
* Writes to ~/.playwright-repl/mcp.log so tool calls are visible
|
|
5
|
+
* regardless of whether the host (Claude Desktop, Claude Code, etc.)
|
|
6
|
+
* captures stderr.
|
|
7
|
+
*/
|
|
8
|
+
import { mkdirSync, appendFileSync } from 'node:fs';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { homedir } from 'node:os';
|
|
11
|
+
const LOG_DIR = join(homedir(), '.playwright-repl');
|
|
12
|
+
const LOG_FILE = join(LOG_DIR, 'mcp.log');
|
|
13
|
+
/** Maximum characters kept from a result text in the log. */
|
|
14
|
+
const MAX_RESULT_LENGTH = 200;
|
|
15
|
+
let enabled = true;
|
|
16
|
+
try {
|
|
17
|
+
mkdirSync(LOG_DIR, { recursive: true });
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
enabled = false;
|
|
21
|
+
}
|
|
22
|
+
function ts() {
|
|
23
|
+
const d = new Date();
|
|
24
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
25
|
+
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
|
26
|
+
}
|
|
27
|
+
function write(level, message) {
|
|
28
|
+
if (!enabled)
|
|
29
|
+
return;
|
|
30
|
+
try {
|
|
31
|
+
appendFileSync(LOG_FILE, `${ts()} [${level}] ${message}\n`);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
// Silently ignore — logging should never break the server.
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** Truncate a string for log readability. */
|
|
38
|
+
function truncate(text, max = MAX_RESULT_LENGTH) {
|
|
39
|
+
if (text.length <= max)
|
|
40
|
+
return text;
|
|
41
|
+
return text.slice(0, max) + `... (${text.length} chars)`;
|
|
42
|
+
}
|
|
43
|
+
// ─── Public API ──────────────────────────────────────────────────────────────
|
|
44
|
+
export function logStartup(mode, detail) {
|
|
45
|
+
write('info', `Server started [${mode}] ${detail}`);
|
|
46
|
+
}
|
|
47
|
+
export function logEvent(event) {
|
|
48
|
+
write('info', event);
|
|
49
|
+
}
|
|
50
|
+
export function logToolCall(tool, input) {
|
|
51
|
+
const args = Object.entries(input)
|
|
52
|
+
.map(([k, v]) => {
|
|
53
|
+
const s = typeof v === 'string' ? v : JSON.stringify(v);
|
|
54
|
+
return `${k}=${truncate(s, 120)}`;
|
|
55
|
+
})
|
|
56
|
+
.join(' ');
|
|
57
|
+
write('info', `→ ${tool}(${args})`);
|
|
58
|
+
}
|
|
59
|
+
export function logToolResult(tool, isError, text, durationMs) {
|
|
60
|
+
const status = isError ? 'ERROR' : 'OK';
|
|
61
|
+
const summary = text ? truncate(text.replace(/\n/g, '\\n')) : '(empty)';
|
|
62
|
+
write('info', `← ${tool} [${status}] ${durationMs}ms ${summary}`);
|
|
63
|
+
}
|
|
64
|
+
export function logError(context, err) {
|
|
65
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
66
|
+
write('error', `${context}: ${msg}`);
|
|
67
|
+
}
|
|
68
|
+
export { LOG_FILE };
|
|
69
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standalone runner — launches a browser via Engine (in-process Playwright).
|
|
3
|
+
*/
|
|
4
|
+
import type { RunnerModule, SnapshotCache } from './types.js';
|
|
5
|
+
export declare const descriptions: {
|
|
6
|
+
readonly runCommandInput: "A keyword command ('snapshot', 'goto https://example.com', 'click Submit', 'fill \"Email\" user@example.com')";
|
|
7
|
+
readonly runCommand: "Run a command in the browser. Supports KEYWORD (.pw) — playwright-repl commands:\n snapshot, goto <url>, click <text>, fill <label> <value>, press <key>,\n verify-text <text>, verify-no-text <text>, screenshot,\n check <label>, select <label> <value>, localstorage-list, localstorage-clear\n\nUpdate commands (click, fill, goto, press, hover, select, check, uncheck, etc.) automatically include a snapshot of the page after the action. You do NOT need to call snapshot separately after these commands.\n\nUse snapshot only for initial exploration or after read-only commands. Use screenshot to visually verify the current state.\n\nIMPORTANT: Before writing .pw commands, run 'help' to get the full list of available commands. Only use commands that appear in the help output. Do not invent commands.";
|
|
8
|
+
readonly runScript: "Run a multi-line .pw keyword script, returning combined pass/fail results.\nEach line is a .pw keyword command, run sequentially. Lines starting with # are skipped. Stops on first error.\nUseful for replaying a known script without per-step round trips.\n\nIMPORTANT: Only use commands listed by 'help'. Run run_command('help') first if unsure which commands are available.";
|
|
9
|
+
readonly scriptOnly: true;
|
|
10
|
+
};
|
|
11
|
+
export declare function createStandaloneRunner(headed: boolean, snapshotCache: SnapshotCache): RunnerModule;
|
|
12
|
+
//# sourceMappingURL=standalone.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"standalone.d.ts","sourceRoot":"","sources":["../src/standalone.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAI9D,eAAO,MAAM,YAAY;;;;;CAsBf,CAAC;AAEX,wBAAgB,sBAAsB,CAClC,MAAM,EAAE,OAAO,EACf,aAAa,EAAE,aAAa,GAC7B,YAAY,CA8Dd"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standalone runner — launches a browser via Engine (in-process Playwright).
|
|
3
|
+
*/
|
|
4
|
+
import { Engine, parseInput, resolveArgs, filterResponse } from '@playwright-repl/core';
|
|
5
|
+
const INCLUDE_SNAPSHOT = { includeSnapshot: true };
|
|
6
|
+
export const descriptions = {
|
|
7
|
+
runCommandInput: `A keyword command ('snapshot', 'goto https://example.com', 'click Submit', \
|
|
8
|
+
'fill "Email" user@example.com')`,
|
|
9
|
+
runCommand: `Run a command in the browser. Supports KEYWORD (.pw) — playwright-repl commands:
|
|
10
|
+
snapshot, goto <url>, click <text>, fill <label> <value>, press <key>,
|
|
11
|
+
verify-text <text>, verify-no-text <text>, screenshot,
|
|
12
|
+
check <label>, select <label> <value>, localstorage-list, localstorage-clear
|
|
13
|
+
|
|
14
|
+
Update commands (click, fill, goto, press, hover, select, check, uncheck, etc.) automatically include a snapshot of the page after the action. You do NOT need to call snapshot separately after these commands.
|
|
15
|
+
|
|
16
|
+
Use snapshot only for initial exploration or after read-only commands. Use screenshot to visually verify the current state.
|
|
17
|
+
|
|
18
|
+
IMPORTANT: Before writing .pw commands, run 'help' to get the full list of available commands. Only use commands that appear in the help output. Do not invent commands.`,
|
|
19
|
+
runScript: `Run a multi-line .pw keyword script, returning combined pass/fail results.
|
|
20
|
+
Each line is a .pw keyword command, run sequentially. Lines starting with # are skipped. Stops on first error.
|
|
21
|
+
Useful for replaying a known script without per-step round trips.
|
|
22
|
+
|
|
23
|
+
IMPORTANT: Only use commands listed by 'help'. Run run_command('help') first if unsure which commands are available.`,
|
|
24
|
+
scriptOnly: true,
|
|
25
|
+
};
|
|
26
|
+
export function createStandaloneRunner(headed, snapshotCache) {
|
|
27
|
+
let engine = null;
|
|
28
|
+
let starting = null;
|
|
29
|
+
function ensureEngine() {
|
|
30
|
+
if (engine)
|
|
31
|
+
return Promise.resolve(engine);
|
|
32
|
+
if (!starting) {
|
|
33
|
+
const e = new Engine();
|
|
34
|
+
starting = e.start({ headed }).then(() => {
|
|
35
|
+
engine = e;
|
|
36
|
+
console.error(`playwright-repl standalone engine started (${headed ? 'headed' : 'headless'})`);
|
|
37
|
+
return e;
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return starting;
|
|
41
|
+
}
|
|
42
|
+
async function runSingleCommand(command) {
|
|
43
|
+
const e = await ensureEngine();
|
|
44
|
+
const args = parseInput(command);
|
|
45
|
+
if (!args)
|
|
46
|
+
return { text: `Unknown command: ${command}`, isError: true };
|
|
47
|
+
const cmdName = args._[0];
|
|
48
|
+
const resolved = resolveArgs(args);
|
|
49
|
+
const result = await e.run(resolved);
|
|
50
|
+
// Cache snapshot for locator command — strip YAML code fences
|
|
51
|
+
if (result.text && !result.isError) {
|
|
52
|
+
const snapshotMatch = result.text.match(/### Snapshot\n([\s\S]*?)(?=\n### |$)/);
|
|
53
|
+
if (snapshotMatch) {
|
|
54
|
+
const raw = snapshotMatch[1].trim();
|
|
55
|
+
const yamlBody = raw.replace(/^```(?:yaml)?\n?/, '').replace(/\n?```$/, '');
|
|
56
|
+
snapshotCache.value = { url: '', snapshotString: yamlBody };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Filter verbose Playwright response sections — keep snapshots for MCP
|
|
60
|
+
if (result.text)
|
|
61
|
+
result.text = filterResponse(result.text, cmdName, INCLUDE_SNAPSHOT);
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
descriptions,
|
|
66
|
+
runner: {
|
|
67
|
+
runCommand: runSingleCommand,
|
|
68
|
+
async runScript(script, language) {
|
|
69
|
+
if (language === 'javascript') {
|
|
70
|
+
return { text: 'JavaScript mode is not supported in standalone mode. Use language="pw" with run-code or eval keywords.', isError: true };
|
|
71
|
+
}
|
|
72
|
+
const lines = script.split('\n').filter(l => l.trim() && !l.trim().startsWith('#'));
|
|
73
|
+
const results = [];
|
|
74
|
+
for (const line of lines) {
|
|
75
|
+
const result = await runSingleCommand(line.trim());
|
|
76
|
+
const status = result.isError ? '✗' : '✓';
|
|
77
|
+
results.push(`${status} ${line.trim()}${result.isError && result.text ? ` — ${result.text}` : ''}`);
|
|
78
|
+
if (result.isError) {
|
|
79
|
+
return { text: results.join('\n'), isError: true };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return { text: results.join('\n'), isError: false };
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=standalone.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"standalone.js","sourceRoot":"","sources":["../src/standalone.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { EngineResult } from '@playwright-repl/core';
|
|
2
|
+
export interface Runner {
|
|
3
|
+
runCommand(command: string): Promise<EngineResult>;
|
|
4
|
+
runScript(script: string, language: 'pw' | 'javascript'): Promise<EngineResult>;
|
|
5
|
+
}
|
|
6
|
+
export interface RunnerDescriptions {
|
|
7
|
+
runCommandInput: string;
|
|
8
|
+
runCommand: string;
|
|
9
|
+
runScript: string;
|
|
10
|
+
/** When true, run_script only accepts 'script' param (no 'language'). */
|
|
11
|
+
scriptOnly: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface RunnerModule {
|
|
14
|
+
runner: Runner;
|
|
15
|
+
descriptions: RunnerDescriptions;
|
|
16
|
+
}
|
|
17
|
+
export interface SnapshotCache {
|
|
18
|
+
value: {
|
|
19
|
+
url: string;
|
|
20
|
+
snapshotString: string;
|
|
21
|
+
} | null;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAE1D,MAAM,WAAW,MAAM;IACnB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IACnD,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,GAAG,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;CACnF;AAED,MAAM,WAAW,kBAAkB;IAC/B,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,yEAAyE;IACzE,UAAU,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,kBAAkB,CAAC;CACpC;AAED,MAAM,WAAW,aAAa;IAC1B,KAAK,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CACzD"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@playwright-repl/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"playwright-repl-mcp": "./dist/index.js"
|
|
@@ -19,5 +19,9 @@
|
|
|
19
19
|
"@modelcontextprotocol/sdk": "^1.10.0",
|
|
20
20
|
"@playwright-repl/core": "workspace:*",
|
|
21
21
|
"zod": "^3.24.0"
|
|
22
|
+
},
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"playwright": "1.59.0-alpha-2026-02-21",
|
|
25
|
+
"playwright-core": "1.59.0-alpha-2026-02-21"
|
|
22
26
|
}
|
|
23
27
|
}
|