publicbrowser 1.0.0__tar.gz
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.
- publicbrowser-1.0.0/.gitignore +82 -0
- publicbrowser-1.0.0/LICENSE +21 -0
- publicbrowser-1.0.0/PKG-INFO +231 -0
- publicbrowser-1.0.0/README.md +196 -0
- publicbrowser-1.0.0/publicbrowser/__init__.py +24 -0
- publicbrowser-1.0.0/publicbrowser/cdp.py +435 -0
- publicbrowser-1.0.0/publicbrowser/chrome.py +156 -0
- publicbrowser-1.0.0/publicbrowser/client.py +378 -0
- publicbrowser-1.0.0/publicbrowser/escape_hatch.py +139 -0
- publicbrowser-1.0.0/publicbrowser/page.py +327 -0
- publicbrowser-1.0.0/publicbrowser/py.typed +0 -0
- publicbrowser-1.0.0/pyproject.toml +77 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Dependencies
|
|
2
|
+
node_modules/
|
|
3
|
+
|
|
4
|
+
# Build output
|
|
5
|
+
build/
|
|
6
|
+
dist/
|
|
7
|
+
*.tsbuildinfo
|
|
8
|
+
|
|
9
|
+
# OS files
|
|
10
|
+
.DS_Store
|
|
11
|
+
Thumbs.db
|
|
12
|
+
|
|
13
|
+
# Editors / IDEs
|
|
14
|
+
.vscode/
|
|
15
|
+
.idea/
|
|
16
|
+
*.swp
|
|
17
|
+
*.swo
|
|
18
|
+
|
|
19
|
+
# Logs
|
|
20
|
+
*.log
|
|
21
|
+
npm-debug.log*
|
|
22
|
+
yarn-debug.log*
|
|
23
|
+
yarn-error.log*
|
|
24
|
+
|
|
25
|
+
# Environment / secrets
|
|
26
|
+
.env
|
|
27
|
+
.env.local
|
|
28
|
+
.env.*.local
|
|
29
|
+
|
|
30
|
+
# Test / tooling caches
|
|
31
|
+
coverage/
|
|
32
|
+
.eslintcache
|
|
33
|
+
.nyc_output/
|
|
34
|
+
|
|
35
|
+
# Python
|
|
36
|
+
__pycache__/
|
|
37
|
+
*.pyc
|
|
38
|
+
|
|
39
|
+
# Browser automation runtime data
|
|
40
|
+
.playwright-mcp/
|
|
41
|
+
.playwright-cli/
|
|
42
|
+
|
|
43
|
+
# Internal dev tooling (not for public repo)
|
|
44
|
+
CLAUDE.md
|
|
45
|
+
.claude/
|
|
46
|
+
_bmad/
|
|
47
|
+
_bmad-output/
|
|
48
|
+
docs/
|
|
49
|
+
# test-hardest: nur Rohdaten, Harness und Liesmich sind oeffentlich; die Seite selbst bleibt privat
|
|
50
|
+
test-hardest/*
|
|
51
|
+
!test-hardest/results/
|
|
52
|
+
test-hardest/results/*
|
|
53
|
+
!test-hardest/results/*.json
|
|
54
|
+
!test-hardest/benchmark-*.json
|
|
55
|
+
!test-hardest/README.md
|
|
56
|
+
!test-hardest/measure-tool-calls.sh
|
|
57
|
+
!test-hardest/measure-session-cost.sh
|
|
58
|
+
!test-hardest/blind-run.mjs
|
|
59
|
+
!test-hardest/blind-run.test.mjs
|
|
60
|
+
!test-hardest/blind-prompt.md
|
|
61
|
+
!test-hardest/fixtures/
|
|
62
|
+
test-hardest/fixtures/*
|
|
63
|
+
!test-hardest/fixtures/blind-run/
|
|
64
|
+
test-stress/
|
|
65
|
+
live-test/
|
|
66
|
+
marketing/
|
|
67
|
+
research/
|
|
68
|
+
prompt.md
|
|
69
|
+
PUBLIC_BROWSER_FINDINGS.md
|
|
70
|
+
compare-*.png
|
|
71
|
+
|
|
72
|
+
# scripts/: nur die oeffentlich nutzbaren Helfer liegen im Repo
|
|
73
|
+
scripts/*
|
|
74
|
+
!scripts/check-pack.mjs
|
|
75
|
+
!scripts/dev-mode.sh
|
|
76
|
+
!scripts/publish.ts
|
|
77
|
+
!scripts/publish.test.ts
|
|
78
|
+
!scripts/token-count.mjs
|
|
79
|
+
!scripts/make-benchmark-chart-2026-09.py
|
|
80
|
+
|
|
81
|
+
# Registry-Publish-Tokens (lokal, niemals committen)
|
|
82
|
+
.mcpregistry_*_token
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Silbercue
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: publicbrowser
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Minimal CDP client for Chrome browser automation — control Chrome from Python via the DevTools Protocol.
|
|
5
|
+
Project-URL: Homepage, https://github.com/Silbercue/public-browser
|
|
6
|
+
Project-URL: Documentation, https://github.com/Silbercue/public-browser#script-api-python
|
|
7
|
+
Project-URL: Repository, https://github.com/Silbercue/public-browser
|
|
8
|
+
Project-URL: Issues, https://github.com/Silbercue/public-browser/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/Silbercue/public-browser/blob/master/CHANGELOG.md
|
|
10
|
+
Author: Julian Friedrich
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: automation,browser,cdp,chrome,devtools,scraping,websocket
|
|
14
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Browsers
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
25
|
+
Classifier: Topic :: Software Development :: Testing
|
|
26
|
+
Classifier: Typing :: Typed
|
|
27
|
+
Requires-Python: >=3.10
|
|
28
|
+
Requires-Dist: websockets>=14.0
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: build>=1.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
|
|
32
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
# Public Browser — Python Script API
|
|
37
|
+
|
|
38
|
+
Python client for Public Browser automation. Scripts use the same tool implementations as the MCP server (Shared Core) — every improvement to `click`, `navigate`, `fill_form` etc. automatically benefits your scripts too. One codebase, one test suite (1600+ tests), two access paths.
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
The Python package is not currently published on PyPI. From the repository root, install the local package:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
python -m pip install ./python
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
If you are already in this `python/` directory, use `python -m pip install .` instead. No manual Chrome launch is needed — `Chrome.connect()` starts everything automatically via a local `public-browser` binary or the `npx` fallback.
|
|
49
|
+
|
|
50
|
+
Dependencies: `websockets` (for the Escape Hatch / `CdpClient` low-level access). The main Shared Core API uses `urllib` (built-in).
|
|
51
|
+
|
|
52
|
+
## Quick Start
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from publicbrowser import Chrome
|
|
56
|
+
|
|
57
|
+
chrome = Chrome.connect()
|
|
58
|
+
|
|
59
|
+
with chrome.new_page() as page:
|
|
60
|
+
page.navigate("https://example.com")
|
|
61
|
+
title = page.evaluate("document.title")
|
|
62
|
+
print(title) # "Example Domain"
|
|
63
|
+
|
|
64
|
+
chrome.close()
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`Chrome.connect()` auto-starts the Public Browser server as a subprocess, which in turn launches Chrome. When you call `chrome.close()`, the server subprocess is terminated.
|
|
68
|
+
|
|
69
|
+
## How it works
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
Python Script Escape Hatch (Power User)
|
|
73
|
+
│ │
|
|
74
|
+
▼ ▼
|
|
75
|
+
HTTP POST /tool/{name} WebSocket (CDP)
|
|
76
|
+
Port 9223 Port 9222
|
|
77
|
+
│ │
|
|
78
|
+
▼ │
|
|
79
|
+
Public Browser Server │
|
|
80
|
+
│ │
|
|
81
|
+
▼ │
|
|
82
|
+
registry.executeTool() │
|
|
83
|
+
│ │
|
|
84
|
+
▼ │
|
|
85
|
+
Tool Handler │
|
|
86
|
+
(click.ts, navigate.ts, ...) │
|
|
87
|
+
│ │
|
|
88
|
+
▼ ▼
|
|
89
|
+
Chrome ◄─────────── CDP ────────────────►
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Your script sends HTTP requests to the Public Browser server on port 9223. The server executes the exact same tool handlers that the MCP server uses — selector resolution, Shadow DOM traversal, scroll-into-view, paint-order filtering, ambient context — all server-side.
|
|
93
|
+
|
|
94
|
+
## Auto-Start
|
|
95
|
+
|
|
96
|
+
`Chrome.connect()` finds and starts the server automatically:
|
|
97
|
+
|
|
98
|
+
1. **Running server** — checks if port 9223 already responds, connects immediately
|
|
99
|
+
2. **PATH binary** — finds `public-browser` in PATH (e.g. via Homebrew), starts it with `--script`
|
|
100
|
+
3. **npx fallback** — runs `npx -y public-browser@latest -- --script`
|
|
101
|
+
4. **Explicit path** — `Chrome.connect(server_path="/path/to/public-browser")` for custom setups
|
|
102
|
+
|
|
103
|
+
## Login and Data Extraction
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from publicbrowser import Chrome
|
|
107
|
+
|
|
108
|
+
chrome = Chrome.connect()
|
|
109
|
+
|
|
110
|
+
with chrome.new_page() as page:
|
|
111
|
+
page.navigate("https://app.example.com/login")
|
|
112
|
+
|
|
113
|
+
# Fill login form
|
|
114
|
+
page.fill({
|
|
115
|
+
"#email": "user@example.com",
|
|
116
|
+
"#password": "secret",
|
|
117
|
+
})
|
|
118
|
+
page.click("#submit")
|
|
119
|
+
|
|
120
|
+
# Wait for dashboard
|
|
121
|
+
page.wait_for("text=Dashboard")
|
|
122
|
+
|
|
123
|
+
# Extract data
|
|
124
|
+
data = page.evaluate("""
|
|
125
|
+
Array.from(document.querySelectorAll('.item'))
|
|
126
|
+
.map(el => ({ name: el.textContent, href: el.href }))
|
|
127
|
+
""")
|
|
128
|
+
print(data)
|
|
129
|
+
|
|
130
|
+
chrome.close()
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## API Reference
|
|
134
|
+
|
|
135
|
+
### `Chrome`
|
|
136
|
+
|
|
137
|
+
| Method | Description |
|
|
138
|
+
|---|---|
|
|
139
|
+
| `Chrome.connect(host="localhost", port=9223, *, server_path=None, auto_start=True)` | Connect to or auto-start the Public Browser server |
|
|
140
|
+
| `chrome.new_page()` | Context manager: open a new tab, auto-closes on exit |
|
|
141
|
+
| `chrome.close()` | Close the connection and terminate any auto-started server |
|
|
142
|
+
|
|
143
|
+
### `Page` (via `chrome.new_page()`)
|
|
144
|
+
|
|
145
|
+
| Method | Description |
|
|
146
|
+
|---|---|
|
|
147
|
+
| `page.navigate(url)` | Navigate to URL and wait for load |
|
|
148
|
+
| `page.click(selector)` | Click element by CSS selector, text, or ref |
|
|
149
|
+
| `page.type(selector, text)` | Type text into input element |
|
|
150
|
+
| `page.fill({"sel": "val", ...})` | Fill multiple form fields at once |
|
|
151
|
+
| `page.wait_for(condition)` | Wait for JS condition or `"text=..."` shorthand |
|
|
152
|
+
| `page.evaluate(expression)` | Run JavaScript, return result |
|
|
153
|
+
| `page.download()` | Enable downloads, return download dir |
|
|
154
|
+
| `page.close()` | Close the tab (auto-called by context manager) |
|
|
155
|
+
| `page.cdp` | Escape Hatch — returns a `CdpEscapeHatch` for direct CDP access (see below) |
|
|
156
|
+
|
|
157
|
+
### Escape Hatch: `page.cdp.send()`
|
|
158
|
+
|
|
159
|
+
For use cases the high-level API doesn't cover — network interception, console log subscriptions, performance tracing, cookie management, PDF generation — you can drop down to raw CDP commands via `page.cdp.send()`:
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
with chrome.new_page() as page:
|
|
163
|
+
page.navigate("https://example.com")
|
|
164
|
+
|
|
165
|
+
# Enable network tracking
|
|
166
|
+
page.cdp.send("Network.enable")
|
|
167
|
+
|
|
168
|
+
# Get all cookies
|
|
169
|
+
cookies = page.cdp.send("Network.getAllCookies")
|
|
170
|
+
|
|
171
|
+
# Performance tracing
|
|
172
|
+
page.cdp.send("Tracing.start", {"categories": "-*,devtools.timeline"})
|
|
173
|
+
|
|
174
|
+
# Register event handler
|
|
175
|
+
page.cdp.on("Network.requestWillBeSent", lambda e: print(e["request"]["url"]))
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
The Escape Hatch communicates directly with Chrome via WebSocket (port 9222), bypassing the server entirely. It connects lazily on the first `send()` call and reuses the connection. Each page gets its own WebSocket routed to the correct tab.
|
|
179
|
+
|
|
180
|
+
| Method | Description |
|
|
181
|
+
|---|---|
|
|
182
|
+
| `page.cdp.send(method, params=None, *, timeout=30.0)` | Send a CDP command and return the result |
|
|
183
|
+
| `page.cdp.on(event, handler)` | Register a callback for a CDP event |
|
|
184
|
+
| `page.cdp.close()` | Close the WebSocket (auto-called when the page context manager exits) |
|
|
185
|
+
|
|
186
|
+
### `CdpClient` (low-level, legacy)
|
|
187
|
+
|
|
188
|
+
For direct CDP access without the Shared Core server. This is the v1 code path — it works, but does not benefit from server-side improvements. Use `page.cdp.send()` instead for most Escape Hatch use cases.
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
from publicbrowser import CdpClient
|
|
192
|
+
|
|
193
|
+
# Async API
|
|
194
|
+
client = await CdpClient.connect(port=9222)
|
|
195
|
+
result = await client.send("Runtime.evaluate", {"expression": "1+1"})
|
|
196
|
+
await client.close()
|
|
197
|
+
|
|
198
|
+
# Sync API
|
|
199
|
+
client = CdpClient.connect_sync(port=9222)
|
|
200
|
+
result = client.send_sync("Runtime.evaluate", {"expression": "1+1"})
|
|
201
|
+
client.close_sync()
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## MCP Coexistence
|
|
205
|
+
|
|
206
|
+
When the MCP server and Python scripts need to run at the same time, add `--script` to the MCP config. `Chrome.connect()` handles the rest — each script works in its own tab, MCP tabs are never touched.
|
|
207
|
+
|
|
208
|
+
**Claude Code:**
|
|
209
|
+
```bash
|
|
210
|
+
claude mcp add --scope user public-browser npx -y public-browser@latest -- --script
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
**Cursor / Cline (`mcp.json`):**
|
|
214
|
+
```json
|
|
215
|
+
{
|
|
216
|
+
"mcpServers": {
|
|
217
|
+
"public-browser": {
|
|
218
|
+
"command": "npx",
|
|
219
|
+
"args": ["-y", "public-browser@latest", "--", "--script"]
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Legacy: Single-File Alternative
|
|
226
|
+
|
|
227
|
+
For quick prototyping, you can copy `publicbrowser_standalone.py` into your project. This uses the v1 code path (direct CDP via WebSocket) and does **not** benefit from server-side improvements. Use the local `publicbrowser` package for the full Shared Core experience.
|
|
228
|
+
|
|
229
|
+
## License
|
|
230
|
+
|
|
231
|
+
MIT
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# Public Browser — Python Script API
|
|
2
|
+
|
|
3
|
+
Python client for Public Browser automation. Scripts use the same tool implementations as the MCP server (Shared Core) — every improvement to `click`, `navigate`, `fill_form` etc. automatically benefits your scripts too. One codebase, one test suite (1600+ tests), two access paths.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
The Python package is not currently published on PyPI. From the repository root, install the local package:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
python -m pip install ./python
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
If you are already in this `python/` directory, use `python -m pip install .` instead. No manual Chrome launch is needed — `Chrome.connect()` starts everything automatically via a local `public-browser` binary or the `npx` fallback.
|
|
14
|
+
|
|
15
|
+
Dependencies: `websockets` (for the Escape Hatch / `CdpClient` low-level access). The main Shared Core API uses `urllib` (built-in).
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
from publicbrowser import Chrome
|
|
21
|
+
|
|
22
|
+
chrome = Chrome.connect()
|
|
23
|
+
|
|
24
|
+
with chrome.new_page() as page:
|
|
25
|
+
page.navigate("https://example.com")
|
|
26
|
+
title = page.evaluate("document.title")
|
|
27
|
+
print(title) # "Example Domain"
|
|
28
|
+
|
|
29
|
+
chrome.close()
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`Chrome.connect()` auto-starts the Public Browser server as a subprocess, which in turn launches Chrome. When you call `chrome.close()`, the server subprocess is terminated.
|
|
33
|
+
|
|
34
|
+
## How it works
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
Python Script Escape Hatch (Power User)
|
|
38
|
+
│ │
|
|
39
|
+
▼ ▼
|
|
40
|
+
HTTP POST /tool/{name} WebSocket (CDP)
|
|
41
|
+
Port 9223 Port 9222
|
|
42
|
+
│ │
|
|
43
|
+
▼ │
|
|
44
|
+
Public Browser Server │
|
|
45
|
+
│ │
|
|
46
|
+
▼ │
|
|
47
|
+
registry.executeTool() │
|
|
48
|
+
│ │
|
|
49
|
+
▼ │
|
|
50
|
+
Tool Handler │
|
|
51
|
+
(click.ts, navigate.ts, ...) │
|
|
52
|
+
│ │
|
|
53
|
+
▼ ▼
|
|
54
|
+
Chrome ◄─────────── CDP ────────────────►
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Your script sends HTTP requests to the Public Browser server on port 9223. The server executes the exact same tool handlers that the MCP server uses — selector resolution, Shadow DOM traversal, scroll-into-view, paint-order filtering, ambient context — all server-side.
|
|
58
|
+
|
|
59
|
+
## Auto-Start
|
|
60
|
+
|
|
61
|
+
`Chrome.connect()` finds and starts the server automatically:
|
|
62
|
+
|
|
63
|
+
1. **Running server** — checks if port 9223 already responds, connects immediately
|
|
64
|
+
2. **PATH binary** — finds `public-browser` in PATH (e.g. via Homebrew), starts it with `--script`
|
|
65
|
+
3. **npx fallback** — runs `npx -y public-browser@latest -- --script`
|
|
66
|
+
4. **Explicit path** — `Chrome.connect(server_path="/path/to/public-browser")` for custom setups
|
|
67
|
+
|
|
68
|
+
## Login and Data Extraction
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from publicbrowser import Chrome
|
|
72
|
+
|
|
73
|
+
chrome = Chrome.connect()
|
|
74
|
+
|
|
75
|
+
with chrome.new_page() as page:
|
|
76
|
+
page.navigate("https://app.example.com/login")
|
|
77
|
+
|
|
78
|
+
# Fill login form
|
|
79
|
+
page.fill({
|
|
80
|
+
"#email": "user@example.com",
|
|
81
|
+
"#password": "secret",
|
|
82
|
+
})
|
|
83
|
+
page.click("#submit")
|
|
84
|
+
|
|
85
|
+
# Wait for dashboard
|
|
86
|
+
page.wait_for("text=Dashboard")
|
|
87
|
+
|
|
88
|
+
# Extract data
|
|
89
|
+
data = page.evaluate("""
|
|
90
|
+
Array.from(document.querySelectorAll('.item'))
|
|
91
|
+
.map(el => ({ name: el.textContent, href: el.href }))
|
|
92
|
+
""")
|
|
93
|
+
print(data)
|
|
94
|
+
|
|
95
|
+
chrome.close()
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## API Reference
|
|
99
|
+
|
|
100
|
+
### `Chrome`
|
|
101
|
+
|
|
102
|
+
| Method | Description |
|
|
103
|
+
|---|---|
|
|
104
|
+
| `Chrome.connect(host="localhost", port=9223, *, server_path=None, auto_start=True)` | Connect to or auto-start the Public Browser server |
|
|
105
|
+
| `chrome.new_page()` | Context manager: open a new tab, auto-closes on exit |
|
|
106
|
+
| `chrome.close()` | Close the connection and terminate any auto-started server |
|
|
107
|
+
|
|
108
|
+
### `Page` (via `chrome.new_page()`)
|
|
109
|
+
|
|
110
|
+
| Method | Description |
|
|
111
|
+
|---|---|
|
|
112
|
+
| `page.navigate(url)` | Navigate to URL and wait for load |
|
|
113
|
+
| `page.click(selector)` | Click element by CSS selector, text, or ref |
|
|
114
|
+
| `page.type(selector, text)` | Type text into input element |
|
|
115
|
+
| `page.fill({"sel": "val", ...})` | Fill multiple form fields at once |
|
|
116
|
+
| `page.wait_for(condition)` | Wait for JS condition or `"text=..."` shorthand |
|
|
117
|
+
| `page.evaluate(expression)` | Run JavaScript, return result |
|
|
118
|
+
| `page.download()` | Enable downloads, return download dir |
|
|
119
|
+
| `page.close()` | Close the tab (auto-called by context manager) |
|
|
120
|
+
| `page.cdp` | Escape Hatch — returns a `CdpEscapeHatch` for direct CDP access (see below) |
|
|
121
|
+
|
|
122
|
+
### Escape Hatch: `page.cdp.send()`
|
|
123
|
+
|
|
124
|
+
For use cases the high-level API doesn't cover — network interception, console log subscriptions, performance tracing, cookie management, PDF generation — you can drop down to raw CDP commands via `page.cdp.send()`:
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
with chrome.new_page() as page:
|
|
128
|
+
page.navigate("https://example.com")
|
|
129
|
+
|
|
130
|
+
# Enable network tracking
|
|
131
|
+
page.cdp.send("Network.enable")
|
|
132
|
+
|
|
133
|
+
# Get all cookies
|
|
134
|
+
cookies = page.cdp.send("Network.getAllCookies")
|
|
135
|
+
|
|
136
|
+
# Performance tracing
|
|
137
|
+
page.cdp.send("Tracing.start", {"categories": "-*,devtools.timeline"})
|
|
138
|
+
|
|
139
|
+
# Register event handler
|
|
140
|
+
page.cdp.on("Network.requestWillBeSent", lambda e: print(e["request"]["url"]))
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The Escape Hatch communicates directly with Chrome via WebSocket (port 9222), bypassing the server entirely. It connects lazily on the first `send()` call and reuses the connection. Each page gets its own WebSocket routed to the correct tab.
|
|
144
|
+
|
|
145
|
+
| Method | Description |
|
|
146
|
+
|---|---|
|
|
147
|
+
| `page.cdp.send(method, params=None, *, timeout=30.0)` | Send a CDP command and return the result |
|
|
148
|
+
| `page.cdp.on(event, handler)` | Register a callback for a CDP event |
|
|
149
|
+
| `page.cdp.close()` | Close the WebSocket (auto-called when the page context manager exits) |
|
|
150
|
+
|
|
151
|
+
### `CdpClient` (low-level, legacy)
|
|
152
|
+
|
|
153
|
+
For direct CDP access without the Shared Core server. This is the v1 code path — it works, but does not benefit from server-side improvements. Use `page.cdp.send()` instead for most Escape Hatch use cases.
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
from publicbrowser import CdpClient
|
|
157
|
+
|
|
158
|
+
# Async API
|
|
159
|
+
client = await CdpClient.connect(port=9222)
|
|
160
|
+
result = await client.send("Runtime.evaluate", {"expression": "1+1"})
|
|
161
|
+
await client.close()
|
|
162
|
+
|
|
163
|
+
# Sync API
|
|
164
|
+
client = CdpClient.connect_sync(port=9222)
|
|
165
|
+
result = client.send_sync("Runtime.evaluate", {"expression": "1+1"})
|
|
166
|
+
client.close_sync()
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## MCP Coexistence
|
|
170
|
+
|
|
171
|
+
When the MCP server and Python scripts need to run at the same time, add `--script` to the MCP config. `Chrome.connect()` handles the rest — each script works in its own tab, MCP tabs are never touched.
|
|
172
|
+
|
|
173
|
+
**Claude Code:**
|
|
174
|
+
```bash
|
|
175
|
+
claude mcp add --scope user public-browser npx -y public-browser@latest -- --script
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
**Cursor / Cline (`mcp.json`):**
|
|
179
|
+
```json
|
|
180
|
+
{
|
|
181
|
+
"mcpServers": {
|
|
182
|
+
"public-browser": {
|
|
183
|
+
"command": "npx",
|
|
184
|
+
"args": ["-y", "public-browser@latest", "--", "--script"]
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Legacy: Single-File Alternative
|
|
191
|
+
|
|
192
|
+
For quick prototyping, you can copy `publicbrowser_standalone.py` into your project. This uses the v1 code path (direct CDP via WebSocket) and does **not** benefit from server-side improvements. Use the local `publicbrowser` package for the full Shared Core experience.
|
|
193
|
+
|
|
194
|
+
## License
|
|
195
|
+
|
|
196
|
+
MIT
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Public Browser — Python client for Chrome browser automation.
|
|
2
|
+
|
|
3
|
+
v2: Uses the Public Browser Script API server (HTTP on port 9223).
|
|
4
|
+
All browser automation logic runs server-side for maximum quality.
|
|
5
|
+
|
|
6
|
+
CdpClient and CdpError are re-exported for backward compatibility
|
|
7
|
+
and for the cdp.py escape hatch (Story 9.9).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from publicbrowser.cdp import CdpClient, CdpError
|
|
11
|
+
from publicbrowser.chrome import Chrome
|
|
12
|
+
from publicbrowser.client import ScriptApiClient
|
|
13
|
+
from publicbrowser.escape_hatch import CdpEscapeHatch
|
|
14
|
+
from publicbrowser.page import Page
|
|
15
|
+
|
|
16
|
+
__version__ = "1.0.0"
|
|
17
|
+
__all__ = [
|
|
18
|
+
"Chrome",
|
|
19
|
+
"Page",
|
|
20
|
+
"ScriptApiClient",
|
|
21
|
+
"CdpClient",
|
|
22
|
+
"CdpError",
|
|
23
|
+
"CdpEscapeHatch",
|
|
24
|
+
]
|