browsercli 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.
- browsercli-1.0.0/PKG-INFO +191 -0
- browsercli-1.0.0/README.md +168 -0
- browsercli-1.0.0/browsercli/__init__.py +26 -0
- browsercli-1.0.0/browsercli/client.py +630 -0
- browsercli-1.0.0/browsercli/exceptions.py +69 -0
- browsercli-1.0.0/browsercli.egg-info/PKG-INFO +191 -0
- browsercli-1.0.0/browsercli.egg-info/SOURCES.txt +10 -0
- browsercli-1.0.0/browsercli.egg-info/dependency_links.txt +1 -0
- browsercli-1.0.0/browsercli.egg-info/top_level.txt +1 -0
- browsercli-1.0.0/pyproject.toml +33 -0
- browsercli-1.0.0/setup.cfg +4 -0
- browsercli-1.0.0/tests/test_client.py +939 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: browsercli
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Python client for the browsercli browser workspace daemon
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/justinhuangcode/browsercli
|
|
7
|
+
Project-URL: Repository, https://github.com/justinhuangcode/browsercli
|
|
8
|
+
Project-URL: Documentation, https://github.com/justinhuangcode/browsercli#readme
|
|
9
|
+
Project-URL: Bug Reports, https://github.com/justinhuangcode/browsercli/issues
|
|
10
|
+
Keywords: browser,automation,rpc,cdp,devtools
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Software Development :: Testing
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Requires-Python: >=3.9
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# browsercli Python Client
|
|
25
|
+
|
|
26
|
+
Zero-dependency Python client for the [browsercli](https://github.com/justinhuangcode/browsercli) browser workspace daemon.
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install -e clients/python # from the repo root
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Platform Support
|
|
35
|
+
|
|
36
|
+
The client auto-detects the RPC transport from the session file:
|
|
37
|
+
|
|
38
|
+
- **macOS / Linux** — connects via Unix socket (`socket_path` in session.json)
|
|
39
|
+
- **Windows** — connects via TCP localhost (`rpc_port` in session.json)
|
|
40
|
+
|
|
41
|
+
No code changes are needed — `BrowserCLI.connect()` handles both transports.
|
|
42
|
+
|
|
43
|
+
## Quick Start
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from browsercli import BrowserCLI
|
|
47
|
+
|
|
48
|
+
# Connect to a running daemon
|
|
49
|
+
# macOS/Linux: reads ~/.browsercli/session.json
|
|
50
|
+
# Windows: reads %LOCALAPPDATA%\browsercli\session.json
|
|
51
|
+
ac = BrowserCLI.connect()
|
|
52
|
+
|
|
53
|
+
# Navigate and inspect
|
|
54
|
+
ac.goto("/")
|
|
55
|
+
title = ac.dom_query("h1", mode="text")
|
|
56
|
+
print(f"Title: {title}")
|
|
57
|
+
|
|
58
|
+
# Evaluate JavaScript
|
|
59
|
+
result = ac.eval("1 + 1")
|
|
60
|
+
print(f"Result: {result}")
|
|
61
|
+
|
|
62
|
+
# Screenshot
|
|
63
|
+
ac.screenshot(out="page.png")
|
|
64
|
+
|
|
65
|
+
# Stop the daemon
|
|
66
|
+
ac.stop()
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Error Handling
|
|
70
|
+
|
|
71
|
+
All exceptions inherit from `BrowserCLIError`, so you can catch the whole family or handle specific cases:
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from browsercli import (
|
|
75
|
+
BrowserCLI,
|
|
76
|
+
BrowserCLIError,
|
|
77
|
+
ConnectionError,
|
|
78
|
+
AuthenticationError,
|
|
79
|
+
SessionError,
|
|
80
|
+
NotFoundError,
|
|
81
|
+
ServerError,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
ac = BrowserCLI.connect()
|
|
86
|
+
ac.dom_query("#missing-element", mode="text")
|
|
87
|
+
except SessionError:
|
|
88
|
+
print("Daemon not running — start it with: browsercli start")
|
|
89
|
+
except ConnectionError:
|
|
90
|
+
print("Cannot reach daemon — is the socket file valid?")
|
|
91
|
+
except AuthenticationError:
|
|
92
|
+
print("Token rejected — daemon may have restarted, reconnect")
|
|
93
|
+
except NotFoundError as e:
|
|
94
|
+
print(f"Element or endpoint not found: {e}")
|
|
95
|
+
except ServerError as e:
|
|
96
|
+
print(f"Daemon internal error (HTTP {e.status_code}): {e.error_message}")
|
|
97
|
+
except BrowserCLIError as e:
|
|
98
|
+
print(f"Unexpected client error: {e}")
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Exception Hierarchy
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
BrowserCLIError # Base — catch-all
|
|
105
|
+
├── SessionError # session.json missing/invalid
|
|
106
|
+
├── ConnectionError # RPC endpoint unreachable (socket or TCP)
|
|
107
|
+
├── AuthenticationError # HTTP 401 (bad token)
|
|
108
|
+
└── RPCError # Any HTTP 4xx/5xx with status_code + error_message
|
|
109
|
+
├── BadRequestError # HTTP 400
|
|
110
|
+
├── NotFoundError # HTTP 404
|
|
111
|
+
└── ServerError # HTTP 5xx
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Constructor
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
BrowserCLI(socket_path="", token="", timeout=30.0, *, rpc_port=0)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
| Parameter | Type | Description |
|
|
121
|
+
| --- | --- | --- |
|
|
122
|
+
| `socket_path` | `str` | Unix socket path (macOS/Linux). Mutually exclusive with `rpc_port`. |
|
|
123
|
+
| `token` | `str` | Bearer token for daemon authentication |
|
|
124
|
+
| `timeout` | `float` | Request timeout in seconds (default `30.0`) |
|
|
125
|
+
| `rpc_port` | `int` | TCP port on localhost (Windows). Keyword-only. |
|
|
126
|
+
|
|
127
|
+
You must provide either `socket_path` or `rpc_port`. In most cases, use the `connect()` factory instead of calling the constructor directly.
|
|
128
|
+
|
|
129
|
+
## API Reference
|
|
130
|
+
|
|
131
|
+
| Method | Description |
|
|
132
|
+
| --- | --- |
|
|
133
|
+
| `BrowserCLI.connect(session_path=None, timeout=30.0)` | Create client from session file (auto-detects Unix socket or TCP) |
|
|
134
|
+
| `status()` | Daemon and browser status |
|
|
135
|
+
| `version()` | RPC and schema version info |
|
|
136
|
+
| `goto(url)` | Navigate to a path or URL |
|
|
137
|
+
| `eval(expression)` | Evaluate JavaScript |
|
|
138
|
+
| `reload()` | Reload the page |
|
|
139
|
+
| `dom_query(selector, mode)` | Query a single DOM element |
|
|
140
|
+
| `dom_all(selector, mode)` | Query all matching elements |
|
|
141
|
+
| `dom_attr(selector, name)` | Get an element attribute |
|
|
142
|
+
| `dom_click(selector)` | Click an element |
|
|
143
|
+
| `dom_type(selector, text, clear)` | Type text into an input |
|
|
144
|
+
| `dom_wait(selector, state, timeout_ms)` | Wait for element state |
|
|
145
|
+
| `screenshot(selector, out)` | Capture screenshot (PNG bytes) |
|
|
146
|
+
| `console(level, limit, clear)` | Fetch console entries |
|
|
147
|
+
| `network(limit, clear)` | Fetch network log entries |
|
|
148
|
+
| `perf()` | Page performance metrics |
|
|
149
|
+
| `stop()` | Stop the daemon |
|
|
150
|
+
| `plugin_list()` | List installed plugins |
|
|
151
|
+
| `plugin_rpc(rpc_path, body=None)` | Call a custom plugin RPC endpoint (`/x/...`) |
|
|
152
|
+
|
|
153
|
+
### Valid Parameter Values
|
|
154
|
+
|
|
155
|
+
| Parameter | Valid Values | Default |
|
|
156
|
+
| --- | --- | --- |
|
|
157
|
+
| `dom_query` / `dom_all` `mode` | `"outer_html"`, `"text"` | `"outer_html"` |
|
|
158
|
+
| `dom_wait` `state` | `"visible"`, `"hidden"`, `"attached"`, `"detached"` | `"visible"` |
|
|
159
|
+
| `console` `level` | `""` (all), `"log"`, `"warn"`, `"error"`, `"info"` | `""` |
|
|
160
|
+
|
|
161
|
+
Invalid values raise `ValueError` before any RPC call is made.
|
|
162
|
+
|
|
163
|
+
## Logging
|
|
164
|
+
|
|
165
|
+
The client uses Python's `logging` module under the logger name `browsercli`:
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
import logging
|
|
169
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
170
|
+
# Now all RPC requests and responses are logged.
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Running Tests
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
cd clients/python
|
|
177
|
+
python -m unittest tests.test_client -v
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Tests include:
|
|
181
|
+
- **Session parsing**: valid/invalid/missing session files (including `rpc_port` for Windows)
|
|
182
|
+
- **Parameter validation**: client-side checks for all method arguments
|
|
183
|
+
- **Contract tests**: mock server (Unix socket on macOS/Linux, TCP on Windows) emulating the Rust daemon
|
|
184
|
+
- **Error handling**: auth failures, 400/404/500 errors, connection errors
|
|
185
|
+
- **Exception hierarchy**: inheritance and attribute verification
|
|
186
|
+
|
|
187
|
+
## Requirements
|
|
188
|
+
|
|
189
|
+
- Python 3.9+
|
|
190
|
+
- A running `browsercli` daemon (`browsercli start`)
|
|
191
|
+
- No external dependencies (stdlib only)
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# browsercli Python Client
|
|
2
|
+
|
|
3
|
+
Zero-dependency Python client for the [browsercli](https://github.com/justinhuangcode/browsercli) browser workspace daemon.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install -e clients/python # from the repo root
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Platform Support
|
|
12
|
+
|
|
13
|
+
The client auto-detects the RPC transport from the session file:
|
|
14
|
+
|
|
15
|
+
- **macOS / Linux** — connects via Unix socket (`socket_path` in session.json)
|
|
16
|
+
- **Windows** — connects via TCP localhost (`rpc_port` in session.json)
|
|
17
|
+
|
|
18
|
+
No code changes are needed — `BrowserCLI.connect()` handles both transports.
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
from browsercli import BrowserCLI
|
|
24
|
+
|
|
25
|
+
# Connect to a running daemon
|
|
26
|
+
# macOS/Linux: reads ~/.browsercli/session.json
|
|
27
|
+
# Windows: reads %LOCALAPPDATA%\browsercli\session.json
|
|
28
|
+
ac = BrowserCLI.connect()
|
|
29
|
+
|
|
30
|
+
# Navigate and inspect
|
|
31
|
+
ac.goto("/")
|
|
32
|
+
title = ac.dom_query("h1", mode="text")
|
|
33
|
+
print(f"Title: {title}")
|
|
34
|
+
|
|
35
|
+
# Evaluate JavaScript
|
|
36
|
+
result = ac.eval("1 + 1")
|
|
37
|
+
print(f"Result: {result}")
|
|
38
|
+
|
|
39
|
+
# Screenshot
|
|
40
|
+
ac.screenshot(out="page.png")
|
|
41
|
+
|
|
42
|
+
# Stop the daemon
|
|
43
|
+
ac.stop()
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Error Handling
|
|
47
|
+
|
|
48
|
+
All exceptions inherit from `BrowserCLIError`, so you can catch the whole family or handle specific cases:
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from browsercli import (
|
|
52
|
+
BrowserCLI,
|
|
53
|
+
BrowserCLIError,
|
|
54
|
+
ConnectionError,
|
|
55
|
+
AuthenticationError,
|
|
56
|
+
SessionError,
|
|
57
|
+
NotFoundError,
|
|
58
|
+
ServerError,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
ac = BrowserCLI.connect()
|
|
63
|
+
ac.dom_query("#missing-element", mode="text")
|
|
64
|
+
except SessionError:
|
|
65
|
+
print("Daemon not running — start it with: browsercli start")
|
|
66
|
+
except ConnectionError:
|
|
67
|
+
print("Cannot reach daemon — is the socket file valid?")
|
|
68
|
+
except AuthenticationError:
|
|
69
|
+
print("Token rejected — daemon may have restarted, reconnect")
|
|
70
|
+
except NotFoundError as e:
|
|
71
|
+
print(f"Element or endpoint not found: {e}")
|
|
72
|
+
except ServerError as e:
|
|
73
|
+
print(f"Daemon internal error (HTTP {e.status_code}): {e.error_message}")
|
|
74
|
+
except BrowserCLIError as e:
|
|
75
|
+
print(f"Unexpected client error: {e}")
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Exception Hierarchy
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
BrowserCLIError # Base — catch-all
|
|
82
|
+
├── SessionError # session.json missing/invalid
|
|
83
|
+
├── ConnectionError # RPC endpoint unreachable (socket or TCP)
|
|
84
|
+
├── AuthenticationError # HTTP 401 (bad token)
|
|
85
|
+
└── RPCError # Any HTTP 4xx/5xx with status_code + error_message
|
|
86
|
+
├── BadRequestError # HTTP 400
|
|
87
|
+
├── NotFoundError # HTTP 404
|
|
88
|
+
└── ServerError # HTTP 5xx
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Constructor
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
BrowserCLI(socket_path="", token="", timeout=30.0, *, rpc_port=0)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
| Parameter | Type | Description |
|
|
98
|
+
| --- | --- | --- |
|
|
99
|
+
| `socket_path` | `str` | Unix socket path (macOS/Linux). Mutually exclusive with `rpc_port`. |
|
|
100
|
+
| `token` | `str` | Bearer token for daemon authentication |
|
|
101
|
+
| `timeout` | `float` | Request timeout in seconds (default `30.0`) |
|
|
102
|
+
| `rpc_port` | `int` | TCP port on localhost (Windows). Keyword-only. |
|
|
103
|
+
|
|
104
|
+
You must provide either `socket_path` or `rpc_port`. In most cases, use the `connect()` factory instead of calling the constructor directly.
|
|
105
|
+
|
|
106
|
+
## API Reference
|
|
107
|
+
|
|
108
|
+
| Method | Description |
|
|
109
|
+
| --- | --- |
|
|
110
|
+
| `BrowserCLI.connect(session_path=None, timeout=30.0)` | Create client from session file (auto-detects Unix socket or TCP) |
|
|
111
|
+
| `status()` | Daemon and browser status |
|
|
112
|
+
| `version()` | RPC and schema version info |
|
|
113
|
+
| `goto(url)` | Navigate to a path or URL |
|
|
114
|
+
| `eval(expression)` | Evaluate JavaScript |
|
|
115
|
+
| `reload()` | Reload the page |
|
|
116
|
+
| `dom_query(selector, mode)` | Query a single DOM element |
|
|
117
|
+
| `dom_all(selector, mode)` | Query all matching elements |
|
|
118
|
+
| `dom_attr(selector, name)` | Get an element attribute |
|
|
119
|
+
| `dom_click(selector)` | Click an element |
|
|
120
|
+
| `dom_type(selector, text, clear)` | Type text into an input |
|
|
121
|
+
| `dom_wait(selector, state, timeout_ms)` | Wait for element state |
|
|
122
|
+
| `screenshot(selector, out)` | Capture screenshot (PNG bytes) |
|
|
123
|
+
| `console(level, limit, clear)` | Fetch console entries |
|
|
124
|
+
| `network(limit, clear)` | Fetch network log entries |
|
|
125
|
+
| `perf()` | Page performance metrics |
|
|
126
|
+
| `stop()` | Stop the daemon |
|
|
127
|
+
| `plugin_list()` | List installed plugins |
|
|
128
|
+
| `plugin_rpc(rpc_path, body=None)` | Call a custom plugin RPC endpoint (`/x/...`) |
|
|
129
|
+
|
|
130
|
+
### Valid Parameter Values
|
|
131
|
+
|
|
132
|
+
| Parameter | Valid Values | Default |
|
|
133
|
+
| --- | --- | --- |
|
|
134
|
+
| `dom_query` / `dom_all` `mode` | `"outer_html"`, `"text"` | `"outer_html"` |
|
|
135
|
+
| `dom_wait` `state` | `"visible"`, `"hidden"`, `"attached"`, `"detached"` | `"visible"` |
|
|
136
|
+
| `console` `level` | `""` (all), `"log"`, `"warn"`, `"error"`, `"info"` | `""` |
|
|
137
|
+
|
|
138
|
+
Invalid values raise `ValueError` before any RPC call is made.
|
|
139
|
+
|
|
140
|
+
## Logging
|
|
141
|
+
|
|
142
|
+
The client uses Python's `logging` module under the logger name `browsercli`:
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
import logging
|
|
146
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
147
|
+
# Now all RPC requests and responses are logged.
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Running Tests
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
cd clients/python
|
|
154
|
+
python -m unittest tests.test_client -v
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Tests include:
|
|
158
|
+
- **Session parsing**: valid/invalid/missing session files (including `rpc_port` for Windows)
|
|
159
|
+
- **Parameter validation**: client-side checks for all method arguments
|
|
160
|
+
- **Contract tests**: mock server (Unix socket on macOS/Linux, TCP on Windows) emulating the Rust daemon
|
|
161
|
+
- **Error handling**: auth failures, 400/404/500 errors, connection errors
|
|
162
|
+
- **Exception hierarchy**: inheritance and attribute verification
|
|
163
|
+
|
|
164
|
+
## Requirements
|
|
165
|
+
|
|
166
|
+
- Python 3.9+
|
|
167
|
+
- A running `browsercli` daemon (`browsercli start`)
|
|
168
|
+
- No external dependencies (stdlib only)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""browsercli Python client — zero-dependency wrapper for the Unix socket RPC API."""
|
|
2
|
+
|
|
3
|
+
from browsercli.client import BrowserCLI
|
|
4
|
+
from browsercli.exceptions import (
|
|
5
|
+
BrowserCLIError,
|
|
6
|
+
AuthenticationError,
|
|
7
|
+
BadRequestError,
|
|
8
|
+
ConnectionError,
|
|
9
|
+
NotFoundError,
|
|
10
|
+
RPCError,
|
|
11
|
+
ServerError,
|
|
12
|
+
SessionError,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"BrowserCLI",
|
|
17
|
+
"BrowserCLIError",
|
|
18
|
+
"AuthenticationError",
|
|
19
|
+
"BadRequestError",
|
|
20
|
+
"ConnectionError",
|
|
21
|
+
"NotFoundError",
|
|
22
|
+
"RPCError",
|
|
23
|
+
"ServerError",
|
|
24
|
+
"SessionError",
|
|
25
|
+
]
|
|
26
|
+
__version__ = "0.4.0"
|