playwriter 0.3.1 → 0.4.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/dist/bippy.js +5 -5
- package/dist/browser-config.d.ts.map +1 -1
- package/dist/browser-config.js +8 -2
- package/dist/browser-config.js.map +1 -1
- package/dist/browser-install.d.ts +16 -0
- package/dist/browser-install.d.ts.map +1 -0
- package/dist/browser-install.js +237 -0
- package/dist/browser-install.js.map +1 -0
- package/dist/cdp-relay.d.ts.map +1 -1
- package/dist/cdp-relay.js +254 -18
- package/dist/cdp-relay.js.map +1 -1
- package/dist/chrome-discovery.d.ts.map +1 -1
- package/dist/chrome-discovery.js +8 -0
- package/dist/chrome-discovery.js.map +1 -1
- package/dist/cli.js +568 -6
- package/dist/cli.js.map +1 -1
- package/dist/cloud-client.d.ts +56 -0
- package/dist/cloud-client.d.ts.map +1 -0
- package/dist/cloud-client.js +120 -0
- package/dist/cloud-client.js.map +1 -0
- package/dist/executor.d.ts +46 -2
- package/dist/executor.d.ts.map +1 -1
- package/dist/executor.js +245 -22
- package/dist/executor.js.map +1 -1
- package/dist/extension/background.js +106 -23
- package/dist/extension/manifest.json +1 -1
- package/dist/playwright-import.d.ts +19 -0
- package/dist/playwright-import.d.ts.map +1 -0
- package/dist/playwright-import.js +39 -0
- package/dist/playwright-import.js.map +1 -0
- package/dist/prompt.md +32 -0
- package/dist/readability.js +1 -1
- package/dist/relay-state.d.ts +1 -0
- package/dist/relay-state.d.ts.map +1 -1
- package/dist/relay-state.js +18 -0
- package/dist/relay-state.js.map +1 -1
- package/dist/relay-state.test.js +22 -0
- package/dist/relay-state.test.js.map +1 -1
- package/dist/selector-generator.js +1 -1
- package/package.json +3 -1
- package/src/browser-config.ts +11 -2
- package/src/browser-install.ts +283 -0
- package/src/cdp-relay.ts +300 -19
- package/src/chrome-discovery.ts +9 -0
- package/src/cli.ts +635 -7
- package/src/cloud-client.ts +172 -0
- package/src/executor.ts +291 -23
- package/src/playwright-import.ts +58 -0
- package/src/relay-state.test.ts +32 -0
- package/src/relay-state.ts +19 -1
- package/src/skill.md +154 -14
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conditional playwright-core import. When patchright mode is enabled via
|
|
3
|
+
* PLAYWRITER_PATCHRIGHT=1 env var, imports from @playwriter/patchright-core
|
|
4
|
+
* instead of @xmorse/playwright-core. Both packages expose identical APIs.
|
|
5
|
+
*
|
|
6
|
+
* Type imports continue to use @xmorse/playwright-core directly (types are
|
|
7
|
+
* the same in both packages) so TypeScript resolution works without the
|
|
8
|
+
* optional patchright dep being installed.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export type {
|
|
12
|
+
Page,
|
|
13
|
+
Frame,
|
|
14
|
+
Browser,
|
|
15
|
+
BrowserContext,
|
|
16
|
+
Locator,
|
|
17
|
+
FrameLocator,
|
|
18
|
+
ElementHandle,
|
|
19
|
+
CDPSession,
|
|
20
|
+
MouseActionEvent,
|
|
21
|
+
} from '@xmorse/playwright-core'
|
|
22
|
+
|
|
23
|
+
export type { BrowserType } from '@xmorse/playwright-core'
|
|
24
|
+
|
|
25
|
+
type Chromium = typeof import('@xmorse/playwright-core').chromium
|
|
26
|
+
|
|
27
|
+
let _chromium: Chromium | undefined
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Returns the chromium BrowserType, loading from patchright-core if enabled.
|
|
31
|
+
* Caches after first call.
|
|
32
|
+
*/
|
|
33
|
+
export async function getChromium(): Promise<Chromium> {
|
|
34
|
+
if (_chromium) {
|
|
35
|
+
return _chromium
|
|
36
|
+
}
|
|
37
|
+
if (isPatchrightEnabled()) {
|
|
38
|
+
try {
|
|
39
|
+
// Dynamic import — @playwriter/patchright-core is an optional dependency.
|
|
40
|
+
// Types come from @xmorse/playwright-core (identical API surface).
|
|
41
|
+
const mod: { chromium: Chromium } = await import('@playwriter/patchright-core' as string)
|
|
42
|
+
_chromium = mod.chromium
|
|
43
|
+
} catch (e: unknown) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
'@playwriter/patchright-core is not installed. Install it with: pnpm add @playwriter/patchright-core',
|
|
46
|
+
{ cause: e },
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
} else {
|
|
50
|
+
const mod = await import('@xmorse/playwright-core')
|
|
51
|
+
_chromium = mod.chromium
|
|
52
|
+
}
|
|
53
|
+
return _chromium!
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function isPatchrightEnabled(): boolean {
|
|
57
|
+
return process.env.PLAYWRITER_PATCHRIGHT === '1' || process.env.PLAYWRITER_PATCHRIGHT === 'true'
|
|
58
|
+
}
|
package/src/relay-state.test.ts
CHANGED
|
@@ -55,6 +55,38 @@ describe('createRelayStore', () => {
|
|
|
55
55
|
})
|
|
56
56
|
})
|
|
57
57
|
|
|
58
|
+
describe('buildStableExtensionKey', () => {
|
|
59
|
+
test('uses install id before account identity so same-account profiles stay separate', () => {
|
|
60
|
+
const profiles = ['profile-a-install', 'profile-b-install'].map((installId) => {
|
|
61
|
+
return relayState.buildStableExtensionKey(
|
|
62
|
+
{
|
|
63
|
+
browser: 'Chrome',
|
|
64
|
+
email: 'tommy@example.com',
|
|
65
|
+
id: 'same-google-account-id',
|
|
66
|
+
installId,
|
|
67
|
+
},
|
|
68
|
+
'connection-fallback',
|
|
69
|
+
)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
expect(profiles).toMatchInlineSnapshot(`
|
|
73
|
+
[
|
|
74
|
+
"install:Chrome:profile-a-install",
|
|
75
|
+
"install:Chrome:profile-b-install",
|
|
76
|
+
]
|
|
77
|
+
`)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test('falls back to account identity for older extensions without install ids', () => {
|
|
81
|
+
const key = relayState.buildStableExtensionKey(
|
|
82
|
+
{ browser: 'Chrome', email: 'tommy@example.com', id: 'google-account-id' },
|
|
83
|
+
'connection-fallback',
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
expect(key).toMatchInlineSnapshot(`"profile:google-account-id"`)
|
|
87
|
+
})
|
|
88
|
+
})
|
|
89
|
+
|
|
58
90
|
// ---------------------------------------------------------------------------
|
|
59
91
|
// addExtension / removeExtension
|
|
60
92
|
// ---------------------------------------------------------------------------
|
package/src/relay-state.ts
CHANGED
|
@@ -94,6 +94,25 @@ export function findExtensionByStableKey(state: RelayState, stableKey: string):
|
|
|
94
94
|
return match
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
export function buildStableExtensionKey(info: ExtensionInfo, connectionId: string): string {
|
|
98
|
+
// chrome.identity ids and emails identify the signed-in Google account, not
|
|
99
|
+
// the Chrome profile. Use the per-profile extension storage install id first
|
|
100
|
+
// so two profiles signed into the same account never replace each other.
|
|
101
|
+
if (info.installId) {
|
|
102
|
+
return `install:${info.browser || 'unknown'}:${info.installId}`
|
|
103
|
+
}
|
|
104
|
+
if (info.id) {
|
|
105
|
+
return `profile:${info.id}`
|
|
106
|
+
}
|
|
107
|
+
if (info.email) {
|
|
108
|
+
return `email:${info.email}`
|
|
109
|
+
}
|
|
110
|
+
if (info.browser) {
|
|
111
|
+
return `browser:${info.browser}`
|
|
112
|
+
}
|
|
113
|
+
return `connection:${connectionId}`
|
|
114
|
+
}
|
|
115
|
+
|
|
97
116
|
/** Find which extension owns a CDP tab sessionId (e.g. "pw-tab-1"). */
|
|
98
117
|
export function findExtensionIdByCdpSession(state: RelayState, cdpSessionId: string): string | null {
|
|
99
118
|
for (const [connectionId, ext] of state.extensions.entries()) {
|
|
@@ -494,4 +513,3 @@ export function updateTargetUrl(
|
|
|
494
513
|
newExtensions.set(extensionId, { ...ext, connectedTargets: newTargets })
|
|
495
514
|
return { ...state, extensions: newExtensions }
|
|
496
515
|
}
|
|
497
|
-
|
package/src/skill.md
CHANGED
|
@@ -44,33 +44,141 @@ Reset a session if the browser connection is stale or broken:
|
|
|
44
44
|
playwriter session reset <sessionId>
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
###
|
|
47
|
+
### Remote access (control browser from another machine)
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
Playwriter can control a Chrome browser running on a different machine over the internet. The host machine runs `playwriter serve` with a [traforo](https://traforo.dev) tunnel, and the remote machine connects through the tunnel URL.
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
```bash
|
|
52
|
+
# Host machine (has Chrome + extension)
|
|
53
|
+
npx -y traforo -p 19988 -- npx -y playwriter serve --token MY_SECRET_TOKEN
|
|
54
|
+
|
|
55
|
+
# Remote machine
|
|
56
|
+
export PLAYWRITER_HOST=https://<tunnel-id>-tunnel.traforo.dev
|
|
57
|
+
export PLAYWRITER_TOKEN=MY_SECRET_TOKEN
|
|
58
|
+
playwriter session new
|
|
59
|
+
playwriter -s 1 -e "await page.goto('https://example.com')"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
For the full guide (Docker, LAN, MCP config, security), see: https://playwriter.dev/docs/remote-access
|
|
63
|
+
|
|
64
|
+
### Direct CDP connection (no extension needed)
|
|
65
|
+
|
|
66
|
+
Playwriter can connect directly to a Chrome instance via the Chrome DevTools Protocol, bypassing the browser extension entirely. This is useful for:
|
|
52
67
|
|
|
53
|
-
|
|
54
|
-
-
|
|
55
|
-
-
|
|
68
|
+
- Chrome running with remote debugging enabled (CI, Docker, headless environments)
|
|
69
|
+
- Cloud browser providers that expose a CDP endpoint (e.g. `wss://xxx.cdp.browser-use.com`)
|
|
70
|
+
- Any service or machine that gives you a `ws://` or `wss://` URL to a Chrome DevTools session
|
|
56
71
|
|
|
57
|
-
|
|
72
|
+
**Prerequisites:** you need a CDP-enabled Chrome. Either:
|
|
73
|
+
|
|
74
|
+
- Open `chrome://inspect/#remote-debugging` in Chrome
|
|
75
|
+
- Launch Chrome with `--remote-debugging-port=9222`
|
|
76
|
+
- Use `playwriter browser start` (enables debugging automatically)
|
|
77
|
+
- Use a cloud browser provider URL (no local Chrome needed)
|
|
78
|
+
|
|
79
|
+
**CLI usage:**
|
|
58
80
|
|
|
59
81
|
```bash
|
|
82
|
+
# Auto-discover local Chrome instances with debugging enabled
|
|
60
83
|
playwriter session new --direct
|
|
84
|
+
|
|
85
|
+
# Connect to a specific CDP endpoint (local or cloud browser provider)
|
|
86
|
+
playwriter session new --direct ws://localhost:9222/devtools/browser/...
|
|
87
|
+
playwriter session new --direct wss://xxx.cdp.browser-use.com
|
|
88
|
+
|
|
89
|
+
# Connect to a remote Chrome instance (host:port auto-resolves to ws://)
|
|
90
|
+
playwriter session new --direct 192.168.1.50:9222
|
|
91
|
+
|
|
92
|
+
# Then use the session normally
|
|
93
|
+
playwriter -s 1 -e "await page.goto('https://example.com')"
|
|
61
94
|
```
|
|
62
95
|
|
|
63
|
-
|
|
96
|
+
**MCP configuration** (for AI assistants): set the `PLAYWRITER_DIRECT` env var in your MCP client config. If the user provides a CDP URL (like `wss://xxx.cdp.browser-use.com`), use it as the value:
|
|
64
97
|
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"mcpServers": {
|
|
101
|
+
"playwriter": {
|
|
102
|
+
"command": "npx",
|
|
103
|
+
"args": ["-y", "playwriter@latest"],
|
|
104
|
+
"env": {
|
|
105
|
+
"PLAYWRITER_DIRECT": "wss://xxx.cdp.browser-use.com"
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
`PLAYWRITER_DIRECT` accepts:
|
|
113
|
+
|
|
114
|
+
- `1` — auto-discover Chrome on port 9222
|
|
115
|
+
- `ws://` or `wss://` URL — explicit WebSocket endpoint (local or cloud browser provider)
|
|
116
|
+
- `host:port` — resolves via HTTP probe to a ws:// URL
|
|
117
|
+
|
|
118
|
+
**Limitations:** screen recording (`recording.start`/`recording.stop`) is not available in direct CDP mode since it relies on the extension's `chrome.tabCapture` API.
|
|
119
|
+
|
|
120
|
+
### Headless browser (no extension, no user browser)
|
|
121
|
+
|
|
122
|
+
Launch a headless Chrome automatically. No extension setup, no user browser involvement. Useful when the user doesn't want their personal browser used, in CI/server environments, or for fully autonomous automation.
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# Install Chrome for Testing (first time only, if no Chrome is available)
|
|
126
|
+
playwriter browser install
|
|
127
|
+
|
|
128
|
+
# Launch headless Chrome and create a session
|
|
129
|
+
playwriter session new --browser headless
|
|
130
|
+
|
|
131
|
+
# Use the session normally
|
|
132
|
+
playwriter -s 1 -e "await page.goto('https://example.com')"
|
|
133
|
+
playwriter -s 1 -e "console.log(await snapshot({ page }))"
|
|
69
134
|
```
|
|
70
135
|
|
|
71
|
-
|
|
136
|
+
Multiple sessions reuse the same headless Chrome process. Recording is not available in headless mode.
|
|
137
|
+
|
|
138
|
+
If no Chrome binary is found, `playwriter session new --browser headless` will tell you to run `playwriter browser install` first to download Chrome for Testing.
|
|
139
|
+
|
|
140
|
+
### Cloud browsers (stealth, proxies, CAPTCHA solving)
|
|
141
|
+
|
|
142
|
+
Cloud browsers are full Chromium instances running in the cloud. They work exactly like a local Chrome session but with stealth and anti-detection built in. No local Chrome or extension needed.
|
|
72
143
|
|
|
73
|
-
**
|
|
144
|
+
**When to use cloud browsers:**
|
|
145
|
+
|
|
146
|
+
- **CAPTCHA bypass.** Cloudflare Turnstile, reCAPTCHA v2/v3, and hCaptcha are solved automatically via token injection. No API keys, no manual solving, no extra code.
|
|
147
|
+
- **Anti-detection.** Stealth Chromium patches remove `navigator.webdriver`, CDP leak fingerprints, and other automation signals. Sites that block Playwright, Puppeteer, or Selenium work normally.
|
|
148
|
+
- **Residential proxies.** Route traffic through residential IPs in 195+ countries with `--proxy <region>`. Proxy is disabled by default to save cost; enable it only when you need anti-detection or geo-targeting.
|
|
149
|
+
- **VPS and headless environments.** Run browser automation from any server without installing Chrome. The cloud browser runs remotely and you connect via CDP.
|
|
150
|
+
- **Parallel execution.** Spin up multiple cloud browsers to run tasks in parallel with subagents. Each browser is an isolated instance with its own IP, fingerprint, and cookie jar.
|
|
151
|
+
- **Multiple identities.** Control separate logged-in accounts on the same site simultaneously. Each cloud browser has independent cookies and storage, so sessions don't interfere with each other.
|
|
152
|
+
|
|
153
|
+
**Authentication:** two options depending on your environment.
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
# Option 1: Interactive login (opens browser for OAuth)
|
|
157
|
+
playwriter cloud login
|
|
158
|
+
|
|
159
|
+
# Option 2: API key (for CI, VPS, headless — no browser needed)
|
|
160
|
+
# Create one at https://playwriter.dev/dashboard, then:
|
|
161
|
+
export PLAYWRITER_API_KEY=pw_xxxxx
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
# Check active cloud sessions
|
|
166
|
+
playwriter cloud status
|
|
167
|
+
|
|
168
|
+
# Start a cloud browser session (no proxy, cheapest)
|
|
169
|
+
playwriter session new --browser cloud
|
|
170
|
+
|
|
171
|
+
# Start with US residential proxy (for anti-detection / geo-targeting)
|
|
172
|
+
playwriter session new --browser cloud --proxy us
|
|
173
|
+
|
|
174
|
+
# Use a different region
|
|
175
|
+
playwriter session new --browser cloud --proxy de
|
|
176
|
+
|
|
177
|
+
# Use a custom proxy
|
|
178
|
+
playwriter session new --browser cloud --custom-proxy user:pass@host:8080
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Cloud sessions auto-stop after 10 minutes of inactivity. When proxy is enabled, raster images are blocked by default to reduce bandwidth costs. Pass `--disable-proxy-bandwidth-acceleration` if you need images to load.
|
|
74
182
|
|
|
75
183
|
### Execute code
|
|
76
184
|
|
|
@@ -197,6 +305,24 @@ start chrome.exe --profile-directory=Default --allowlisted-extension-id=jfeammnj
|
|
|
197
305
|
|
|
198
306
|
You can collaborate with the user - they can help with captchas, difficult elements, or reproducing bugs.
|
|
199
307
|
|
|
308
|
+
**Direct CDP mode (no extension needed):** Playwriter can connect directly to Chrome's DevTools Protocol, bypassing the extension. This is useful in CI, Docker, headless environments, when Chrome has `--remote-debugging-port=9222`, or with cloud browser providers (e.g. `wss://xxx.cdp.browser-use.com`). If the user provides a CDP URL, set `PLAYWRITER_DIRECT` in the MCP client config:
|
|
309
|
+
|
|
310
|
+
```json
|
|
311
|
+
{
|
|
312
|
+
"mcpServers": {
|
|
313
|
+
"playwriter": {
|
|
314
|
+
"command": "npx",
|
|
315
|
+
"args": ["-y", "playwriter@latest"],
|
|
316
|
+
"env": {
|
|
317
|
+
"PLAYWRITER_DIRECT": "wss://xxx.cdp.browser-use.com"
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
`PLAYWRITER_DIRECT` accepts `1` (auto-discover Chrome on port 9222), a `ws://` or `wss://` endpoint (including cloud browser providers), or `host:port`. Screen recording is not available in direct CDP mode since it relies on the extension's `chrome.tabCapture` API.
|
|
325
|
+
|
|
200
326
|
## context variables
|
|
201
327
|
|
|
202
328
|
- `state` - object persisted between calls **within your session**. Each session has its own isolated state. Use to store pages, data, listeners (e.g., `state.page = await context.newPage()`)
|
|
@@ -597,6 +723,20 @@ console.log(cookies)
|
|
|
597
723
|
|
|
598
724
|
MUST use this for page-scoped cookies in extension mode. `Storage.getCookies` is a root-session command and will fail in playwriter.
|
|
599
725
|
|
|
726
|
+
**NEVER use `Network.clearBrowserCookies` or `Network.clearBrowserCache`** — these CDP commands are **profile-wide destructive operations** that wipe ALL cookies/cache across every domain in the user's Chrome profile. They will log the user out of Gmail, GitHub, and every authenticated session.
|
|
727
|
+
|
|
728
|
+
**Clear cookies for a specific domain** — use `Network.getCookies` to fetch cookies scoped to URLs, then delete them individually with `Network.deleteCookies`:
|
|
729
|
+
|
|
730
|
+
```js
|
|
731
|
+
const cdp = await getCDPSession({ page: state.page })
|
|
732
|
+
const { cookies } = await cdp.send('Network.getCookies', {
|
|
733
|
+
urls: ['https://example.com', 'https://www.example.com'],
|
|
734
|
+
})
|
|
735
|
+
for (const cookie of cookies) {
|
|
736
|
+
await cdp.send('Network.deleteCookies', { name: cookie.name, domain: cookie.domain })
|
|
737
|
+
}
|
|
738
|
+
```
|
|
739
|
+
|
|
600
740
|
**Downloading large data** - console output truncates large strings. Trigger a browser download instead:
|
|
601
741
|
|
|
602
742
|
```js
|