pydoll-python 2.9.0__tar.gz → 2.9.2__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.
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/PKG-INFO +14 -1
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/README.md +13 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/browser/chromium/base.py +136 -21
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/browser/chromium/chrome.py +8 -1
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/browser/chromium/edge.py +8 -1
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/browser/managers/browser_options_manager.py +11 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/browser/managers/browser_process_manager.py +19 -2
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/browser/managers/proxy_manager.py +22 -2
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/browser/managers/temp_dir_manager.py +10 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/browser/requests/request.py +50 -9
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/browser/requests/response.py +11 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/browser/tab.py +154 -23
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/connection/connection_handler.py +36 -5
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/connection/managers/commands_manager.py +6 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/connection/managers/events_manager.py +9 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/elements/mixins/find_elements_mixin.py +42 -1
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/elements/web_element.py +56 -4
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/protocol/page/events.py +2 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pyproject.toml +1 -1
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/LICENSE +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/__init__.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/browser/__init__.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/browser/chromium/__init__.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/browser/interfaces.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/browser/managers/__init__.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/browser/options.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/browser/requests/__init__.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/commands/__init__.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/commands/browser_commands.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/commands/dom_commands.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/commands/fetch_commands.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/commands/input_commands.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/commands/network_commands.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/commands/page_commands.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/commands/runtime_commands.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/commands/storage_commands.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/commands/target_commands.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/connection/__init__.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/connection/managers/__init__.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/constants.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/elements/__init__.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/elements/mixins/__init__.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/exceptions.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/protocol/__init__.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/protocol/base.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/protocol/browser/__init__.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/protocol/browser/events.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/protocol/browser/methods.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/protocol/browser/types.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/protocol/debugger/types.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/protocol/dom/__init__.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/protocol/dom/events.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/protocol/dom/methods.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/protocol/dom/types.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/protocol/emulation/types.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/protocol/fetch/__init__.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/protocol/fetch/events.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/protocol/fetch/methods.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/protocol/fetch/types.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/protocol/input/__init__.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/protocol/input/events.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/protocol/input/methods.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/protocol/input/types.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/protocol/io/types.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/protocol/network/__init__.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/protocol/network/events.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/protocol/network/methods.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/protocol/network/types.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/protocol/page/__init__.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/protocol/page/methods.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/protocol/page/types.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/protocol/runtime/__init__.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/protocol/runtime/events.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/protocol/runtime/methods.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/protocol/runtime/types.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/protocol/security/types.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/protocol/storage/__init__.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/protocol/storage/events.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/protocol/storage/methods.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/protocol/storage/types.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/protocol/target/__init__.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/protocol/target/events.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/protocol/target/methods.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/protocol/target/types.py +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/py.typed +0 -0
- {pydoll_python-2.9.0 → pydoll_python-2.9.2}/pydoll/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pydoll-python
|
|
3
|
-
Version: 2.9.
|
|
3
|
+
Version: 2.9.2
|
|
4
4
|
Summary: Pydoll is a library for automating chromium-based browsers without a WebDriver, offering realistic interactions.
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Author: Thalison Fernandes
|
|
@@ -73,6 +73,19 @@ pip install pydoll-python
|
|
|
73
73
|
|
|
74
74
|
And that's it! Just install and start automating.
|
|
75
75
|
|
|
76
|
+
|
|
77
|
+
## ⭐ Sponsors
|
|
78
|
+
The support from sponsors is essential to keep the project alive, evolving, and accessible to the entire community. Each partnership helps cover costs, drive new features, and ensure ongoing development.
|
|
79
|
+
We are truly grateful to everyone who believes in and supports the project!
|
|
80
|
+
|
|
81
|
+
<br>
|
|
82
|
+
<p style="font-size:21px; color:black;">Browser testing via
|
|
83
|
+
<a href="https://www.lambdatest.com/?utm_source=pydoll&utm_medium=sponsor" target="_blank">
|
|
84
|
+
<img src="https://www.lambdatest.com/blue-logo.png" style="vertical-align: middle;" width="250" height="45" />
|
|
85
|
+
</a>
|
|
86
|
+
</p>
|
|
87
|
+
<br>
|
|
88
|
+
|
|
76
89
|
## 🚀 Getting Started
|
|
77
90
|
|
|
78
91
|
### Your first automation
|
|
@@ -53,6 +53,19 @@ pip install pydoll-python
|
|
|
53
53
|
|
|
54
54
|
And that's it! Just install and start automating.
|
|
55
55
|
|
|
56
|
+
|
|
57
|
+
## ⭐ Sponsors
|
|
58
|
+
The support from sponsors is essential to keep the project alive, evolving, and accessible to the entire community. Each partnership helps cover costs, drive new features, and ensure ongoing development.
|
|
59
|
+
We are truly grateful to everyone who believes in and supports the project!
|
|
60
|
+
|
|
61
|
+
<br>
|
|
62
|
+
<p style="font-size:21px; color:black;">Browser testing via
|
|
63
|
+
<a href="https://www.lambdatest.com/?utm_source=pydoll&utm_medium=sponsor" target="_blank">
|
|
64
|
+
<img src="https://www.lambdatest.com/blue-logo.png" style="vertical-align: middle;" width="250" height="45" />
|
|
65
|
+
</a>
|
|
66
|
+
</p>
|
|
67
|
+
<br>
|
|
68
|
+
|
|
56
69
|
## 🚀 Getting Started
|
|
57
70
|
|
|
58
71
|
### Your first automation
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import json
|
|
3
|
+
import logging
|
|
3
4
|
import os
|
|
4
5
|
import shutil
|
|
5
6
|
import warnings
|
|
@@ -59,6 +60,8 @@ from pydoll.protocol.target.methods import (
|
|
|
59
60
|
)
|
|
60
61
|
from pydoll.protocol.target.types import TargetInfo
|
|
61
62
|
|
|
63
|
+
logger = logging.getLogger(__name__)
|
|
64
|
+
|
|
62
65
|
|
|
63
66
|
class Browser(ABC): # noqa: PLR0904
|
|
64
67
|
"""
|
|
@@ -95,14 +98,21 @@ class Browser(ABC): # noqa: PLR0904
|
|
|
95
98
|
self._backup_preferences_dir = ''
|
|
96
99
|
self._tabs_opened: dict[str, Tab] = {}
|
|
97
100
|
self._context_proxy_auth: dict[str, tuple[str, str]] = {}
|
|
101
|
+
logger.debug(
|
|
102
|
+
f'Browser initialized: port={self._connection_port}, '
|
|
103
|
+
f'headless={getattr(self.options, "headless", None)}'
|
|
104
|
+
)
|
|
98
105
|
|
|
99
106
|
async def __aenter__(self) -> 'Browser':
|
|
100
107
|
"""Async context manager entry."""
|
|
108
|
+
logger.debug('Entering browser async context')
|
|
101
109
|
return self
|
|
102
110
|
|
|
103
111
|
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
104
112
|
"""Async context manager exit with cleanup."""
|
|
113
|
+
logger.debug(f'Exiting browser async context: exc_type={exc_type}')
|
|
105
114
|
if self._backup_preferences_dir:
|
|
115
|
+
logger.debug(f'Restoring backup preferences directory: {self._backup_preferences_dir}')
|
|
106
116
|
user_data_dir = self._get_user_data_dir()
|
|
107
117
|
shutil.copy2(
|
|
108
118
|
self._backup_preferences_dir,
|
|
@@ -129,8 +139,10 @@ class Browser(ABC): # noqa: PLR0904
|
|
|
129
139
|
You are supposed to use this method only if you want to connect to a browser
|
|
130
140
|
that is already running.
|
|
131
141
|
"""
|
|
142
|
+
logger.info(f'Connecting to browser via WebSocket: {ws_address}')
|
|
132
143
|
await self._setup_ws_address(ws_address)
|
|
133
144
|
tabs = await self.get_opened_tabs()
|
|
145
|
+
logger.info(f'Connected. Tabs available: {len(tabs)}')
|
|
134
146
|
return tabs[0]
|
|
135
147
|
|
|
136
148
|
async def start(self, headless: bool = False) -> Tab:
|
|
@@ -156,19 +168,24 @@ class Browser(ABC): # noqa: PLR0904
|
|
|
156
168
|
self.options.headless = headless
|
|
157
169
|
|
|
158
170
|
binary_location = self.options.binary_location or self._get_default_binary_location()
|
|
171
|
+
logger.debug('Resolved binary location: %s', binary_location)
|
|
159
172
|
|
|
160
173
|
self._setup_user_dir()
|
|
174
|
+
logger.debug('User data directory configured')
|
|
161
175
|
proxy_config = self._proxy_manager.get_proxy_credentials()
|
|
162
176
|
|
|
177
|
+
logger.info(f'Starting browser process on port {self._connection_port}')
|
|
163
178
|
self._browser_process_manager.start_browser_process(
|
|
164
179
|
binary_location, self._connection_port, self.options.arguments
|
|
165
180
|
)
|
|
166
181
|
await self._verify_browser_running()
|
|
182
|
+
logger.info('Browser process started and responsive')
|
|
167
183
|
await self._configure_proxy(proxy_config[0], proxy_config[1])
|
|
168
184
|
|
|
169
185
|
valid_tab_id = await self._get_valid_tab_id(await self.get_targets())
|
|
170
186
|
tab = Tab(self, target_id=valid_tab_id, connection_port=self._connection_port)
|
|
171
187
|
self._tabs_opened[valid_tab_id] = tab
|
|
188
|
+
logger.info(f'Initial tab attached: {valid_tab_id}')
|
|
172
189
|
return tab
|
|
173
190
|
|
|
174
191
|
async def stop(self):
|
|
@@ -182,12 +199,15 @@ class Browser(ABC): # noqa: PLR0904
|
|
|
182
199
|
BrowserNotRunning: If the browser is not currently running.
|
|
183
200
|
"""
|
|
184
201
|
if not await self._is_browser_running():
|
|
202
|
+
logger.error('Stop called but browser is not running')
|
|
185
203
|
raise BrowserNotRunning()
|
|
186
204
|
|
|
205
|
+
logger.info('Stopping browser process')
|
|
187
206
|
await self._execute_command(BrowserCommands.close())
|
|
188
207
|
self._browser_process_manager.stop_process()
|
|
189
208
|
self._temp_directory_manager.cleanup()
|
|
190
209
|
await self._connection_handler.close()
|
|
210
|
+
logger.info('Browser process stopped and resources cleaned up')
|
|
191
211
|
|
|
192
212
|
async def create_browser_context(
|
|
193
213
|
self, proxy_server: Optional[str] = None, proxy_bypass_list: Optional[str] = None
|
|
@@ -210,6 +230,10 @@ class Browser(ABC): # noqa: PLR0904
|
|
|
210
230
|
extracted_auth: Optional[tuple[str, str]] = None
|
|
211
231
|
if proxy_server:
|
|
212
232
|
sanitized_proxy, extracted_auth = self._sanitize_proxy_and_extract_auth(proxy_server)
|
|
233
|
+
logger.debug(
|
|
234
|
+
f'Creating browser context with proxy: {sanitized_proxy}'
|
|
235
|
+
f'(credentials provided={bool(extracted_auth)})'
|
|
236
|
+
)
|
|
213
237
|
|
|
214
238
|
response: CreateBrowserContextResponse = await self._execute_command(
|
|
215
239
|
TargetCommands.create_browser_context(
|
|
@@ -220,6 +244,7 @@ class Browser(ABC): # noqa: PLR0904
|
|
|
220
244
|
context_id = response['result']['browserContextId']
|
|
221
245
|
if extracted_auth:
|
|
222
246
|
self._context_proxy_auth[context_id] = extracted_auth
|
|
247
|
+
logger.info(f'Created browser context: {context_id}')
|
|
223
248
|
return context_id
|
|
224
249
|
|
|
225
250
|
async def delete_browser_context(self, browser_context_id: str):
|
|
@@ -232,6 +257,7 @@ class Browser(ABC): # noqa: PLR0904
|
|
|
232
257
|
Note:
|
|
233
258
|
Closes all associated tabs immediately.
|
|
234
259
|
"""
|
|
260
|
+
logger.info(f'Deleting browser context: {browser_context_id}')
|
|
235
261
|
return await self._execute_command(
|
|
236
262
|
TargetCommands.dispose_browser_context(browser_context_id)
|
|
237
263
|
)
|
|
@@ -241,6 +267,7 @@ class Browser(ABC): # noqa: PLR0904
|
|
|
241
267
|
response: GetBrowserContextsResponse = await self._execute_command(
|
|
242
268
|
TargetCommands.get_browser_contexts()
|
|
243
269
|
)
|
|
270
|
+
logger.debug(f'Fetched {len(response["result"]["browserContextIds"])} browser contexts')
|
|
244
271
|
return response['result']['browserContextIds']
|
|
245
272
|
|
|
246
273
|
async def new_tab(self, url: str = '', browser_context_id: Optional[str] = None) -> Tab:
|
|
@@ -254,6 +281,7 @@ class Browser(ABC): # noqa: PLR0904
|
|
|
254
281
|
Returns:
|
|
255
282
|
Tab instance for page navigation and element interaction.
|
|
256
283
|
"""
|
|
284
|
+
logger.info(f'Creating new tab (context={browser_context_id})')
|
|
257
285
|
response: CreateTargetResponse = await self._execute_command(
|
|
258
286
|
TargetCommands.create_target(
|
|
259
287
|
browser_context_id=browser_context_id,
|
|
@@ -263,7 +291,9 @@ class Browser(ABC): # noqa: PLR0904
|
|
|
263
291
|
tab = Tab(self, **self._get_tab_kwargs(target_id, browser_context_id))
|
|
264
292
|
self._tabs_opened[target_id] = tab
|
|
265
293
|
await self._setup_context_proxy_auth_for_tab(tab, browser_context_id)
|
|
266
|
-
if url:
|
|
294
|
+
if url:
|
|
295
|
+
await tab.go_to(url)
|
|
296
|
+
logger.info(f'New tab created: {target_id}')
|
|
267
297
|
return tab
|
|
268
298
|
|
|
269
299
|
async def get_targets(self) -> list[TargetInfo]:
|
|
@@ -277,6 +307,7 @@ class Browser(ABC): # noqa: PLR0904
|
|
|
277
307
|
List of TargetInfo objects.
|
|
278
308
|
"""
|
|
279
309
|
response: GetTargetsResponse = await self._execute_command(TargetCommands.get_targets())
|
|
310
|
+
logger.debug(f'Fetched {len(response["result"]["targetInfos"])} targets')
|
|
280
311
|
return response['result']['targetInfos']
|
|
281
312
|
|
|
282
313
|
async def get_opened_tabs(self) -> list[Tab]:
|
|
@@ -305,10 +336,14 @@ class Browser(ABC): # noqa: PLR0904
|
|
|
305
336
|
for target_id in reversed(remaining_target_ids)
|
|
306
337
|
]
|
|
307
338
|
self._tabs_opened.update(dict(zip(remaining_target_ids, new_tabs)))
|
|
339
|
+
logger.debug(
|
|
340
|
+
f'Opened tabs resolved: existing={len(existing_tabs)}, new={len(new_tabs)}',
|
|
341
|
+
)
|
|
308
342
|
return existing_tabs + new_tabs
|
|
309
343
|
|
|
310
344
|
async def set_download_path(self, path: str, browser_context_id: Optional[str] = None):
|
|
311
345
|
"""Set download directory path (convenience method for set_download_behavior)."""
|
|
346
|
+
logger.info(f'Setting download path: {path} (context={browser_context_id})')
|
|
312
347
|
return await self._execute_command(
|
|
313
348
|
BrowserCommands.set_download_behavior(
|
|
314
349
|
behavior=DownloadBehavior.ALLOW,
|
|
@@ -333,6 +368,11 @@ class Browser(ABC): # noqa: PLR0904
|
|
|
333
368
|
browser_context_id: Context to apply to (default if None).
|
|
334
369
|
events_enabled: Generate download events for progress tracking.
|
|
335
370
|
"""
|
|
371
|
+
logger.info(
|
|
372
|
+
f'Setting download behavior: behavior={behavior},'
|
|
373
|
+
f'path={download_path}, context={browser_context_id},'
|
|
374
|
+
f'events={events_enabled}'
|
|
375
|
+
)
|
|
336
376
|
return await self._execute_command(
|
|
337
377
|
BrowserCommands.set_download_behavior(
|
|
338
378
|
behavior=behavior,
|
|
@@ -344,12 +384,14 @@ class Browser(ABC): # noqa: PLR0904
|
|
|
344
384
|
|
|
345
385
|
async def delete_all_cookies(self, browser_context_id: Optional[str] = None):
|
|
346
386
|
"""Delete all cookies (session, persistent, third-party) from browser or context."""
|
|
387
|
+
logger.info(f'Clearing all cookies (context={browser_context_id})')
|
|
347
388
|
return await self._execute_command(StorageCommands.clear_cookies(browser_context_id))
|
|
348
389
|
|
|
349
390
|
async def set_cookies(
|
|
350
391
|
self, cookies: list[CookieParam], browser_context_id: Optional[str] = None
|
|
351
392
|
):
|
|
352
393
|
"""Set multiple cookies in browser or context."""
|
|
394
|
+
logger.debug(f'Setting {len(cookies)} cookies (context={browser_context_id})')
|
|
353
395
|
return await self._execute_command(StorageCommands.set_cookies(cookies, browser_context_id))
|
|
354
396
|
|
|
355
397
|
async def get_cookies(self, browser_context_id: Optional[str] = None) -> list[Cookie]:
|
|
@@ -357,11 +399,15 @@ class Browser(ABC): # noqa: PLR0904
|
|
|
357
399
|
response: GetCookiesResponse = await self._execute_command(
|
|
358
400
|
StorageCommands.get_cookies(browser_context_id)
|
|
359
401
|
)
|
|
402
|
+
logger.debug(
|
|
403
|
+
f'Retrieved {len(response["result"]["cookies"])} cookies (context={browser_context_id})'
|
|
404
|
+
)
|
|
360
405
|
return response['result']['cookies']
|
|
361
406
|
|
|
362
407
|
async def get_version(self) -> GetVersionResult:
|
|
363
408
|
"""Get browser version and CDP protocol information."""
|
|
364
409
|
response: GetVersionResponse = await self._execute_command(BrowserCommands.get_version())
|
|
410
|
+
logger.debug(f'Browser version: {response["result"]}')
|
|
365
411
|
return response['result']
|
|
366
412
|
|
|
367
413
|
async def get_window_id_for_target(self, target_id: str) -> int:
|
|
@@ -369,12 +415,14 @@ class Browser(ABC): # noqa: PLR0904
|
|
|
369
415
|
response: GetWindowForTargetResponse = await self._execute_command(
|
|
370
416
|
BrowserCommands.get_window_for_target(target_id)
|
|
371
417
|
)
|
|
418
|
+
logger.debug(f'Window id for target {target_id}: {response["result"]["windowId"]}')
|
|
372
419
|
return response['result']['windowId']
|
|
373
420
|
|
|
374
421
|
async def get_window_id_for_tab(self, tab: Tab) -> int:
|
|
375
422
|
"""Get window ID for tab (convenience method)."""
|
|
376
423
|
target_id = tab._target_id or (tab._ws_address.split('/')[-1] if tab._ws_address else None)
|
|
377
424
|
if not target_id:
|
|
425
|
+
logger.error('Missing target id or ws address for tab when getting window id')
|
|
378
426
|
raise MissingTargetOrWebSocket()
|
|
379
427
|
return await self.get_window_id_for_target(target_id)
|
|
380
428
|
|
|
@@ -392,11 +440,13 @@ class Browser(ABC): # noqa: PLR0904
|
|
|
392
440
|
async def set_window_maximized(self):
|
|
393
441
|
"""Maximize browser window (affects all tabs in window)."""
|
|
394
442
|
window_id = await self.get_window_id()
|
|
443
|
+
logger.info(f'Maximizing window: id={window_id}')
|
|
395
444
|
return await self._execute_command(BrowserCommands.set_window_maximized(window_id))
|
|
396
445
|
|
|
397
446
|
async def set_window_minimized(self):
|
|
398
447
|
"""Minimize browser window to taskbar/dock."""
|
|
399
448
|
window_id = await self.get_window_id()
|
|
449
|
+
logger.info(f'Minimizing window: id={window_id}')
|
|
400
450
|
return await self._execute_command(BrowserCommands.set_window_minimized(window_id))
|
|
401
451
|
|
|
402
452
|
async def set_window_bounds(self, bounds: Bounds):
|
|
@@ -408,6 +458,7 @@ class Browser(ABC): # noqa: PLR0904
|
|
|
408
458
|
Only specified properties are changed.
|
|
409
459
|
"""
|
|
410
460
|
window_id = await self.get_window_id()
|
|
461
|
+
logger.info(f'Setting window bounds: id={window_id}, bounds={bounds}')
|
|
411
462
|
return await self._execute_command(BrowserCommands.set_window_bounds(window_id, bounds))
|
|
412
463
|
|
|
413
464
|
async def grant_permissions(
|
|
@@ -426,12 +477,16 @@ class Browser(ABC): # noqa: PLR0904
|
|
|
426
477
|
origin: Origin to grant to (all origins if None).
|
|
427
478
|
browser_context_id: Context to apply to (default if None).
|
|
428
479
|
"""
|
|
480
|
+
logger.info(
|
|
481
|
+
f'Granting permissions: {permissions} (origin={origin}, context={browser_context_id})',
|
|
482
|
+
)
|
|
429
483
|
return await self._execute_command(
|
|
430
484
|
BrowserCommands.grant_permissions(permissions, origin, browser_context_id)
|
|
431
485
|
)
|
|
432
486
|
|
|
433
487
|
async def reset_permissions(self, browser_context_id: Optional[str] = None):
|
|
434
488
|
"""Reset all permissions to defaults and restore prompting behavior."""
|
|
489
|
+
logger.info(f'Resetting permissions (context={browser_context_id})')
|
|
435
490
|
return await self._execute_command(BrowserCommands.reset_permissions(browser_context_id))
|
|
436
491
|
|
|
437
492
|
@overload
|
|
@@ -467,12 +522,17 @@ class Browser(ABC): # noqa: PLR0904
|
|
|
467
522
|
function_to_register = callback_wrapper
|
|
468
523
|
else:
|
|
469
524
|
function_to_register = callback
|
|
525
|
+
logger.debug(
|
|
526
|
+
f'Registering callback: event={event_name}, temporary={temporary}, '
|
|
527
|
+
f'async={asyncio.iscoroutinefunction(callback)}'
|
|
528
|
+
)
|
|
470
529
|
return await self._connection_handler.register_callback(
|
|
471
530
|
event_name, function_to_register, temporary
|
|
472
531
|
)
|
|
473
532
|
|
|
474
533
|
async def remove_callback(self, callback_id: int):
|
|
475
534
|
"""Remove callback from browser."""
|
|
535
|
+
logger.debug(f'Removing callback: id={callback_id}')
|
|
476
536
|
return await self._connection_handler.remove_callback(callback_id)
|
|
477
537
|
|
|
478
538
|
async def enable_fetch_events(
|
|
@@ -493,6 +553,10 @@ class Browser(ABC): # noqa: PLR0904
|
|
|
493
553
|
Note:
|
|
494
554
|
Paused requests must be continued or they will timeout.
|
|
495
555
|
"""
|
|
556
|
+
logger.debug(
|
|
557
|
+
f'Enabling Fetch events: handle_auth={handle_auth_requests}, '
|
|
558
|
+
f'resource_type={resource_type}'
|
|
559
|
+
)
|
|
496
560
|
return await self._connection_handler.execute_command(
|
|
497
561
|
FetchCommands.enable(
|
|
498
562
|
handle_auth_requests=handle_auth_requests,
|
|
@@ -502,14 +566,17 @@ class Browser(ABC): # noqa: PLR0904
|
|
|
502
566
|
|
|
503
567
|
async def disable_fetch_events(self):
|
|
504
568
|
"""Disable request interception and release any paused requests."""
|
|
569
|
+
logger.debug('Disabling Fetch events')
|
|
505
570
|
return await self._connection_handler.execute_command(FetchCommands.disable())
|
|
506
571
|
|
|
507
572
|
async def enable_runtime_events(self):
|
|
508
573
|
"""Enable runtime events."""
|
|
574
|
+
logger.debug('Enabling Runtime events')
|
|
509
575
|
return await self._connection_handler.execute_command(RuntimeCommands.enable())
|
|
510
576
|
|
|
511
577
|
async def disable_runtime_events(self):
|
|
512
578
|
"""Disable runtime events."""
|
|
579
|
+
logger.debug('Disabling Runtime events')
|
|
513
580
|
return await self._connection_handler.execute_command(RuntimeCommands.disable())
|
|
514
581
|
|
|
515
582
|
async def continue_request(
|
|
@@ -524,6 +591,7 @@ class Browser(ABC): # noqa: PLR0904
|
|
|
524
591
|
"""
|
|
525
592
|
Continue paused request without modifications.
|
|
526
593
|
"""
|
|
594
|
+
logger.debug(f'Continuing request: id={request_id}')
|
|
527
595
|
return await self._execute_command(
|
|
528
596
|
FetchCommands.continue_request(
|
|
529
597
|
request_id=request_id,
|
|
@@ -537,6 +605,7 @@ class Browser(ABC): # noqa: PLR0904
|
|
|
537
605
|
|
|
538
606
|
async def fail_request(self, request_id: str, error_reason: ErrorReason):
|
|
539
607
|
"""Fail request with error code."""
|
|
608
|
+
logger.debug(f'Failing request: id={request_id}, reason={error_reason}')
|
|
540
609
|
return await self._execute_command(FetchCommands.fail_request(request_id, error_reason))
|
|
541
610
|
|
|
542
611
|
async def fulfill_request(
|
|
@@ -548,6 +617,10 @@ class Browser(ABC): # noqa: PLR0904
|
|
|
548
617
|
response_phrase: Optional[str] = None,
|
|
549
618
|
):
|
|
550
619
|
"""Fulfill request with response data."""
|
|
620
|
+
logger.debug(
|
|
621
|
+
f'Fulfilling request: id={request_id}, code={response_code}, '
|
|
622
|
+
f'headers={bool(response_headers)}, body={bool(body)}'
|
|
623
|
+
)
|
|
551
624
|
return await self._execute_command(
|
|
552
625
|
FetchCommands.fulfill_request(
|
|
553
626
|
request_id=request_id,
|
|
@@ -562,11 +635,13 @@ class Browser(ABC): # noqa: PLR0904
|
|
|
562
635
|
def _validate_connection_port(connection_port: Optional[int]):
|
|
563
636
|
"""Validate connection port."""
|
|
564
637
|
if connection_port and connection_port < 0:
|
|
638
|
+
logger.error(f'Invalid connection port: {connection_port}')
|
|
565
639
|
raise InvalidConnectionPort()
|
|
566
640
|
|
|
567
641
|
async def _continue_request_callback(self, event: RequestPausedEvent):
|
|
568
642
|
"""Internal callback to continue paused requests."""
|
|
569
643
|
request_id = event['params']['requestId']
|
|
644
|
+
logger.debug(f'[Fetch] REQUEST_PAUSED -> continue: id={request_id}')
|
|
570
645
|
return await self.continue_request(request_id)
|
|
571
646
|
|
|
572
647
|
async def _continue_request_with_auth_callback(
|
|
@@ -577,6 +652,10 @@ class Browser(ABC): # noqa: PLR0904
|
|
|
577
652
|
):
|
|
578
653
|
"""Internal callback for proxy authentication."""
|
|
579
654
|
request_id = event['params']['requestId']
|
|
655
|
+
logger.debug(
|
|
656
|
+
f'[Fetch] AUTH_REQUIRED -> provide credentials: id={request_id}, '
|
|
657
|
+
f'user_set={bool(proxy_username)}'
|
|
658
|
+
)
|
|
580
659
|
response: Response = await self._execute_command(
|
|
581
660
|
FetchCommands.continue_request_with_auth(
|
|
582
661
|
request_id,
|
|
@@ -592,6 +671,7 @@ class Browser(ABC): # noqa: PLR0904
|
|
|
592
671
|
async def _tab_continue_request_callback(event: RequestPausedEvent, tab: Tab):
|
|
593
672
|
"""Internal callback to continue paused requests at Tab level."""
|
|
594
673
|
request_id = event['params']['requestId']
|
|
674
|
+
logger.debug(f'[Tab Fetch] REQUEST_PAUSED -> continue: id={request_id}')
|
|
595
675
|
return await tab.continue_request(request_id)
|
|
596
676
|
|
|
597
677
|
@staticmethod
|
|
@@ -603,6 +683,10 @@ class Browser(ABC): # noqa: PLR0904
|
|
|
603
683
|
):
|
|
604
684
|
"""Internal callback for proxy/server authentication at Tab level."""
|
|
605
685
|
request_id = event['params']['requestId']
|
|
686
|
+
logger.debug(
|
|
687
|
+
f'[Tab Fetch] AUTH_REQUIRED -> provide credentials: id={request_id}, '
|
|
688
|
+
f'user_set={bool(proxy_username)}'
|
|
689
|
+
)
|
|
606
690
|
response: Response = await tab.continue_with_auth(
|
|
607
691
|
request_id=request_id,
|
|
608
692
|
auth_challenge_response=AuthChallengeResponseType.PROVIDE_CREDENTIALS,
|
|
@@ -622,6 +706,10 @@ class Browser(ABC): # noqa: PLR0904
|
|
|
622
706
|
if not creds:
|
|
623
707
|
return
|
|
624
708
|
username, password = creds
|
|
709
|
+
logger.debug(
|
|
710
|
+
f'Enabling context-level proxy auth for tab (context={browser_context_id}, '
|
|
711
|
+
f'user_set={bool(username)}'
|
|
712
|
+
)
|
|
625
713
|
await tab.enable_fetch_events(handle_auth=True)
|
|
626
714
|
await tab.on(
|
|
627
715
|
FetchEvent.REQUEST_PAUSED,
|
|
@@ -649,29 +737,37 @@ class Browser(ABC): # noqa: PLR0904
|
|
|
649
737
|
Raises:
|
|
650
738
|
FailedToStartBrowser: If the browser failed to start.
|
|
651
739
|
"""
|
|
740
|
+
logger.debug(f'Verifying browser is running (timeout={self.options.start_timeout})')
|
|
652
741
|
if not await self._is_browser_running(self.options.start_timeout):
|
|
742
|
+
logger.error('Browser failed to start within timeout')
|
|
653
743
|
raise FailedToStartBrowser()
|
|
654
744
|
|
|
655
745
|
async def _configure_proxy(
|
|
656
746
|
self, private_proxy: bool, proxy_credentials: tuple[Optional[str], Optional[str]]
|
|
657
747
|
):
|
|
658
748
|
"""Setup proxy authentication handling if needed."""
|
|
659
|
-
if private_proxy:
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
749
|
+
if not private_proxy:
|
|
750
|
+
return
|
|
751
|
+
|
|
752
|
+
logger.debug(
|
|
753
|
+
'Configuring proxy authentication: '
|
|
754
|
+
f'credentials provided={bool(proxy_credentials[0] or proxy_credentials[1])}'
|
|
755
|
+
)
|
|
756
|
+
await self.enable_fetch_events(handle_auth_requests=True)
|
|
757
|
+
await self.on(
|
|
758
|
+
FetchEvent.REQUEST_PAUSED,
|
|
759
|
+
self._continue_request_callback,
|
|
760
|
+
temporary=True,
|
|
761
|
+
)
|
|
762
|
+
await self.on(
|
|
763
|
+
FetchEvent.AUTH_REQUIRED,
|
|
764
|
+
partial(
|
|
765
|
+
self._continue_request_with_auth_callback,
|
|
766
|
+
proxy_username=proxy_credentials[0],
|
|
767
|
+
proxy_password=proxy_credentials[1],
|
|
768
|
+
),
|
|
769
|
+
temporary=True,
|
|
770
|
+
)
|
|
675
771
|
|
|
676
772
|
@staticmethod
|
|
677
773
|
def _is_valid_tab(target: TargetInfo) -> bool:
|
|
@@ -696,10 +792,12 @@ class Browser(ABC): # noqa: PLR0904
|
|
|
696
792
|
)
|
|
697
793
|
|
|
698
794
|
if not valid_tab:
|
|
795
|
+
logger.error(f'No valid tab found among {len(targets)} targets')
|
|
699
796
|
raise NoValidTabFound()
|
|
700
797
|
|
|
701
798
|
tab_id = valid_tab.get('targetId')
|
|
702
799
|
if not tab_id:
|
|
800
|
+
logger.error('Valid tab missing targetId')
|
|
703
801
|
raise NoValidTabFound('Tab missing targetId')
|
|
704
802
|
|
|
705
803
|
return tab_id
|
|
@@ -717,6 +815,7 @@ class Browser(ABC): # noqa: PLR0904
|
|
|
717
815
|
self, command: Command[T_CommandParams, T_CommandResponse], timeout: int = 10
|
|
718
816
|
) -> T_CommandResponse:
|
|
719
817
|
"""Execute CDP command and return result (core method for browser communication)."""
|
|
818
|
+
logger.debug(f'Executing command: {command.get("method")} (timeout={timeout})')
|
|
720
819
|
return await self._connection_handler.execute_command(command, timeout=timeout)
|
|
721
820
|
|
|
722
821
|
def _setup_user_dir(self):
|
|
@@ -730,6 +829,7 @@ class Browser(ABC): # noqa: PLR0904
|
|
|
730
829
|
self.options.arguments.append(f'--user-data-dir={temp_dir.name}')
|
|
731
830
|
if self.options.browser_preferences:
|
|
732
831
|
self._set_browser_preferences_in_temp_dir(temp_dir)
|
|
832
|
+
logger.debug(f'User dir setup complete: {self._get_user_data_dir()}')
|
|
733
833
|
|
|
734
834
|
def _set_browser_preferences_in_temp_dir(self, temp_dir: TemporaryDirectory):
|
|
735
835
|
os.mkdir(os.path.join(temp_dir.name, 'Default'))
|
|
@@ -738,6 +838,7 @@ class Browser(ABC): # noqa: PLR0904
|
|
|
738
838
|
os.path.join(temp_dir.name, 'Default', 'Preferences'), 'w', encoding='utf-8'
|
|
739
839
|
) as json_file:
|
|
740
840
|
json.dump(preferences, json_file)
|
|
841
|
+
logger.debug('Wrote browser preferences to temp user dir')
|
|
741
842
|
|
|
742
843
|
def _set_browser_preferences_in_user_data_dir(self, user_data_dir: str):
|
|
743
844
|
"""
|
|
@@ -769,6 +870,7 @@ class Browser(ABC): # noqa: PLR0904
|
|
|
769
870
|
preferences.update(self.options.browser_preferences)
|
|
770
871
|
with open(preferences_path, 'w', encoding='utf-8') as json_file:
|
|
771
872
|
json.dump(preferences, json_file, indent=2)
|
|
873
|
+
logger.debug(f'Updated browser preferences in user data dir: {preferences_path}')
|
|
772
874
|
|
|
773
875
|
def _get_user_data_dir(self) -> Optional[str]:
|
|
774
876
|
for arg in self.options.arguments:
|
|
@@ -781,8 +883,10 @@ class Browser(ABC): # noqa: PLR0904
|
|
|
781
883
|
"""Validate WebSocket address."""
|
|
782
884
|
min_slashes = 4
|
|
783
885
|
if not ws_address.startswith('ws://'):
|
|
886
|
+
logger.error('Invalid WebSocket address: missing ws:// prefix')
|
|
784
887
|
raise InvalidWebSocketAddress('WebSocket address must start with ws://')
|
|
785
888
|
if len(ws_address.split('/')) < min_slashes:
|
|
889
|
+
logger.error('Invalid WebSocket address: not enough slashes')
|
|
786
890
|
raise InvalidWebSocketAddress(
|
|
787
891
|
f'WebSocket address must contain at least {min_slashes} slashes'
|
|
788
892
|
)
|
|
@@ -793,6 +897,7 @@ class Browser(ABC): # noqa: PLR0904
|
|
|
793
897
|
self._ws_address = ws_address
|
|
794
898
|
self._connection_handler._ws_address = self._ws_address
|
|
795
899
|
await self._connection_handler._ensure_active_connection()
|
|
900
|
+
logger.info('WebSocket address set for browser-level connection')
|
|
796
901
|
|
|
797
902
|
def _get_tab_kwargs(self, target_id: str, browser_context_id: Optional[str] = None) -> dict:
|
|
798
903
|
"""
|
|
@@ -815,18 +920,28 @@ class Browser(ABC): # noqa: PLR0904
|
|
|
815
920
|
kwargs['ws_address'] = self._get_tab_ws_address(target_id)
|
|
816
921
|
else:
|
|
817
922
|
kwargs['connection_port'] = self._connection_port
|
|
923
|
+
logger.debug(f'Tab kwargs resolved for {target_id}: using_ws={bool(self._ws_address)}')
|
|
818
924
|
return kwargs
|
|
819
925
|
|
|
820
926
|
def _get_tab_ws_address(self, tab_id: str) -> str:
|
|
821
927
|
"""
|
|
822
|
-
Get WebSocket address for tab
|
|
823
|
-
|
|
928
|
+
Get WebSocket address for a specific tab, preserving any query or fragment
|
|
929
|
+
components present in the original browser-level WebSocket URL.
|
|
930
|
+
|
|
931
|
+
This ensures authentication tokens passed via query string (e.g.,
|
|
932
|
+
ws://host/devtools/browser/abc?token=XYZ) are retained when switching
|
|
933
|
+
to the page-level endpoint (devtools/page/<tab_id>), which is critical
|
|
934
|
+
for providers like Browserless or authenticated CDP proxies.
|
|
824
935
|
"""
|
|
825
936
|
if not self._ws_address:
|
|
826
937
|
raise InvalidWebSocketAddress('WebSocket address is not set')
|
|
827
938
|
|
|
828
|
-
|
|
829
|
-
|
|
939
|
+
parts = urlsplit(self._ws_address)
|
|
940
|
+
# Preserve scheme and netloc; build the page path and keep query/fragment
|
|
941
|
+
page_path = f'/devtools/page/{tab_id}'
|
|
942
|
+
ws = urlunsplit((parts.scheme, parts.netloc, page_path, parts.query, parts.fragment))
|
|
943
|
+
logger.debug(f'Resolved tab WebSocket address: {ws}')
|
|
944
|
+
return ws
|
|
830
945
|
|
|
831
946
|
@staticmethod
|
|
832
947
|
def _sanitize_proxy_and_extract_auth(
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
import platform
|
|
2
3
|
from typing import Optional
|
|
3
4
|
|
|
@@ -7,6 +8,8 @@ from pydoll.browser.options import ChromiumOptions
|
|
|
7
8
|
from pydoll.exceptions import UnsupportedOS
|
|
8
9
|
from pydoll.utils import validate_browser_paths
|
|
9
10
|
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
10
13
|
|
|
11
14
|
class Chrome(Browser):
|
|
12
15
|
"""Chrome browser implementation for CDP automation."""
|
|
@@ -39,6 +42,7 @@ class Chrome(Browser):
|
|
|
39
42
|
ValueError: If executable not found at default location.
|
|
40
43
|
"""
|
|
41
44
|
os_name = platform.system()
|
|
45
|
+
logger.debug(f'Resolving default Chrome binary for OS: {os_name}')
|
|
42
46
|
|
|
43
47
|
browser_paths = {
|
|
44
48
|
'Windows': [
|
|
@@ -57,6 +61,9 @@ class Chrome(Browser):
|
|
|
57
61
|
browser_path = browser_paths.get(os_name)
|
|
58
62
|
|
|
59
63
|
if not browser_path:
|
|
64
|
+
logger.error(f'Unsupported OS: {os_name}')
|
|
60
65
|
raise UnsupportedOS(f'Unsupported OS: {os_name}')
|
|
61
66
|
|
|
62
|
-
|
|
67
|
+
path = validate_browser_paths(browser_path)
|
|
68
|
+
logger.debug(f'Using Chrome binary: {path}')
|
|
69
|
+
return path
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
import platform
|
|
2
3
|
from typing import Optional
|
|
3
4
|
|
|
@@ -7,6 +8,8 @@ from pydoll.browser.options import Options
|
|
|
7
8
|
from pydoll.exceptions import UnsupportedOS
|
|
8
9
|
from pydoll.utils import validate_browser_paths
|
|
9
10
|
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
10
13
|
|
|
11
14
|
class Edge(Browser):
|
|
12
15
|
"""Edge browser implementation for CDP automation."""
|
|
@@ -39,6 +42,7 @@ class Edge(Browser):
|
|
|
39
42
|
ValueError: If executable not found at default location.
|
|
40
43
|
"""
|
|
41
44
|
os_name = platform.system()
|
|
45
|
+
logger.debug(f'Resolving default Edge binary for OS: {os_name}')
|
|
42
46
|
|
|
43
47
|
browser_paths = {
|
|
44
48
|
'Windows': [
|
|
@@ -62,6 +66,9 @@ class Edge(Browser):
|
|
|
62
66
|
browser_path = browser_paths.get(os_name)
|
|
63
67
|
|
|
64
68
|
if not browser_path:
|
|
69
|
+
logger.error(f'Unsupported OS: {os_name}')
|
|
65
70
|
raise UnsupportedOS()
|
|
66
71
|
|
|
67
|
-
|
|
72
|
+
path = validate_browser_paths(browser_path)
|
|
73
|
+
logger.debug(f'Using Edge binary: {path}')
|
|
74
|
+
return path
|