pypecdp 0.3.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.
Files changed (80) hide show
  1. pypecdp-0.3.0/.github/workflows/publish-to-pypi.yml +70 -0
  2. pypecdp-0.3.0/.gitignore +56 -0
  3. pypecdp-0.3.0/LICENSE +21 -0
  4. pypecdp-0.3.0/MANIFEST.in +3 -0
  5. pypecdp-0.3.0/PKG-INFO +203 -0
  6. pypecdp-0.3.0/README.md +181 -0
  7. pypecdp-0.3.0/example/console_logger.py +165 -0
  8. pypecdp-0.3.0/example/custom_events.py +191 -0
  9. pypecdp-0.3.0/example/dom_selection.py +240 -0
  10. pypecdp-0.3.0/example/form_interaction.py +89 -0
  11. pypecdp-0.3.0/example/multi_tab.py +148 -0
  12. pypecdp-0.3.0/example/network_intercept.py +203 -0
  13. pypecdp-0.3.0/example/pdf_generation.py +130 -0
  14. pypecdp-0.3.0/example/quickstart.py +34 -0
  15. pypecdp-0.3.0/example/screenshot.py +121 -0
  16. pypecdp-0.3.0/pyproject.toml +53 -0
  17. pypecdp-0.3.0/src/pypecdp/__init__.py +26 -0
  18. pypecdp-0.3.0/src/pypecdp/browser.py +530 -0
  19. pypecdp-0.3.0/src/pypecdp/cdp/README.md +5 -0
  20. pypecdp-0.3.0/src/pypecdp/cdp/__init__.py +6 -0
  21. pypecdp-0.3.0/src/pypecdp/cdp/accessibility.py +668 -0
  22. pypecdp-0.3.0/src/pypecdp/cdp/animation.py +494 -0
  23. pypecdp-0.3.0/src/pypecdp/cdp/audits.py +1941 -0
  24. pypecdp-0.3.0/src/pypecdp/cdp/autofill.py +292 -0
  25. pypecdp-0.3.0/src/pypecdp/cdp/background_service.py +215 -0
  26. pypecdp-0.3.0/src/pypecdp/cdp/bluetooth_emulation.py +626 -0
  27. pypecdp-0.3.0/src/pypecdp/cdp/browser.py +821 -0
  28. pypecdp-0.3.0/src/pypecdp/cdp/cache_storage.py +311 -0
  29. pypecdp-0.3.0/src/pypecdp/cdp/cast.py +172 -0
  30. pypecdp-0.3.0/src/pypecdp/cdp/console.py +107 -0
  31. pypecdp-0.3.0/src/pypecdp/cdp/css.py +2622 -0
  32. pypecdp-0.3.0/src/pypecdp/cdp/debugger.py +1405 -0
  33. pypecdp-0.3.0/src/pypecdp/cdp/device_access.py +141 -0
  34. pypecdp-0.3.0/src/pypecdp/cdp/device_orientation.py +45 -0
  35. pypecdp-0.3.0/src/pypecdp/cdp/dom.py +2229 -0
  36. pypecdp-0.3.0/src/pypecdp/cdp/dom_debugger.py +321 -0
  37. pypecdp-0.3.0/src/pypecdp/cdp/dom_snapshot.py +876 -0
  38. pypecdp-0.3.0/src/pypecdp/cdp/dom_storage.py +222 -0
  39. pypecdp-0.3.0/src/pypecdp/cdp/emulation.py +1663 -0
  40. pypecdp-0.3.0/src/pypecdp/cdp/event_breakpoints.py +56 -0
  41. pypecdp-0.3.0/src/pypecdp/cdp/extensions.py +165 -0
  42. pypecdp-0.3.0/src/pypecdp/cdp/fed_cm.py +283 -0
  43. pypecdp-0.3.0/src/pypecdp/cdp/fetch.py +507 -0
  44. pypecdp-0.3.0/src/pypecdp/cdp/file_system.py +115 -0
  45. pypecdp-0.3.0/src/pypecdp/cdp/headless_experimental.py +115 -0
  46. pypecdp-0.3.0/src/pypecdp/cdp/heap_profiler.py +401 -0
  47. pypecdp-0.3.0/src/pypecdp/cdp/indexed_db.py +528 -0
  48. pypecdp-0.3.0/src/pypecdp/cdp/input_.py +701 -0
  49. pypecdp-0.3.0/src/pypecdp/cdp/inspector.py +95 -0
  50. pypecdp-0.3.0/src/pypecdp/cdp/io.py +101 -0
  51. pypecdp-0.3.0/src/pypecdp/cdp/layer_tree.py +464 -0
  52. pypecdp-0.3.0/src/pypecdp/cdp/log.py +190 -0
  53. pypecdp-0.3.0/src/pypecdp/cdp/media.py +313 -0
  54. pypecdp-0.3.0/src/pypecdp/cdp/memory.py +305 -0
  55. pypecdp-0.3.0/src/pypecdp/cdp/network.py +4673 -0
  56. pypecdp-0.3.0/src/pypecdp/cdp/overlay.py +1397 -0
  57. pypecdp-0.3.0/src/pypecdp/cdp/page.py +4029 -0
  58. pypecdp-0.3.0/src/pypecdp/cdp/performance.py +124 -0
  59. pypecdp-0.3.0/src/pypecdp/cdp/performance_timeline.py +200 -0
  60. pypecdp-0.3.0/src/pypecdp/cdp/preload.py +569 -0
  61. pypecdp-0.3.0/src/pypecdp/cdp/profiler.py +420 -0
  62. pypecdp-0.3.0/src/pypecdp/cdp/pwa.py +278 -0
  63. pypecdp-0.3.0/src/pypecdp/cdp/py.typed +0 -0
  64. pypecdp-0.3.0/src/pypecdp/cdp/runtime.py +1589 -0
  65. pypecdp-0.3.0/src/pypecdp/cdp/schema.py +50 -0
  66. pypecdp-0.3.0/src/pypecdp/cdp/security.py +518 -0
  67. pypecdp-0.3.0/src/pypecdp/cdp/service_worker.py +401 -0
  68. pypecdp-0.3.0/src/pypecdp/cdp/storage.py +2438 -0
  69. pypecdp-0.3.0/src/pypecdp/cdp/system_info.py +327 -0
  70. pypecdp-0.3.0/src/pypecdp/cdp/target.py +818 -0
  71. pypecdp-0.3.0/src/pypecdp/cdp/tethering.py +65 -0
  72. pypecdp-0.3.0/src/pypecdp/cdp/tracing.py +377 -0
  73. pypecdp-0.3.0/src/pypecdp/cdp/util.py +19 -0
  74. pypecdp-0.3.0/src/pypecdp/cdp/web_audio.py +606 -0
  75. pypecdp-0.3.0/src/pypecdp/cdp/web_authn.py +581 -0
  76. pypecdp-0.3.0/src/pypecdp/cdp_pipe.py +122 -0
  77. pypecdp-0.3.0/src/pypecdp/config.py +106 -0
  78. pypecdp-0.3.0/src/pypecdp/elem.py +235 -0
  79. pypecdp-0.3.0/src/pypecdp/logger.py +48 -0
  80. pypecdp-0.3.0/src/pypecdp/tab.py +358 -0
