This commit is contained in:
Tommy D. Rossi
2026-01-06 12:32:32 +01:00
parent 9c1eadb536
commit d7056292cd
+43 -52
View File
@@ -41,9 +41,7 @@
"mcpServers": { "mcpServers": {
"playwriter": { "playwriter": {
"command": "npx", "command": "npx",
"args": [ "args": ["playwriter@latest"]
"playwriter@latest"
]
} }
} }
} }
@@ -62,6 +60,7 @@
3. Click the extension icon - it will turn green when connected 3. Click the extension icon - it will turn green when connected
Once enabled on one or more tabs, your AI assistant can: Once enabled on one or more tabs, your AI assistant can:
- Control all enabled tabs through the `execute` tool - Control all enabled tabs through the `execute` tool
- Switch between tabs using playwright's context and page APIs - Switch between tabs using playwright's context and page APIs
- Create new tabs programmatically - Create new tabs programmatically
@@ -77,10 +76,8 @@ You can use playwriter programmatically with playwright-core:
import { chromium } from 'playwright-core' import { chromium } from 'playwright-core'
import { startPlayWriterCDPRelayServer, getCdpUrl } from 'playwriter' import { startPlayWriterCDPRelayServer, getCdpUrl } from 'playwriter'
const server = await startPlayWriterCDPRelayServer() const server = await startPlayWriterCDPRelayServer()
const browser = await chromium.connectOverCDP(getCdpUrl()) const browser = await chromium.connectOverCDP(getCdpUrl())
const context = browser.contexts()[0] const context = browser.contexts()[0]
@@ -116,6 +113,7 @@ await screenshotWithAccessibilityLabels({ page })
The function automatically shows labels, takes a screenshot, hides labels, and includes both the image and accessibility snapshot in the MCP response. Screenshots are saved to `./tmp/` with unique filenames. The function automatically shows labels, takes a screenshot, hides labels, and includes both the image and accessibility snapshot in the MCP response. Screenshots are saved to `./tmp/` with unique filenames.
**Features:** **Features:**
- **Role filtering** - Only shows labels for interactive elements (buttons, links, inputs, etc.) - **Role filtering** - Only shows labels for interactive elements (buttons, links, inputs, etc.)
- **Visibility detection** - Skips elements covered by modals or overlays - **Visibility detection** - Skips elements covered by modals or overlays
- **Overlap prevention** - Skips labels that would overlap with already-placed ones - **Overlap prevention** - Skips labels that would overlap with already-placed ones
@@ -124,15 +122,15 @@ The function automatically shows labels, takes a screenshot, hides labels, and i
**Color legend:** **Color legend:**
| Color | Element Types | | Color | Element Types |
|-------|---------------| | ------ | ------------------------------------------ |
| Yellow | Links | | Yellow | Links |
| Orange | Buttons | | Orange | Buttons |
| Coral | Text inputs (textbox, combobox, searchbox) | | Coral | Text inputs (textbox, combobox, searchbox) |
| Pink | Checkboxes, radios, switches | | Pink | Checkboxes, radios, switches |
| Peach | Sliders | | Peach | Sliders |
| Salmon | Menu items | | Salmon | Menu items |
| Amber | Tabs, options | | Amber | Tabs, options |
### Environment Variables ### Environment Variables
@@ -229,10 +227,10 @@ Claude Code has a built-in Chrome extension ("Claude in Chrome") that uses Nativ
Claude's extension sends screenshots to the LLM for visual understanding, then uses DOM inspection for interactions. Playwriter uses accessibility snapshots instead - structured text describing interactive elements with their roles, names, and states: Claude's extension sends screenshots to the LLM for visual understanding, then uses DOM inspection for interactions. Playwriter uses accessibility snapshots instead - structured text describing interactive elements with their roles, names, and states:
| Approach | What's sent to LLM | Typical size | | Approach | What's sent to LLM | Typical size |
|----------|-------------------|--------------| | ------------------ | ----------------------- | -------------------- |
| Claude's extension | Screenshots | 100KB-1MB+ per image | | Claude's extension | Screenshots | 100KB-1MB+ per image |
| Playwriter | Accessibility snapshots | 5-20KB text | | Playwriter | Accessibility snapshots | 5-20KB text |
**Full Playwright API vs Step-by-Step Navigation:** **Full Playwright API vs Step-by-Step Navigation:**
@@ -240,12 +238,9 @@ Claude's extension navigates pages step-by-step with screenshots. Playwriter exp
```js ```js
// Complex interactions in one call // Complex interactions in one call
await page.locator('tr').filter({ hasText: 'John' }).locator('button').click(); await page.locator('tr').filter({ hasText: 'John' }).locator('button').click()
const [download] = await Promise.all([ const [download] = await Promise.all([page.waitForEvent('download'), page.click('button.export')])
page.waitForEvent('download'), await download.saveAs('/tmp/report.pdf')
page.click('button.export')
]);
await download.saveAs('/tmp/report.pdf');
``` ```
**Advanced DevTools Features:** **Advanced DevTools Features:**
@@ -254,49 +249,49 @@ Playwriter provides capabilities Claude's extension doesn't have:
```js ```js
// Set breakpoints and inspect variables at runtime // Set breakpoints and inspect variables at runtime
const dbg = createDebugger({ cdp }); const dbg = createDebugger({ cdp })
await dbg.setBreakpoint({ file: 'app.js', line: 42 }); await dbg.setBreakpoint({ file: 'app.js', line: 42 })
// when paused: await dbg.inspectLocalVariables() // when paused: await dbg.inspectLocalVariables()
// Live-edit page scripts without reload // Live-edit page scripts without reload
const editor = createEditor({ cdp }); const editor = createEditor({ cdp })
await editor.edit({ url: 'app.js', oldString: 'DEBUG=false', newString: 'DEBUG=true' }); await editor.edit({ url: 'app.js', oldString: 'DEBUG=false', newString: 'DEBUG=true' })
// Inspect CSS like DevTools Styles panel // Inspect CSS like DevTools Styles panel
const styles = await getStylesForLocator({ locator: page.locator('.btn'), cdp }); const styles = await getStylesForLocator({ locator: page.locator('.btn'), cdp })
// Intercept network requests for API reverse-engineering // Intercept network requests for API reverse-engineering
page.on('response', async res => { page.on('response', async (res) => {
if (res.url().includes('/api/')) state.responses.push(await res.json()); if (res.url().includes('/api/')) state.responses.push(await res.json())
}); })
// Find React component source locations // Find React component source locations
const source = await getReactSource({ locator: page.locator('aria-ref=e5') }); const source = await getReactSource({ locator: page.locator('aria-ref=e5') })
// => { fileName: 'Button.tsx', lineNumber: 42 } // => { fileName: 'Button.tsx', lineNumber: 42 }
``` ```
**User Collaboration Features:** **User Collaboration Features:**
- **Right-click → "Copy Playwriter Element Reference"** - Pin any element and reference it as `globalThis.playwriterPinnedElem1` in your automation code - **Right-click → "Copy Playwriter Element Reference"** - Pin any element and reference it as `globalThis.playwriterPinnedElem1` in your automation code
- **Vimium-style visual labels** - `screenshotWithAccessibilityLabels()` captures screenshots with clickable labels on all interactive elements - **Vimium-style visual labels** - `screenshotWithAccessibilityLabels()` captures screenshots with clickable labels on all interactive elements
- **Tab group organization** - Connected tabs are grouped together with a green "playwriter" label - **Tab group organization** - Connected tabs are grouped together with a green "playwriter" label
- **Bypass automation detection** - Disconnect the extension temporarily to pass bot detection (e.g., Google login), then reconnect - **Bypass automation detection** - Disconnect the extension temporarily to pass bot detection (e.g., Google login), then reconnect
**Summary:** **Summary:**
| Feature | Claude's Extension | Playwriter | | Feature | Claude's Extension | Playwriter |
|---------|-------------------|------------| | ----------------------- | ------------------ | ---------------------------- |
| Windows WSL | No | Yes | | Windows WSL | No | Yes |
| Works with any agent | No (Claude only) | Yes (any MCP client) | | Works with any agent | No (Claude only) | Yes (any MCP client) |
| Context method | Screenshots | A11y snapshots (90% smaller) | | Context method | Screenshots | A11y snapshots (90% smaller) |
| Full Playwright API | No | Yes | | Full Playwright API | No | Yes |
| Debugger (breakpoints) | No | Yes | | Debugger (breakpoints) | No | Yes |
| Live code editing | No | Yes | | Live code editing | No | Yes |
| CSS inspection | No | Yes | | CSS inspection | No | Yes |
| Network interception | Limited | Full | | Network interception | Limited | Full |
| React source finding | No | Yes | | React source finding | No | Yes |
| Right-click pin element | No | Yes | | Right-click pin element | No | Yes |
| Raw CDP access | No | Yes | | Raw CDP access | No | Yes |
## Architecture ## Architecture
@@ -346,11 +341,7 @@ Configure your MCP client with the host and token. You can pass them as CLI argu
"mcpServers": { "mcpServers": {
"playwriter": { "playwriter": {
"command": "npx", "command": "npx",
"args": [ "args": ["playwriter@latest", "--host", "host.docker.internal", "--token", "<secret>"]
"playwriter@latest",
"--host", "host.docker.internal",
"--token", "<secret>"
]
} }
} }
} }