pydoll-python 2.11.0__tar.gz → 2.12.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.
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/PKG-INFO +54 -2
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/README.md +52 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/browser/tab.py +225 -55
- pydoll_python-2.12.0/pydoll/decorators.py +140 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/elements/web_element.py +73 -15
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pyproject.toml +2 -2
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/LICENSE +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/__init__.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/browser/__init__.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/browser/chromium/__init__.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/browser/chromium/base.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/browser/chromium/chrome.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/browser/chromium/edge.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/browser/interfaces.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/browser/managers/__init__.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/browser/managers/browser_options_manager.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/browser/managers/browser_process_manager.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/browser/managers/proxy_manager.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/browser/managers/temp_dir_manager.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/browser/options.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/browser/requests/__init__.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/browser/requests/request.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/browser/requests/response.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/commands/__init__.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/commands/browser_commands.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/commands/dom_commands.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/commands/fetch_commands.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/commands/input_commands.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/commands/network_commands.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/commands/page_commands.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/commands/runtime_commands.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/commands/storage_commands.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/commands/target_commands.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/connection/__init__.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/connection/connection_handler.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/connection/managers/__init__.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/connection/managers/commands_manager.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/connection/managers/events_manager.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/constants.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/elements/__init__.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/elements/mixins/__init__.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/elements/mixins/find_elements_mixin.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/exceptions.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/interactions/__init__.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/interactions/keyboard.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/interactions/scroll.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/protocol/__init__.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/protocol/base.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/protocol/browser/__init__.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/protocol/browser/events.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/protocol/browser/methods.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/protocol/browser/types.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/protocol/debugger/types.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/protocol/dom/__init__.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/protocol/dom/events.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/protocol/dom/methods.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/protocol/dom/types.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/protocol/emulation/types.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/protocol/fetch/__init__.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/protocol/fetch/events.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/protocol/fetch/methods.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/protocol/fetch/types.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/protocol/input/__init__.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/protocol/input/events.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/protocol/input/methods.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/protocol/input/types.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/protocol/io/types.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/protocol/network/__init__.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/protocol/network/events.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/protocol/network/methods.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/protocol/network/types.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/protocol/page/__init__.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/protocol/page/events.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/protocol/page/methods.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/protocol/page/types.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/protocol/runtime/__init__.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/protocol/runtime/events.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/protocol/runtime/methods.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/protocol/runtime/types.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/protocol/security/types.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/protocol/storage/__init__.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/protocol/storage/events.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/protocol/storage/methods.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/protocol/storage/types.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/protocol/target/__init__.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/protocol/target/events.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/protocol/target/methods.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/protocol/target/types.py +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/py.typed +0 -0
- {pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pydoll-python
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.12.0
|
|
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
|
|
@@ -12,7 +12,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.12
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.13
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
-
Requires-Dist: aiofiles (>=
|
|
15
|
+
Requires-Dist: aiofiles (>=25.1.0,<26.0.0)
|
|
16
16
|
Requires-Dist: aiohttp (>=3.9.5,<4.0.0)
|
|
17
17
|
Requires-Dist: typing_extensions (>=4.14.0,<5.0.0)
|
|
18
18
|
Requires-Dist: websockets (>=14,<15)
|
|
@@ -112,6 +112,58 @@ await tab.keyboard.up(Key.SHIFT)
|
|
|
112
112
|
|
|
113
113
|
> **⚠️ CDP Limitation:** Browser UI shortcuts (like Ctrl+T for new tab, F12 for DevTools) don't work via CDP. Use Pydoll's methods instead: `await browser.new_tab()`, `await tab.close()`.
|
|
114
114
|
|
|
115
|
+
### Retry Decorator: Production-Ready Error Recovery
|
|
116
|
+
|
|
117
|
+
Transform fragile scripts into robust production scrapers with the `@retry` decorator. Automatically recover from network failures, timeouts, and transient errors with exponential backoff and custom recovery strategies:
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
import asyncio
|
|
121
|
+
from pydoll.browser.chromium import Chrome
|
|
122
|
+
from pydoll.decorators import retry
|
|
123
|
+
from pydoll.exceptions import ElementNotFound, NetworkError
|
|
124
|
+
|
|
125
|
+
class ProductScraper:
|
|
126
|
+
def __init__(self):
|
|
127
|
+
self.tab = None
|
|
128
|
+
self.retry_count = 0
|
|
129
|
+
|
|
130
|
+
# Recovery callback executed before each retry
|
|
131
|
+
async def recover_from_failure(self):
|
|
132
|
+
self.retry_count += 1
|
|
133
|
+
print(f"Attempt {self.retry_count} failed. Recovering...")
|
|
134
|
+
|
|
135
|
+
# Refresh page and restore state
|
|
136
|
+
if self.tab:
|
|
137
|
+
await self.tab.refresh()
|
|
138
|
+
await asyncio.sleep(2)
|
|
139
|
+
|
|
140
|
+
@retry(
|
|
141
|
+
max_retries=3,
|
|
142
|
+
exceptions=[ElementNotFound, NetworkError],
|
|
143
|
+
on_retry=recover_from_failure, # Execute recovery logic
|
|
144
|
+
delay=2.0,
|
|
145
|
+
exponential_backoff=True
|
|
146
|
+
)
|
|
147
|
+
async def scrape_product(self, url: str):
|
|
148
|
+
if not self.tab:
|
|
149
|
+
browser = Chrome()
|
|
150
|
+
self.tab = await browser.start()
|
|
151
|
+
|
|
152
|
+
await self.tab.go_to(url)
|
|
153
|
+
title = await self.tab.find(class_name='product-title', timeout=5)
|
|
154
|
+
return await title.text
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**Powerful features:**
|
|
158
|
+
- **Smart retry logic**: Only retry on specific exceptions you define
|
|
159
|
+
- **Exponential backoff**: Progressively increase wait times (1s → 2s → 4s → 8s)
|
|
160
|
+
- **Recovery callbacks**: Execute custom logic between retries (refresh page, switch proxy, restart browser)
|
|
161
|
+
- **Production-tested**: Handle the chaos of real-world scraping with confidence
|
|
162
|
+
|
|
163
|
+
Perfect for handling rate limits, network instability, dynamic content loading, and CAPTCHA detection. Turn unreliable scrapers into bulletproof automation.
|
|
164
|
+
|
|
165
|
+
[**📖 Full Documentation**](https://pydoll.tech/docs/features/advanced/decorators/)
|
|
166
|
+
|
|
115
167
|
## 📦 Installation
|
|
116
168
|
|
|
117
169
|
```bash
|
|
@@ -92,6 +92,58 @@ await tab.keyboard.up(Key.SHIFT)
|
|
|
92
92
|
|
|
93
93
|
> **⚠️ CDP Limitation:** Browser UI shortcuts (like Ctrl+T for new tab, F12 for DevTools) don't work via CDP. Use Pydoll's methods instead: `await browser.new_tab()`, `await tab.close()`.
|
|
94
94
|
|
|
95
|
+
### Retry Decorator: Production-Ready Error Recovery
|
|
96
|
+
|
|
97
|
+
Transform fragile scripts into robust production scrapers with the `@retry` decorator. Automatically recover from network failures, timeouts, and transient errors with exponential backoff and custom recovery strategies:
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
import asyncio
|
|
101
|
+
from pydoll.browser.chromium import Chrome
|
|
102
|
+
from pydoll.decorators import retry
|
|
103
|
+
from pydoll.exceptions import ElementNotFound, NetworkError
|
|
104
|
+
|
|
105
|
+
class ProductScraper:
|
|
106
|
+
def __init__(self):
|
|
107
|
+
self.tab = None
|
|
108
|
+
self.retry_count = 0
|
|
109
|
+
|
|
110
|
+
# Recovery callback executed before each retry
|
|
111
|
+
async def recover_from_failure(self):
|
|
112
|
+
self.retry_count += 1
|
|
113
|
+
print(f"Attempt {self.retry_count} failed. Recovering...")
|
|
114
|
+
|
|
115
|
+
# Refresh page and restore state
|
|
116
|
+
if self.tab:
|
|
117
|
+
await self.tab.refresh()
|
|
118
|
+
await asyncio.sleep(2)
|
|
119
|
+
|
|
120
|
+
@retry(
|
|
121
|
+
max_retries=3,
|
|
122
|
+
exceptions=[ElementNotFound, NetworkError],
|
|
123
|
+
on_retry=recover_from_failure, # Execute recovery logic
|
|
124
|
+
delay=2.0,
|
|
125
|
+
exponential_backoff=True
|
|
126
|
+
)
|
|
127
|
+
async def scrape_product(self, url: str):
|
|
128
|
+
if not self.tab:
|
|
129
|
+
browser = Chrome()
|
|
130
|
+
self.tab = await browser.start()
|
|
131
|
+
|
|
132
|
+
await self.tab.go_to(url)
|
|
133
|
+
title = await self.tab.find(class_name='product-title', timeout=5)
|
|
134
|
+
return await title.text
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**Powerful features:**
|
|
138
|
+
- **Smart retry logic**: Only retry on specific exceptions you define
|
|
139
|
+
- **Exponential backoff**: Progressively increase wait times (1s → 2s → 4s → 8s)
|
|
140
|
+
- **Recovery callbacks**: Execute custom logic between retries (refresh page, switch proxy, restart browser)
|
|
141
|
+
- **Production-tested**: Handle the chaos of real-world scraping with confidence
|
|
142
|
+
|
|
143
|
+
Perfect for handling rate limits, network instability, dynamic content loading, and CAPTCHA detection. Turn unreliable scrapers into bulletproof automation.
|
|
144
|
+
|
|
145
|
+
[**📖 Full Documentation**](https://pydoll.tech/docs/features/advanced/decorators/)
|
|
146
|
+
|
|
95
147
|
## 📦 Installation
|
|
96
148
|
|
|
97
149
|
```bash
|
|
@@ -4,6 +4,7 @@ import asyncio
|
|
|
4
4
|
import base64 as _b64
|
|
5
5
|
import logging
|
|
6
6
|
import shutil
|
|
7
|
+
import warnings
|
|
7
8
|
from contextlib import asynccontextmanager
|
|
8
9
|
from functools import partial
|
|
9
10
|
from pathlib import Path
|
|
@@ -54,10 +55,16 @@ from pydoll.interactions import KeyboardAPI, ScrollAPI
|
|
|
54
55
|
from pydoll.protocol.browser.types import DownloadBehavior, DownloadProgressState
|
|
55
56
|
from pydoll.protocol.page.events import PageEvent
|
|
56
57
|
from pydoll.protocol.page.types import ScreenshotFormat
|
|
58
|
+
from pydoll.protocol.runtime.methods import (
|
|
59
|
+
CallFunctionOnResponse,
|
|
60
|
+
EvaluateResponse,
|
|
61
|
+
SerializationOptions,
|
|
62
|
+
)
|
|
63
|
+
from pydoll.protocol.runtime.types import CallArgument
|
|
64
|
+
from pydoll.protocol.storage.methods import GetCookiesResponse
|
|
57
65
|
from pydoll.utils import (
|
|
58
66
|
decode_base64_to_bytes,
|
|
59
67
|
has_return_outside_function,
|
|
60
|
-
is_script_already_function,
|
|
61
68
|
)
|
|
62
69
|
|
|
63
70
|
if TYPE_CHECKING:
|
|
@@ -762,37 +769,174 @@ class Tab(FindElementsMixin):
|
|
|
762
769
|
)
|
|
763
770
|
|
|
764
771
|
@overload
|
|
765
|
-
async def execute_script(
|
|
772
|
+
async def execute_script(
|
|
773
|
+
self,
|
|
774
|
+
script: str,
|
|
775
|
+
*,
|
|
776
|
+
object_group: Optional[str] = None,
|
|
777
|
+
include_command_line_api: Optional[bool] = None,
|
|
778
|
+
silent: Optional[bool] = None,
|
|
779
|
+
context_id: Optional[int] = None,
|
|
780
|
+
return_by_value: Optional[bool] = None,
|
|
781
|
+
generate_preview: Optional[bool] = None,
|
|
782
|
+
user_gesture: Optional[bool] = None,
|
|
783
|
+
await_promise: Optional[bool] = None,
|
|
784
|
+
throw_on_side_effect: Optional[bool] = None,
|
|
785
|
+
timeout: Optional[float] = None,
|
|
786
|
+
disable_breaks: Optional[bool] = None,
|
|
787
|
+
repl_mode: Optional[bool] = None,
|
|
788
|
+
allow_unsafe_eval_blocked_by_csp: Optional[bool] = None,
|
|
789
|
+
unique_context_id: Optional[str] = None,
|
|
790
|
+
serialization_options: Optional[SerializationOptions] = None,
|
|
791
|
+
) -> EvaluateResponse: ...
|
|
766
792
|
|
|
767
793
|
@overload
|
|
768
794
|
async def execute_script(
|
|
769
|
-
self,
|
|
795
|
+
self,
|
|
796
|
+
script: str,
|
|
797
|
+
element: WebElement,
|
|
798
|
+
*,
|
|
799
|
+
arguments: Optional[list[CallArgument]] = None,
|
|
800
|
+
silent: Optional[bool] = None,
|
|
801
|
+
return_by_value: Optional[bool] = None,
|
|
802
|
+
generate_preview: Optional[bool] = None,
|
|
803
|
+
user_gesture: Optional[bool] = None,
|
|
804
|
+
await_promise: Optional[bool] = None,
|
|
805
|
+
execution_context_id: Optional[int] = None,
|
|
806
|
+
object_group: Optional[str] = None,
|
|
807
|
+
throw_on_side_effect: Optional[bool] = None,
|
|
808
|
+
unique_context_id: Optional[str] = None,
|
|
809
|
+
serialization_options: Optional[SerializationOptions] = None,
|
|
770
810
|
) -> CallFunctionOnResponse: ...
|
|
771
811
|
|
|
772
812
|
async def execute_script(
|
|
773
|
-
self,
|
|
813
|
+
self,
|
|
814
|
+
script: str,
|
|
815
|
+
element: Optional[WebElement] = None,
|
|
816
|
+
*,
|
|
817
|
+
arguments: Optional[list[CallArgument]] = None,
|
|
818
|
+
object_group: Optional[str] = None,
|
|
819
|
+
include_command_line_api: Optional[bool] = None,
|
|
820
|
+
silent: Optional[bool] = None,
|
|
821
|
+
context_id: Optional[int] = None,
|
|
822
|
+
return_by_value: Optional[bool] = None,
|
|
823
|
+
generate_preview: Optional[bool] = None,
|
|
824
|
+
user_gesture: Optional[bool] = None,
|
|
825
|
+
await_promise: Optional[bool] = None,
|
|
826
|
+
execution_context_id: Optional[int] = None,
|
|
827
|
+
throw_on_side_effect: Optional[bool] = None,
|
|
828
|
+
timeout: Optional[float] = None,
|
|
829
|
+
disable_breaks: Optional[bool] = None,
|
|
830
|
+
repl_mode: Optional[bool] = None,
|
|
831
|
+
allow_unsafe_eval_blocked_by_csp: Optional[bool] = None,
|
|
832
|
+
unique_context_id: Optional[str] = None,
|
|
833
|
+
serialization_options: Optional[SerializationOptions] = None,
|
|
774
834
|
) -> Union[EvaluateResponse, CallFunctionOnResponse]:
|
|
775
835
|
"""
|
|
776
836
|
Execute JavaScript in page context.
|
|
777
837
|
|
|
778
838
|
Args:
|
|
779
|
-
script: JavaScript code to execute.
|
|
780
|
-
element:
|
|
839
|
+
script (str): JavaScript code to execute.
|
|
840
|
+
element (Optional[WebElement]): Optional WebElement to execute script on.
|
|
841
|
+
arguments (Optional[list[CallArgument]]): Arguments to pass to the function.
|
|
842
|
+
object_group (Optional[str]): Symbolic group name for the result (Runtime.evaluate).
|
|
843
|
+
include_command_line_api (Optional[bool]): Whether to include command line API
|
|
844
|
+
(Runtime.evaluate).
|
|
845
|
+
silent (Optional[bool]): Whether to silence exceptions (Runtime.evaluate).
|
|
846
|
+
context_id (Optional[int]): ID of the execution context to evaluate in
|
|
847
|
+
(Runtime.evaluate).
|
|
848
|
+
return_by_value (Optional[bool]): Whether to return the result by value instead of
|
|
849
|
+
reference (Runtime.evaluate).
|
|
850
|
+
generate_preview (Optional[bool]): Whether to generate a preview for the result
|
|
851
|
+
(Runtime.evaluate).
|
|
852
|
+
user_gesture (Optional[bool]): Whether to treat evaluation as initiated by user
|
|
853
|
+
gesture (Runtime.evaluate).
|
|
854
|
+
await_promise (Optional[bool]): Whether to await promise result (Runtime.evaluate).
|
|
855
|
+
execution_context_id (Optional[int]): ID of the execution context to call the
|
|
856
|
+
function in.
|
|
857
|
+
throw_on_side_effect (Optional[bool]): Whether to throw if side effect cannot be
|
|
858
|
+
ruled out (Runtime.evaluate).
|
|
859
|
+
timeout (Optional[float]): Timeout in milliseconds (Runtime.evaluate).
|
|
860
|
+
disable_breaks (Optional[bool]): Whether to disable breakpoints during evaluation
|
|
861
|
+
(Runtime.evaluate).
|
|
862
|
+
repl_mode (Optional[bool]): Whether to execute in REPL mode (Runtime.evaluate).
|
|
863
|
+
allow_unsafe_eval_blocked_by_csp (Optional[bool]): Allow unsafe evaluation
|
|
864
|
+
(Runtime.evaluate).
|
|
865
|
+
unique_context_id (Optional[str]): Unique context ID for evaluation
|
|
866
|
+
(Runtime.evaluate).
|
|
867
|
+
serialization_options (Optional[SerializationOptions]): Serialization options for
|
|
868
|
+
the result (Runtime.evaluate).
|
|
869
|
+
|
|
870
|
+
Returns:
|
|
871
|
+
Union[EvaluateResponse, CallFunctionOnResponse]: The result of the script execution.
|
|
872
|
+
|
|
873
|
+
Raises:
|
|
874
|
+
InvalidScriptWithElement: If script uses 'argument' keyword but no element is provided.
|
|
781
875
|
|
|
782
876
|
Examples:
|
|
877
|
+
# Execute a simple script to log a message
|
|
878
|
+
await page.execute_script('console.log("Hello World")')
|
|
879
|
+
|
|
880
|
+
# Execute a script that returns the page title
|
|
881
|
+
await page.execute_script('return document.title')
|
|
882
|
+
|
|
883
|
+
# Execute a script on an element to click it
|
|
783
884
|
await page.execute_script('argument.click()', element)
|
|
784
|
-
await page.execute_script('argument.value = "Hello"', element)
|
|
785
885
|
|
|
786
|
-
|
|
787
|
-
|
|
886
|
+
# Execute a script on an element to set its value
|
|
887
|
+
await page.execute_script('argument.value = "Hello"', element)
|
|
788
888
|
"""
|
|
789
|
-
if 'argument' in script and element is None:
|
|
790
|
-
raise InvalidScriptWithElement('Script contains "argument" but no element was provided')
|
|
791
|
-
|
|
792
889
|
logger.debug(f'Executing script: with_element={bool(element)}, length={len(script)}')
|
|
793
|
-
if element:
|
|
794
|
-
|
|
795
|
-
|
|
890
|
+
if element is not None:
|
|
891
|
+
warnings.warn(
|
|
892
|
+
'Passing a WebElement to Tab.execute_script() is deprecated. '
|
|
893
|
+
'Use WebElement.execute_script() instead.',
|
|
894
|
+
DeprecationWarning,
|
|
895
|
+
stacklevel=2,
|
|
896
|
+
)
|
|
897
|
+
|
|
898
|
+
return await element.execute_script(
|
|
899
|
+
script,
|
|
900
|
+
arguments=arguments,
|
|
901
|
+
silent=silent,
|
|
902
|
+
return_by_value=return_by_value,
|
|
903
|
+
generate_preview=generate_preview,
|
|
904
|
+
user_gesture=user_gesture,
|
|
905
|
+
await_promise=await_promise,
|
|
906
|
+
execution_context_id=execution_context_id,
|
|
907
|
+
object_group=object_group,
|
|
908
|
+
throw_on_side_effect=throw_on_side_effect,
|
|
909
|
+
unique_context_id=unique_context_id,
|
|
910
|
+
serialization_options=serialization_options,
|
|
911
|
+
)
|
|
912
|
+
|
|
913
|
+
if has_return_outside_function(script):
|
|
914
|
+
script = f'(function(){{ {script} }})()'
|
|
915
|
+
|
|
916
|
+
command = self._get_evaluate_command(
|
|
917
|
+
script,
|
|
918
|
+
object_group=object_group,
|
|
919
|
+
include_command_line_api=include_command_line_api,
|
|
920
|
+
silent=silent,
|
|
921
|
+
context_id=context_id,
|
|
922
|
+
return_by_value=return_by_value,
|
|
923
|
+
generate_preview=generate_preview,
|
|
924
|
+
user_gesture=user_gesture,
|
|
925
|
+
await_promise=await_promise,
|
|
926
|
+
throw_on_side_effect=throw_on_side_effect,
|
|
927
|
+
timeout=timeout,
|
|
928
|
+
disable_breaks=disable_breaks,
|
|
929
|
+
repl_mode=repl_mode,
|
|
930
|
+
allow_unsafe_eval_blocked_by_csp=allow_unsafe_eval_blocked_by_csp,
|
|
931
|
+
unique_context_id=unique_context_id,
|
|
932
|
+
serialization_options=serialization_options,
|
|
933
|
+
)
|
|
934
|
+
logger.debug(f'Executing script without element: length={len(script)}')
|
|
935
|
+
result: Union[EvaluateResponse, CallFunctionOnResponse] = await self._execute_command(
|
|
936
|
+
command
|
|
937
|
+
)
|
|
938
|
+
self._validate_argument_error(result)
|
|
939
|
+
return result
|
|
796
940
|
|
|
797
941
|
# TODO: think about how to remove these duplications with the base class
|
|
798
942
|
async def continue_request(
|
|
@@ -1168,46 +1312,45 @@ class Tab(FindElementsMixin):
|
|
|
1168
1312
|
)
|
|
1169
1313
|
return ConnectionHandler(self._connection_port, self._target_id)
|
|
1170
1314
|
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1315
|
+
@staticmethod
|
|
1316
|
+
def _get_evaluate_command(
|
|
1317
|
+
script: str,
|
|
1318
|
+
*,
|
|
1319
|
+
object_group: Optional[str] = None,
|
|
1320
|
+
include_command_line_api: Optional[bool] = None,
|
|
1321
|
+
silent: Optional[bool] = None,
|
|
1322
|
+
context_id: Optional[int] = None,
|
|
1323
|
+
return_by_value: Optional[bool] = None,
|
|
1324
|
+
generate_preview: Optional[bool] = None,
|
|
1325
|
+
user_gesture: Optional[bool] = None,
|
|
1326
|
+
await_promise: Optional[bool] = None,
|
|
1327
|
+
throw_on_side_effect: Optional[bool] = None,
|
|
1328
|
+
timeout: Optional[float] = None,
|
|
1329
|
+
disable_breaks: Optional[bool] = None,
|
|
1330
|
+
repl_mode: Optional[bool] = None,
|
|
1331
|
+
allow_unsafe_eval_blocked_by_csp: Optional[bool] = None,
|
|
1332
|
+
unique_context_id: Optional[str] = None,
|
|
1333
|
+
serialization_options: Optional[SerializationOptions] = None,
|
|
1334
|
+
):
|
|
1335
|
+
"""Create an evaluate command with the given parameters."""
|
|
1336
|
+
return RuntimeCommands.evaluate(
|
|
1337
|
+
expression=script,
|
|
1338
|
+
object_group=object_group,
|
|
1339
|
+
include_command_line_api=include_command_line_api,
|
|
1340
|
+
silent=silent,
|
|
1341
|
+
context_id=context_id,
|
|
1342
|
+
return_by_value=return_by_value,
|
|
1343
|
+
generate_preview=generate_preview,
|
|
1344
|
+
user_gesture=user_gesture,
|
|
1345
|
+
await_promise=await_promise,
|
|
1346
|
+
throw_on_side_effect=throw_on_side_effect,
|
|
1347
|
+
timeout=timeout,
|
|
1348
|
+
disable_breaks=disable_breaks,
|
|
1349
|
+
repl_mode=repl_mode,
|
|
1350
|
+
allow_unsafe_eval_blocked_by_csp=allow_unsafe_eval_blocked_by_csp,
|
|
1351
|
+
unique_context_id=unique_context_id,
|
|
1352
|
+
serialization_options=serialization_options,
|
|
1192
1353
|
)
|
|
1193
|
-
return await self._execute_command(command)
|
|
1194
|
-
|
|
1195
|
-
async def _execute_script_without_element(self, script: str):
|
|
1196
|
-
"""
|
|
1197
|
-
Execute script without element context.
|
|
1198
|
-
|
|
1199
|
-
Args:
|
|
1200
|
-
script: JavaScript code to execute.
|
|
1201
|
-
|
|
1202
|
-
Returns:
|
|
1203
|
-
The result of the script execution.
|
|
1204
|
-
"""
|
|
1205
|
-
if has_return_outside_function(script):
|
|
1206
|
-
script = f'(function(){{ {script} }})()'
|
|
1207
|
-
|
|
1208
|
-
command = RuntimeCommands.evaluate(expression=script)
|
|
1209
|
-
logger.debug(f'Executing script without element: length={len(script)}')
|
|
1210
|
-
return await self._execute_command(command)
|
|
1211
1354
|
|
|
1212
1355
|
async def _refresh_if_url_not_changed(self, url: str) -> bool:
|
|
1213
1356
|
"""Refresh page if URL hasn't changed."""
|
|
@@ -1217,6 +1360,33 @@ class Tab(FindElementsMixin):
|
|
|
1217
1360
|
return True
|
|
1218
1361
|
return False
|
|
1219
1362
|
|
|
1363
|
+
@staticmethod
|
|
1364
|
+
def _validate_argument_error(response: EvaluateResponse) -> None:
|
|
1365
|
+
"""
|
|
1366
|
+
Validate that script didn't fail with ReferenceError about 'argument' being undefined.
|
|
1367
|
+
|
|
1368
|
+
Raises:
|
|
1369
|
+
InvalidScriptWithElement: If script uses 'argument' keyword but no element was provided.
|
|
1370
|
+
"""
|
|
1371
|
+
evaluate_result = response.get('result')
|
|
1372
|
+
if not isinstance(evaluate_result, dict):
|
|
1373
|
+
return
|
|
1374
|
+
|
|
1375
|
+
remote_object = evaluate_result.get('result')
|
|
1376
|
+
if not isinstance(remote_object, dict):
|
|
1377
|
+
return
|
|
1378
|
+
|
|
1379
|
+
if not (
|
|
1380
|
+
remote_object.get('type') == 'object'
|
|
1381
|
+
and remote_object.get('subtype') == 'error'
|
|
1382
|
+
and remote_object.get('className') == 'ReferenceError'
|
|
1383
|
+
):
|
|
1384
|
+
return
|
|
1385
|
+
|
|
1386
|
+
description = remote_object.get('description', '')
|
|
1387
|
+
if 'argument is not defined' in description:
|
|
1388
|
+
raise InvalidScriptWithElement('Script contains "argument" but no element was provided')
|
|
1389
|
+
|
|
1220
1390
|
async def _wait_page_load(self, timeout: int = 300):
|
|
1221
1391
|
"""
|
|
1222
1392
|
Wait for page to finish loading.
|
|
@@ -1253,7 +1423,7 @@ class Tab(FindElementsMixin):
|
|
|
1253
1423
|
element = cast('WebElement', element)
|
|
1254
1424
|
if element:
|
|
1255
1425
|
# adjust the external div size to shadow root width (usually 300px)
|
|
1256
|
-
await
|
|
1426
|
+
await element.execute_script('this.style="width: 300px"')
|
|
1257
1427
|
await asyncio.sleep(time_before_click)
|
|
1258
1428
|
await element.click()
|
|
1259
1429
|
except Exception as exc:
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
import traceback
|
|
4
|
+
from functools import wraps
|
|
5
|
+
from typing import Any, Callable, Coroutine, List, Optional, Type, TypeVar, Union
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
T = TypeVar('T')
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class RetryConfig:
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
max_retries: int = 5,
|
|
16
|
+
exceptions: Union[Type[Exception], List[Type[Exception]]] = Exception,
|
|
17
|
+
on_retry: Optional[Callable] = None,
|
|
18
|
+
delay: float = 0,
|
|
19
|
+
exponential_backoff: bool = False,
|
|
20
|
+
):
|
|
21
|
+
self.max_retries = max_retries
|
|
22
|
+
self.exceptions = exceptions
|
|
23
|
+
self.on_retry = on_retry
|
|
24
|
+
self.delay = delay
|
|
25
|
+
self.exponential_backoff = exponential_backoff
|
|
26
|
+
|
|
27
|
+
def calculate_delay(self, attempt: int) -> float:
|
|
28
|
+
if not self.delay:
|
|
29
|
+
return 0
|
|
30
|
+
return self.delay * (2**attempt if self.exponential_backoff else 1)
|
|
31
|
+
|
|
32
|
+
async def call_callback(self, caller_instance: Any) -> None:
|
|
33
|
+
if not self.on_retry:
|
|
34
|
+
return
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
await self.on_retry(caller_instance)
|
|
38
|
+
except TypeError as e:
|
|
39
|
+
error_msg = str(e)
|
|
40
|
+
if (
|
|
41
|
+
'takes 1 positional argument but 2 were given' in error_msg
|
|
42
|
+
or 'takes 0 positional arguments but 1 was given' in error_msg
|
|
43
|
+
):
|
|
44
|
+
try:
|
|
45
|
+
await self.on_retry()
|
|
46
|
+
return
|
|
47
|
+
except Exception as e_inner:
|
|
48
|
+
raise e_inner
|
|
49
|
+
raise e
|
|
50
|
+
except Exception as e:
|
|
51
|
+
raise e
|
|
52
|
+
|
|
53
|
+
async def handle_delay(self, attempt: int) -> None:
|
|
54
|
+
"""
|
|
55
|
+
Wait for delay.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
attempt (int): The current attempt number
|
|
59
|
+
"""
|
|
60
|
+
wait_time = self.calculate_delay(attempt)
|
|
61
|
+
if wait_time:
|
|
62
|
+
await asyncio.sleep(wait_time)
|
|
63
|
+
|
|
64
|
+
def is_matching_exception(self, exc: Exception) -> bool:
|
|
65
|
+
if isinstance(self.exceptions, (list, tuple)):
|
|
66
|
+
return any(isinstance(exc, e) for e in self.exceptions)
|
|
67
|
+
return isinstance(exc, self.exceptions)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def retry(
|
|
71
|
+
max_retries: int = 5,
|
|
72
|
+
exceptions: Union[Type[Exception], List[Type[Exception]]] = Exception,
|
|
73
|
+
on_retry: Optional[Callable] = None,
|
|
74
|
+
delay: float = 0,
|
|
75
|
+
exponential_backoff: bool = False,
|
|
76
|
+
exception_to_raise: Optional[Exception] = None,
|
|
77
|
+
):
|
|
78
|
+
"""
|
|
79
|
+
Decorator to try to execute a function again in case of exception.
|
|
80
|
+
For greater control, it is a good practice to specify the exceptions that should be handled.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
max_retries (int): Maximum number of attempts
|
|
84
|
+
exceptions (Union[Type[Exception], List[Type[Exception]]]): Exception types that should be
|
|
85
|
+
handled
|
|
86
|
+
on_retry (Optional[Callable], optional): Function called after each failed attempt
|
|
87
|
+
delay (float): Delay between attempts in seconds
|
|
88
|
+
exponential_backoff (bool): If True, increase the delay exponentially
|
|
89
|
+
|
|
90
|
+
Usage:
|
|
91
|
+
@retry_on_exception(
|
|
92
|
+
max_retries=3,
|
|
93
|
+
exceptions=[ValueError, TypeError],
|
|
94
|
+
delay=1
|
|
95
|
+
)
|
|
96
|
+
def my_function():
|
|
97
|
+
...
|
|
98
|
+
"""
|
|
99
|
+
config = RetryConfig(
|
|
100
|
+
max_retries=max_retries,
|
|
101
|
+
exceptions=exceptions,
|
|
102
|
+
on_retry=on_retry,
|
|
103
|
+
delay=delay,
|
|
104
|
+
exponential_backoff=exponential_backoff,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
def decorator(
|
|
108
|
+
func: Callable[..., Coroutine[Any, Any, T]],
|
|
109
|
+
) -> Callable[..., Coroutine[Any, Any, T]]:
|
|
110
|
+
@wraps(func)
|
|
111
|
+
async def wrapper(*args: Any, **kwargs: Any) -> T:
|
|
112
|
+
last_exception: Optional[Exception] = None
|
|
113
|
+
caller_instance = args[0] if args else None
|
|
114
|
+
|
|
115
|
+
for attempt in range(config.max_retries + 1):
|
|
116
|
+
try:
|
|
117
|
+
return await func(*args, **kwargs)
|
|
118
|
+
except Exception as exc:
|
|
119
|
+
logger.error(
|
|
120
|
+
f'Error trying to execute the function {func.__name__}: '
|
|
121
|
+
f'{traceback.format_exc()}'
|
|
122
|
+
)
|
|
123
|
+
if not config.is_matching_exception(exc):
|
|
124
|
+
raise exc
|
|
125
|
+
|
|
126
|
+
last_exception = exc
|
|
127
|
+
|
|
128
|
+
if attempt < config.max_retries:
|
|
129
|
+
await config.handle_delay(attempt + 1)
|
|
130
|
+
await config.call_callback(caller_instance)
|
|
131
|
+
continue
|
|
132
|
+
|
|
133
|
+
if last_exception is not None:
|
|
134
|
+
raise exception_to_raise or last_exception
|
|
135
|
+
|
|
136
|
+
raise RuntimeError('Unreachable: all retries exhausted without exception')
|
|
137
|
+
|
|
138
|
+
return wrapper
|
|
139
|
+
|
|
140
|
+
return decorator
|
|
@@ -37,10 +37,16 @@ from pydoll.protocol.input.types import (
|
|
|
37
37
|
MouseEventType,
|
|
38
38
|
)
|
|
39
39
|
from pydoll.protocol.page.types import ScreenshotFormat, Viewport
|
|
40
|
+
from pydoll.protocol.runtime.methods import (
|
|
41
|
+
CallFunctionOnResponse,
|
|
42
|
+
GetPropertiesResponse,
|
|
43
|
+
SerializationOptions,
|
|
44
|
+
)
|
|
40
45
|
from pydoll.protocol.runtime.types import CallArgument
|
|
41
46
|
from pydoll.utils import (
|
|
42
47
|
decode_base64_to_bytes,
|
|
43
48
|
extract_text_from_html,
|
|
49
|
+
is_script_already_function,
|
|
44
50
|
)
|
|
45
51
|
|
|
46
52
|
if TYPE_CHECKING:
|
|
@@ -639,33 +645,85 @@ class WebElement(FindElementsMixin): # noqa: PLR0904
|
|
|
639
645
|
async def execute_script(
|
|
640
646
|
self,
|
|
641
647
|
script: str,
|
|
642
|
-
|
|
648
|
+
*,
|
|
643
649
|
arguments: Optional[list[CallArgument]] = None,
|
|
644
|
-
|
|
650
|
+
silent: Optional[bool] = None,
|
|
651
|
+
return_by_value: Optional[bool] = None,
|
|
652
|
+
generate_preview: Optional[bool] = None,
|
|
653
|
+
user_gesture: Optional[bool] = None,
|
|
654
|
+
await_promise: Optional[bool] = None,
|
|
655
|
+
execution_context_id: Optional[int] = None,
|
|
656
|
+
object_group: Optional[str] = None,
|
|
657
|
+
throw_on_side_effect: Optional[bool] = None,
|
|
658
|
+
unique_context_id: Optional[str] = None,
|
|
659
|
+
serialization_options: Optional[SerializationOptions] = None,
|
|
660
|
+
) -> CallFunctionOnResponse:
|
|
645
661
|
"""
|
|
646
662
|
Execute JavaScript in element context.
|
|
647
663
|
|
|
648
664
|
Args:
|
|
649
|
-
script: JavaScript
|
|
650
|
-
|
|
651
|
-
|
|
665
|
+
script (str): JavaScript code to execute. Use 'this' to reference this element.
|
|
666
|
+
arguments (Optional[list[CallArgument]]): Arguments to pass to the function
|
|
667
|
+
(Runtime.callFunctionOn).
|
|
668
|
+
silent (Optional[bool]): Whether to silence exceptions (Runtime.callFunctionOn).
|
|
669
|
+
return_by_value (Optional[bool]): Whether to return the result by value instead of
|
|
670
|
+
reference (Runtime.callFunctionOn).
|
|
671
|
+
generate_preview (Optional[bool]): Whether to generate a preview for the result
|
|
672
|
+
(Runtime.callFunctionOn).
|
|
673
|
+
user_gesture (Optional[bool]): Whether to treat the call as initiated by user
|
|
674
|
+
gesture (Runtime.callFunctionOn).
|
|
675
|
+
await_promise (Optional[bool]): Whether to await promise result
|
|
676
|
+
(Runtime.callFunctionOn).
|
|
677
|
+
execution_context_id (Optional[int]): ID of the execution context to call the
|
|
678
|
+
function in (Runtime.callFunctionOn).
|
|
679
|
+
object_group (Optional[str]): Symbolic group name for the result
|
|
680
|
+
(Runtime.callFunctionOn).
|
|
681
|
+
throw_on_side_effect (Optional[bool]): Whether to throw if side effect cannot be
|
|
682
|
+
ruled out (Runtime.callFunctionOn).
|
|
683
|
+
unique_context_id (Optional[str]): Unique context ID for the function call
|
|
684
|
+
(Runtime.callFunctionOn).
|
|
685
|
+
serialization_options (Optional[SerializationOptions]): Serialization options for
|
|
686
|
+
the result (Runtime.callFunctionOn).
|
|
652
687
|
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
688
|
+
Returns:
|
|
689
|
+
CallFunctionOnResponse: The result of the script execution.
|
|
690
|
+
|
|
691
|
+
Examples:
|
|
692
|
+
# Click the element
|
|
693
|
+
await element.execute_script('this.click()')
|
|
694
|
+
|
|
695
|
+
# Modify element style
|
|
696
|
+
await element.execute_script('this.style.border = "2px solid red"')
|
|
697
|
+
|
|
698
|
+
# Get element text
|
|
699
|
+
result = await element.execute_script('return this.textContent', return_by_value=True)
|
|
700
|
+
|
|
701
|
+
# Set element content
|
|
702
|
+
await element.execute_script('this.textContent = "Hello World"')
|
|
656
703
|
"""
|
|
704
|
+
if not is_script_already_function(script):
|
|
705
|
+
script = f'function(){{ {script} }}'
|
|
706
|
+
|
|
657
707
|
logger.debug(
|
|
658
708
|
f'Executing script on element: return_by_value={return_by_value}, '
|
|
659
709
|
f'length={len(script)}, args={len(arguments) if arguments else 0}'
|
|
660
710
|
)
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
711
|
+
command = RuntimeCommands.call_function_on(
|
|
712
|
+
function_declaration=script,
|
|
713
|
+
object_id=self._object_id,
|
|
714
|
+
arguments=arguments,
|
|
715
|
+
silent=silent,
|
|
716
|
+
return_by_value=return_by_value,
|
|
717
|
+
generate_preview=generate_preview,
|
|
718
|
+
user_gesture=user_gesture,
|
|
719
|
+
await_promise=await_promise,
|
|
720
|
+
execution_context_id=execution_context_id,
|
|
721
|
+
object_group=object_group,
|
|
722
|
+
throw_on_side_effect=throw_on_side_effect,
|
|
723
|
+
unique_context_id=unique_context_id,
|
|
724
|
+
serialization_options=serialization_options,
|
|
668
725
|
)
|
|
726
|
+
return await self._execute_command(command)
|
|
669
727
|
|
|
670
728
|
async def _get_family_elements(
|
|
671
729
|
self, script: str, max_depth: int = 1, tag_filter: list[str] = []
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "pydoll-python"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.12.0"
|
|
4
4
|
description = "Pydoll is a library for automating chromium-based browsers without a WebDriver, offering realistic interactions."
|
|
5
5
|
authors = ["Thalison Fernandes <thalissfernandes99@gmail.com>"]
|
|
6
6
|
readme = "README.md"
|
|
@@ -13,7 +13,7 @@ include = ["pydoll/py.typed"]
|
|
|
13
13
|
python = "^3.10"
|
|
14
14
|
websockets = "^14"
|
|
15
15
|
aiohttp = "^3.9.5"
|
|
16
|
-
aiofiles = "^
|
|
16
|
+
aiofiles = "^25.1.0"
|
|
17
17
|
typing_extensions = "^4.14.0"
|
|
18
18
|
|
|
19
19
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/browser/managers/browser_options_manager.py
RENAMED
|
File without changes
|
{pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/browser/managers/browser_process_manager.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pydoll_python-2.11.0 → pydoll_python-2.12.0}/pydoll/connection/managers/commands_manager.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|