cdpify 0.1.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.
- cdpify-0.1.0/PKG-INFO +203 -0
- cdpify-0.1.0/README.md +192 -0
- cdpify-0.1.0/cdpify/__init__.py +17 -0
- cdpify-0.1.0/cdpify/client.py +243 -0
- cdpify-0.1.0/cdpify/domains/__init__.py +89 -0
- cdpify-0.1.0/cdpify/domains/accessibility/__init__.py +8 -0
- cdpify-0.1.0/cdpify/domains/accessibility/client.py +170 -0
- cdpify-0.1.0/cdpify/domains/accessibility/commands.py +103 -0
- cdpify-0.1.0/cdpify/domains/accessibility/events.py +31 -0
- cdpify-0.1.0/cdpify/domains/accessibility/types.py +189 -0
- cdpify-0.1.0/cdpify/domains/animation/__init__.py +8 -0
- cdpify-0.1.0/cdpify/domains/animation/client.py +172 -0
- cdpify-0.1.0/cdpify/domains/animation/commands.py +82 -0
- cdpify-0.1.0/cdpify/domains/animation/events.py +47 -0
- cdpify-0.1.0/cdpify/domains/animation/types.py +74 -0
- cdpify-0.1.0/cdpify/domains/audits/__init__.py +8 -0
- cdpify-0.1.0/cdpify/domains/audits/client.py +90 -0
- cdpify-0.1.0/cdpify/domains/audits/commands.py +41 -0
- cdpify-0.1.0/cdpify/domains/audits/events.py +16 -0
- cdpify-0.1.0/cdpify/domains/audits/types.py +769 -0
- cdpify-0.1.0/cdpify/domains/backgroundservice/__init__.py +8 -0
- cdpify-0.1.0/cdpify/domains/backgroundservice/client.py +87 -0
- cdpify-0.1.0/cdpify/domains/backgroundservice/commands.py +40 -0
- cdpify-0.1.0/cdpify/domains/backgroundservice/events.py +33 -0
- cdpify-0.1.0/cdpify/domains/backgroundservice/types.py +42 -0
- cdpify-0.1.0/cdpify/domains/base.py +13 -0
- cdpify-0.1.0/cdpify/domains/browser/__init__.py +8 -0
- cdpify-0.1.0/cdpify/domains/browser/client.py +367 -0
- cdpify-0.1.0/cdpify/domains/browser/commands.py +183 -0
- cdpify-0.1.0/cdpify/domains/browser/events.py +40 -0
- cdpify-0.1.0/cdpify/domains/browser/types.py +116 -0
- cdpify-0.1.0/cdpify/domains/cachestorage/__init__.py +8 -0
- cdpify-0.1.0/cdpify/domains/cachestorage/client.py +125 -0
- cdpify-0.1.0/cdpify/domains/cachestorage/commands.py +71 -0
- cdpify-0.1.0/cdpify/domains/cachestorage/events.py +8 -0
- cdpify-0.1.0/cdpify/domains/cachestorage/types.py +61 -0
- cdpify-0.1.0/cdpify/domains/cast/__init__.py +8 -0
- cdpify-0.1.0/cdpify/domains/cast/client.py +109 -0
- cdpify-0.1.0/cdpify/domains/cast/commands.py +51 -0
- cdpify-0.1.0/cdpify/domains/cast/events.py +31 -0
- cdpify-0.1.0/cdpify/domains/cast/types.py +11 -0
- cdpify-0.1.0/cdpify/domains/console/__init__.py +8 -0
- cdpify-0.1.0/cdpify/domains/console/client.py +48 -0
- cdpify-0.1.0/cdpify/domains/console/commands.py +7 -0
- cdpify-0.1.0/cdpify/domains/console/events.py +20 -0
- cdpify-0.1.0/cdpify/domains/console/types.py +31 -0
- cdpify-0.1.0/cdpify/domains/css/__init__.py +8 -0
- cdpify-0.1.0/cdpify/domains/css/client.py +664 -0
- cdpify-0.1.0/cdpify/domains/css/commands.py +423 -0
- cdpify-0.1.0/cdpify/domains/css/events.py +68 -0
- cdpify-0.1.0/cdpify/domains/css/types.py +505 -0
- cdpify-0.1.0/cdpify/domains/debugger/__init__.py +8 -0
- cdpify-0.1.0/cdpify/domains/debugger/client.py +629 -0
- cdpify-0.1.0/cdpify/domains/debugger/commands.py +389 -0
- cdpify-0.1.0/cdpify/domains/debugger/events.py +121 -0
- cdpify-0.1.0/cdpify/domains/debugger/types.py +128 -0
- cdpify-0.1.0/cdpify/domains/deviceorientation/__init__.py +8 -0
- cdpify-0.1.0/cdpify/domains/deviceorientation/client.py +47 -0
- cdpify-0.1.0/cdpify/domains/deviceorientation/commands.py +17 -0
- cdpify-0.1.0/cdpify/domains/deviceorientation/events.py +8 -0
- cdpify-0.1.0/cdpify/domains/deviceorientation/types.py +7 -0
- cdpify-0.1.0/cdpify/domains/dom/__init__.py +8 -0
- cdpify-0.1.0/cdpify/domains/dom/client.py +975 -0
- cdpify-0.1.0/cdpify/domains/dom/commands.py +579 -0
- cdpify-0.1.0/cdpify/domains/dom/events.py +195 -0
- cdpify-0.1.0/cdpify/domains/dom/types.py +217 -0
- cdpify-0.1.0/cdpify/domains/domdebugger/__init__.py +8 -0
- cdpify-0.1.0/cdpify/domains/domdebugger/client.py +194 -0
- cdpify-0.1.0/cdpify/domains/domdebugger/commands.py +101 -0
- cdpify-0.1.0/cdpify/domains/domdebugger/events.py +8 -0
- cdpify-0.1.0/cdpify/domains/domdebugger/types.py +36 -0
- cdpify-0.1.0/cdpify/domains/domsnapshot/__init__.py +8 -0
- cdpify-0.1.0/cdpify/domains/domsnapshot/client.py +92 -0
- cdpify-0.1.0/cdpify/domains/domsnapshot/commands.py +47 -0
- cdpify-0.1.0/cdpify/domains/domsnapshot/events.py +8 -0
- cdpify-0.1.0/cdpify/domains/domsnapshot/types.py +194 -0
- cdpify-0.1.0/cdpify/domains/domstorage/__init__.py +8 -0
- cdpify-0.1.0/cdpify/domains/domstorage/client.py +112 -0
- cdpify-0.1.0/cdpify/domains/domstorage/commands.py +30 -0
- cdpify-0.1.0/cdpify/domains/domstorage/events.py +37 -0
- cdpify-0.1.0/cdpify/domains/domstorage/types.py +24 -0
- cdpify-0.1.0/cdpify/domains/emulation/__init__.py +8 -0
- cdpify-0.1.0/cdpify/domains/emulation/client.py +853 -0
- cdpify-0.1.0/cdpify/domains/emulation/commands.py +395 -0
- cdpify-0.1.0/cdpify/domains/emulation/events.py +21 -0
- cdpify-0.1.0/cdpify/domains/emulation/types.py +176 -0
- cdpify-0.1.0/cdpify/domains/eventbreakpoints/__init__.py +8 -0
- cdpify-0.1.0/cdpify/domains/eventbreakpoints/client.py +61 -0
- cdpify-0.1.0/cdpify/domains/eventbreakpoints/commands.py +23 -0
- cdpify-0.1.0/cdpify/domains/eventbreakpoints/events.py +8 -0
- cdpify-0.1.0/cdpify/domains/eventbreakpoints/types.py +7 -0
- cdpify-0.1.0/cdpify/domains/fetch/__init__.py +8 -0
- cdpify-0.1.0/cdpify/domains/fetch/client.py +207 -0
- cdpify-0.1.0/cdpify/domains/fetch/commands.py +117 -0
- cdpify-0.1.0/cdpify/domains/fetch/events.py +56 -0
- cdpify-0.1.0/cdpify/domains/fetch/types.py +58 -0
- cdpify-0.1.0/cdpify/domains/heapprofiler/__init__.py +8 -0
- cdpify-0.1.0/cdpify/domains/heapprofiler/client.py +220 -0
- cdpify-0.1.0/cdpify/domains/heapprofiler/commands.py +69 -0
- cdpify-0.1.0/cdpify/domains/heapprofiler/events.py +51 -0
- cdpify-0.1.0/cdpify/domains/heapprofiler/types.py +45 -0
- cdpify-0.1.0/cdpify/domains/indexeddb/__init__.py +8 -0
- cdpify-0.1.0/cdpify/domains/indexeddb/client.py +232 -0
- cdpify-0.1.0/cdpify/domains/indexeddb/commands.py +113 -0
- cdpify-0.1.0/cdpify/domains/indexeddb/events.py +8 -0
- cdpify-0.1.0/cdpify/domains/indexeddb/types.py +84 -0
- cdpify-0.1.0/cdpify/domains/input/__init__.py +8 -0
- cdpify-0.1.0/cdpify/domains/input/client.py +377 -0
- cdpify-0.1.0/cdpify/domains/input/commands.py +177 -0
- cdpify-0.1.0/cdpify/domains/input/events.py +21 -0
- cdpify-0.1.0/cdpify/domains/input/types.py +43 -0
- cdpify-0.1.0/cdpify/domains/io/__init__.py +8 -0
- cdpify-0.1.0/cdpify/domains/io/client.py +74 -0
- cdpify-0.1.0/cdpify/domains/io/commands.py +46 -0
- cdpify-0.1.0/cdpify/domains/io/events.py +8 -0
- cdpify-0.1.0/cdpify/domains/io/types.py +11 -0
- cdpify-0.1.0/cdpify/domains/layertree/__init__.py +8 -0
- cdpify-0.1.0/cdpify/domains/layertree/client.py +177 -0
- cdpify-0.1.0/cdpify/domains/layertree/commands.py +93 -0
- cdpify-0.1.0/cdpify/domains/layertree/events.py +26 -0
- cdpify-0.1.0/cdpify/domains/layertree/types.py +78 -0
- cdpify-0.1.0/cdpify/domains/log/__init__.py +8 -0
- cdpify-0.1.0/cdpify/domains/log/client.py +82 -0
- cdpify-0.1.0/cdpify/domains/log/commands.py +15 -0
- cdpify-0.1.0/cdpify/domains/log/events.py +20 -0
- cdpify-0.1.0/cdpify/domains/log/types.py +58 -0
- cdpify-0.1.0/cdpify/domains/media/__init__.py +8 -0
- cdpify-0.1.0/cdpify/domains/media/client.py +37 -0
- cdpify-0.1.0/cdpify/domains/media/commands.py +7 -0
- cdpify-0.1.0/cdpify/domains/media/events.py +64 -0
- cdpify-0.1.0/cdpify/domains/media/types.py +70 -0
- cdpify-0.1.0/cdpify/domains/memory/__init__.py +8 -0
- cdpify-0.1.0/cdpify/domains/memory/client.py +166 -0
- cdpify-0.1.0/cdpify/domains/memory/commands.py +54 -0
- cdpify-0.1.0/cdpify/domains/memory/events.py +8 -0
- cdpify-0.1.0/cdpify/domains/memory/types.py +50 -0
- cdpify-0.1.0/cdpify/domains/network/__init__.py +8 -0
- cdpify-0.1.0/cdpify/domains/network/client.py +742 -0
- cdpify-0.1.0/cdpify/domains/network/commands.py +399 -0
- cdpify-0.1.0/cdpify/domains/network/events.py +534 -0
- cdpify-0.1.0/cdpify/domains/network/types.py +890 -0
- cdpify-0.1.0/cdpify/domains/overlay/__init__.py +8 -0
- cdpify-0.1.0/cdpify/domains/overlay/client.py +547 -0
- cdpify-0.1.0/cdpify/domains/overlay/commands.py +247 -0
- cdpify-0.1.0/cdpify/domains/overlay/events.py +53 -0
- cdpify-0.1.0/cdpify/domains/overlay/types.py +194 -0
- cdpify-0.1.0/cdpify/domains/page/__init__.py +8 -0
- cdpify-0.1.0/cdpify/domains/page/client.py +1084 -0
- cdpify-0.1.0/cdpify/domains/page/commands.py +538 -0
- cdpify-0.1.0/cdpify/domains/page/events.py +326 -0
- cdpify-0.1.0/cdpify/domains/page/types.py +793 -0
- cdpify-0.1.0/cdpify/domains/performance/__init__.py +8 -0
- cdpify-0.1.0/cdpify/domains/performance/client.py +73 -0
- cdpify-0.1.0/cdpify/domains/performance/commands.py +30 -0
- cdpify-0.1.0/cdpify/domains/performance/events.py +21 -0
- cdpify-0.1.0/cdpify/domains/performance/types.py +14 -0
- cdpify-0.1.0/cdpify/domains/profiler/__init__.py +8 -0
- cdpify-0.1.0/cdpify/domains/profiler/client.py +137 -0
- cdpify-0.1.0/cdpify/domains/profiler/commands.py +45 -0
- cdpify-0.1.0/cdpify/domains/profiler/events.py +48 -0
- cdpify-0.1.0/cdpify/domains/profiler/types.py +73 -0
- cdpify-0.1.0/cdpify/domains/runtime/__init__.py +8 -0
- cdpify-0.1.0/cdpify/domains/runtime/client.py +478 -0
- cdpify-0.1.0/cdpify/domains/runtime/commands.py +234 -0
- cdpify-0.1.0/cdpify/domains/runtime/events.py +116 -0
- cdpify-0.1.0/cdpify/domains/runtime/types.py +355 -0
- cdpify-0.1.0/cdpify/domains/schema/__init__.py +8 -0
- cdpify-0.1.0/cdpify/domains/schema/client.py +30 -0
- cdpify-0.1.0/cdpify/domains/schema/commands.py +11 -0
- cdpify-0.1.0/cdpify/domains/schema/events.py +8 -0
- cdpify-0.1.0/cdpify/domains/schema/types.py +14 -0
- cdpify-0.1.0/cdpify/domains/security/__init__.py +8 -0
- cdpify-0.1.0/cdpify/domains/security/client.py +93 -0
- cdpify-0.1.0/cdpify/domains/security/commands.py +34 -0
- cdpify-0.1.0/cdpify/domains/security/events.py +47 -0
- cdpify-0.1.0/cdpify/domains/security/types.py +106 -0
- cdpify-0.1.0/cdpify/domains/serviceworker/__init__.py +8 -0
- cdpify-0.1.0/cdpify/domains/serviceworker/client.py +217 -0
- cdpify-0.1.0/cdpify/domains/serviceworker/commands.py +50 -0
- cdpify-0.1.0/cdpify/domains/serviceworker/events.py +26 -0
- cdpify-0.1.0/cdpify/domains/serviceworker/types.py +60 -0
- cdpify-0.1.0/cdpify/domains/storage/__init__.py +8 -0
- cdpify-0.1.0/cdpify/domains/storage/client.py +649 -0
- cdpify-0.1.0/cdpify/domains/storage/commands.py +355 -0
- cdpify-0.1.0/cdpify/domains/storage/events.py +190 -0
- cdpify-0.1.0/cdpify/domains/storage/types.py +422 -0
- cdpify-0.1.0/cdpify/domains/systeminfo/__init__.py +8 -0
- cdpify-0.1.0/cdpify/domains/systeminfo/client.py +59 -0
- cdpify-0.1.0/cdpify/domains/systeminfo/commands.py +30 -0
- cdpify-0.1.0/cdpify/domains/systeminfo/events.py +8 -0
- cdpify-0.1.0/cdpify/domains/systeminfo/types.py +87 -0
- cdpify-0.1.0/cdpify/domains/target/__init__.py +8 -0
- cdpify-0.1.0/cdpify/domains/target/client.py +394 -0
- cdpify-0.1.0/cdpify/domains/target/commands.py +234 -0
- cdpify-0.1.0/cdpify/domains/target/events.py +85 -0
- cdpify-0.1.0/cdpify/domains/target/types.py +60 -0
- cdpify-0.1.0/cdpify/domains/tethering/__init__.py +8 -0
- cdpify-0.1.0/cdpify/domains/tethering/client.py +50 -0
- cdpify-0.1.0/cdpify/domains/tethering/commands.py +23 -0
- cdpify-0.1.0/cdpify/domains/tethering/events.py +21 -0
- cdpify-0.1.0/cdpify/domains/tethering/types.py +7 -0
- cdpify-0.1.0/cdpify/domains/tracing/__init__.py +8 -0
- cdpify-0.1.0/cdpify/domains/tracing/client.py +131 -0
- cdpify-0.1.0/cdpify/domains/tracing/commands.py +54 -0
- cdpify-0.1.0/cdpify/domains/tracing/events.py +46 -0
- cdpify-0.1.0/cdpify/domains/tracing/types.py +58 -0
- cdpify-0.1.0/cdpify/domains/webaudio/__init__.py +8 -0
- cdpify-0.1.0/cdpify/domains/webaudio/client.py +61 -0
- cdpify-0.1.0/cdpify/domains/webaudio/commands.py +19 -0
- cdpify-0.1.0/cdpify/domains/webaudio/events.py +148 -0
- cdpify-0.1.0/cdpify/domains/webaudio/types.py +110 -0
- cdpify-0.1.0/cdpify/domains/webauthn/__init__.py +8 -0
- cdpify-0.1.0/cdpify/domains/webauthn/client.py +261 -0
- cdpify-0.1.0/cdpify/domains/webauthn/commands.py +132 -0
- cdpify-0.1.0/cdpify/domains/webauthn/events.py +53 -0
- cdpify-0.1.0/cdpify/domains/webauthn/types.py +44 -0
- cdpify-0.1.0/cdpify/events.py +55 -0
- cdpify-0.1.0/cdpify/exceptions.py +18 -0
- cdpify-0.1.0/cdpify/generator/__main__.py +27 -0
- cdpify-0.1.0/cdpify/generator/config.py +104 -0
- cdpify-0.1.0/cdpify/generator/constants.py +2 -0
- cdpify-0.1.0/cdpify/generator/downloader.py +43 -0
- cdpify-0.1.0/cdpify/generator/generator.py +170 -0
- cdpify-0.1.0/cdpify/generator/generators/__init__.py +11 -0
- cdpify-0.1.0/cdpify/generator/generators/base.py +67 -0
- cdpify-0.1.0/cdpify/generator/generators/client.py +246 -0
- cdpify-0.1.0/cdpify/generator/generators/commands.py +107 -0
- cdpify-0.1.0/cdpify/generator/generators/events.py +116 -0
- cdpify-0.1.0/cdpify/generator/generators/types.py +132 -0
- cdpify-0.1.0/cdpify/generator/generators/utils.py +115 -0
- cdpify-0.1.0/cdpify/generator/models.py +81 -0
- cdpify-0.1.0/cdpify/generator/parser.py +23 -0
- cdpify-0.1.0/cdpify.egg-info/PKG-INFO +203 -0
- cdpify-0.1.0/cdpify.egg-info/SOURCES.txt +237 -0
- cdpify-0.1.0/cdpify.egg-info/dependency_links.txt +1 -0
- cdpify-0.1.0/cdpify.egg-info/requires.txt +4 -0
- cdpify-0.1.0/cdpify.egg-info/top_level.txt +1 -0
- cdpify-0.1.0/pyproject.toml +53 -0
- cdpify-0.1.0/setup.cfg +4 -0
cdpify-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cdpify
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Requires-Python: >=3.14
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: httpx>=0.28.1
|
|
8
|
+
Requires-Dist: pre-commit>=4.5.1
|
|
9
|
+
Requires-Dist: pydantic>=2.12.5
|
|
10
|
+
Requires-Dist: websockets>=15.0.1
|
|
11
|
+
|
|
12
|
+
# Pydantic CDP
|
|
13
|
+
|
|
14
|
+
Type-safe Python client for the Chrome DevTools Protocol (CDP) with Pydantic validation.
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install cdpify
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## What it does
|
|
23
|
+
|
|
24
|
+
This library provides Python bindings for the Chrome DevTools Protocol with full type safety through Pydantic models. All CDP domains, commands, events, and types are automatically generated from the official Chrome DevTools Protocol specifications.
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
### Basic Connection
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
import asyncio
|
|
32
|
+
from cdpify import CDPClient
|
|
33
|
+
|
|
34
|
+
async def main():
|
|
35
|
+
async with CDPClient("ws://localhost:9222/devtools/browser/...") as client:
|
|
36
|
+
# Send CDP commands
|
|
37
|
+
result = await client.send_raw(
|
|
38
|
+
method="Target.getTargets",
|
|
39
|
+
params=None
|
|
40
|
+
)
|
|
41
|
+
print(f"Targets: {result}")
|
|
42
|
+
|
|
43
|
+
asyncio.run(main())
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Using Domain Clients
|
|
47
|
+
|
|
48
|
+
Domain-specific clients provide typed methods for all CDP commands:
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from cdpify import CDPClient
|
|
52
|
+
from cdpify.domains import PageClient, RuntimeClient
|
|
53
|
+
|
|
54
|
+
async def main():
|
|
55
|
+
async with CDPClient("ws://localhost:9222/devtools/page/...") as client:
|
|
56
|
+
# Initialize domain clients
|
|
57
|
+
page = PageClient(client)
|
|
58
|
+
runtime = RuntimeClient(client)
|
|
59
|
+
|
|
60
|
+
# Navigate to a page
|
|
61
|
+
await page.navigate(url="https://example.com")
|
|
62
|
+
|
|
63
|
+
# Evaluate JavaScript
|
|
64
|
+
result = await runtime.evaluate(
|
|
65
|
+
expression="document.title",
|
|
66
|
+
return_by_value=True
|
|
67
|
+
)
|
|
68
|
+
print(f"Page title: {result.result.value}")
|
|
69
|
+
|
|
70
|
+
asyncio.run(main())
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Event Handling
|
|
74
|
+
|
|
75
|
+
Register handlers for CDP events:
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
from cdpify import CDPClient
|
|
79
|
+
from cdpify.domains import PageClient
|
|
80
|
+
|
|
81
|
+
async def main():
|
|
82
|
+
client = CDPClient("ws://localhost:9222/devtools/page/...")
|
|
83
|
+
await client.connect()
|
|
84
|
+
|
|
85
|
+
# Register event handler
|
|
86
|
+
@client.on("Page.loadEventFired")
|
|
87
|
+
async def on_load(params, session_id):
|
|
88
|
+
print(f"Page loaded at timestamp: {params['timestamp']}")
|
|
89
|
+
|
|
90
|
+
# Wildcard handler for all events
|
|
91
|
+
@client.on()
|
|
92
|
+
async def on_any_event(params, session_id):
|
|
93
|
+
print(f"Event received: {params}")
|
|
94
|
+
|
|
95
|
+
page = PageClient(client)
|
|
96
|
+
await page.enable()
|
|
97
|
+
await page.navigate(url="https://example.com")
|
|
98
|
+
|
|
99
|
+
await asyncio.sleep(5)
|
|
100
|
+
await client.disconnect()
|
|
101
|
+
|
|
102
|
+
asyncio.run(main())
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Configuration
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
client = CDPClient(
|
|
109
|
+
url="ws://localhost:9222/devtools/browser/...",
|
|
110
|
+
additional_headers={"Authorization": "Bearer token"},
|
|
111
|
+
max_frame_size=100 * 1024 * 1024, # 100MB
|
|
112
|
+
default_timeout=30.0 # seconds
|
|
113
|
+
)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Available Domain Clients
|
|
117
|
+
|
|
118
|
+
All CDP domains are available as typed clients:
|
|
119
|
+
|
|
120
|
+
- `PageClient` - Page lifecycle, navigation, screenshots
|
|
121
|
+
- `RuntimeClient` - JavaScript execution, console, objects
|
|
122
|
+
- `NetworkClient` - Network monitoring, request interception
|
|
123
|
+
- `DOMClient` - DOM tree access and manipulation
|
|
124
|
+
- `DebuggerClient` - JavaScript debugging
|
|
125
|
+
- `EmulationClient` - Device emulation, geolocation
|
|
126
|
+
- `PerformanceClient` - Performance metrics
|
|
127
|
+
- `SecurityClient` - Security state, certificates
|
|
128
|
+
- And 40+ more domains...
|
|
129
|
+
|
|
130
|
+
Import them from the root package:
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
from cdpify import (
|
|
134
|
+
CDPClient,
|
|
135
|
+
PageClient,
|
|
136
|
+
NetworkClient,
|
|
137
|
+
RuntimeClient,
|
|
138
|
+
# ... all other domain clients
|
|
139
|
+
)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Type Safety
|
|
143
|
+
|
|
144
|
+
All commands and events use Pydantic models for validation:
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
# Parameters are validated
|
|
148
|
+
await page.navigate(
|
|
149
|
+
url="https://example.com",
|
|
150
|
+
referrer="https://google.com", # Optional parameter
|
|
151
|
+
transition_type="link" # Validated against allowed values
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
# Return values are typed
|
|
155
|
+
result = await runtime.evaluate(expression="1 + 1")
|
|
156
|
+
print(result.result.value) # Pydantic model with full IDE support
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Code Generation
|
|
160
|
+
|
|
161
|
+
The CDP bindings are generated from the official Chrome DevTools Protocol specifications. To regenerate:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
uv run python -m pydantic_cpd.generator
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
This downloads the latest protocol definitions and generates:
|
|
168
|
+
- `pydantic_cpd/domains/*/types.py` - Type definitions
|
|
169
|
+
- `pydantic_cpd/domains/*/commands.py` - Command parameters and results
|
|
170
|
+
- `pydantic_cpd/domains/*/events.py` - Event definitions
|
|
171
|
+
- `pydantic_cpd/domains/*/library.py` - Domain client classes
|
|
172
|
+
|
|
173
|
+
## Project Structure
|
|
174
|
+
|
|
175
|
+
```
|
|
176
|
+
pydantic_cpd/
|
|
177
|
+
├── client.py # Core CDP WebSocket client
|
|
178
|
+
├── events.py # Event dispatcher
|
|
179
|
+
├── exceptions.py # CDP exceptions
|
|
180
|
+
├── domains/ # Generated CDP bindings
|
|
181
|
+
│ ├── page/
|
|
182
|
+
│ ├── runtime/
|
|
183
|
+
│ ├── network/
|
|
184
|
+
│ └── ... (42 domains)
|
|
185
|
+
└── generator/ # Code generation tools
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Requirements
|
|
189
|
+
|
|
190
|
+
- Python 3.14+
|
|
191
|
+
- pydantic >= 2.12
|
|
192
|
+
- websockets >= 15.0
|
|
193
|
+
- httpx >= 0.28
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
## Inspiration
|
|
197
|
+
|
|
198
|
+
The idea for automatic code generation and the overall approach is inspired by [cdp-use](https://github.com/browser-use/cdp-use). This project adapts that concept for Python, with a slightly different API design and type system using Pydantic models.
|
|
199
|
+
|
|
200
|
+
## Links
|
|
201
|
+
|
|
202
|
+
- [Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/)
|
|
203
|
+
- [Protocol Repository](https://github.com/ChromeDevTools/devtools-protocol)
|
cdpify-0.1.0/README.md
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# Pydantic CDP
|
|
2
|
+
|
|
3
|
+
Type-safe Python client for the Chrome DevTools Protocol (CDP) with Pydantic validation.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install cdpify
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## What it does
|
|
12
|
+
|
|
13
|
+
This library provides Python bindings for the Chrome DevTools Protocol with full type safety through Pydantic models. All CDP domains, commands, events, and types are automatically generated from the official Chrome DevTools Protocol specifications.
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
### Basic Connection
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
import asyncio
|
|
21
|
+
from cdpify import CDPClient
|
|
22
|
+
|
|
23
|
+
async def main():
|
|
24
|
+
async with CDPClient("ws://localhost:9222/devtools/browser/...") as client:
|
|
25
|
+
# Send CDP commands
|
|
26
|
+
result = await client.send_raw(
|
|
27
|
+
method="Target.getTargets",
|
|
28
|
+
params=None
|
|
29
|
+
)
|
|
30
|
+
print(f"Targets: {result}")
|
|
31
|
+
|
|
32
|
+
asyncio.run(main())
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Using Domain Clients
|
|
36
|
+
|
|
37
|
+
Domain-specific clients provide typed methods for all CDP commands:
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from cdpify import CDPClient
|
|
41
|
+
from cdpify.domains import PageClient, RuntimeClient
|
|
42
|
+
|
|
43
|
+
async def main():
|
|
44
|
+
async with CDPClient("ws://localhost:9222/devtools/page/...") as client:
|
|
45
|
+
# Initialize domain clients
|
|
46
|
+
page = PageClient(client)
|
|
47
|
+
runtime = RuntimeClient(client)
|
|
48
|
+
|
|
49
|
+
# Navigate to a page
|
|
50
|
+
await page.navigate(url="https://example.com")
|
|
51
|
+
|
|
52
|
+
# Evaluate JavaScript
|
|
53
|
+
result = await runtime.evaluate(
|
|
54
|
+
expression="document.title",
|
|
55
|
+
return_by_value=True
|
|
56
|
+
)
|
|
57
|
+
print(f"Page title: {result.result.value}")
|
|
58
|
+
|
|
59
|
+
asyncio.run(main())
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Event Handling
|
|
63
|
+
|
|
64
|
+
Register handlers for CDP events:
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from cdpify import CDPClient
|
|
68
|
+
from cdpify.domains import PageClient
|
|
69
|
+
|
|
70
|
+
async def main():
|
|
71
|
+
client = CDPClient("ws://localhost:9222/devtools/page/...")
|
|
72
|
+
await client.connect()
|
|
73
|
+
|
|
74
|
+
# Register event handler
|
|
75
|
+
@client.on("Page.loadEventFired")
|
|
76
|
+
async def on_load(params, session_id):
|
|
77
|
+
print(f"Page loaded at timestamp: {params['timestamp']}")
|
|
78
|
+
|
|
79
|
+
# Wildcard handler for all events
|
|
80
|
+
@client.on()
|
|
81
|
+
async def on_any_event(params, session_id):
|
|
82
|
+
print(f"Event received: {params}")
|
|
83
|
+
|
|
84
|
+
page = PageClient(client)
|
|
85
|
+
await page.enable()
|
|
86
|
+
await page.navigate(url="https://example.com")
|
|
87
|
+
|
|
88
|
+
await asyncio.sleep(5)
|
|
89
|
+
await client.disconnect()
|
|
90
|
+
|
|
91
|
+
asyncio.run(main())
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Configuration
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
client = CDPClient(
|
|
98
|
+
url="ws://localhost:9222/devtools/browser/...",
|
|
99
|
+
additional_headers={"Authorization": "Bearer token"},
|
|
100
|
+
max_frame_size=100 * 1024 * 1024, # 100MB
|
|
101
|
+
default_timeout=30.0 # seconds
|
|
102
|
+
)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Available Domain Clients
|
|
106
|
+
|
|
107
|
+
All CDP domains are available as typed clients:
|
|
108
|
+
|
|
109
|
+
- `PageClient` - Page lifecycle, navigation, screenshots
|
|
110
|
+
- `RuntimeClient` - JavaScript execution, console, objects
|
|
111
|
+
- `NetworkClient` - Network monitoring, request interception
|
|
112
|
+
- `DOMClient` - DOM tree access and manipulation
|
|
113
|
+
- `DebuggerClient` - JavaScript debugging
|
|
114
|
+
- `EmulationClient` - Device emulation, geolocation
|
|
115
|
+
- `PerformanceClient` - Performance metrics
|
|
116
|
+
- `SecurityClient` - Security state, certificates
|
|
117
|
+
- And 40+ more domains...
|
|
118
|
+
|
|
119
|
+
Import them from the root package:
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
from cdpify import (
|
|
123
|
+
CDPClient,
|
|
124
|
+
PageClient,
|
|
125
|
+
NetworkClient,
|
|
126
|
+
RuntimeClient,
|
|
127
|
+
# ... all other domain clients
|
|
128
|
+
)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Type Safety
|
|
132
|
+
|
|
133
|
+
All commands and events use Pydantic models for validation:
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
# Parameters are validated
|
|
137
|
+
await page.navigate(
|
|
138
|
+
url="https://example.com",
|
|
139
|
+
referrer="https://google.com", # Optional parameter
|
|
140
|
+
transition_type="link" # Validated against allowed values
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
# Return values are typed
|
|
144
|
+
result = await runtime.evaluate(expression="1 + 1")
|
|
145
|
+
print(result.result.value) # Pydantic model with full IDE support
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Code Generation
|
|
149
|
+
|
|
150
|
+
The CDP bindings are generated from the official Chrome DevTools Protocol specifications. To regenerate:
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
uv run python -m pydantic_cpd.generator
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
This downloads the latest protocol definitions and generates:
|
|
157
|
+
- `pydantic_cpd/domains/*/types.py` - Type definitions
|
|
158
|
+
- `pydantic_cpd/domains/*/commands.py` - Command parameters and results
|
|
159
|
+
- `pydantic_cpd/domains/*/events.py` - Event definitions
|
|
160
|
+
- `pydantic_cpd/domains/*/library.py` - Domain client classes
|
|
161
|
+
|
|
162
|
+
## Project Structure
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
pydantic_cpd/
|
|
166
|
+
├── client.py # Core CDP WebSocket client
|
|
167
|
+
├── events.py # Event dispatcher
|
|
168
|
+
├── exceptions.py # CDP exceptions
|
|
169
|
+
├── domains/ # Generated CDP bindings
|
|
170
|
+
│ ├── page/
|
|
171
|
+
│ ├── runtime/
|
|
172
|
+
│ ├── network/
|
|
173
|
+
│ └── ... (42 domains)
|
|
174
|
+
└── generator/ # Code generation tools
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Requirements
|
|
178
|
+
|
|
179
|
+
- Python 3.14+
|
|
180
|
+
- pydantic >= 2.12
|
|
181
|
+
- websockets >= 15.0
|
|
182
|
+
- httpx >= 0.28
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
## Inspiration
|
|
186
|
+
|
|
187
|
+
The idea for automatic code generation and the overall approach is inspired by [cdp-use](https://github.com/browser-use/cdp-use). This project adapts that concept for Python, with a slightly different API design and type system using Pydantic models.
|
|
188
|
+
|
|
189
|
+
## Links
|
|
190
|
+
|
|
191
|
+
- [Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/)
|
|
192
|
+
- [Protocol Repository](https://github.com/ChromeDevTools/devtools-protocol)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from .client import CDPClient
|
|
2
|
+
from .exceptions import (
|
|
3
|
+
CDPCommandException,
|
|
4
|
+
CDPConnectionException,
|
|
5
|
+
CDPException,
|
|
6
|
+
CDPTimeoutException,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
# CDPClient
|
|
11
|
+
"CDPClient",
|
|
12
|
+
# Exceptions
|
|
13
|
+
"CDPException",
|
|
14
|
+
"CDPConnectionException",
|
|
15
|
+
"CDPCommandException",
|
|
16
|
+
"CDPTimeoutException",
|
|
17
|
+
]
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import websockets
|
|
7
|
+
from websockets.asyncio.client import ClientConnection, connect
|
|
8
|
+
|
|
9
|
+
from cdpify.events import EventDispatcher, EventHandler
|
|
10
|
+
from cdpify.exceptions import (
|
|
11
|
+
CDPCommandException,
|
|
12
|
+
CDPConnectionException,
|
|
13
|
+
CDPTimeoutException,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CDPClient:
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
url: str,
|
|
23
|
+
*,
|
|
24
|
+
additional_headers: dict[str, str] | None = None,
|
|
25
|
+
max_frame_size: int = 100 * 1024 * 1024,
|
|
26
|
+
default_timeout: float = 30.0,
|
|
27
|
+
) -> None:
|
|
28
|
+
self.url: str = url
|
|
29
|
+
self.additional_headers: dict[str, str] | None = additional_headers
|
|
30
|
+
self.max_frame_size: int = max_frame_size
|
|
31
|
+
self.default_timeout: float = default_timeout
|
|
32
|
+
|
|
33
|
+
self._ws: ClientConnection | None = None
|
|
34
|
+
self._next_message_id: int = 0
|
|
35
|
+
self._pending_requests: dict[int, asyncio.Future[dict[str, Any]]] = {}
|
|
36
|
+
self._message_loop_task: asyncio.Task[None] | None = None
|
|
37
|
+
self._events: EventDispatcher = EventDispatcher()
|
|
38
|
+
self._is_shutting_down: bool = False
|
|
39
|
+
|
|
40
|
+
async def __aenter__(self) -> CDPClient:
|
|
41
|
+
await self.connect()
|
|
42
|
+
return self
|
|
43
|
+
|
|
44
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
45
|
+
await self.disconnect()
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def is_connected(self) -> bool:
|
|
49
|
+
return self._ws is not None
|
|
50
|
+
|
|
51
|
+
def on(self, event_name: str | None = None):
|
|
52
|
+
"""
|
|
53
|
+
Register event handler.
|
|
54
|
+
|
|
55
|
+
Usage:
|
|
56
|
+
@client.on("Page.loadEventFired")
|
|
57
|
+
async def on_load(params, session_id):
|
|
58
|
+
print("Page loaded!")
|
|
59
|
+
|
|
60
|
+
@client.on() # Wildcard - all events
|
|
61
|
+
async def on_any(params, session_id):
|
|
62
|
+
print(f"Event: {params}")
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
def decorator(func: EventHandler) -> EventHandler:
|
|
66
|
+
self._events.add_handler(event_name, func)
|
|
67
|
+
return func
|
|
68
|
+
|
|
69
|
+
return decorator
|
|
70
|
+
|
|
71
|
+
def add_event_listener(self, event_name: str | None, handler: EventHandler) -> None:
|
|
72
|
+
self._events.add_handler(event_name, handler)
|
|
73
|
+
|
|
74
|
+
def remove_event_listener(
|
|
75
|
+
self, event_name: str | None, handler: EventHandler
|
|
76
|
+
) -> None:
|
|
77
|
+
self._events.remove_handler(event_name, handler)
|
|
78
|
+
|
|
79
|
+
async def connect(self) -> None:
|
|
80
|
+
if self._ws is not None:
|
|
81
|
+
raise CDPConnectionException("Already connected")
|
|
82
|
+
|
|
83
|
+
logger.info(f"Connecting to {self.url}")
|
|
84
|
+
|
|
85
|
+
try:
|
|
86
|
+
self._ws = await connect(
|
|
87
|
+
self.url,
|
|
88
|
+
max_size=self.max_frame_size,
|
|
89
|
+
additional_headers=self.additional_headers,
|
|
90
|
+
)
|
|
91
|
+
self._is_shutting_down = False
|
|
92
|
+
self._message_loop_task = asyncio.create_task(self._run_message_loop())
|
|
93
|
+
logger.info("Connected")
|
|
94
|
+
except Exception as e:
|
|
95
|
+
raise CDPConnectionException(f"Connection failed: {e}") from e
|
|
96
|
+
|
|
97
|
+
async def disconnect(self) -> None:
|
|
98
|
+
if self._is_shutting_down:
|
|
99
|
+
return
|
|
100
|
+
|
|
101
|
+
self._is_shutting_down = True
|
|
102
|
+
logger.info("Disconnecting...")
|
|
103
|
+
|
|
104
|
+
await self._stop_message_loop()
|
|
105
|
+
self._cancel_pending_requests()
|
|
106
|
+
await self._close_websocket()
|
|
107
|
+
|
|
108
|
+
logger.info("Disconnected")
|
|
109
|
+
|
|
110
|
+
async def send_raw(
|
|
111
|
+
self,
|
|
112
|
+
method: str,
|
|
113
|
+
params: dict[str, Any] | None = None,
|
|
114
|
+
session_id: str | None = None,
|
|
115
|
+
timeout: float | None = None,
|
|
116
|
+
) -> dict[str, Any]:
|
|
117
|
+
if not self.is_connected:
|
|
118
|
+
raise CDPConnectionException("Not connected")
|
|
119
|
+
|
|
120
|
+
timeout = timeout or self.default_timeout
|
|
121
|
+
msg_id = self._next_message_id
|
|
122
|
+
self._next_message_id += 1
|
|
123
|
+
|
|
124
|
+
message = self._build_message(msg_id, method, params, session_id)
|
|
125
|
+
future = self._create_pending_request(msg_id)
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
await self._send(msg_id, method, message)
|
|
129
|
+
return await self._await_response(msg_id, method, future, timeout)
|
|
130
|
+
finally:
|
|
131
|
+
self._pending_requests.pop(msg_id, None)
|
|
132
|
+
|
|
133
|
+
def _build_message(
|
|
134
|
+
self,
|
|
135
|
+
msg_id: int,
|
|
136
|
+
method: str,
|
|
137
|
+
params: dict[str, Any] | None,
|
|
138
|
+
session_id: str | None,
|
|
139
|
+
) -> dict[str, Any]:
|
|
140
|
+
message = {"id": msg_id, "method": method, "params": params or {}}
|
|
141
|
+
if session_id:
|
|
142
|
+
message["sessionId"] = session_id
|
|
143
|
+
return message
|
|
144
|
+
|
|
145
|
+
def _create_pending_request(self, msg_id: int) -> asyncio.Future[dict[str, Any]]:
|
|
146
|
+
future: asyncio.Future[dict[str, Any]] = asyncio.Future()
|
|
147
|
+
self._pending_requests[msg_id] = future
|
|
148
|
+
return future
|
|
149
|
+
|
|
150
|
+
async def _send(self, msg_id: int, method: str, message: dict[str, Any]) -> None:
|
|
151
|
+
logger.debug(f"→ #{msg_id}: {method}")
|
|
152
|
+
await self._ws.send(json.dumps(message))
|
|
153
|
+
|
|
154
|
+
async def _await_response(
|
|
155
|
+
self, msg_id: int, method: str, future: asyncio.Future, timeout: float
|
|
156
|
+
) -> dict[str, Any]:
|
|
157
|
+
try:
|
|
158
|
+
result = await asyncio.wait_for(future, timeout=timeout)
|
|
159
|
+
logger.debug(f"← #{msg_id}: OK")
|
|
160
|
+
return result
|
|
161
|
+
except asyncio.TimeoutError:
|
|
162
|
+
raise CDPTimeoutException(f"{method} timed out after {timeout}s") from None
|
|
163
|
+
except (CDPCommandException, CDPConnectionException):
|
|
164
|
+
raise
|
|
165
|
+
except Exception as e:
|
|
166
|
+
raise CDPConnectionException(f"Command failed: {e}") from e
|
|
167
|
+
|
|
168
|
+
async def _run_message_loop(self) -> None:
|
|
169
|
+
try:
|
|
170
|
+
async for raw_message in self._ws:
|
|
171
|
+
if self._is_shutting_down:
|
|
172
|
+
break
|
|
173
|
+
await self._process_message(raw_message)
|
|
174
|
+
except websockets.exceptions.ConnectionClosed:
|
|
175
|
+
logger.info("Connection closed")
|
|
176
|
+
except asyncio.CancelledError:
|
|
177
|
+
logger.debug("Message loop cancelled")
|
|
178
|
+
raise
|
|
179
|
+
except Exception as e:
|
|
180
|
+
logger.exception(f"Message loop error: {e}")
|
|
181
|
+
finally:
|
|
182
|
+
if not self._is_shutting_down:
|
|
183
|
+
await self.disconnect()
|
|
184
|
+
|
|
185
|
+
async def _process_message(self, raw: str) -> None:
|
|
186
|
+
try:
|
|
187
|
+
msg = json.loads(raw)
|
|
188
|
+
|
|
189
|
+
if "id" in msg:
|
|
190
|
+
await self._handle_response(msg)
|
|
191
|
+
elif "method" in msg:
|
|
192
|
+
await self._handle_event(msg)
|
|
193
|
+
else:
|
|
194
|
+
logger.warning(f"Unknown message format: {msg}")
|
|
195
|
+
except json.JSONDecodeError as e:
|
|
196
|
+
logger.error(f"Invalid JSON: {e}")
|
|
197
|
+
|
|
198
|
+
async def _handle_response(self, msg: dict[str, Any]) -> None:
|
|
199
|
+
msg_id = msg["id"]
|
|
200
|
+
future = self._pending_requests.get(msg_id)
|
|
201
|
+
|
|
202
|
+
if not future or future.done():
|
|
203
|
+
return
|
|
204
|
+
|
|
205
|
+
if "error" in msg:
|
|
206
|
+
future.set_exception(CDPCommandException(msg["error"]))
|
|
207
|
+
else:
|
|
208
|
+
future.set_result(msg.get("result", {}))
|
|
209
|
+
|
|
210
|
+
async def _handle_event(self, msg: dict[str, Any]) -> None:
|
|
211
|
+
method = msg["method"]
|
|
212
|
+
params = msg.get("params", {})
|
|
213
|
+
session_id = msg.get("sessionId")
|
|
214
|
+
|
|
215
|
+
logger.debug(f"Event: {method}")
|
|
216
|
+
handled = await self._events.dispatch(method, params, session_id)
|
|
217
|
+
|
|
218
|
+
if not handled:
|
|
219
|
+
logger.debug(f"Unhandled event: {method}")
|
|
220
|
+
|
|
221
|
+
async def _stop_message_loop(self) -> None:
|
|
222
|
+
if self._message_loop_task and not self._message_loop_task.done():
|
|
223
|
+
self._message_loop_task.cancel()
|
|
224
|
+
try:
|
|
225
|
+
await self._message_loop_task
|
|
226
|
+
except asyncio.CancelledError:
|
|
227
|
+
pass
|
|
228
|
+
|
|
229
|
+
def _cancel_pending_requests(self) -> None:
|
|
230
|
+
error = CDPConnectionException("Disconnected")
|
|
231
|
+
for future in self._pending_requests.values():
|
|
232
|
+
if not future.done():
|
|
233
|
+
future.set_exception(error)
|
|
234
|
+
self._pending_requests.clear()
|
|
235
|
+
|
|
236
|
+
async def _close_websocket(self) -> None:
|
|
237
|
+
if self._ws:
|
|
238
|
+
try:
|
|
239
|
+
await self._ws.close()
|
|
240
|
+
except Exception as e:
|
|
241
|
+
logger.debug(f"Websocket close error: {e}")
|
|
242
|
+
finally:
|
|
243
|
+
self._ws = None
|