Skip to content

Commit cdba299

Browse files
Copilothyp3ri0n-ng
andcommitted
Update documentation for I/O capabilities
Co-authored-by: hyp3ri0n-ng <3106718+hyp3ri0n-ng@users.noreply.github.com>
1 parent cdb471c commit cdba299

File tree

2 files changed

+260
-4
lines changed

2 files changed

+260
-4
lines changed

README.md

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,75 @@ not catch any typos in your JSON objects, and you wouldn't get autocomplete for
2121
any parts of the JSON data structure. By providing a set of native Python
2222
wrappers, this project makes it easier and faster to write CDP client code.
2323

24-
**This library does not perform any I/O!** In order to maximize
25-
flexibility, this library does not actually handle any network I/O, such as
26-
opening a socket or negotiating a WebSocket protocol. Instead, that
27-
responsibility is left to higher-level libraries, for example
24+
## Two Usage Modes
25+
26+
**Sans-I/O Mode (original):** The core library provides type wrappers without performing any I/O.
27+
This maximizes flexibility and allows integration with any async framework. This is ideal for users
28+
who want to integrate CDP with their own I/O stack or use libraries like
2829
[trio-chrome-devtools-protocol](https://github.com/hyperiongray/trio-chrome-devtools-protocol).
2930

31+
**I/O Mode (new):** The library now includes `cdp.connection` module that provides WebSocket I/O,
32+
JSON-RPC message framing, and command multiplexing out of the box. This makes it easy to get started
33+
with CDP without writing any I/O code yourself.
34+
35+
## Installation
36+
37+
**Basic installation (Sans-I/O mode only):**
38+
39+
```bash
40+
pip install chrome-devtools-protocol
41+
```
42+
43+
**With I/O support:**
44+
45+
```bash
46+
pip install chrome-devtools-protocol[io]
47+
```
48+
49+
## Quick Start with I/O Mode
50+
51+
```python
52+
import asyncio
53+
from cdp.connection import CDPConnection
54+
from cdp import page
55+
56+
async def main():
57+
# Connect to a Chrome DevTools Protocol endpoint
58+
async with CDPConnection("ws://localhost:9222/devtools/page/YOUR_PAGE_ID") as conn:
59+
# Navigate to a URL
60+
frame_id, loader_id, error = await conn.execute(
61+
page.navigate(url="https://example.com")
62+
)
63+
print(f"Navigated to example.com, frame_id: {frame_id}")
64+
65+
asyncio.run(main())
66+
```
67+
68+
### Key Features of I/O Mode
69+
70+
- **WebSocket Management**: Automatic connection lifecycle management with async context managers
71+
- **JSON-RPC Framing**: Automatic message ID assignment and request/response matching
72+
- **Command Multiplexing**: Execute multiple commands concurrently with proper tracking
73+
- **Event Handling**: Async iterator for receiving browser events
74+
- **Error Handling**: Comprehensive error handling with typed exceptions
75+
76+
See the [examples directory](examples/) for more usage patterns.
77+
78+
## Sans-I/O Mode (Original)
79+
80+
For users who prefer to manage their own I/O:
81+
82+
## Sans-I/O Mode (Original)
83+
84+
For users who prefer to manage their own I/O:
85+
86+
```python
87+
from cdp import page
88+
89+
frame_id = page.FrameId('my id')
90+
assert repr(frame_id) == "FrameId('my id')"
91+
```
92+
3093
For more information, see the [complete documentation](https://py-cdp.readthedocs.io).
3194

3295
<a href="https://www.hyperiongray.com/?pk_campaign=github&pk_kwd=pycdp"><img alt="define hyperion gray" width="500px" src="https://hyperiongray.s3.amazonaws.com/define-hg.svg"></a>

docs/connection.md

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
# CDP Connection Module
2+
3+
The `cdp.connection` module provides I/O capabilities for the Chrome DevTools Protocol, including WebSocket management, JSON-RPC message framing, and command multiplexing.
4+
5+
## Installation
6+
7+
Install the library with I/O support:
8+
9+
```bash
10+
pip install chrome-devtools-protocol[io]
11+
```
12+
13+
## Quick Start
14+
15+
```python
16+
import asyncio
17+
from cdp.connection import CDPConnection
18+
from cdp import page, runtime
19+
20+
async def main():
21+
# Connect using async context manager
22+
async with CDPConnection("ws://localhost:9222/devtools/page/YOUR_PAGE_ID") as conn:
23+
# Execute a command
24+
frame_id, loader_id, error = await conn.execute(
25+
page.navigate(url="https://example.com")
26+
)
27+
28+
# Evaluate JavaScript
29+
result, exception = await conn.execute(
30+
runtime.evaluate(expression="document.title")
31+
)
32+
print(f"Page title: {result.value}")
33+
34+
asyncio.run(main())
35+
```
36+
37+
## Finding the WebSocket URL
38+
39+
To get the WebSocket URL for a Chrome tab:
40+
41+
1. Start Chrome with remote debugging:
42+
```bash
43+
chrome --remote-debugging-port=9222
44+
```
45+
46+
2. Open `chrome://inspect/#devices` in Chrome
47+
48+
3. Find your tab and copy the WebSocket URL (looks like `ws://localhost:9222/devtools/page/...`)
49+
50+
## Features
51+
52+
### WebSocket Connection Management
53+
54+
The `CDPConnection` class handles the WebSocket connection lifecycle:
55+
56+
```python
57+
# Manual connection management
58+
conn = CDPConnection(url)
59+
await conn.connect()
60+
# ... use connection ...
61+
await conn.close()
62+
63+
# Or use async context manager (recommended)
64+
async with CDPConnection(url) as conn:
65+
# ... use connection ...
66+
pass # Automatically closed
67+
```
68+
69+
### JSON-RPC Message Framing
70+
71+
The connection automatically:
72+
- Assigns unique IDs to each command
73+
- Tracks pending commands
74+
- Matches responses to their corresponding requests
75+
76+
This is all handled transparently when you call `execute()`.
77+
78+
### Command Multiplexing
79+
80+
Execute multiple commands concurrently:
81+
82+
```python
83+
async with CDPConnection(url) as conn:
84+
# Start multiple commands at once
85+
task1 = conn.execute(runtime.evaluate(expression="1 + 1"))
86+
task2 = conn.execute(runtime.evaluate(expression="2 + 2"))
87+
task3 = conn.execute(runtime.evaluate(expression="3 + 3"))
88+
89+
# Wait for all to complete
90+
results = await asyncio.gather(task1, task2, task3)
91+
92+
# Results come back in order, even if responses arrive out of order
93+
print(results[0][0].value) # 2
94+
print(results[1][0].value) # 4
95+
print(results[2][0].value) # 6
96+
```
97+
98+
### Event Handling
99+
100+
Listen for browser events using an async iterator:
101+
102+
```python
103+
async with CDPConnection(url) as conn:
104+
# Enable events
105+
await conn.execute(page.enable())
106+
107+
# Listen for events
108+
async for event in conn.listen():
109+
if isinstance(event, page.LoadEventFired):
110+
print(f"Page loaded at {event.timestamp}")
111+
elif isinstance(event, page.FrameNavigated):
112+
print(f"Navigated to {event.frame.url}")
113+
```
114+
115+
You can also get events without blocking:
116+
117+
```python
118+
event = conn.get_event_nowait() # Returns None if no events
119+
if event:
120+
print(f"Got event: {event}")
121+
```
122+
123+
### Error Handling
124+
125+
The connection module provides typed exceptions:
126+
127+
```python
128+
from cdp.connection import CDPError, CDPConnectionError, CDPCommandError
129+
130+
try:
131+
async with CDPConnection(url) as conn:
132+
result = await conn.execute(some_command())
133+
except CDPConnectionError as e:
134+
print(f"Connection failed: {e}")
135+
except CDPCommandError as e:
136+
print(f"Command failed: {e.code} - {e.message}")
137+
except asyncio.TimeoutError:
138+
print("Command timed out")
139+
```
140+
141+
### Timeouts
142+
143+
Set a default timeout for all commands, or override per command:
144+
145+
```python
146+
# Set default timeout to 10 seconds
147+
conn = CDPConnection(url, timeout=10.0)
148+
149+
# Override timeout for specific command
150+
result = await conn.execute(some_command(), timeout=30.0)
151+
```
152+
153+
## API Reference
154+
155+
### CDPConnection
156+
157+
```python
158+
class CDPConnection:
159+
def __init__(self, url: str, timeout: float = 30.0)
160+
async def connect(self) -> None
161+
async def close(self) -> None
162+
async def execute(self, cmd, timeout: Optional[float] = None) -> Any
163+
async def listen(self) -> AsyncIterator[Any]
164+
def get_event_nowait(self) -> Optional[Any]
165+
166+
@property
167+
def is_connected(self) -> bool
168+
169+
@property
170+
def pending_command_count(self) -> int
171+
```
172+
173+
### Exceptions
174+
175+
- `CDPError`: Base exception for all CDP errors
176+
- `CDPConnectionError`: Raised when there's a connection problem
177+
- `CDPCommandError`: Raised when a command returns an error
178+
- `.code`: Error code
179+
- `.message`: Error message
180+
- `.data`: Optional additional error data
181+
182+
## Examples
183+
184+
See the [examples directory](../examples/connection_example.py) for complete working examples including:
185+
186+
- Basic navigation and JavaScript evaluation
187+
- Event handling patterns
188+
- Concurrent command execution
189+
- Error handling
190+
191+
## Backward Compatibility
192+
193+
The connection module is completely optional. The core library still works in Sans-I/O mode without the `websockets` dependency. Existing code that uses the Sans-I/O API continues to work unchanged.

0 commit comments

Comments
 (0)