@@ -0,0 +1,70 @@
1
+ name: Publish distribution 📦 to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ build:
9
+ name: Build distribution 📦
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v6
13
+ with:
14
+ persist-credentials: false
15
+ - name: Set up Python
16
+ uses: actions/setup-python@v6
17
+ with:
18
+ python-version: "3.12"
19
+ - name: Install pypa/build
20
+ run: >-
21
+ python3 -m
22
+ pip install
23
+ build
24
+ --user
25
+ - name: Build a binary wheel and a source tarball
26
+ run: python3 -m build
27
+ - name: Verify package can be imported
28
+ run: |
29
+ python3 -m pip install dist/*.whl
30
+ python3 -c "import pypecdp; print(f'✓ Package version: {pypecdp.__version__}')"
31
+ - name: Store the distribution packages
32
+ uses: actions/upload-artifact@v5
33
+ with:
34
+ name: python-package-distributions
35
+ path: dist/
36
+ publish-to-pypi:
37
+ name: >-
38
+ Publish distribution 📦 to PyPI
39
+ if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes
40
+ needs:
41
+ - build
42
+ runs-on: ubuntu-latest
43
+ environment:
44
+ name: pypi
45
+ url: https://pypi.org/p/pypecdp
46
+ permissions:
47
+ id-token: write # IMPORTANT: mandatory for trusted publishing
48
+ steps:
49
+ - name: Download all the dists
50
+ uses: actions/download-artifact@v6
51
+ with:
52
+ name: python-package-distributions
53
+ path: dist/
54
+ - name: Validate version matches tag
55
+ run: |
56
+ # Extract version from package filename
57
+ WHEEL_FILE=$(ls dist/*.whl)
58
+ PKG_VERSION=$(echo $WHEEL_FILE | sed -n 's/.*pypecdp-\([0-9.]*\)-.*/\1/p')
59
+ # Extract version from tag (remove 'v' prefix if present)
60
+ TAG_VERSION=${GITHUB_REF#refs/tags/}
61
+ TAG_VERSION=${TAG_VERSION#v}
62
+ echo "Package version: $PKG_VERSION"
63
+ echo "Tag version: $TAG_VERSION"
64
+ if [ "$PKG_VERSION" != "$TAG_VERSION" ]; then
65
+ echo "❌ Error: Package version ($PKG_VERSION) does not match tag ($TAG_VERSION)"
66
+ exit 1
67
+ fi
68
+ echo "✓ Version validation passed"
69
+ - name: Publish distribution 📦 to PyPI
70
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,56 @@
1
+ # Temporary and binary files
2
+ *~
3
+ *.py[cod]
4
+ *.so
5
+ *.cfg
6
+ !.isort.cfg
7
+ !setup.cfg
8
+ *.orig
9
+ *.log
10
+ *.pot
11
+ __pycache__/*
12
+ .cache/*
13
+ .*.swp
14
+ */.ipynb_checkpoints/*
15
+ .DS_Store
16
+ src/*.py
17
+
18
+ # Project files
19
+ .ropeproject
20
+ .project
21
+ .pydevproject
22
+ .settings
23
+ .idea
24
+ .vscode
25
+ tags
26
+
27
+ # Package files
28
+ *.egg
29
+ *.eggs/
30
+ .installed.cfg
31
+ *.egg-info
32
+
33
+ # Unittest and coverage
34
+ htmlcov/*
35
+ .coverage
36
+ .coverage.*
37
+ .tox
38
+ junit*.xml
39
+ coverage.xml
40
+ .pytest_cache/
41
+
42
+ # Build and docs folder/files
43
+ build/*
44
+ dist/*
45
+ sdist/*
46
+ docs/api/*
47
+ docs/_rst/*
48
+ docs/_build/*
49
+ cover/*
50
+ MANIFEST
51
+
52
+ # Per-project virtualenvs
53
+ .venv*/
54
+ .conda*/
55
+ .python-version
56
+ /.mypy_cache/
pypecdp-0.3.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 sohaib17
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,3 @@
1
+ include LICENSE
2
+ include README.md
3
+ recursive-include src/pypecdp *.py
pypecdp-0.3.0/PKG-INFO ADDED
@@ -0,0 +1,203 @@
1
+ Metadata-Version: 2.4
2
+ Name: pypecdp
3
+ Version: 0.3.0
4
+ Summary: Async Chrome DevTools Protocol over POSIX pipes.
5
+ Project-URL: Homepage, https://github.com/sohaib17/pypecdp
6
+ Project-URL: Source, https://github.com/sohaib17/pypecdp
7
+ Project-URL: Issues, https://github.com/sohaib17/pypecdp/issues
8
+ Author: sohaib17
9
+ License: MIT License
10
+ License-File: LICENSE
11
+ Keywords: asyncio,automation,cdp,chrome,chromium,devtools,headless
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: POSIX :: Linux
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Internet :: WWW/HTTP
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Requires-Python: >=3.12
20
+ Requires-Dist: deprecated>=1.2.0
21
+ Description-Content-Type: text/markdown
22
+
23
+ # pypecdp
24
+
25
+ Fully async Chrome DevTools Protocol over POSIX pipes with a high-level Browser/Tab/Elem API for Python 3.12+ on Linux.
26
+
27
+ Chrome automation using `--remote-debugging-pipe` (no websockets, no ports, just pipes) with bundled CDP protocol classes.
28
+
29
+ Inspired by [playwright-python](https://github.com/microsoft/playwright-python), [python-cdp](https://github.com/HMaker/python-cdp) and [nodriver](https://github.com/ultrafunkamsterdam/nodriver).
30
+
31
+ ## Features
32
+
33
+ - **Fully Async**: Built from ground up with asyncio for concurrent operations
34
+ - **Fast**: Direct pipe communication via file descriptors - no websockets, no network overhead
35
+ - **Zero dependencies**: No external dependencies required - built-in Python libraries only
36
+ - **Secure**: Browser only communicates over local pipes, no open ports accessible to other processes
37
+ - **No zombies**: No risk of orphaned Chrome processes - automatic lifecycle management
38
+ - **Linux focused**: Leverages POSIX pipes and process management
39
+
40
+ ## Install
41
+
42
+ ```bash
43
+ pip install pypecdp
44
+ ```
45
+
46
+ Install Chromium if needed:
47
+
48
+ ```bash
49
+ # Ubuntu/Debian
50
+ sudo apt-get install chromium-browser
51
+
52
+ # Fedora
53
+ sudo dnf install chromium
54
+
55
+ # Arch
56
+ sudo pacman -S chromium
57
+ ```
58
+
59
+ ## Quick Start
60
+
61
+ ```python
62
+ import asyncio
63
+ from pypecdp import Browser
64
+
65
+ async def main():
66
+ # Launch browser
67
+ browser = await Browser.start(
68
+ chrome_path="chromium",
69
+ headless=True
70
+ )
71
+
72
+ # Open a tab
73
+ tab = await browser.navigate("https://example.com")
74
+
75
+ # Select and interact with elements
76
+ h1 = await tab.select("h1")
77
+ if h1:
78
+ text = await h1.text()
79
+ print(f"Page heading: {text}")
80
+
81
+ # Evaluate JavaScript
82
+ result = await tab.eval("document.title")
83
+ print(f"Title: {result.value}")
84
+
85
+ # Close browser
86
+ await browser.close()
87
+
88
+ asyncio.run(main())
89
+ ```
90
+
91
+ ## Usage Guide
92
+
93
+ ### Browser Management
94
+
95
+ ```python
96
+ from pypecdp import Browser, Config
97
+
98
+ # Simple start
99
+ browser = await Browser.start(chrome_path="chromium", headless=True)
100
+
101
+ # Advanced configuration
102
+ config = Config(
103
+ chrome_path="/usr/bin/google-chrome",
104
+ user_data_dir="/tmp/chrome-profile",
105
+ headless=True,
106
+ extra_args=["--no-sandbox", "--disable-gpu"],
107
+ switches={"disable-blink-features": "AutomationControlled"},
108
+ env={"LANG": "en_US.UTF-8"}
109
+ )
110
+ browser = await Browser.start(config=config)
111
+
112
+ # Close browser
113
+ await browser.close()
114
+ ```
115
+
116
+ ### Event Handlers
117
+
118
+ ```python
119
+ from pypecdp import cdp
120
+
121
+ # Tab-level events (requires domain enable!)
122
+ await tab.send(cdp.runtime.enable()) # Required for runtime events!
123
+
124
+ async def on_console(event):
125
+ print(f"Console {event.type_}: {event.args}")
126
+
127
+ tab.on(cdp.runtime.ConsoleAPICalled, on_console)
128
+
129
+ # Browser-level events
130
+ async def on_target_created(event):
131
+ info = event.target_info
132
+ print(f"Target created: {info.type_} - {info.url}")
133
+
134
+ browser.on(cdp.target.TargetCreated, on_target_created)
135
+ ```
136
+
137
+ ### Logging
138
+
139
+ pypecdp uses Python's standard logging module. Configure via environment variables:
140
+
141
+ ```bash
142
+ # Set log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
143
+ export PYPECDP_LOG_LEVEL=DEBUG
144
+
145
+ # Set custom logger name
146
+ export PYPECDP_LOGGER=myapp.browser
147
+ ```
148
+
149
+ Or configure the logger directly in Python:
150
+
151
+ ```python
152
+ from pypecdp import logger
153
+ import logging
154
+
155
+ # Set log level
156
+ logger.setLevel(logging.DEBUG)
157
+
158
+ # Add custom handler
159
+ handler = logging.FileHandler("pypecdp.log")
160
+ logger.addHandler(handler)
161
+ ```
162
+
163
+ ## Error Handling
164
+
165
+ ```python
166
+ try:
167
+ browser = await Browser.start()
168
+ tab = await browser.navigate("https://example.com")
169
+
170
+ # Your automation code
171
+ result = await tab.eval("document.title")
172
+
173
+ except RuntimeError as e:
174
+ # CDP protocol errors
175
+ print(f"CDP Error: {e}")
176
+ except ConnectionError as e:
177
+ # Connection lost
178
+ print(f"Connection Error: {e}")
179
+ except Exception as e:
180
+ # Other errors
181
+ print(f"Error: {e}")
182
+ finally:
183
+ # Always cleanup
184
+ await browser.close()
185
+ ```
186
+
187
+ ## Requirements
188
+
189
+ - Python 3.12+
190
+ - Linux (uses POSIX pipes and `preexec_fn`)
191
+ - Chromium or Google Chrome
192
+
193
+ ## Links
194
+
195
+ - [Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/)
196
+
197
+ ## License
198
+
199
+ MIT License - See LICENSE file for details.
200
+
201
+ ## Contributing
202
+
203
+ Contributions welcome! This project aims to provide a clean, type-safe interface to Chrome automation on Linux.
@@ -0,0 +1,181 @@
1
+ # pypecdp
2
+
3
+ Fully async Chrome DevTools Protocol over POSIX pipes with a high-level Browser/Tab/Elem API for Python 3.12+ on Linux.
4
+
5
+ Chrome automation using `--remote-debugging-pipe` (no websockets, no ports, just pipes) with bundled CDP protocol classes.
6
+
7
+ Inspired by [playwright-python](https://github.com/microsoft/playwright-python), [python-cdp](https://github.com/HMaker/python-cdp) and [nodriver](https://github.com/ultrafunkamsterdam/nodriver).
8
+
9
+ ## Features
10
+
11
+ - **Fully Async**: Built from ground up with asyncio for concurrent operations
12
+ - **Fast**: Direct pipe communication via file descriptors - no websockets, no network overhead
13
+ - **Zero dependencies**: No external dependencies required - built-in Python libraries only
14
+ - **Secure**: Browser only communicates over local pipes, no open ports accessible to other processes
15
+ - **No zombies**: No risk of orphaned Chrome processes - automatic lifecycle management
16
+ - **Linux focused**: Leverages POSIX pipes and process management
17
+
18
+ ## Install
19
+
20
+ ```bash
21
+ pip install pypecdp
22
+ ```
23
+
24
+ Install Chromium if needed:
25
+
26
+ ```bash
27
+ # Ubuntu/Debian
28
+ sudo apt-get install chromium-browser
29
+
30
+ # Fedora
31
+ sudo dnf install chromium
32
+
33
+ # Arch
34
+ sudo pacman -S chromium
35
+ ```
36
+
37
+ ## Quick Start
38
+
39
+ ```python
40
+ import asyncio
41
+ from pypecdp import Browser
42
+
43
+ async def main():
44
+ # Launch browser
45
+ browser = await Browser.start(
46
+ chrome_path="chromium",
47
+ headless=True
48
+ )
49
+
50
+ # Open a tab
51
+ tab = await browser.navigate("https://example.com")
52
+
53
+ # Select and interact with elements
54
+ h1 = await tab.select("h1")
55
+ if h1:
56
+ text = await h1.text()
57
+ print(f"Page heading: {text}")
58
+
59
+ # Evaluate JavaScript
60
+ result = await tab.eval("document.title")
61
+ print(f"Title: {result.value}")
62
+
63
+ # Close browser
64
+ await browser.close()
65
+
66
+ asyncio.run(main())
67
+ ```
68
+
69
+ ## Usage Guide
70
+
71
+ ### Browser Management
72
+
73
+ ```python
74
+ from pypecdp import Browser, Config
75
+
76
+ # Simple start
77
+ browser = await Browser.start(chrome_path="chromium", headless=True)
78
+
79
+ # Advanced configuration
80
+ config = Config(
81
+ chrome_path="/usr/bin/google-chrome",
82
+ user_data_dir="/tmp/chrome-profile",
83
+ headless=True,
84
+ extra_args=["--no-sandbox", "--disable-gpu"],
85
+ switches={"disable-blink-features": "AutomationControlled"},
86
+ env={"LANG": "en_US.UTF-8"}
87
+ )
88
+ browser = await Browser.start(config=config)
89
+
90
+ # Close browser
91
+ await browser.close()
92
+ ```
93
+
94
+ ### Event Handlers
95
+
96
+ ```python
97
+ from pypecdp import cdp
98
+
99
+ # Tab-level events (requires domain enable!)
100
+ await tab.send(cdp.runtime.enable()) # Required for runtime events!
101
+
102
+ async def on_console(event):
103
+ print(f"Console {event.type_}: {event.args}")
104
+
105
+ tab.on(cdp.runtime.ConsoleAPICalled, on_console)
106
+
107
+ # Browser-level events
108
+ async def on_target_created(event):
109
+ info = event.target_info
110
+ print(f"Target created: {info.type_} - {info.url}")
111
+
112
+ browser.on(cdp.target.TargetCreated, on_target_created)
113
+ ```
114
+
115
+ ### Logging
116
+
117
+ pypecdp uses Python's standard logging module. Configure via environment variables:
118
+
119
+ ```bash
120
+ # Set log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
121
+ export PYPECDP_LOG_LEVEL=DEBUG
122
+
123
+ # Set custom logger name
124
+ export PYPECDP_LOGGER=myapp.browser
125
+ ```
126
+
127
+ Or configure the logger directly in Python:
128
+
129
+ ```python
130
+ from pypecdp import logger
131
+ import logging
132
+
133
+ # Set log level
134
+ logger.setLevel(logging.DEBUG)
135
+
136
+ # Add custom handler
137
+ handler = logging.FileHandler("pypecdp.log")
138
+ logger.addHandler(handler)
139
+ ```
140
+
141
+ ## Error Handling
142
+
143
+ ```python
144
+ try:
145
+ browser = await Browser.start()
146
+ tab = await browser.navigate("https://example.com")
147
+
148
+ # Your automation code
149
+ result = await tab.eval("document.title")
150
+
151
+ except RuntimeError as e:
152
+ # CDP protocol errors
153
+ print(f"CDP Error: {e}")
154
+ except ConnectionError as e:
155
+ # Connection lost
156
+ print(f"Connection Error: {e}")
157
+ except Exception as e:
158
+ # Other errors
159
+ print(f"Error: {e}")
160
+ finally:
161
+ # Always cleanup
162
+ await browser.close()
163
+ ```
164
+
165
+ ## Requirements
166
+
167
+ - Python 3.12+
168
+ - Linux (uses POSIX pipes and `preexec_fn`)
169
+ - Chromium or Google Chrome
170
+
171
+ ## Links
172
+
173
+ - [Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/)
174
+
175
+ ## License
176
+
177
+ MIT License - See LICENSE file for details.
178
+
179
+ ## Contributing
180
+
181
+ Contributions welcome! This project aims to provide a clean, type-safe interface to Chrome automation on Linux.
@@ -0,0 +1,165 @@
1
+ """Example: Console logging using Runtime.consoleAPICalled events.
2
+
3
+ Demonstrates how to capture and monitor console messages from the browser:
4
+ - Subscribing to Runtime.consoleAPICalled events
5
+ - Capturing console.log, console.warn, console.error messages
6
+ - Extracting and displaying console message arguments
7
+ - Real-time console monitoring during page execution
8
+ """
9
+
10
+ import asyncio
11
+ import base64
12
+ import os
13
+ from typing import Any
14
+
15
+ from pypecdp import Browser, cdp
16
+
17
+
18
+ async def main() -> None:
19
+ """Main."""
20
+ # Launch browser
21
+ browser = await Browser.start(
22
+ chrome_path=os.environ.get("PYPECDP_CHROME_PATH", "chromium"),
23
+ headless=True,
24
+ extra_args=["--no-sandbox"],
25
+ )
26
+
27
+ tab = await browser.navigate("about:blank")
28
+ await tab.send(cdp.runtime.enable())
29
+ print("Browser launched\n")
30
+
31
+ # Storage for console messages
32
+ console_messages: list[tuple[str, str]] = []
33
+
34
+ # Handler for console API calls
35
+ async def handle_console(event: Any) -> None:
36
+ """Handle Runtime.consoleAPICalled events."""
37
+ # event is a ConsoleAPICalled object
38
+ msg_type = event.type_
39
+ args = event.args
40
+
41
+ # Extract values from arguments
42
+ values = []
43
+ for arg in args:
44
+ arg_type = arg.type_
45
+ if arg_type == "string":
46
+ values.append(arg.value if hasattr(arg, "value") else "")
47
+ elif arg_type == "number":
48
+ values.append(str(arg.value if hasattr(arg, "value") else ""))
49
+ elif arg_type == "boolean":
50
+ values.append(str(arg.value if hasattr(arg, "value") else ""))
51
+ elif arg_type == "object":
52
+ desc = (
53
+ arg.description
54
+ if hasattr(arg, "description")
55
+ else "Object"
56
+ )
57
+ values.append(desc)
58
+ else:
59
+ values.append(f"[{arg_type}]")
60
+
61
+ message = " ".join(values)
62
+
63
+ # Format with prefix markers
64
+ prefix = {
65
+ "log": "[i]",
66
+ "info": "[i]",
67
+ "warn": "[!]",
68
+ "error": "[x]",
69
+ "debug": "[d]",
70
+ }.get(msg_type, "[*]")
71
+
72
+ formatted = f"{prefix} [{msg_type.upper()}] {message}"
73
+ console_messages.append((msg_type, message))
74
+ print(formatted)
75
+
76
+ # Register the console handler
77
+ tab.on(cdp.runtime.ConsoleAPICalled, handle_console)
78
+
79
+ print("=" * 60)
80
+ print("Capturing console messages...")
81
+ print("=" * 60 + "\n")
82
+
83
+ # Execute various console commands
84
+ await tab.eval("console.log('Hello from pypecdp!')")
85
+ await asyncio.sleep(0.1)
86
+
87
+ await tab.eval("console.log('Multiple', 'arguments', 123)")
88
+ await asyncio.sleep(0.1)
89
+
90
+ await tab.eval("console.warn('This is a warning')")
91
+ await asyncio.sleep(0.1)
92
+
93
+ await tab.eval("console.error('This is an error')")
94
+ await asyncio.sleep(0.1)
95
+
96
+ await tab.eval("console.info('Info message with number:', 42)")
97
+ await asyncio.sleep(0.1)
98
+
99
+ await tab.eval("console.log('Boolean:', true, false)")
100
+ await asyncio.sleep(0.1)
101
+
102
+ await tab.eval("console.log('Object:', {foo: 'bar', num: 123})")
103
+ await asyncio.sleep(0.1)
104
+
105
+ await tab.eval("console.log('Array:', [1, 2, 3])")
106
+ await asyncio.sleep(0.1)
107
+
108
+ # Navigate to a page and capture its console output
109
+ print("\n" + "=" * 60)
110
+ print("Navigating to page with console output...")
111
+ print("=" * 60 + "\n")
112
+
113
+ # Create a page with console output
114
+ html_content = """
115
+ <!DOCTYPE html>
116
+ <html>
117
+ <head><title>Console Test</title></head>
118
+ <body>
119
+ <h1>Console Test Page</h1>
120
+ <script>
121
+ console.log('Page loaded!');
122
+ console.info('Current URL:', window.location.href);
123
+ console.warn('This is a warning from the page');
124
+
125
+ setTimeout(() => {
126
+ console.log('Delayed message after 500ms');
127
+ }, 500);
128
+
129
+ setTimeout(() => {
130
+ console.error('Simulated error after 1000ms');
131
+ }, 1000);
132
+ </script>
133
+ </body>
134
+ </html>
135
+ """
136
+
137
+ # Navigate to data URL with the HTML
138
+ encoded = base64.b64encode(html_content.encode()).decode()
139
+ await tab.navigate(f"data:text/html;base64,{encoded}")
140
+
141
+ # Wait for delayed messages
142
+ await asyncio.sleep(1.5)
143
+
144
+ # Summary
145
+ print("\n" + "=" * 60)
146
+ print("Summary")
147
+ print("=" * 60)
148
+ print(f"Total console messages captured: {len(console_messages)}")
149
+
150
+ # Count by type
151
+ type_counts: dict[str, int] = {}
152
+ for msg_type, _ in console_messages:
153
+ type_counts[msg_type] = type_counts.get(msg_type, 0) + 1
154
+
155
+ print("\nBreakdown by type:")
156
+ for msg_type, count in sorted(type_counts.items()):
157
+ print(f" {msg_type}: {count}")
158
+
159
+ # Clean up
160
+ await browser.close()
161
+ print("\nBrowser closed")
162
+
163
+
164
+ if __name__ == "__main__":
165
+ asyncio.run(main())