pydoll-python 2.10.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.
Files changed (91) hide show
  1. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/PKG-INFO +84 -2
  2. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/README.md +82 -0
  3. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/browser/tab.py +239 -56
  4. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/constants.py +118 -2
  5. pydoll_python-2.12.0/pydoll/decorators.py +140 -0
  6. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/elements/web_element.py +147 -14
  7. pydoll_python-2.12.0/pydoll/interactions/__init__.py +4 -0
  8. pydoll_python-2.12.0/pydoll/interactions/keyboard.py +178 -0
  9. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pyproject.toml +2 -2
  10. pydoll_python-2.10.0/pydoll/interactions/__init__.py +0 -3
  11. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/LICENSE +0 -0
  12. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/__init__.py +0 -0
  13. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/browser/__init__.py +0 -0
  14. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/browser/chromium/__init__.py +0 -0
  15. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/browser/chromium/base.py +0 -0
  16. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/browser/chromium/chrome.py +0 -0
  17. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/browser/chromium/edge.py +0 -0
  18. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/browser/interfaces.py +0 -0
  19. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/browser/managers/__init__.py +0 -0
  20. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/browser/managers/browser_options_manager.py +0 -0
  21. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/browser/managers/browser_process_manager.py +0 -0
  22. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/browser/managers/proxy_manager.py +0 -0
  23. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/browser/managers/temp_dir_manager.py +0 -0
  24. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/browser/options.py +0 -0
  25. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/browser/requests/__init__.py +0 -0
  26. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/browser/requests/request.py +0 -0
  27. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/browser/requests/response.py +0 -0
  28. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/commands/__init__.py +0 -0
  29. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/commands/browser_commands.py +0 -0
  30. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/commands/dom_commands.py +0 -0
  31. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/commands/fetch_commands.py +0 -0
  32. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/commands/input_commands.py +0 -0
  33. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/commands/network_commands.py +0 -0
  34. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/commands/page_commands.py +0 -0
  35. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/commands/runtime_commands.py +0 -0
  36. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/commands/storage_commands.py +0 -0
  37. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/commands/target_commands.py +0 -0
  38. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/connection/__init__.py +0 -0
  39. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/connection/connection_handler.py +0 -0
  40. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/connection/managers/__init__.py +0 -0
  41. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/connection/managers/commands_manager.py +0 -0
  42. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/connection/managers/events_manager.py +0 -0
  43. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/elements/__init__.py +0 -0
  44. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/elements/mixins/__init__.py +0 -0
  45. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/elements/mixins/find_elements_mixin.py +0 -0
  46. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/exceptions.py +0 -0
  47. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/interactions/scroll.py +0 -0
  48. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/protocol/__init__.py +0 -0
  49. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/protocol/base.py +0 -0
  50. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/protocol/browser/__init__.py +0 -0
  51. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/protocol/browser/events.py +0 -0
  52. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/protocol/browser/methods.py +0 -0
  53. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/protocol/browser/types.py +0 -0
  54. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/protocol/debugger/types.py +0 -0
  55. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/protocol/dom/__init__.py +0 -0
  56. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/protocol/dom/events.py +0 -0
  57. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/protocol/dom/methods.py +0 -0
  58. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/protocol/dom/types.py +0 -0
  59. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/protocol/emulation/types.py +0 -0
  60. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/protocol/fetch/__init__.py +0 -0
  61. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/protocol/fetch/events.py +0 -0
  62. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/protocol/fetch/methods.py +0 -0
  63. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/protocol/fetch/types.py +0 -0
  64. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/protocol/input/__init__.py +0 -0
  65. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/protocol/input/events.py +0 -0
  66. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/protocol/input/methods.py +0 -0
  67. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/protocol/input/types.py +0 -0
  68. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/protocol/io/types.py +0 -0
  69. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/protocol/network/__init__.py +0 -0
  70. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/protocol/network/events.py +0 -0
  71. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/protocol/network/methods.py +0 -0
  72. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/protocol/network/types.py +0 -0
  73. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/protocol/page/__init__.py +0 -0
  74. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/protocol/page/events.py +0 -0
  75. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/protocol/page/methods.py +0 -0
  76. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/protocol/page/types.py +0 -0
  77. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/protocol/runtime/__init__.py +0 -0
  78. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/protocol/runtime/events.py +0 -0
  79. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/protocol/runtime/methods.py +0 -0
  80. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/protocol/runtime/types.py +0 -0
  81. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/protocol/security/types.py +0 -0
  82. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/protocol/storage/__init__.py +0 -0
  83. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/protocol/storage/events.py +0 -0
  84. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/protocol/storage/methods.py +0 -0
  85. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/protocol/storage/types.py +0 -0
  86. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/protocol/target/__init__.py +0 -0
  87. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/protocol/target/events.py +0 -0
  88. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/protocol/target/methods.py +0 -0
  89. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/protocol/target/types.py +0 -0
  90. {pydoll_python-2.10.0 → pydoll_python-2.12.0}/pydoll/py.typed +0 -0
  91. {pydoll_python-2.10.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.10.0
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 (>=23.2.1,<24.0.0)
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)
@@ -82,6 +82,88 @@ await tab.scroll.by(ScrollPosition.UP, 300, smooth=False)
82
82
 
