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.
Files changed (51) hide show
  1. package/dist/bippy.js +5 -5
  2. package/dist/browser-config.d.ts.map +1 -1
  3. package/dist/browser-config.js +8 -2
  4. package/dist/browser-config.js.map +1 -1
  5. package/dist/browser-install.d.ts +16 -0
  6. package/dist/browser-install.d.ts.map +1 -0
  7. package/dist/browser-install.js +237 -0
  8. package/dist/browser-install.js.map +1 -0
  9. package/dist/cdp-relay.d.ts.map +1 -1
  10. package/dist/cdp-relay.js +254 -18
  11. package/dist/cdp-relay.js.map +1 -1
  12. package/dist/chrome-discovery.d.ts.map +1 -1
  13. package/dist/chrome-discovery.js +8 -0
  14. package/dist/chrome-discovery.js.map +1 -1
  15. package/dist/cli.js +568 -6
  16. package/dist/cli.js.map +1 -1
  17. package/dist/cloud-client.d.ts +56 -0
  18. package/dist/cloud-client.d.ts.map +1 -0
  19. package/dist/cloud-client.js +120 -0
  20. package/dist/cloud-client.js.map +1 -0
  21. package/dist/executor.d.ts +46 -2
  22. package/dist/executor.d.ts.map +1 -1
  23. package/dist/executor.js +245 -22
  24. package/dist/executor.js.map +1 -1
  25. package/dist/extension/background.js +106 -23
  26. package/dist/extension/manifest.json +1 -1
  27. package/dist/playwright-import.d.ts +19 -0
  28. package/dist/playwright-import.d.ts.map +1 -0
  29. package/dist/playwright-import.js +39 -0
  30. package/dist/playwright-import.js.map +1 -0
  31. package/dist/prompt.md +32 -0
  32. package/dist/readability.js +1 -1
  33. package/dist/relay-state.d.ts +1 -0
  34. package/dist/relay-state.d.ts.map +1 -1
  35. package/dist/relay-state.js +18 -0
  36. package/dist/relay-state.js.map +1 -1
  37. package/dist/relay-state.test.js +22 -0
  38. package/dist/relay-state.test.js.map +1 -1
  39. package/dist/selector-generator.js +1 -1
  40. package/package.json +3 -1
  41. package/src/browser-config.ts +11 -2
  42. package/src/browser-install.ts +283 -0
  43. package/src/cdp-relay.ts +300 -19
  44. package/src/chrome-discovery.ts +9 -0
  45. package/src/cli.ts +635 -7
  46. package/src/cloud-client.ts +172 -0
  47. package/src/executor.ts +291 -23
  48. package/src/playwright-import.ts +58 -0
  49. package/src/relay-state.test.ts +32 -0
  50. package/src/relay-state.ts +19 -1
  51. 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
+ }
@@ -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
  // ---------------------------------------------------------------------------
@@ -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
- ### Direct CDP connection (--direct)
47
+ ### Remote access (control browser from another machine)
48
48
 
49
- Only use `--direct` when the user explicitly asks for it. This mode requires the user to accept a debugging approval dialog in Chrome, so it cannot be used autonomously.
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
- `--direct` connects to Chrome's DevTools Protocol without the extension. Unlike extension mode, it gives access to **all existing pages** in the browser — no need to enable per tab. Works with any Chromium browser (Chrome, Brave, Arc, Edge, etc.).
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
- The user must first enable debugging in Chrome:
54
- - Open `chrome://inspect/#remote-debugging` in Chrome, or
55
- - Launch Chrome with `chrome --remote-debugging-port=9222`
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
- Then create a session:
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
- By default, `context` is bound to the first Chrome profile. If the user has multiple profiles open, use `browser.contexts()` to access other profiles' pages and cookies:
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
- ```js
66
- const contexts = browser.contexts()
67
- // contexts[0] = first profile, contexts[1] = second profile, etc.
68
- const otherProfilePage = contexts[1].pages()[0]
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
- `browser.contexts()` only makes sense when using `--direct`. in extension mode there is just one context for each session.
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
- **Limitations:** screen recording (`recording.start/stop`) is unavailable in direct mode.
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