💡 摘要
Playwriter 通过扩展和 CLI 实现使用 Playwright API 的浏览器自动化。
🎯 适合人群
🤖 AI 吐槽: “看起来很能打,但别让配置把人劝退。”
风险:High。建议检查:是否执行 shell/命令行指令;是否发起外网请求(SSRF/数据外发);API Key/Token 的获取、存储与泄露风险;文件读写范围与路径穿越风险;依赖锁定与供应链风险。以最小权限运行,并在生产环境启用前审计代码与依赖。
Installation
-
Install Extension from Chrome Web Store
-
Click extension icon on a tab → turns green when connected
-
Install the CLI and start automating the browser:
npm i -g playwriter playwriter -s 1 -e "await page.goto('https://example.com')" -
Add skill to your agent:
npx -y skills add remorses/playwriter
Quick Start
playwriter session new # creates stateful sandbox, outputs session id (e.g. 1) playwriter -s 1 -e "await page.goto('https://example.com')" playwriter -s 1 -e "console.log(await accessibilitySnapshot({ page }))" playwriter -s 1 -e "await page.locator('aria-ref=e5').click()"
CLI Usage
Each session has isolated state. Browser tabs are shared across sessions.
# Session management playwriter session new # creates stateful sandbox, outputs id (e.g. 1) playwriter session list # show sessions + state keys playwriter session reset <id> # fix connection issues # Execute (always use -s) playwriter -s 1 -e "await page.goto('https://example.com')" playwriter -s 1 -e "await page.click('button')" playwriter -s 1 -e "console.log(await page.title())"
Create your own page to avoid interference from other agents:
playwriter -s 1 -e "state.myPage = await context.newPage(); await state.myPage.goto('https://example.com')"
Multiline:
playwriter -s 1 -e $' const title = await page.title(); console.log({ title, url: page.url() }); '
Examples
Variables in scope: page, context, state (persists between calls), require, and Node.js globals.
Persist data in state:
playwriter -e "state.users = await page.$$eval('.user', els => els.map(e => e.textContent))" playwriter -e "console.log(state.users)"
Intercept network requests:
playwriter -e "state.requests = []; page.on('response', r => { if (r.url().includes('/api/')) state.requests.push(r.url()) })" playwriter -e "await Promise.all([page.waitForResponse(r => r.url().includes('/api/')), page.click('button')])" playwriter -e "console.log(state.requests)"
Set breakpoints and debug:
playwriter -e "state.cdp = await getCDPSession({ page }); state.dbg = createDebugger({ cdp: state.cdp }); await state.dbg.enable()" playwriter -e "state.scripts = await state.dbg.listScripts({ search: 'app' }); console.log(state.scripts.map(s => s.url))" playwriter -e "await state.dbg.setBreakpoint({ file: state.scripts[0].url, line: 42 })"
Live edit page code:
playwriter -e "state.cdp = await getCDPSession({ page }); state.editor = createEditor({ cdp: state.cdp }); await state.editor.enable()" playwriter -e "await state.editor.edit({ url: 'https://example.com/app.js', oldString: 'const DEBUG = false', newString: 'const DEBUG = true' })"
Screenshot with labels:
playwriter -e "await screenshotWithAccessibilityLabels({ page })"
MCP Setup
Using the CLI with the skill (step 4 above) is the recommended approach. For direct MCP server configuration, see MCP.md.
Visual Labels
Vimium-style labels for AI agents to identify elements:
await screenshotWithAccessibilityLabels({ page }) // Returns screenshot + accessibility snapshot with aria-ref selectors await page.locator('aria-ref=e5').click()
Color-coded: yellow=links, orange=buttons, coral=inputs, pink=checkboxes, peach=sliders, salmon=menus, amber=tabs.
Comparison
vs Playwright MCP
| | Playwright MCP | Playwriter | |---|---|---| | Browser | Spawns new Chrome | Uses your Chrome | | Extensions | None | Your existing ones | | Login state | Fresh | Already logged in | | Bot detection | Always detected | Can bypass (disconnect extension) | | Collaboration | Separate window | Same browser as user |
vs BrowserMCP
| | BrowserMCP | Playwriter |
|---|---|---|
| Tools | 12+ dedicated tools | 1 execute tool |
| API | Limited actions | Full Playwright |
| Context usage | High (tool schemas) | Low |
| LLM knowledge | Must learn tools | Already knows Playwright |
vs Antigravity (Jetski)
| | Jetski | Playwriter | |---|---|---| | Tools | 17+ tools | 1 tool | | Subagent | Spawns for each browser task | Direct execution | | Latency | High (agent overhead) | Low |
vs Claude Browser Extension
| | Claude Extension | Playwriter | |---|---|---| | Agent support | Claude only | Any MCP client | | Windows WSL | No | Yes | | Context method | Screenshots (100KB+) | A11y snapshots (5-20KB) | | Playwright API | No | Full | | Debugger/breakpoints | No | Yes | | Live code editing | No | Yes | | Network interception | Limited | Full | | Raw CDP access | No | Yes |
Architecture
+---------------------+ +-------------------+ +-----------------+
| BROWSER | | LOCALHOST | | MCP CLIENT |
| | | | | |
| +---------------+ | | WebSocket Server | | +-----------+ |
| | Extension |<---------> :19988 | | | AI Agent | |
| +-------+-------+ | WS | | | +-----------+ |
| | | | /extension | | | |
| chrome.debugger | | | | | v |
| v | | v | | +-----------+ |
| +---------------+ | | /cdp/:id <--------------> | execute | |
| | Tab 1 (green) | | +-------------------+ WS | +-----------+ |
| | Tab 2 (green) | | | | |
| | Tab 3 (gray) | | Tab 3 not controlled | Playwright API |
+---------------------+ (no extension click) +-----------------+
Remote CLI
Run CLI from a different machine (devcontainer, VM, SSH) while Chrome runs on your host.
On host:
playwriter serve --token <secret>
From remote:
playwriter --host 192.168.1.10 --token <secret> session new playwriter --host 192.168.1.10 --token <secret> -s 1 -e "await page.goto('https://example.com')"
Or with env vars:
export PLAYWRITER_HOST=192.168.1.10 export PLAYWRITER_TOKEN=<secret> playwriter -s 1 -e "await page.goto('https://example.com')"
Security
- Local only: WebSocket server on
localhost:19988 - Origin validation: Only our extension IDs allowed (browsers can't spoof Origin)
- Explicit consent: Only tabs where you clicked the extension icon
- Visible automation: Chrome shows automation banner on controlled tabs
- No remote access: Malicious websites cannot connect
Playwright API
Connect programmatically (without CLI):
import { chromium } from 'playwright-core' import { startPlayWriterCDPRelayServer, getCdpUrl } from 'playwriter' const server = await startPlayWriterCDPRelayServer() const browser = await chromium.connectOverCDP(getCdpUrl()) const page = browser.contexts()[0].pages()[0] await page.goto('https://example.com') await page.screenshot({ path: 'screenshot.png' }) // Don't call browser.close() - it closes the user's Chrome server.close()
Or connect to a running server:
npx -y playwriter serve --host 127.0.0.1
const browser = await chromium.connectOverCDP('http://127.0.0.1:19988')
Troubleshooting
View relay server logs to debug issues:
playwriter logfile # prints the log file path # typically: /tmp/playwriter/relay-server.log (Linux/macOS)
The relay log contains extension, MCP and WebSocket server logs. A separate CDP JSONL log is also created alongside it (see playwriter logfile). Both are recreated on each server start.
Example: summarize CDP traffic counts by direction + method:
jq -r '.direction + "\t" + (.message.method // "response")' /tmp/playwriter/cdp.jsonl | uniq -c
Known Issues
- If all pages return
about:blank, restart Chrome (Chrome bug inchrome.debuggerAPI) - Browser may switch to light mode on connect (Playwright issue)
优点
- 与 Playwright 无缝集成
- 支持复杂的浏览器自动化任务
- 安全的隔离会话管理
- 灵活的 CLI 命令
缺点
- 需要安装浏览器扩展
- 新用户可能有学习曲线
- 仅限于本地浏览器自动化
- 浏览器状态管理可能存在问题
相关技能
免责声明:本内容来源于 GitHub 开源项目,仅供展示和评分分析使用。
版权归原作者所有 remorses.
