pydoll-python 2.10.0__tar.gz → 2.11.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.10.0 → pydoll_python-2.11.0}/PKG-INFO +31 -1
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/README.md +30 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/browser/tab.py +14 -1
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/constants.py +118 -2
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/elements/web_element.py +83 -8
- pydoll_python-2.11.0/pydoll/interactions/__init__.py +4 -0
- pydoll_python-2.11.0/pydoll/interactions/keyboard.py +178 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pyproject.toml +1 -1
- pydoll_python-2.10.0/pydoll/interactions/__init__.py +0 -3
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/LICENSE +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/__init__.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/browser/__init__.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/browser/chromium/__init__.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/browser/chromium/base.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/browser/chromium/chrome.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/browser/chromium/edge.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/browser/interfaces.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/browser/managers/__init__.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/browser/managers/browser_options_manager.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/browser/managers/browser_process_manager.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/browser/managers/proxy_manager.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/browser/managers/temp_dir_manager.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/browser/options.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/browser/requests/__init__.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/browser/requests/request.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/browser/requests/response.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/commands/__init__.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/commands/browser_commands.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/commands/dom_commands.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/commands/fetch_commands.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/commands/input_commands.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/commands/network_commands.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/commands/page_commands.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/commands/runtime_commands.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/commands/storage_commands.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/commands/target_commands.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/connection/__init__.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/connection/connection_handler.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/connection/managers/__init__.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/connection/managers/commands_manager.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/connection/managers/events_manager.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/elements/__init__.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/elements/mixins/__init__.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/elements/mixins/find_elements_mixin.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/exceptions.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/interactions/scroll.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/__init__.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/base.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/browser/__init__.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/browser/events.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/browser/methods.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/browser/types.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/debugger/types.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/dom/__init__.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/dom/events.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/dom/methods.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/dom/types.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/emulation/types.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/fetch/__init__.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/fetch/events.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/fetch/methods.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/fetch/types.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/input/__init__.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/input/events.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/input/methods.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/input/types.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/io/types.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/network/__init__.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/network/events.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/network/methods.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/network/types.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/page/__init__.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/page/events.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/page/methods.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/page/types.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/runtime/__init__.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/runtime/events.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/runtime/methods.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/runtime/types.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/security/types.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/storage/__init__.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/storage/events.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/storage/methods.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/storage/types.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/target/__init__.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/target/events.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/target/methods.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/target/types.py +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/py.typed +0 -0
- {pydoll_python-2.10.0 → pydoll_python-2.11.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.11.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
|
|
@@ -82,6 +82,36 @@ 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
|
+
|
|
85
115
|
## 📦 Installation
|
|
86
116
|
|
|
87
117
|
```bash
|
|
@@ -62,6 +62,36 @@ 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
|
+
|
|
65
95
|
## 📦 Installation
|
|
66
96
|
|
|
67
97
|
```bash
|
|
@@ -50,7 +50,7 @@ from pydoll.exceptions import (
|
|
|
50
50
|
TopLevelTargetRequired,
|
|
51
51
|
WaitElementTimeout,
|
|
52
52
|
)
|
|
53
|
-
from pydoll.interactions
|
|
53
|
+
from pydoll.interactions import KeyboardAPI, ScrollAPI
|
|
54
54
|
from pydoll.protocol.browser.types import DownloadBehavior, DownloadProgressState
|
|
55
55
|
from pydoll.protocol.page.events import PageEvent
|
|
56
56
|
from pydoll.protocol.page.types import ScreenshotFormat
|
|
@@ -133,6 +133,7 @@ class Tab(FindElementsMixin):
|
|
|
133
133
|
self._cloudflare_captcha_callback_id: Optional[int] = None
|
|
134
134
|
self._request: Optional[Request] = None
|
|
135
135
|
self._scroll: Optional[ScrollAPI] = None
|
|
136
|
+
self._keyboard: Optional[KeyboardAPI] = None
|
|
136
137
|
logger.debug(
|
|
137
138
|
(
|
|
138
139
|
f'Tab initialized: target_id={self._target_id}, '
|
|
@@ -190,6 +191,18 @@ class Tab(FindElementsMixin):
|
|
|
190
191
|
self._scroll = ScrollAPI(self)
|
|
191
192
|
return self._scroll
|
|
192
193
|
|
|
194
|
+
@property
|
|
195
|
+
def keyboard(self) -> KeyboardAPI:
|
|
196
|
+
"""
|
|
197
|
+
Get the keyboard API for controlling keyboard input at page level.
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
KeyboardAPI: An instance of the KeyboardAPI class for keyboard operations.
|
|
201
|
+
"""
|
|
202
|
+
if self._keyboard is None:
|
|
203
|
+
self._keyboard = KeyboardAPI(self)
|
|
204
|
+
return self._keyboard
|
|
205
|
+
|
|
193
206
|
@property
|
|
194
207
|
def intercept_file_chooser_dialog_enabled(self) -> bool:
|
|
195
208
|
"""Whether file chooser dialog interception is active."""
|
|
@@ -356,6 +356,66 @@ new Promise((resolve) => {{
|
|
|
356
356
|
}});
|
|
357
357
|
"""
|
|
358
358
|
|
|
359
|
+
INSERT_TEXT = """
|
|
360
|
+
function() {
|
|
361
|
+
const el = this;
|
|
362
|
+
const text = arguments[0];
|
|
363
|
+
|
|
364
|
+
// Standard input/textarea
|
|
365
|
+
if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA') {
|
|
366
|
+
const start = el.selectionStart || el.value.length;
|
|
367
|
+
const end = el.selectionEnd || el.value.length;
|
|
368
|
+
const before = el.value.substring(0, start);
|
|
369
|
+
const after = el.value.substring(end);
|
|
370
|
+
el.value = before + text + after;
|
|
371
|
+
el.selectionStart = el.selectionEnd = start + text.length;
|
|
372
|
+
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
373
|
+
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
374
|
+
return true;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// ContentEditable elements
|
|
378
|
+
if (el.isContentEditable) {
|
|
379
|
+
el.focus();
|
|
380
|
+
const selection = window.getSelection();
|
|
381
|
+
const range = selection.getRangeAt(0);
|
|
382
|
+
range.deleteContents();
|
|
383
|
+
const textNode = document.createTextNode(text);
|
|
384
|
+
range.insertNode(textNode);
|
|
385
|
+
range.setStartAfter(textNode);
|
|
386
|
+
range.setEndAfter(textNode);
|
|
387
|
+
selection.removeAllRanges();
|
|
388
|
+
selection.addRange(range);
|
|
389
|
+
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
390
|
+
return true;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return false;
|
|
394
|
+
}
|
|
395
|
+
"""
|
|
396
|
+
|
|
397
|
+
IS_EDITABLE = """
|
|
398
|
+
function() {
|
|
399
|
+
const el = this;
|
|
400
|
+
|
|
401
|
+
// Check standard input elements
|
|
402
|
+
if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA') {
|
|
403
|
+
return !el.disabled && !el.readOnly;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Check contenteditable (including inherited)
|
|
407
|
+
let current = el;
|
|
408
|
+
while (current) {
|
|
409
|
+
if (current.isContentEditable) {
|
|
410
|
+
return true;
|
|
411
|
+
}
|
|
412
|
+
current = current.parentElement;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
417
|
+
"""
|
|
418
|
+
|
|
359
419
|
|
|
360
420
|
class Key(tuple[str, int], Enum):
|
|
361
421
|
BACKSPACE = ('Backspace', 8)
|
|
@@ -379,11 +439,64 @@ class Key(tuple[str, int], Enum):
|
|
|
379
439
|
PRINTSCREEN = ('PrintScreen', 44)
|
|
380
440
|
INSERT = ('Insert', 45)
|
|
381
441
|
DELETE = ('Delete', 46)
|
|
442
|
+
|
|
443
|
+
DIGIT0 = ('0', 48)
|
|
444
|
+
DIGIT1 = ('1', 49)
|
|
445
|
+
DIGIT2 = ('2', 50)
|
|
446
|
+
DIGIT3 = ('3', 51)
|
|
447
|
+
DIGIT4 = ('4', 52)
|
|
448
|
+
DIGIT5 = ('5', 53)
|
|
449
|
+
DIGIT6 = ('6', 54)
|
|
450
|
+
DIGIT7 = ('7', 55)
|
|
451
|
+
DIGIT8 = ('8', 56)
|
|
452
|
+
DIGIT9 = ('9', 57)
|
|
453
|
+
|
|
454
|
+
A = ('A', 65)
|
|
455
|
+
B = ('B', 66)
|
|
456
|
+
C = ('C', 67)
|
|
457
|
+
D = ('D', 68)
|
|
458
|
+
E = ('E', 69)
|
|
459
|
+
F = ('F', 70)
|
|
460
|
+
G = ('G', 71)
|
|
461
|
+
H = ('H', 72)
|
|
462
|
+
I = ('I', 73) # noqa: E741
|
|
463
|
+
J = ('J', 74)
|
|
464
|
+
K = ('K', 75)
|
|
465
|
+
L = ('L', 76)
|
|
466
|
+
M = ('M', 77)
|
|
467
|
+
N = ('N', 78)
|
|
468
|
+
O = ('O', 79) # noqa: E741
|
|
469
|
+
P = ('P', 80)
|
|
470
|
+
Q = ('Q', 81)
|
|
471
|
+
R = ('R', 82)
|
|
472
|
+
S = ('S', 83)
|
|
473
|
+
T = ('T', 84)
|
|
474
|
+
U = ('U', 85)
|
|
475
|
+
V = ('V', 86)
|
|
476
|
+
W = ('W', 87)
|
|
477
|
+
X = ('X', 88)
|
|
478
|
+
Y = ('Y', 89)
|
|
479
|
+
Z = ('Z', 90)
|
|
480
|
+
|
|
382
481
|
META = ('Meta', 91)
|
|
383
482
|
METARIGHT = ('MetaRight', 92)
|
|
384
483
|
CONTEXTMENU = ('ContextMenu', 93)
|
|
385
|
-
|
|
386
|
-
|
|
484
|
+
|
|
485
|
+
NUMPAD0 = ('Numpad0', 96)
|
|
486
|
+
NUMPAD1 = ('Numpad1', 97)
|
|
487
|
+
NUMPAD2 = ('Numpad2', 98)
|
|
488
|
+
NUMPAD3 = ('Numpad3', 99)
|
|
489
|
+
NUMPAD4 = ('Numpad4', 100)
|
|
490
|
+
NUMPAD5 = ('Numpad5', 101)
|
|
491
|
+
NUMPAD6 = ('Numpad6', 102)
|
|
492
|
+
NUMPAD7 = ('Numpad7', 103)
|
|
493
|
+
NUMPAD8 = ('Numpad8', 104)
|
|
494
|
+
NUMPAD9 = ('Numpad9', 105)
|
|
495
|
+
NUMPADMULTIPLY = ('NumpadMultiply', 106)
|
|
496
|
+
NUMPADADD = ('NumpadAdd', 107)
|
|
497
|
+
NUMPADSUBTRACT = ('NumpadSubtract', 109)
|
|
498
|
+
NUMPADDECIMAL = ('NumpadDecimal', 110)
|
|
499
|
+
NUMPADDIVIDE = ('NumpadDivide', 111)
|
|
387
500
|
|
|
388
501
|
F1 = ('F1', 112)
|
|
389
502
|
F2 = ('F2', 113)
|
|
@@ -398,6 +511,9 @@ class Key(tuple[str, int], Enum):
|
|
|
398
511
|
F11 = ('F11', 122)
|
|
399
512
|
F12 = ('F12', 123)
|
|
400
513
|
|
|
514
|
+
NUMLOCK = ('NumLock', 144)
|
|
515
|
+
SCROLLLOCK = ('ScrollLock', 145)
|
|
516
|
+
|
|
401
517
|
SEMICOLON = ('Semicolon', 186)
|
|
402
518
|
EQUALSIGN = ('EqualSign', 187)
|
|
403
519
|
COMMA = ('Comma', 188)
|
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import json
|
|
5
5
|
import logging
|
|
6
|
+
import warnings
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
from typing import TYPE_CHECKING
|
|
8
9
|
|
|
@@ -36,6 +37,7 @@ from pydoll.protocol.input.types import (
|
|
|
36
37
|
MouseEventType,
|
|
37
38
|
)
|
|
38
39
|
from pydoll.protocol.page.types import ScreenshotFormat, Viewport
|
|
40
|
+
from pydoll.protocol.runtime.types import CallArgument
|
|
39
41
|
from pydoll.utils import (
|
|
40
42
|
decode_base64_to_bytes,
|
|
41
43
|
extract_text_from_html,
|
|
@@ -457,13 +459,31 @@ class WebElement(FindElementsMixin): # noqa: PLR0904
|
|
|
457
459
|
|
|
458
460
|
async def insert_text(self, text: str):
|
|
459
461
|
"""
|
|
460
|
-
Insert text
|
|
462
|
+
Insert text into element using JavaScript.
|
|
463
|
+
|
|
464
|
+
Supports standard inputs, textareas, contenteditable elements, and rich text editors.
|
|
465
|
+
Inserts text at cursor position or replaces selected text.
|
|
466
|
+
|
|
467
|
+
Args:
|
|
468
|
+
text: Text to insert.
|
|
469
|
+
|
|
470
|
+
Raises:
|
|
471
|
+
ElementNotInteractable: If element does not accept text input.
|
|
461
472
|
|
|
462
473
|
Note:
|
|
463
|
-
|
|
474
|
+
Uses JavaScript for maximum compatibility with all input types.
|
|
475
|
+
Automatically handles input/textarea and contenteditable elements.
|
|
464
476
|
"""
|
|
465
|
-
logger.info(f'Inserting text
|
|
466
|
-
await self.
|
|
477
|
+
logger.info(f'Inserting text (length={len(text)})')
|
|
478
|
+
result = await self.execute_script(
|
|
479
|
+
Scripts.INSERT_TEXT, return_by_value=True, arguments=[CallArgument(value=text)]
|
|
480
|
+
)
|
|
481
|
+
logger.debug(f'Insert text result: {result}')
|
|
482
|
+
success = result['result'].get('result', {}).get('value', False)
|
|
483
|
+
|
|
484
|
+
if not success:
|
|
485
|
+
logger.error('Element does not accept text input')
|
|
486
|
+
raise ElementNotInteractable('Element does not accept text input')
|
|
467
487
|
|
|
468
488
|
async def set_input_files(self, files: str | Path | list[str | Path]):
|
|
469
489
|
"""
|
|
@@ -507,9 +527,18 @@ class WebElement(FindElementsMixin): # noqa: PLR0904
|
|
|
507
527
|
"""
|
|
508
528
|
Send key down event.
|
|
509
529
|
|
|
530
|
+
.. deprecated::
|
|
531
|
+
This method is deprecated. Use ``tab.keyboard.down()`` instead.
|
|
532
|
+
|
|
510
533
|
Note:
|
|
511
534
|
Only sends key down without release. Pair with key_up() for complete keypress.
|
|
512
535
|
"""
|
|
536
|
+
warnings.warn(
|
|
537
|
+
'WebElement.key_down() is deprecated. '
|
|
538
|
+
'Use tab.keyboard API instead: await tab.keyboard.down(key, modifiers)',
|
|
539
|
+
DeprecationWarning,
|
|
540
|
+
stacklevel=2,
|
|
541
|
+
)
|
|
513
542
|
key_name, code = key
|
|
514
543
|
logger.info(f'Key down: key={key_name} code={code} modifiers={modifiers}')
|
|
515
544
|
await self._execute_command(
|
|
@@ -523,7 +552,18 @@ class WebElement(FindElementsMixin): # noqa: PLR0904
|
|
|
523
552
|
)
|
|
524
553
|
|
|
525
554
|
async def key_up(self, key: Key):
|
|
526
|
-
"""
|
|
555
|
+
"""
|
|
556
|
+
Send key up event (should follow corresponding key_down()).
|
|
557
|
+
|
|
558
|
+
.. deprecated::
|
|
559
|
+
This method is deprecated. Use ``tab.keyboard.up()`` instead.
|
|
560
|
+
"""
|
|
561
|
+
warnings.warn(
|
|
562
|
+
'WebElement.key_up() is deprecated. '
|
|
563
|
+
'Use tab.keyboard API instead: await tab.keyboard.up(key)',
|
|
564
|
+
DeprecationWarning,
|
|
565
|
+
stacklevel=2,
|
|
566
|
+
)
|
|
527
567
|
key_name, code = key
|
|
528
568
|
logger.info(f'Key up: key={key_name} code={code}')
|
|
529
569
|
await self._execute_command(
|
|
@@ -544,12 +584,33 @@ class WebElement(FindElementsMixin): # noqa: PLR0904
|
|
|
544
584
|
"""
|
|
545
585
|
Press and release keyboard key with configurable timing.
|
|
546
586
|
|
|
587
|
+
.. deprecated::
|
|
588
|
+
This method is deprecated. Use ``tab.keyboard.press()`` instead.
|
|
589
|
+
|
|
547
590
|
Better for special keys (Enter, Tab, etc.) than type_text().
|
|
548
591
|
"""
|
|
592
|
+
warnings.warn(
|
|
593
|
+
'WebElement.press_keyboard_key() is deprecated. '
|
|
594
|
+
'Use tab.keyboard API instead: await tab.keyboard.press(key, modifiers, interval)',
|
|
595
|
+
DeprecationWarning,
|
|
596
|
+
stacklevel=2,
|
|
597
|
+
)
|
|
549
598
|
await self.key_down(key, modifiers)
|
|
550
599
|
await asyncio.sleep(interval)
|
|
551
600
|
await self.key_up(key)
|
|
552
601
|
|
|
602
|
+
async def is_editable(self) -> bool:
|
|
603
|
+
"""
|
|
604
|
+
Check if element can accept text input.
|
|
605
|
+
|
|
606
|
+
Returns:
|
|
607
|
+
True if element is editable (input, textarea, or contenteditable).
|
|
608
|
+
"""
|
|
609
|
+
result = await self.execute_script(Scripts.IS_EDITABLE, return_by_value=True)
|
|
610
|
+
is_editable = result['result']['result']['value']
|
|
611
|
+
logger.debug(f'Element editable check: {is_editable}')
|
|
612
|
+
return is_editable
|
|
613
|
+
|
|
553
614
|
async def _click_option_tag(self):
|
|
554
615
|
"""Specialized method for clicking <option> elements in dropdowns."""
|
|
555
616
|
await self._execute_command(
|
|
@@ -575,20 +636,34 @@ class WebElement(FindElementsMixin): # noqa: PLR0904
|
|
|
575
636
|
result = await self.execute_script(Scripts.ELEMENT_INTERACTIVE, return_by_value=True)
|
|
576
637
|
return result['result']['result']['value']
|
|
577
638
|
|
|
578
|
-
async def execute_script(
|
|
639
|
+
async def execute_script(
|
|
640
|
+
self,
|
|
641
|
+
script: str,
|
|
642
|
+
return_by_value: bool = False,
|
|
643
|
+
arguments: Optional[list[CallArgument]] = None,
|
|
644
|
+
):
|
|
579
645
|
"""
|
|
580
646
|
Execute JavaScript in element context.
|
|
581
647
|
|
|
582
|
-
|
|
648
|
+
Args:
|
|
649
|
+
script: JavaScript function to execute.
|
|
650
|
+
return_by_value: Whether to return result by value.
|
|
651
|
+
arguments: Optional list of arguments to pass to the function.
|
|
652
|
+
|
|
653
|
+
Note:
|
|
654
|
+
Element is available as 'this' within the script.
|
|
655
|
+
Arguments are accessible via 'arguments' array in JavaScript.
|
|
583
656
|
"""
|
|
584
657
|
logger.debug(
|
|
585
|
-
f'Executing script on element: return_by_value={return_by_value},
|
|
658
|
+
f'Executing script on element: return_by_value={return_by_value}, '
|
|
659
|
+
f'length={len(script)}, args={len(arguments) if arguments else 0}'
|
|
586
660
|
)
|
|
587
661
|
return await self._execute_command(
|
|
588
662
|
RuntimeCommands.call_function_on(
|
|
589
663
|
object_id=self._object_id,
|
|
590
664
|
function_declaration=script,
|
|
591
665
|
return_by_value=return_by_value,
|
|
666
|
+
arguments=arguments,
|
|
592
667
|
)
|
|
593
668
|
)
|
|
594
669
|
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import logging
|
|
5
|
+
from typing import TYPE_CHECKING, Optional, cast
|
|
6
|
+
|
|
7
|
+
from pydoll.commands import InputCommands
|
|
8
|
+
from pydoll.constants import Key
|
|
9
|
+
from pydoll.protocol.input.types import KeyEventType, KeyModifier
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from pydoll.browser.tab import Tab
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class KeyboardAPI:
|
|
18
|
+
"""
|
|
19
|
+
API for controlling keyboard input at page level.
|
|
20
|
+
|
|
21
|
+
Provides methods for simulating keyboard input, key combinations,
|
|
22
|
+
and realistic typing using CDP Input domain.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, tab: Tab):
|
|
26
|
+
"""
|
|
27
|
+
Initialize the KeyboardAPI with a tab instance.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
tab: Tab instance to execute keyboard commands on.
|
|
31
|
+
"""
|
|
32
|
+
logger.debug(f'Initializing KeyboardAPI for tab: {tab}')
|
|
33
|
+
self._tab = tab
|
|
34
|
+
|
|
35
|
+
async def press(
|
|
36
|
+
self,
|
|
37
|
+
key: Key,
|
|
38
|
+
modifiers: Optional[KeyModifier] = None,
|
|
39
|
+
interval: float = 0.1,
|
|
40
|
+
):
|
|
41
|
+
"""
|
|
42
|
+
Press and release a key (down + wait + up).
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
key: Key to press (from Key enum).
|
|
46
|
+
modifiers: Optional key modifiers (Alt=1, Ctrl=2, Meta=4, Shift=8).
|
|
47
|
+
interval: Time to hold the key down in seconds.
|
|
48
|
+
|
|
49
|
+
Example:
|
|
50
|
+
await tab.keyboard.press(Key.ENTER)
|
|
51
|
+
await tab.keyboard.press(Key.A, modifiers=KeyModifier.CTRL)
|
|
52
|
+
"""
|
|
53
|
+
logger.info(f'Pressing key: {key} with modifiers: {modifiers} and interval: {interval}')
|
|
54
|
+
await self.down(key, modifiers)
|
|
55
|
+
await asyncio.sleep(interval)
|
|
56
|
+
await self.up(key)
|
|
57
|
+
|
|
58
|
+
async def down(self, key: Key, modifiers: Optional[KeyModifier] = None):
|
|
59
|
+
"""
|
|
60
|
+
Press a key down (without releasing).
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
key: Key to press down (from Key enum).
|
|
64
|
+
modifiers: Optional key modifiers (Alt=1, Ctrl=2, Meta=4, Shift=8).
|
|
65
|
+
|
|
66
|
+
Example:
|
|
67
|
+
await tab.keyboard.down(Key.SHIFT)
|
|
68
|
+
"""
|
|
69
|
+
key_name, code = key
|
|
70
|
+
logger.info(f'Pressing key down: {key_name} with modifiers: {modifiers}')
|
|
71
|
+
command = InputCommands.dispatch_key_event(
|
|
72
|
+
type=KeyEventType.KEY_DOWN,
|
|
73
|
+
key=key_name,
|
|
74
|
+
windows_virtual_key_code=code,
|
|
75
|
+
native_virtual_key_code=code,
|
|
76
|
+
modifiers=modifiers,
|
|
77
|
+
)
|
|
78
|
+
await self._tab._execute_command(command)
|
|
79
|
+
|
|
80
|
+
async def up(self, key: Key):
|
|
81
|
+
"""
|
|
82
|
+
Release a key (key up event).
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
key: Key to release (from Key enum).
|
|
86
|
+
|
|
87
|
+
Example:
|
|
88
|
+
await tab.keyboard.up(Key.SHIFT)
|
|
89
|
+
"""
|
|
90
|
+
logger.info(f'Pressing key up: {key}')
|
|
91
|
+
key_name, code = key
|
|
92
|
+
command = InputCommands.dispatch_key_event(
|
|
93
|
+
type=KeyEventType.KEY_UP,
|
|
94
|
+
key=key_name,
|
|
95
|
+
windows_virtual_key_code=code,
|
|
96
|
+
native_virtual_key_code=code,
|
|
97
|
+
)
|
|
98
|
+
await self._tab._execute_command(command)
|
|
99
|
+
|
|
100
|
+
async def hotkey(self, key1: Key, key2: Key, key3: Optional[Key] = None):
|
|
101
|
+
"""
|
|
102
|
+
Execute a key combination (hotkey) with up to 3 keys.
|
|
103
|
+
|
|
104
|
+
Automatically detects modifier keys (Ctrl, Shift, Alt, Meta) and applies
|
|
105
|
+
them correctly when pressing non-modifier keys.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
key1: First key (usually a modifier like Ctrl, Shift, Alt).
|
|
109
|
+
key2: Second key.
|
|
110
|
+
key3: Optional third key.
|
|
111
|
+
|
|
112
|
+
Example:
|
|
113
|
+
await tab.keyboard.hotkey(Key.CONTROL, Key.C) # Ctrl+C
|
|
114
|
+
await tab.keyboard.hotkey(Key.CONTROL, Key.SHIFT, Key.T) # Ctrl+Shift+T
|
|
115
|
+
"""
|
|
116
|
+
logger.info(f'Executing hotkey: {key1} {key2} {key3}')
|
|
117
|
+
keys = [key1, key2]
|
|
118
|
+
if key3 is not None:
|
|
119
|
+
keys.append(key3)
|
|
120
|
+
|
|
121
|
+
modifiers, non_modifiers = self._split_modifiers_and_keys(keys)
|
|
122
|
+
modifier_value = self._calculate_modifier_value(modifiers)
|
|
123
|
+
|
|
124
|
+
logger.debug(f'Modifiers: {modifiers} modifier_value: {modifier_value}')
|
|
125
|
+
for key in non_modifiers:
|
|
126
|
+
await self.down(key, modifiers=modifier_value)
|
|
127
|
+
await asyncio.sleep(0.05)
|
|
128
|
+
|
|
129
|
+
await asyncio.sleep(0.1)
|
|
130
|
+
|
|
131
|
+
for key in reversed(non_modifiers):
|
|
132
|
+
await self.up(key)
|
|
133
|
+
await asyncio.sleep(0.05)
|
|
134
|
+
|
|
135
|
+
@staticmethod
|
|
136
|
+
def _split_modifiers_and_keys(keys: list[Key]) -> tuple[list[Key], list[Key]]:
|
|
137
|
+
"""
|
|
138
|
+
Split keys into modifiers and non-modifiers.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
keys: List of keys to split.
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
Tuple of (modifiers, non_modifiers).
|
|
145
|
+
"""
|
|
146
|
+
modifier_keys = {Key.CONTROL, Key.SHIFT, Key.ALT, Key.META}
|
|
147
|
+
modifiers = [k for k in keys if k in modifier_keys]
|
|
148
|
+
non_modifiers = [k for k in keys if k not in modifier_keys]
|
|
149
|
+
logger.debug(f'Modifiers: {modifiers} Non-modifiers: {non_modifiers}')
|
|
150
|
+
return modifiers, non_modifiers
|
|
151
|
+
|
|
152
|
+
@staticmethod
|
|
153
|
+
def _calculate_modifier_value(modifiers: list[Key]) -> Optional[KeyModifier]:
|
|
154
|
+
"""
|
|
155
|
+
Calculate the KeyModifier value from a list of modifier keys.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
modifiers: List of modifier keys.
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
Combined KeyModifier value, or None if no modifiers.
|
|
162
|
+
"""
|
|
163
|
+
logger.debug(f'Calculating modifier value for: {modifiers}')
|
|
164
|
+
if not modifiers:
|
|
165
|
+
return None
|
|
166
|
+
|
|
167
|
+
modifier_map = {
|
|
168
|
+
Key.ALT: 1,
|
|
169
|
+
Key.CONTROL: 2,
|
|
170
|
+
Key.META: 4,
|
|
171
|
+
Key.SHIFT: 8,
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
value = 0
|
|
175
|
+
for modifier in modifiers:
|
|
176
|
+
value += modifier_map.get(modifier, 0)
|
|
177
|
+
|
|
178
|
+
return cast(KeyModifier, value) if value > 0 else None
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "pydoll-python"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.11.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"
|
|
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.10.0 → pydoll_python-2.11.0}/pydoll/browser/managers/browser_options_manager.py
RENAMED
|
File without changes
|
{pydoll_python-2.10.0 → pydoll_python-2.11.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.10.0 → pydoll_python-2.11.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
|