83
83
  Unlike `execute_script("window.scrollBy(...)")` which returns immediately, the scroll API uses CDP's `awaitPromise` to wait for the browser's `scrollend` event, ensuring your next actions only execute after scrolling completely finishes. Perfect for taking screenshots, loading lazy content, or creating realistic reading patterns.
84
84
 
85
+ ### Keyboard API: Complete Control Over Keyboard Input
86
+
87
+ The new `KeyboardAPI` provides a clean, centralized interface for all keyboard interactions at the page level:
88
+
89
+ ```python
90
+ from pydoll.constants import Key
91
+
92
+ # Press individual keys
93
+ await tab.keyboard.press(Key.ENTER)
94
+ await tab.keyboard.press(Key.TAB)
95
+
96
+ # Use hotkeys/shortcuts with up to 3 keys
97
+ await tab.keyboard.hotkey(Key.CONTROL, Key.A) # Select all (works!)
98
+ await tab.keyboard.hotkey(Key.CONTROL, Key.C) # Copy (works!)
99
+ await tab.keyboard.hotkey(Key.CONTROL, Key.SHIFT, Key.ARROWRIGHT) # Select word right
100
+
101
+ # Manual control for complex sequences
102
+ await tab.keyboard.down(Key.SHIFT)
103
+ await tab.keyboard.press(Key.ARROWRIGHT) # Select text while holding Shift
104
+ await tab.keyboard.up(Key.SHIFT)
105
+ ```
106
+
107
+ **Key improvements:**
108
+ - **Centralized**: All keyboard operations accessible via `tab.keyboard`
109
+ - **Smart modifier detection**: Hotkeys automatically detect and apply modifiers (Ctrl, Shift, Alt, Meta)
110
+ - **Complete key support**: 26 letters (A-Z), 10 digits (0-9), all function keys, numpad, and special keys
111
+ - **Page-level shortcuts**: Works for Ctrl+C, Ctrl+V, Ctrl+A, etc.
112
+
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
+
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
+
85
167
  ## 📦 Installation
86
168
 
