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.
Files changed (90) hide show
  1. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/PKG-INFO +31 -1
  2. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/README.md +30 -0
  3. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/browser/tab.py +14 -1
  4. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/constants.py +118 -2
  5. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/elements/web_element.py +83 -8
  6. pydoll_python-2.11.0/pydoll/interactions/__init__.py +4 -0
  7. pydoll_python-2.11.0/pydoll/interactions/keyboard.py +178 -0
  8. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pyproject.toml +1 -1
  9. pydoll_python-2.10.0/pydoll/interactions/__init__.py +0 -3
  10. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/LICENSE +0 -0
  11. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/__init__.py +0 -0
  12. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/browser/__init__.py +0 -0
  13. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/browser/chromium/__init__.py +0 -0
  14. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/browser/chromium/base.py +0 -0
  15. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/browser/chromium/chrome.py +0 -0
  16. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/browser/chromium/edge.py +0 -0
  17. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/browser/interfaces.py +0 -0
  18. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/browser/managers/__init__.py +0 -0
  19. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/browser/managers/browser_options_manager.py +0 -0
  20. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/browser/managers/browser_process_manager.py +0 -0
  21. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/browser/managers/proxy_manager.py +0 -0
  22. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/browser/managers/temp_dir_manager.py +0 -0
  23. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/browser/options.py +0 -0
  24. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/browser/requests/__init__.py +0 -0
  25. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/browser/requests/request.py +0 -0
  26. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/browser/requests/response.py +0 -0
  27. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/commands/__init__.py +0 -0
  28. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/commands/browser_commands.py +0 -0
  29. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/commands/dom_commands.py +0 -0
  30. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/commands/fetch_commands.py +0 -0
  31. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/commands/input_commands.py +0 -0
  32. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/commands/network_commands.py +0 -0
  33. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/commands/page_commands.py +0 -0
  34. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/commands/runtime_commands.py +0 -0
  35. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/commands/storage_commands.py +0 -0
  36. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/commands/target_commands.py +0 -0
  37. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/connection/__init__.py +0 -0
  38. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/connection/connection_handler.py +0 -0
  39. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/connection/managers/__init__.py +0 -0
  40. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/connection/managers/commands_manager.py +0 -0
  41. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/connection/managers/events_manager.py +0 -0
  42. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/elements/__init__.py +0 -0
  43. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/elements/mixins/__init__.py +0 -0
  44. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/elements/mixins/find_elements_mixin.py +0 -0
  45. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/exceptions.py +0 -0
  46. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/interactions/scroll.py +0 -0
  47. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/__init__.py +0 -0
  48. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/base.py +0 -0
  49. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/browser/__init__.py +0 -0
  50. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/browser/events.py +0 -0
  51. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/browser/methods.py +0 -0
  52. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/browser/types.py +0 -0
  53. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/debugger/types.py +0 -0
  54. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/dom/__init__.py +0 -0
  55. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/dom/events.py +0 -0
  56. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/dom/methods.py +0 -0
  57. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/dom/types.py +0 -0
  58. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/emulation/types.py +0 -0
  59. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/fetch/__init__.py +0 -0
  60. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/fetch/events.py +0 -0
  61. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/fetch/methods.py +0 -0
  62. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/fetch/types.py +0 -0
  63. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/input/__init__.py +0 -0
  64. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/input/events.py +0 -0
  65. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/input/methods.py +0 -0
  66. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/input/types.py +0 -0
  67. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/io/types.py +0 -0
  68. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/network/__init__.py +0 -0
  69. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/network/events.py +0 -0
  70. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/network/methods.py +0 -0
  71. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/network/types.py +0 -0
  72. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/page/__init__.py +0 -0
  73. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/page/events.py +0 -0
  74. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/page/methods.py +0 -0
  75. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/page/types.py +0 -0
  76. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/runtime/__init__.py +0 -0
  77. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/runtime/events.py +0 -0
  78. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/runtime/methods.py +0 -0
  79. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/runtime/types.py +0 -0
  80. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/security/types.py +0 -0
  81. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/storage/__init__.py +0 -0
  82. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/storage/events.py +0 -0
  83. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/storage/methods.py +0 -0
  84. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/storage/types.py +0 -0
  85. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/target/__init__.py +0 -0
  86. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/target/events.py +0 -0
  87. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/target/methods.py +0 -0
  88. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/protocol/target/types.py +0 -0
  89. {pydoll_python-2.10.0 → pydoll_python-2.11.0}/pydoll/py.typed +0 -0
  90. {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.10.0
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.scroll import ScrollAPI
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
- NUMLOCK = ('NumLock', 144)
386
- SCROLLLOCK = ('ScrollLock', 145)
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 in single operation (faster but less realistic than typing).
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
- Element should already be focused for text to be inserted correctly.
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 on element (length={len(text)})')
466
- await self._execute_command(InputCommands.insert_text(text))
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
- """Send key up event (should follow corresponding key_down())."""
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(self, script: str, return_by_value: bool = False):
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
- Element is available as 'this' within the script.
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}, length={len(script)}'
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,4 @@
1
+ from pydoll.interactions.keyboard import KeyboardAPI
2
+ from pydoll.interactions.scroll import ScrollAPI
3
+
4
+ __all__ = ['KeyboardAPI', 'ScrollAPI']
@@ -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.10.0"
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"
@@ -1,3 +0,0 @@
1
- from pydoll.interactions.scroll import ScrollAPI
2
-
3
- __all__ = ['ScrollAPI']
File without changes