pydoll-python 2.18.0__tar.gz → 2.19.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.18.0 → pydoll_python-2.19.0}/PKG-INFO +46 -18
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/README.md +45 -17
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/browser/chromium/base.py +50 -5
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/browser/options.py +15 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/browser/tab.py +21 -3
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/commands/__init__.py +2 -0
- pydoll_python-2.19.0/pydoll/commands/emulation_commands.py +61 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/elements/mixins/find_elements_mixin.py +16 -2
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/elements/web_element.py +31 -8
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/interactions/__init__.py +4 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/interactions/keyboard.py +3 -3
- pydoll_python-2.19.0/pydoll/interactions/mouse.py +475 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/interactions/scroll.py +4 -76
- pydoll_python-2.19.0/pydoll/interactions/utils.py +167 -0
- pydoll_python-2.19.0/pydoll/protocol/emulation/__init__.py +1 -0
- pydoll_python-2.19.0/pydoll/protocol/emulation/methods.py +25 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/utils/__init__.py +2 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/utils/socks5_proxy_forwarder.py +165 -71
- pydoll_python-2.19.0/pydoll/utils/user_agent_parser.py +289 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pyproject.toml +1 -1
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/LICENSE +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/__init__.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/browser/__init__.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/browser/chromium/__init__.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/browser/chromium/chrome.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/browser/chromium/edge.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/browser/interfaces.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/browser/managers/__init__.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/browser/managers/browser_options_manager.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/browser/managers/browser_process_manager.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/browser/managers/proxy_manager.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/browser/managers/temp_dir_manager.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/browser/requests/__init__.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/browser/requests/request.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/browser/requests/response.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/commands/browser_commands.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/commands/dom_commands.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/commands/fetch_commands.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/commands/input_commands.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/commands/network_commands.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/commands/page_commands.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/commands/runtime_commands.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/commands/storage_commands.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/commands/target_commands.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/connection/__init__.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/connection/connection_handler.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/connection/managers/__init__.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/connection/managers/commands_manager.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/connection/managers/events_manager.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/constants.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/decorators.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/elements/__init__.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/elements/mixins/__init__.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/elements/shadow_root.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/elements/utils/__init__.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/elements/utils/selector_parser.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/exceptions.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/interactions/iframe.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/protocol/__init__.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/protocol/base.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/protocol/browser/__init__.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/protocol/browser/events.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/protocol/browser/methods.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/protocol/browser/types.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/protocol/debugger/types.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/protocol/dom/__init__.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/protocol/dom/events.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/protocol/dom/methods.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/protocol/dom/types.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/protocol/emulation/types.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/protocol/fetch/__init__.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/protocol/fetch/events.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/protocol/fetch/methods.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/protocol/fetch/types.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/protocol/input/__init__.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/protocol/input/events.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/protocol/input/methods.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/protocol/input/types.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/protocol/io/types.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/protocol/network/__init__.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/protocol/network/events.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/protocol/network/methods.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/protocol/network/types.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/protocol/page/__init__.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/protocol/page/events.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/protocol/page/methods.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/protocol/page/types.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/protocol/runtime/__init__.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/protocol/runtime/events.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/protocol/runtime/methods.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/protocol/runtime/types.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/protocol/security/types.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/protocol/storage/__init__.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/protocol/storage/events.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/protocol/storage/methods.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/protocol/storage/types.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/protocol/target/__init__.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/protocol/target/events.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/protocol/target/methods.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/protocol/target/types.py +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/py.typed +0 -0
- {pydoll_python-2.18.0 → pydoll_python-2.19.0}/pydoll/utils/general.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pydoll-python
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.19.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
|
|
@@ -125,29 +125,28 @@ for sr in shadow_roots:
|
|
|
125
125
|
</details>
|
|
126
126
|
|
|
127
127
|
<details>
|
|
128
|
-
<summary><b>Humanized Keyboard Input
|
|
128
|
+
<summary><b>Humanized Keyboard Input</b></summary>
|
|
129
129
|
<br>
|
|
130
130
|
|
|
131
|
-
Pydoll
|
|
131
|
+
Pydoll's typing engine simulates realistic human typing behavior out of the box:
|
|
132
132
|
|
|
133
133
|
- **Variable keystroke timing**: 30-120ms between keys (not fixed intervals)
|
|
134
134
|
- **Realistic typos**: ~2% error rate with automatic correction behavior
|
|
135
|
-
- **No more `interval` parameter**: Just use `humanize=True` for anti-bot evasion
|
|
136
135
|
|
|
137
136
|
```python
|
|
138
|
-
#
|
|
139
|
-
await element.type_text("hello"
|
|
137
|
+
# Realistic typing by default
|
|
138
|
+
await element.type_text("hello")
|
|
140
139
|
|
|
141
|
-
#
|
|
142
|
-
await element.type_text("hello", humanize=
|
|
140
|
+
# Opt out when speed is critical
|
|
141
|
+
await element.type_text("hello", humanize=False)
|
|
143
142
|
```
|
|
144
143
|
</details>
|
|
145
144
|
|
|
146
145
|
<details>
|
|
147
|
-
<summary><b>Humanized Scroll with Physics Engine
|
|
146
|
+
<summary><b>Humanized Scroll with Physics Engine</b></summary>
|
|
148
147
|
<br>
|
|
149
148
|
|
|
150
|
-
The scroll API
|
|
149
|
+
The scroll API features a **Cubic Bezier curve physics engine** for realistic scrolling:
|
|
151
150
|
|
|
152
151
|
- **Momentum & friction**: Natural acceleration and deceleration
|
|
153
152
|
- **Micro-pauses**: Brief stops during long scrolls (simulates reading)
|
|
@@ -155,23 +154,52 @@ The scroll API now features a **Cubic Bezier curve physics engine** for realisti
|
|
|
155
154
|
- **Overshoot correction**: Occasionally scrolls past target and corrects back
|
|
156
155
|
|
|
157
156
|
```python
|
|
158
|
-
#
|
|
159
|
-
await tab.scroll.by(ScrollPosition.DOWN, 500
|
|
157
|
+
# Humanized by default (physics engine, anti-bot)
|
|
158
|
+
await tab.scroll.by(ScrollPosition.DOWN, 500)
|
|
159
|
+
await tab.scroll.to_bottom()
|
|
160
160
|
|
|
161
|
-
#
|
|
162
|
-
await tab.scroll.by(ScrollPosition.DOWN, 500, humanize=True)
|
|
163
|
-
await tab.scroll.to_bottom(humanize=True)
|
|
161
|
+
# CSS smooth scroll (predictable timing)
|
|
162
|
+
await tab.scroll.by(ScrollPosition.DOWN, 500, humanize=False, smooth=True)
|
|
164
163
|
```
|
|
165
164
|
|
|
166
165
|
| Mode | Parameter | Use Case |
|
|
167
166
|
|------|-----------|----------|
|
|
168
|
-
| **
|
|
169
|
-
| **Smooth** | `smooth=True` | General browsing simulation |
|
|
170
|
-
| **
|
|
167
|
+
| **Humanized** | default | **Anti-bot evasion** |
|
|
168
|
+
| **Smooth** | `humanize=False, smooth=True` | General browsing simulation |
|
|
169
|
+
| **Instant** | `humanize=False, smooth=False` | Speed-critical operations |
|
|
171
170
|
|
|
172
171
|
[**📖 Human-Like Interactions Docs**](https://pydoll.tech/docs/features/automation/human-interactions/)
|
|
173
172
|
</details>
|
|
174
173
|
|
|
174
|
+
<details>
|
|
175
|
+
<summary><b>Humanized Mouse Movement</b></summary>
|
|
176
|
+
<br>
|
|
177
|
+
|
|
178
|
+
All mouse operations produce **human-like cursor movement** by default, using a multi-layered simulation pipeline:
|
|
179
|
+
|
|
180
|
+
- **Bezier curve paths**: Curved trajectories with asymmetric control points
|
|
181
|
+
- **Fitts's Law timing**: Movement duration scales naturally with distance
|
|
182
|
+
- **Minimum-jerk velocity**: Bell-shaped speed profile (slow start, peak, slow end)
|
|
183
|
+
- **Physiological tremor**: Gaussian noise (σ ≈ 1px) scaled inversely with velocity
|
|
184
|
+
- **Overshoot correction**: ~70% chance of overshooting fast movements, then correcting back
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
# All operations are humanized by default
|
|
188
|
+
await tab.mouse.move(500, 300)
|
|
189
|
+
await tab.mouse.click(500, 300)
|
|
190
|
+
await tab.mouse.drag(100, 200, 500, 400)
|
|
191
|
+
|
|
192
|
+
# Element clicks also use realistic Bezier curve movement
|
|
193
|
+
button = await tab.find(id='submit')
|
|
194
|
+
await button.click()
|
|
195
|
+
|
|
196
|
+
# Opt out when speed matters
|
|
197
|
+
await tab.mouse.click(500, 300, humanize=False)
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
[**📖 Mouse Control Docs**](https://pydoll.tech/docs/features/automation/mouse-control/)
|
|
201
|
+
</details>
|
|
202
|
+
|
|
175
203
|
## 🚀 Getting Started in 60 Seconds
|
|
176
204
|
|
|
177
205
|
Thanks to its `async` architecture and context managers, Pydoll is clean and efficient.
|
|
@@ -105,29 +105,28 @@ for sr in shadow_roots:
|
|
|
105
105
|
</details>
|
|
106
106
|
|
|
107
107
|
<details>
|
|
108
|
-
<summary><b>Humanized Keyboard Input
|
|
108
|
+
<summary><b>Humanized Keyboard Input</b></summary>
|
|
109
109
|
<br>
|
|
110
110
|
|
|
111
|
-
Pydoll
|
|
111
|
+
Pydoll's typing engine simulates realistic human typing behavior out of the box:
|
|
112
112
|
|
|
113
113
|
- **Variable keystroke timing**: 30-120ms between keys (not fixed intervals)
|
|
114
114
|
- **Realistic typos**: ~2% error rate with automatic correction behavior
|
|
115
|
-
- **No more `interval` parameter**: Just use `humanize=True` for anti-bot evasion
|
|
116
115
|
|
|
117
116
|
```python
|
|
118
|
-
#
|
|
119
|
-
await element.type_text("hello"
|
|
117
|
+
# Realistic typing by default
|
|
118
|
+
await element.type_text("hello")
|
|
120
119
|
|
|
121
|
-
#
|
|
122
|
-
await element.type_text("hello", humanize=
|
|
120
|
+
# Opt out when speed is critical
|
|
121
|
+
await element.type_text("hello", humanize=False)
|
|
123
122
|
```
|
|
124
123
|
</details>
|
|
125
124
|
|
|
126
125
|
<details>
|
|
127
|
-
<summary><b>Humanized Scroll with Physics Engine
|
|
126
|
+
<summary><b>Humanized Scroll with Physics Engine</b></summary>
|
|
128
127
|
<br>
|
|
129
128
|
|
|
130
|
-
The scroll API
|
|
129
|
+
The scroll API features a **Cubic Bezier curve physics engine** for realistic scrolling:
|
|
131
130
|
|
|
132
131
|
- **Momentum & friction**: Natural acceleration and deceleration
|
|
133
132
|
- **Micro-pauses**: Brief stops during long scrolls (simulates reading)
|
|
@@ -135,23 +134,52 @@ The scroll API now features a **Cubic Bezier curve physics engine** for realisti
|
|
|
135
134
|
- **Overshoot correction**: Occasionally scrolls past target and corrects back
|
|
136
135
|
|
|
137
136
|
```python
|
|
138
|
-
#
|
|
139
|
-
await tab.scroll.by(ScrollPosition.DOWN, 500
|
|
137
|
+
# Humanized by default (physics engine, anti-bot)
|
|
138
|
+
await tab.scroll.by(ScrollPosition.DOWN, 500)
|
|
139
|
+
await tab.scroll.to_bottom()
|
|
140
140
|
|
|
141
|
-
#
|
|
142
|
-
await tab.scroll.by(ScrollPosition.DOWN, 500, humanize=True)
|
|
143
|
-
await tab.scroll.to_bottom(humanize=True)
|
|
141
|
+
# CSS smooth scroll (predictable timing)
|
|
142
|
+
await tab.scroll.by(ScrollPosition.DOWN, 500, humanize=False, smooth=True)
|
|
144
143
|
```
|
|
145
144
|
|
|
146
145
|
| Mode | Parameter | Use Case |
|
|
147
146
|
|------|-----------|----------|
|
|
148
|
-
| **
|
|
149
|
-
| **Smooth** | `smooth=True` | General browsing simulation |
|
|
150
|
-
| **
|
|
147
|
+
| **Humanized** | default | **Anti-bot evasion** |
|
|
148
|
+
| **Smooth** | `humanize=False, smooth=True` | General browsing simulation |
|
|
149
|
+
| **Instant** | `humanize=False, smooth=False` | Speed-critical operations |
|
|
151
150
|
|
|
152
151
|
[**📖 Human-Like Interactions Docs**](https://pydoll.tech/docs/features/automation/human-interactions/)
|
|
153
152
|
</details>
|
|
154
153
|
|
|
154
|
+
<details>
|
|
155
|
+
<summary><b>Humanized Mouse Movement</b></summary>
|
|
156
|
+
<br>
|
|
157
|
+
|
|
158
|
+
All mouse operations produce **human-like cursor movement** by default, using a multi-layered simulation pipeline:
|
|
159
|
+
|
|
160
|
+
- **Bezier curve paths**: Curved trajectories with asymmetric control points
|
|
161
|
+
- **Fitts's Law timing**: Movement duration scales naturally with distance
|
|
162
|
+
- **Minimum-jerk velocity**: Bell-shaped speed profile (slow start, peak, slow end)
|
|
163
|
+
- **Physiological tremor**: Gaussian noise (σ ≈ 1px) scaled inversely with velocity
|
|
164
|
+
- **Overshoot correction**: ~70% chance of overshooting fast movements, then correcting back
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
# All operations are humanized by default
|
|
168
|
+
await tab.mouse.move(500, 300)
|
|
169
|
+
await tab.mouse.click(500, 300)
|
|
170
|
+
await tab.mouse.drag(100, 200, 500, 400)
|
|
171
|
+
|
|
172
|
+
# Element clicks also use realistic Bezier curve movement
|
|
173
|
+
button = await tab.find(id='submit')
|
|
174
|
+
await button.click()
|
|
175
|
+
|
|
176
|
+
# Opt out when speed matters
|
|
177
|
+
await tab.mouse.click(500, 300, humanize=False)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
[**📖 Mouse Control Docs**](https://pydoll.tech/docs/features/automation/mouse-control/)
|
|
181
|
+
</details>
|
|
182
|
+
|
|
155
183
|
## 🚀 Getting Started in 60 Seconds
|
|
156
184
|
|
|
157
185
|
Thanks to its `async` architecture and context managers, Pydoll is clean and efficient.
|
|
@@ -21,7 +21,9 @@ from pydoll.browser.managers import (
|
|
|
21
21
|
from pydoll.browser.tab import Tab
|
|
22
22
|
from pydoll.commands import (
|
|
23
23
|
BrowserCommands,
|
|
24
|
+
EmulationCommands,
|
|
24
25
|
FetchCommands,
|
|
26
|
+
PageCommands,
|
|
25
27
|
RuntimeCommands,
|
|
26
28
|
StorageCommands,
|
|
27
29
|
TargetCommands,
|
|
@@ -38,6 +40,7 @@ from pydoll.exceptions import (
|
|
|
38
40
|
from pydoll.protocol.browser.types import DownloadBehavior
|
|
39
41
|
from pydoll.protocol.fetch.events import FetchEvent
|
|
40
42
|
from pydoll.protocol.fetch.types import AuthChallengeResponseType
|
|
43
|
+
from pydoll.utils.user_agent_parser import UserAgentParser
|
|
41
44
|
|
|
42
45
|
if TYPE_CHECKING:
|
|
43
46
|
from tempfile import TemporaryDirectory
|
|
@@ -193,6 +196,7 @@ class Browser(ABC): # noqa: PLR0904
|
|
|
193
196
|
valid_tab_id = await self._get_valid_tab_id(await self.get_targets())
|
|
194
197
|
tab = Tab(self, target_id=valid_tab_id, connection_port=self._connection_port)
|
|
195
198
|
self._tabs_opened[valid_tab_id] = tab
|
|
199
|
+
await self._apply_user_agent_override(tab)
|
|
196
200
|
logger.info(f'Initial tab attached: {valid_tab_id}')
|
|
197
201
|
return tab
|
|
198
202
|
|
|
@@ -306,6 +310,7 @@ class Browser(ABC): # noqa: PLR0904
|
|
|
306
310
|
target_id = response['result']['targetId']
|
|
307
311
|
tab = Tab(self, **self._get_tab_kwargs(target_id, browser_context_id))
|
|
308
312
|
self._tabs_opened[target_id] = tab
|
|
313
|
+
await self._apply_user_agent_override(tab)
|
|
309
314
|
await self._setup_context_proxy_auth_for_tab(tab, browser_context_id)
|
|
310
315
|
if url:
|
|
311
316
|
await tab.go_to(url)
|
|
@@ -347,10 +352,11 @@ class Browser(ABC): # noqa: PLR0904
|
|
|
347
352
|
target_id for target_id in all_target_ids if target_id not in existing_target_ids
|
|
348
353
|
]
|
|
349
354
|
existing_tabs = [self._tabs_opened[target_id] for target_id in existing_target_ids]
|
|
350
|
-
new_tabs = [
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
355
|
+
new_tabs = []
|
|
356
|
+
for target_id in reversed(remaining_target_ids):
|
|
357
|
+
tab = Tab(self, **self._get_tab_kwargs(target_id))
|
|
358
|
+
await self._apply_user_agent_override(tab)
|
|
359
|
+
new_tabs.append(tab)
|
|
354
360
|
self._tabs_opened.update(dict(zip(remaining_target_ids, new_tabs)))
|
|
355
361
|
logger.debug(
|
|
356
362
|
f'Opened tabs resolved: existing={len(existing_tabs)}, new={len(new_tabs)}',
|
|
@@ -358,7 +364,9 @@ class Browser(ABC): # noqa: PLR0904
|
|
|
358
364
|
return existing_tabs + new_tabs
|
|
359
365
|
|
|
360
366
|
async def get_tab_by_target(self, target: TargetInfo) -> Tab:
|
|
361
|
-
|
|
367
|
+
tab = Tab(self, **self._get_tab_kwargs(target['targetId']))
|
|
368
|
+
await self._apply_user_agent_override(tab)
|
|
369
|
+
return tab
|
|
362
370
|
|
|
363
371
|
async def set_download_path(self, path: str, browser_context_id: Optional[str] = None):
|
|
364
372
|
"""Set download directory path (convenience method for set_download_behavior)."""
|
|
@@ -754,6 +762,43 @@ class Browser(ABC): # noqa: PLR0904
|
|
|
754
762
|
temporary=True,
|
|
755
763
|
)
|
|
756
764
|
|
|
765
|
+
async def _apply_user_agent_override(self, tab: Tab) -> None:
|
|
766
|
+
"""Apply consistent User-Agent override to a tab if --user-agent= is set.
|
|
767
|
+
|
|
768
|
+
Detects the --user-agent= argument in browser options and automatically
|
|
769
|
+
synchronizes HTTP headers, navigator JS properties, and Client Hints
|
|
770
|
+
via CDP Emulation.setUserAgentOverride + JS injection.
|
|
771
|
+
"""
|
|
772
|
+
user_agent = self._get_user_agent_from_options()
|
|
773
|
+
if not user_agent:
|
|
774
|
+
return
|
|
775
|
+
|
|
776
|
+
parsed = UserAgentParser.parse(user_agent)
|
|
777
|
+
logger.debug('Applying User-Agent override: %s', user_agent[:60])
|
|
778
|
+
|
|
779
|
+
await tab._execute_command(
|
|
780
|
+
EmulationCommands.set_user_agent_override(
|
|
781
|
+
user_agent=user_agent,
|
|
782
|
+
platform=parsed.platform,
|
|
783
|
+
user_agent_metadata=parsed.user_agent_metadata,
|
|
784
|
+
)
|
|
785
|
+
)
|
|
786
|
+
|
|
787
|
+
if parsed.navigator_override_js:
|
|
788
|
+
await tab._execute_command(
|
|
789
|
+
PageCommands.add_script_to_evaluate_on_new_document(
|
|
790
|
+
source=parsed.navigator_override_js,
|
|
791
|
+
run_immediately=True,
|
|
792
|
+
)
|
|
793
|
+
)
|
|
794
|
+
|
|
795
|
+
def _get_user_agent_from_options(self) -> Optional[str]:
|
|
796
|
+
"""Extract User-Agent value from --user-agent= browser argument."""
|
|
797
|
+
for arg in self.options.arguments:
|
|
798
|
+
if arg.startswith('--user-agent='):
|
|
799
|
+
return arg[len('--user-agent=') :]
|
|
800
|
+
return None
|
|
801
|
+
|
|
757
802
|
async def _verify_browser_running(self):
|
|
758
803
|
"""
|
|
759
804
|
Verify browser started successfully.
|
|
@@ -29,6 +29,7 @@ class ChromiumOptions(Options):
|
|
|
29
29
|
self._start_timeout = 10
|
|
30
30
|
self._browser_preferences = {}
|
|
31
31
|
self._headless = False
|
|
32
|
+
self._webrtc_leak_protection = False
|
|
32
33
|
self._page_load_state = PageLoadState.COMPLETE
|
|
33
34
|
|
|
34
35
|
@property
|
|
@@ -319,6 +320,20 @@ class ChromiumOptions(Options):
|
|
|
319
320
|
return
|
|
320
321
|
methods_map[headless]('--headless')
|
|
321
322
|
|
|
323
|
+
@property
|
|
324
|
+
def webrtc_leak_protection(self) -> bool:
|
|
325
|
+
return self._webrtc_leak_protection
|
|
326
|
+
|
|
327
|
+
@webrtc_leak_protection.setter
|
|
328
|
+
def webrtc_leak_protection(self, enabled: bool):
|
|
329
|
+
self._webrtc_leak_protection = enabled
|
|
330
|
+
argument = '--force-webrtc-ip-handling-policy=disable_non_proxied_udp'
|
|
331
|
+
has_argument = argument in self.arguments
|
|
332
|
+
methods_map = {True: self.add_argument, False: self.remove_argument}
|
|
333
|
+
if enabled == has_argument:
|
|
334
|
+
return
|
|
335
|
+
methods_map[enabled](argument)
|
|
336
|
+
|
|
322
337
|
@property
|
|
323
338
|
def page_load_state(self) -> PageLoadState:
|
|
324
339
|
return self._page_load_state
|
|
@@ -56,7 +56,7 @@ from pydoll.exceptions import (
|
|
|
56
56
|
WaitElementTimeout,
|
|
57
57
|
WebSocketConnectionClosed,
|
|
58
58
|
)
|
|
59
|
-
from pydoll.interactions import KeyboardAPI, ScrollAPI
|
|
59
|
+
from pydoll.interactions import KeyboardAPI, MouseAPI, ScrollAPI
|
|
60
60
|
from pydoll.interactions.iframe import IFrameContext
|
|
61
61
|
from pydoll.protocol.browser.types import DownloadBehavior, DownloadProgressState
|
|
62
62
|
from pydoll.protocol.dom.types import Node, ShadowRootType
|
|
@@ -160,6 +160,7 @@ class Tab(FindElementsMixin):
|
|
|
160
160
|
self._request: Optional[Request] = None
|
|
161
161
|
self._scroll: Optional[ScrollAPI] = None
|
|
162
162
|
self._keyboard: Optional[KeyboardAPI] = None
|
|
163
|
+
self._mouse: MouseAPI = MouseAPI(self)
|
|
163
164
|
logger.debug(
|
|
164
165
|
(
|
|
165
166
|
f'Tab initialized: target_id={self._target_id}, '
|
|
@@ -229,6 +230,16 @@ class Tab(FindElementsMixin):
|
|
|
229
230
|
self._keyboard = KeyboardAPI(self)
|
|
230
231
|
return self._keyboard
|
|
231
232
|
|
|
233
|
+
@property
|
|
234
|
+
def mouse(self) -> MouseAPI:
|
|
235
|
+
"""
|
|
236
|
+
Get the mouse API for controlling mouse input.
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
MouseAPI: An instance of the MouseAPI class for mouse operations.
|
|
240
|
+
"""
|
|
241
|
+
return self._mouse
|
|
242
|
+
|
|
232
243
|
@property
|
|
233
244
|
def intercept_file_chooser_dialog_enabled(self) -> bool:
|
|
234
245
|
"""Whether file chooser dialog interception is active."""
|
|
@@ -605,7 +616,9 @@ class Tab(FindElementsMixin):
|
|
|
605
616
|
)
|
|
606
617
|
host_object_id = host_response['result']['object']['objectId']
|
|
607
618
|
host_attrs = await self._get_object_attributes(object_id=host_object_id)
|
|
608
|
-
return WebElement(
|
|
619
|
+
return WebElement(
|
|
620
|
+
host_object_id, self._connection_handler, attributes_list=host_attrs, mouse=self._mouse
|
|
621
|
+
)
|
|
609
622
|
|
|
610
623
|
async def _collect_oopif_shadow_roots(self) -> list[ShadowRoot]:
|
|
611
624
|
"""Discover shadow roots inside cross-origin iframes (OOPIFs)."""
|
|
@@ -748,7 +761,12 @@ class Tab(FindElementsMixin):
|
|
|
748
761
|
tag_name = node_info.get('nodeName', '').lower()
|
|
749
762
|
attributes.extend(['tag_name', tag_name])
|
|
750
763
|
|
|
751
|
-
return WebElement(
|
|
764
|
+
return WebElement(
|
|
765
|
+
host_object_id,
|
|
766
|
+
self._connection_handler,
|
|
767
|
+
attributes_list=attributes,
|
|
768
|
+
mouse=self._mouse,
|
|
769
|
+
)
|
|
752
770
|
except (CommandExecutionTimeout, WebSocketConnectionClosed, KeyError):
|
|
753
771
|
logger.debug(f'Failed to resolve OOPIF shadow host: backend_node_id={host_backend_id}')
|
|
754
772
|
return None
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# global imports
|
|
2
2
|
from pydoll.commands.browser_commands import BrowserCommands
|
|
3
3
|
from pydoll.commands.dom_commands import DomCommands
|
|
4
|
+
from pydoll.commands.emulation_commands import EmulationCommands
|
|
4
5
|
from pydoll.commands.fetch_commands import FetchCommands
|
|
5
6
|
from pydoll.commands.input_commands import InputCommands
|
|
6
7
|
from pydoll.commands.network_commands import NetworkCommands
|
|
@@ -11,6 +12,7 @@ from pydoll.commands.target_commands import TargetCommands
|
|
|
11
12
|
|
|
12
13
|
__all__ = [
|
|
13
14
|
'DomCommands',
|
|
15
|
+
'EmulationCommands',
|
|
14
16
|
'FetchCommands',
|
|
15
17
|
'InputCommands',
|
|
16
18
|
'NetworkCommands',
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Optional
|
|
4
|
+
|
|
5
|
+
from pydoll.protocol.base import Command
|
|
6
|
+
from pydoll.protocol.emulation.methods import (
|
|
7
|
+
EmulationMethod,
|
|
8
|
+
SetUserAgentOverrideParams,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from pydoll.protocol.emulation.methods import SetUserAgentOverrideCommand
|
|
13
|
+
from pydoll.protocol.emulation.types import UserAgentMetadata
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class EmulationCommands:
|
|
17
|
+
"""
|
|
18
|
+
Implementation of Chrome DevTools Protocol for the Emulation domain.
|
|
19
|
+
|
|
20
|
+
This class provides commands for emulating different environments,
|
|
21
|
+
including user agent overrides, device metrics, and other browser
|
|
22
|
+
characteristics useful for testing and automation.
|
|
23
|
+
|
|
24
|
+
See https://chromedevtools.github.io/devtools-protocol/tot/Emulation/
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
@staticmethod
|
|
28
|
+
def set_user_agent_override(
|
|
29
|
+
user_agent: str,
|
|
30
|
+
accept_language: Optional[str] = None,
|
|
31
|
+
platform: Optional[str] = None,
|
|
32
|
+
user_agent_metadata: Optional[UserAgentMetadata] = None,
|
|
33
|
+
) -> SetUserAgentOverrideCommand:
|
|
34
|
+
"""
|
|
35
|
+
Overrides the browser's User-Agent string via the Emulation domain.
|
|
36
|
+
|
|
37
|
+
This is the canonical CDP method for User-Agent override. It modifies
|
|
38
|
+
both HTTP headers and navigator JavaScript properties, ensuring
|
|
39
|
+
consistency between all layers.
|
|
40
|
+
|
|
41
|
+
When userAgentMetadata is provided, Client Hint headers (Sec-CH-UA-*)
|
|
42
|
+
will also be sent consistently with the overridden User-Agent.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
user_agent: Complete User-Agent string to use.
|
|
46
|
+
accept_language: Browser language preference (e.g., 'en-US,en;q=0.9').
|
|
47
|
+
platform: Value for navigator.platform (e.g., 'Win32', 'MacIntel').
|
|
48
|
+
user_agent_metadata: Client Hints metadata for Sec-CH-UA-* headers
|
|
49
|
+
and navigator.userAgentData.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
SetUserAgentOverrideCommand: CDP command to override user agent.
|
|
53
|
+
"""
|
|
54
|
+
params = SetUserAgentOverrideParams(userAgent=user_agent)
|
|
55
|
+
if accept_language is not None:
|
|
56
|
+
params['acceptLanguage'] = accept_language
|
|
57
|
+
if platform is not None:
|
|
58
|
+
params['platform'] = platform
|
|
59
|
+
if user_agent_metadata is not None:
|
|
60
|
+
params['userAgentMetadata'] = user_agent_metadata
|
|
61
|
+
return Command(method=EmulationMethod.SET_USER_AGENT_OVERRIDE, params=params)
|
|
@@ -496,7 +496,14 @@ class FindElementsMixin:
|
|
|
496
496
|
object_id = response_for_command['result']['result']['objectId']
|
|
497
497
|
attributes = await self._get_object_attributes(object_id=object_id)
|
|
498
498
|
logger.debug(f'_find_element() found object_id={object_id}')
|
|
499
|
-
element = create_web_element(
|
|
499
|
+
element = create_web_element(
|
|
500
|
+
object_id,
|
|
501
|
+
self._connection_handler,
|
|
502
|
+
by,
|
|
503
|
+
value,
|
|
504
|
+
attributes,
|
|
505
|
+
mouse=getattr(self, '_mouse', None),
|
|
506
|
+
)
|
|
500
507
|
self._apply_iframe_context_to_element(
|
|
501
508
|
element, iframe_context or getattr(self, '_iframe_context', None)
|
|
502
509
|
)
|
|
@@ -571,7 +578,14 @@ class FindElementsMixin:
|
|
|
571
578
|
tag_name = node_description.get('nodeName', '').lower()
|
|
572
579
|
attributes.extend(['tag_name', tag_name])
|
|
573
580
|
|
|
574
|
-
child = create_web_element(
|
|
581
|
+
child = create_web_element(
|
|
582
|
+
object_id,
|
|
583
|
+
self._connection_handler,
|
|
584
|
+
by,
|
|
585
|
+
value,
|
|
586
|
+
attributes,
|
|
587
|
+
mouse=getattr(self, '_mouse', None),
|
|
588
|
+
)
|
|
575
589
|
self._apply_iframe_context_to_element(child, inherited_context)
|
|
576
590
|
elements.append(child)
|
|
577
591
|
logger.debug(f'_find_elements() returning {len(elements)} elements')
|
|
@@ -57,6 +57,7 @@ from pydoll.utils import (
|
|
|
57
57
|
)
|
|
58
58
|
|
|
59
59
|
if TYPE_CHECKING:
|
|
60
|
+
from pydoll.interactions.mouse import Mouse as MouseType
|
|
60
61
|
from pydoll.protocol.dom.methods import (
|
|
61
62
|
DescribeNodeResponse,
|
|
62
63
|
GetBoxModelResponse,
|
|
@@ -90,6 +91,7 @@ class WebElement(FindElementsMixin): # noqa: PLR0904
|
|
|
90
91
|
method: Optional[str] = None,
|
|
91
92
|
selector: Optional[str] = None,
|
|
92
93
|
attributes_list: list[str] = [],
|
|
94
|
+
mouse: Optional['MouseType'] = None,
|
|
93
95
|
):
|
|
94
96
|
"""
|
|
95
97
|
Initialize WebElement wrapper.
|
|
@@ -100,6 +102,7 @@ class WebElement(FindElementsMixin): # noqa: PLR0904
|
|
|
100
102
|
method: Search method used to find this element (for debugging).
|
|
101
103
|
selector: Selector string used to find this element (for debugging).
|
|
102
104
|
attributes_list: Flat list of alternating attribute names and values.
|
|
105
|
+
mouse: Optional Mouse instance for humanized click behavior.
|
|
103
106
|
"""
|
|
104
107
|
self._object_id = object_id
|
|
105
108
|
self._search_method = method
|
|
@@ -107,6 +110,7 @@ class WebElement(FindElementsMixin): # noqa: PLR0904
|
|
|
107
110
|
self._connection_handler = connection_handler
|
|
108
111
|
self._attributes: dict[str, str] = {}
|
|
109
112
|
self._keyboard: Optional[Keyboard] = None
|
|
113
|
+
self._mouse = mouse
|
|
110
114
|
self._iframe_context: Optional[IFrameContext] = None
|
|
111
115
|
self._iframe_resolver: Optional[IFrameContextResolver] = None
|
|
112
116
|
self._def_attributes(attributes_list)
|
|
@@ -264,7 +268,9 @@ class WebElement(FindElementsMixin): # noqa: PLR0904
|
|
|
264
268
|
object_id = result['result']['result']['objectId']
|
|
265
269
|
attributes = await self._get_object_attributes(object_id=object_id)
|
|
266
270
|
logger.debug(f'Parent element resolved: object_id={object_id}')
|
|
267
|
-
return WebElement(
|
|
271
|
+
return WebElement(
|
|
272
|
+
object_id, self._connection_handler, attributes_list=attributes, mouse=self._mouse
|
|
273
|
+
)
|
|
268
274
|
|
|
269
275
|
async def get_shadow_root(self, timeout: float = 0) -> ShadowRoot:
|
|
270
276
|
"""
|
|
@@ -544,6 +550,7 @@ class WebElement(FindElementsMixin): # noqa: PLR0904
|
|
|
544
550
|
x_offset: int = 0,
|
|
545
551
|
y_offset: int = 0,
|
|
546
552
|
hold_time: float = 0.1,
|
|
553
|
+
humanize: bool = True,
|
|
547
554
|
):
|
|
548
555
|
"""
|
|
549
556
|
Click element using simulated mouse events.
|
|
@@ -551,7 +558,11 @@ class WebElement(FindElementsMixin): # noqa: PLR0904
|
|
|
551
558
|
Args:
|
|
552
559
|
x_offset: Horizontal offset from element center.
|
|
553
560
|
y_offset: Vertical offset from element center.
|
|
554
|
-
hold_time: Duration to hold mouse button down.
|
|
561
|
+
hold_time: Duration to hold mouse button down (used when humanize=False).
|
|
562
|
+
humanize: When True and a Mouse instance is available, uses humanized
|
|
563
|
+
Bezier curve movement from the current tracked position to the
|
|
564
|
+
element center before clicking. When False, dispatches raw CDP
|
|
565
|
+
mousePressed/mouseReleased events directly.
|
|
555
566
|
|
|
556
567
|
Raises:
|
|
557
568
|
ElementNotVisible: If element is not visible.
|
|
@@ -578,14 +589,21 @@ class WebElement(FindElementsMixin): # noqa: PLR0904
|
|
|
578
589
|
except KeyError:
|
|
579
590
|
element_bounds_js = await self.get_bounds_using_js()
|
|
580
591
|
position_to_click = (
|
|
581
|
-
element_bounds_js['x'] + element_bounds_js['width'] / 2,
|
|
582
|
-
element_bounds_js['y'] + element_bounds_js['height'] / 2,
|
|
592
|
+
element_bounds_js['x'] + element_bounds_js['width'] / 2 + x_offset,
|
|
593
|
+
element_bounds_js['y'] + element_bounds_js['height'] / 2 + y_offset,
|
|
594
|
+
)
|
|
595
|
+
|
|
596
|
+
if humanize and self._mouse is not None:
|
|
597
|
+
logger.info(
|
|
598
|
+
f'Clicking element (humanized): x={position_to_click[0]}, y={position_to_click[1]}'
|
|
583
599
|
)
|
|
600
|
+
await self._mouse.click(position_to_click[0], position_to_click[1])
|
|
601
|
+
return
|
|
602
|
+
|
|
584
603
|
logger.info(
|
|
585
604
|
f'Clicking element: x={position_to_click[0]}, '
|
|
586
605
|
f'y={position_to_click[1]}, hold={hold_time}s'
|
|
587
606
|
)
|
|
588
|
-
|
|
589
607
|
press_command = InputCommands.dispatch_mouse_event(
|
|
590
608
|
type=MouseEventType.MOUSE_PRESSED,
|
|
591
609
|
x=int(position_to_click[0]),
|
|
@@ -681,7 +699,7 @@ class WebElement(FindElementsMixin): # noqa: PLR0904
|
|
|
681
699
|
async def type_text(
|
|
682
700
|
self,
|
|
683
701
|
text: str,
|
|
684
|
-
humanize: bool =
|
|
702
|
+
humanize: bool = True,
|
|
685
703
|
interval: Optional[float] = None,
|
|
686
704
|
):
|
|
687
705
|
"""
|
|
@@ -689,7 +707,7 @@ class WebElement(FindElementsMixin): # noqa: PLR0904
|
|
|
689
707
|
|
|
690
708
|
Args:
|
|
691
709
|
text: Text to type into the element.
|
|
692
|
-
humanize: When True, simulates human-like typing.
|
|
710
|
+
humanize: When True (default), simulates human-like typing.
|
|
693
711
|
interval: Deprecated. Use humanize=True instead.
|
|
694
712
|
"""
|
|
695
713
|
logger.info(f'Typing text (length={len(text)}, humanize={humanize})')
|
|
@@ -964,7 +982,12 @@ class WebElement(FindElementsMixin): # noqa: PLR0904
|
|
|
964
982
|
child_object_id = prop['value']['objectId']
|
|
965
983
|
attributes = await self._get_object_attributes(object_id=child_object_id)
|
|
966
984
|
family_elements.append(
|
|
967
|
-
WebElement(
|
|
985
|
+
WebElement(
|
|
986
|
+
child_object_id,
|
|
987
|
+
self._connection_handler,
|
|
988
|
+
attributes_list=attributes,
|
|
989
|
+
mouse=self._mouse,
|
|
990
|
+
)
|
|
968
991
|
)
|
|
969
992
|
|
|
970
993
|
logger.debug(f'Family elements found: {len(family_elements)}')
|
|
@@ -7,6 +7,7 @@ from pydoll.interactions.keyboard import (
|
|
|
7
7
|
TypoConfig,
|
|
8
8
|
TypoResult,
|
|
9
9
|
)
|
|
10
|
+
from pydoll.interactions.mouse import Mouse, MouseAPI, MouseTimingConfig
|
|
10
11
|
from pydoll.interactions.scroll import Scroll, ScrollAPI, ScrollTimingConfig
|
|
11
12
|
|
|
12
13
|
__all__ = [
|
|
@@ -15,6 +16,9 @@ __all__ = [
|
|
|
15
16
|
'IFrameContextResolver',
|
|
16
17
|
'Keyboard',
|
|
17
18
|
'KeyboardAPI',
|
|
19
|
+
'Mouse',
|
|
20
|
+
'MouseAPI',
|
|
21
|
+
'MouseTimingConfig',
|
|
18
22
|
'Scroll',
|
|
19
23
|
'ScrollAPI',
|
|
20
24
|
'ScrollTimingConfig',
|
|
@@ -184,7 +184,7 @@ class Keyboard:
|
|
|
184
184
|
async def type_text(
|
|
185
185
|
self,
|
|
186
186
|
text: str,
|
|
187
|
-
humanize: bool =
|
|
187
|
+
humanize: bool = True,
|
|
188
188
|
interval: Optional[float] = None,
|
|
189
189
|
):
|
|
190
190
|
"""
|
|
@@ -192,13 +192,13 @@ class Keyboard:
|
|
|
192
192
|
|
|
193
193
|
Args:
|
|
194
194
|
text: Text to type.
|
|
195
|
-
humanize: When True, simulates human-like typing with
|
|
195
|
+
humanize: When True (default), simulates human-like typing with
|
|
196
196
|
variable delays and occasional typos (~2%).
|
|
197
197
|
interval: Deprecated. Use humanize=True instead.
|
|
198
198
|
|
|
199
199
|
Example:
|
|
200
200
|
await tab.keyboard.type_text("Hello World")
|
|
201
|
-
await tab.keyboard.type_text("Hello World", humanize=
|
|
201
|
+
await tab.keyboard.type_text("Hello World", humanize=False)
|
|
202
202
|
"""
|
|
203
203
|
if interval is not None:
|
|
204
204
|
warnings.warn(
|