87
169
  ```bash
@@ -62,6 +62,88 @@ await tab.scroll.by(ScrollPosition.UP, 300, smooth=False)
62
62
 
63
63
  Unlike `execute_script("window.scrollBy(...)")` which returns immediately, the scroll API uses CDP's `awaitPromise` to wait for the browser's `scrollend` event, ensuring your next actions only execute after scrolling completely finishes. Perfect for taking screenshots, loading lazy content, or creating realistic reading patterns.
64
64
 
65
+ ### Keyboard API: Complete Control Over Keyboard Input
66
+
67
+ The new `KeyboardAPI` provides a clean, centralized interface for all keyboard interactions at the page level:
68
+
69
+ ```python
70
+ from pydoll.constants import Key
71
+
72
+ # Press individual keys
73
+ await tab.keyboard.press(Key.ENTER)
74
+ await tab.keyboard.press(Key.TAB)
75
+
76
+ # Use hotkeys/shortcuts with up to 3 keys
77
+ await tab.keyboard.hotkey(Key.CONTROL, Key.A) # Select all (works!)
78
+ await tab.keyboard.hotkey(Key.CONTROL, Key.C) # Copy (works!)
79
+ await tab.keyboard.hotkey(Key.CONTROL, Key.SHIFT, Key.ARROWRIGHT) # Select word right
80
+
81
+ # Manual control for complex sequences
82
+ await tab.keyboard.down(Key.SHIFT)
83
+ await tab.keyboard.press(Key.ARROWRIGHT) # Select text while holding Shift
84
+ await tab.keyboard.up(Key.SHIFT)
85
+ ```
86
+
87
+ **Key improvements:**
88
+ - **Centralized**: All keyboard operations accessible via `tab.keyboard`
89
+ - **Smart modifier detection**: Hotkeys automatically detect and apply modifiers (Ctrl, Shift, Alt, Meta)
90
+ - **Complete key support**: 26 letters (A-Z), 10 digits (0-9), all function keys, numpad, and special keys
91
+ - **Page-level shortcuts**: Works for Ctrl+C, Ctrl+V, Ctrl+A, etc.
92
+
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
+
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
+
65
147
  ## 📦 Installation
66
148
 
67
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
@@ -50,14 +51,20 @@ from pydoll.exceptions import (
50
51
  TopLevelTargetRequired,
51
52
  WaitElementTimeout,
52
53
  )
53
- from pydoll.interactions.scroll import ScrollAPI
54
+ 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:
@@ -133,6 +140,7 @@ class Tab(FindElementsMixin):
133
140
  self._cloudflare_captcha_callback_id: Optional[int] = None
134
141
  self._request: Optional[Request] = None
135
142
  self._scroll: Optional[ScrollAPI] = None
143
+ self._keyboard: Optional[KeyboardAPI] = None
136
144
  logger.debug(
137
145
  (
138
146
  f'Tab initialized: target_id={self._target_id}, '
@@ -190,6 +198,18 @@ class Tab(FindElementsMixin):
190
198
  self._scroll = ScrollAPI(self)
191
199
  return self._scroll
192
200
 
201
+ @property
202
+ def keyboard(self) -> KeyboardAPI:
203
+ """
204
+ Get the keyboard API for controlling keyboard input at page level.
205
+
206
+ Returns:
207
+ KeyboardAPI: An instance of the KeyboardAPI class for keyboard operations.
208
+ """
209
+ if self._keyboard is None:
210
+ self._keyboard = KeyboardAPI(self)
211
+ return self._keyboard
212
+
193
213
  @property
194
214
  def intercept_file_chooser_dialog_enabled(self) -> bool:
195
215
  """Whether file chooser dialog interception is active."""
@@ -749,37 +769,174 @@ class Tab(FindElementsMixin):
749
769
  )
750
770
 
751
771
  @overload
752
- async def execute_script(self, script: str) -> EvaluateResponse: ...
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: ...
753
792
 
754
793
  @overload
755
794
  async def execute_script(
756
- self, script: str, element: 'WebElement'
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,
757
810
  ) -> CallFunctionOnResponse: ...
758
811
 
759
812
  async def execute_script(
760
- self, script: str, element: Optional['WebElement'] = None
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,
761
834
  ) -> Union[EvaluateResponse, CallFunctionOnResponse]:
762
835
  """
763
836
  Execute JavaScript in page context.
764
837
 
765
838
  Args:
766
- script: JavaScript code to execute.
767
- element: Element context (use 'argument' in script to reference).
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.
768
875
 
769
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
770
884
  await page.execute_script('argument.click()', element)
771
- await page.execute_script('argument.value = "Hello"', element)
772
885
 
773
- Raises:
774
- InvalidScriptWithElement: If script contains 'argument' but no element is provided.
886
+ # Execute a script on an element to set its value
887
+ await page.execute_script('argument.value = "Hello"', element)
775
888
  """
776
- if 'argument' in script and element is None:
777
- raise InvalidScriptWithElement('Script contains "argument" but no element was provided')
778
-
779
889
  logger.debug(f'Executing script: with_element={bool(element)}, length={len(script)}')
780
- if element:
781
- return await self._execute_script_with_element(script, element)
782
- return await self._execute_script_without_element(script)
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
783
940
 
784
941
  # TODO: think about how to remove these duplications with the base class
785
942
  async def continue_request(
@@ -1155,46 +1312,45 @@ class Tab(FindElementsMixin):
1155
1312
  )
1156
1313
  return ConnectionHandler(self._connection_port, self._target_id)
1157
1314
 
1158
- async def _execute_script_with_element(self, script: str, element: 'WebElement'):
1159
- """
1160
- Execute script with element context.
1161
-
1162
- Args:
1163
- script: JavaScript code to execute.
1164
- element: Element context (use 'argument' in script to reference).
1165
-
1166
- Returns:
1167
- The result of the script execution.
1168
- """
1169
- if 'argument' not in script:
1170
- raise InvalidScriptWithElement('Script does not contain "argument"')
1171
-
1172
- script = script.replace('argument', 'this')
1173
-
1174
- if not is_script_already_function(script):
1175
- script = f'function(){{ {script} }}'
1176
-
1177
- command = RuntimeCommands.call_function_on(
1178
- object_id=element._object_id, function_declaration=script, return_by_value=True
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,
1179
1353
  )
1180
- return await self._execute_command(command)
1181
-
1182
- async def _execute_script_without_element(self, script: str):
1183
- """
1184
- Execute script without element context.
1185
-
1186
- Args:
1187
- script: JavaScript code to execute.
1188
-
1189
- Returns:
1190
- The result of the script execution.
1191
- """
1192
- if has_return_outside_function(script):
1193
- script = f'(function(){{ {script} }})()'
1194
-
1195
- command = RuntimeCommands.evaluate(expression=script)
1196
- logger.debug(f'Executing script without element: length={len(script)}')
1197
- return await self._execute_command(command)
1198
1354
 
1199
1355
  async def _refresh_if_url_not_changed(self, url: str) -> bool:
1200
1356
  """Refresh page if URL hasn't changed."""
@@ -1204,6 +1360,33 @@ class Tab(FindElementsMixin):
1204
1360
  return True
1205
1361
  return False
1206
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
+
1207
1390
  async def _wait_page_load(self, timeout: int = 300):
1208
1391
  """
1209
1392
  Wait for page to finish loading.
@@ -1240,7 +1423,7 @@ class Tab(FindElementsMixin):
1240
1423
  element = cast('WebElement', element)
1241
1424
  if element:
1242
1425
  # adjust the external div size to shadow root width (usually 300px)
1243
- await self.execute_script('argument.style="width: 300px"', element)
1426
+ await element.execute_script('this.style="width: 300px"')
1244
1427
  await asyncio.sleep(time_before_click)
1245
1428
  await element.click()
1246
1429
  except Exception as exc: