Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
SeleniumHQ
GitHub Repository: SeleniumHQ/Selenium
Path: blob/trunk/py/selenium/webdriver/common/action_chains.py
4004 views
1
# Licensed to the Software Freedom Conservancy (SFC) under one
2
# or more contributor license agreements. See the NOTICE file
3
# distributed with this work for additional information
4
# regarding copyright ownership. The SFC licenses this file
5
# to you under the Apache License, Version 2.0 (the
6
# "License"); you may not use this file except in compliance
7
# with the License. You may obtain a copy of the License at
8
#
9
# http://www.apache.org/licenses/LICENSE-2.0
10
#
11
# Unless required by applicable law or agreed to in writing,
12
# software distributed under the License is distributed on an
13
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
# KIND, either express or implied. See the License for the
15
# specific language governing permissions and limitations
16
# under the License.
17
"""The ActionChains implementation."""
18
19
from __future__ import annotations
20
21
from typing import TYPE_CHECKING
22
23
from selenium.webdriver.common.actions.action_builder import ActionBuilder
24
from selenium.webdriver.common.actions.key_input import KeyInput
25
from selenium.webdriver.common.actions.pointer_input import PointerInput
26
from selenium.webdriver.common.actions.wheel_input import ScrollOrigin, WheelInput
27
from selenium.webdriver.common.utils import keys_to_typing
28
from selenium.webdriver.remote.webelement import WebElement
29
30
if TYPE_CHECKING:
31
from selenium.webdriver.remote.webdriver import WebDriver
32
33
34
class ActionChains:
35
"""Automate low-level interactions like mouse movements, button actions, key presses, and context menus.
36
37
ActionChains are a way to automate low level interactions such as mouse
38
movements, mouse button actions, key press, and context menu interactions.
39
This is useful for doing more complex actions like hover over and drag and
40
drop.
41
42
Generate user actions.
43
When you call methods for actions on the ActionChains object,
44
the actions are stored in a queue in the ActionChains object.
45
When you call perform(), the events are fired in the order they
46
are queued up.
47
48
ActionChains can be used in a chain pattern::
49
50
menu = driver.find_element(By.CSS_SELECTOR, ".nav")
51
hidden_submenu = driver.find_element(By.CSS_SELECTOR, ".nav #submenu1")
52
53
ActionChains(driver).move_to_element(menu).click(hidden_submenu).perform()
54
55
Or actions can be queued up one by one, then performed.::
56
57
menu = driver.find_element(By.CSS_SELECTOR, ".nav")
58
hidden_submenu = driver.find_element(By.CSS_SELECTOR, ".nav #submenu1")
59
60
actions = ActionChains(driver)
61
actions.move_to_element(menu)
62
actions.click(hidden_submenu)
63
actions.perform()
64
65
Either way, the actions are performed in the order they are called, one after
66
another.
67
"""
68
69
def __init__(
70
self,
71
driver: WebDriver,
72
duration: int = 250,
73
devices: list[PointerInput | KeyInput | WheelInput] | None = None,
74
) -> None:
75
"""Creates a new ActionChains.
76
77
Args:
78
driver: The WebDriver instance which performs user actions.
79
duration: override the default 250 msecs of DEFAULT_MOVE_DURATION in PointerInput
80
devices: Optional list of input devices (PointerInput, KeyInput, WheelInput) to use.
81
If not provided, default devices will be created.
82
"""
83
self._driver = driver
84
mouse = None
85
keyboard = None
86
wheel = None
87
if devices is not None and isinstance(devices, list):
88
for device in devices:
89
if isinstance(device, PointerInput):
90
mouse = device
91
if isinstance(device, KeyInput):
92
keyboard = device
93
if isinstance(device, WheelInput):
94
wheel = device
95
self.w3c_actions = ActionBuilder(driver, mouse=mouse, keyboard=keyboard, wheel=wheel, duration=duration)
96
97
def perform(self) -> None:
98
"""Performs all stored actions."""
99
self.w3c_actions.perform()
100
101
def reset_actions(self) -> None:
102
"""Clear actions stored locally and on the remote end."""
103
self.w3c_actions.clear_actions()
104
for device in self.w3c_actions.devices:
105
device.clear_actions()
106
107
def click(self, on_element: WebElement | None = None) -> ActionChains:
108
"""Clicks an element.
109
110
Args:
111
on_element: The element to click.
112
If None, clicks on current mouse position.
113
"""
114
if on_element:
115
self.move_to_element(on_element)
116
117
self.w3c_actions.pointer_action.click()
118
self.w3c_actions.key_action.pause()
119
self.w3c_actions.key_action.pause()
120
121
return self
122
123
def click_and_hold(self, on_element: WebElement | None = None) -> ActionChains:
124
"""Holds down the left mouse button on an element.
125
126
Args:
127
on_element: The element to mouse down.
128
If None, clicks on current mouse position.
129
"""
130
if on_element:
131
self.move_to_element(on_element)
132
133
self.w3c_actions.pointer_action.click_and_hold()
134
self.w3c_actions.key_action.pause()
135
136
return self
137
138
def context_click(self, on_element: WebElement | None = None) -> ActionChains:
139
"""Performs a context-click (right click) on an element.
140
141
Args:
142
on_element: The element to context-click.
143
If None, clicks on current mouse position.
144
"""
145
if on_element:
146
self.move_to_element(on_element)
147
148
self.w3c_actions.pointer_action.context_click()
149
self.w3c_actions.key_action.pause()
150
self.w3c_actions.key_action.pause()
151
152
return self
153
154
def double_click(self, on_element: WebElement | None = None) -> ActionChains:
155
"""Double-clicks an element.
156
157
Args:
158
on_element: The element to double-click.
159
If None, clicks on current mouse position.
160
"""
161
if on_element:
162
self.move_to_element(on_element)
163
164
self.w3c_actions.pointer_action.double_click()
165
for _ in range(4):
166
self.w3c_actions.key_action.pause()
167
168
return self
169
170
def drag_and_drop(self, source: WebElement, target: WebElement) -> ActionChains:
171
"""Hold down the left mouse button on an element, then move to target and release.
172
173
Args:
174
source: The element to mouse down.
175
target: The element to mouse up.
176
"""
177
self.click_and_hold(source)
178
self.release(target)
179
return self
180
181
def drag_and_drop_by_offset(self, source: WebElement, xoffset: int, yoffset: int) -> ActionChains:
182
"""Hold down the left mouse button on an element, then move by offset and release.
183
184
Args:
185
source: The element to mouse down.
186
xoffset: X offset to move to.
187
yoffset: Y offset to move to.
188
"""
189
self.click_and_hold(source)
190
self.move_by_offset(xoffset, yoffset)
191
self.release()
192
return self
193
194
def key_down(self, value: str, element: WebElement | None = None) -> ActionChains:
195
"""Send a key press only without releasing it (modifier keys only).
196
197
Args:
198
value: The modifier key to send. Values are defined in `Keys` class.
199
element: The element to send keys.
200
If None, sends a key to current focused element.
201
202
Example, pressing ctrl+c::
203
204
ActionChains(driver).key_down(Keys.CONTROL).send_keys("c").key_up(Keys.CONTROL).perform()
205
"""
206
if element:
207
self.click(element)
208
209
self.w3c_actions.key_action.key_down(value)
210
self.w3c_actions.pointer_action.pause()
211
212
return self
213
214
def key_up(self, value: str, element: WebElement | None = None) -> ActionChains:
215
"""Releases a modifier key.
216
217
Args:
218
value: The modifier key to send. Values are defined in Keys class.
219
element: The element to send keys.
220
If None, sends a key to current focused element.
221
222
Example, pressing ctrl+c::
223
224
ActionChains(driver).key_down(Keys.CONTROL).send_keys("c").key_up(Keys.CONTROL).perform()
225
"""
226
if element:
227
self.click(element)
228
229
self.w3c_actions.key_action.key_up(value)
230
self.w3c_actions.pointer_action.pause()
231
232
return self
233
234
def move_by_offset(self, xoffset: int, yoffset: int) -> ActionChains:
235
"""Moving the mouse to an offset from current mouse position.
236
237
Args:
238
xoffset: X offset to move to, as a positive or negative integer.
239
yoffset: Y offset to move to, as a positive or negative integer.
240
"""
241
self.w3c_actions.pointer_action.move_by(xoffset, yoffset)
242
self.w3c_actions.key_action.pause()
243
244
return self
245
246
def move_to_element(self, to_element: WebElement) -> ActionChains:
247
"""Moving the mouse to the middle of an element.
248
249
Args:
250
to_element: The WebElement to move to.
251
"""
252
self.w3c_actions.pointer_action.move_to(to_element)
253
self.w3c_actions.key_action.pause()
254
255
return self
256
257
def move_to_element_with_offset(self, to_element: WebElement, xoffset: int, yoffset: int) -> ActionChains:
258
"""Move the mouse to an element with the specified offsets.
259
260
Offsets are relative to the in-view center point of the element.
261
262
Args:
263
to_element: The WebElement to move to.
264
xoffset: X offset to move to, as a positive or negative integer.
265
yoffset: Y offset to move to, as a positive or negative integer.
266
"""
267
self.w3c_actions.pointer_action.move_to(to_element, int(xoffset), int(yoffset))
268
self.w3c_actions.key_action.pause()
269
270
return self
271
272
def pause(self, seconds: float | int) -> ActionChains:
273
"""Pause all inputs for the specified duration in seconds."""
274
self.w3c_actions.pointer_action.pause(seconds)
275
self.w3c_actions.key_action.pause(int(seconds))
276
277
return self
278
279
def release(self, on_element: WebElement | None = None) -> ActionChains:
280
"""Releasing a held mouse button on an element.
281
282
Args:
283
on_element: The element to mouse up.
284
If None, releases on current mouse position.
285
"""
286
if on_element:
287
self.move_to_element(on_element)
288
289
self.w3c_actions.pointer_action.release()
290
self.w3c_actions.key_action.pause()
291
292
return self
293
294
def send_keys(self, *keys_to_send: str) -> ActionChains:
295
"""Sends keys to current focused element.
296
297
Args:
298
keys_to_send: The keys to send. Modifier keys constants can be found in the
299
'Keys' class.
300
"""
301
typing = keys_to_typing(keys_to_send)
302
303
for key in typing:
304
self.key_down(key)
305
self.key_up(key)
306
307
return self
308
309
def send_keys_to_element(self, element: WebElement, *keys_to_send: str) -> ActionChains:
310
"""Sends keys to an element.
311
312
Args:
313
element: The element to send keys.
314
keys_to_send: The keys to send. Modifier keys constants can be found in the
315
'Keys' class.
316
"""
317
self.click(element)
318
self.send_keys(*keys_to_send)
319
return self
320
321
def scroll_to_element(self, element: WebElement) -> ActionChains:
322
"""Scroll the element into the viewport if it's outside it.
323
324
Scrolls the bottom of the element to the bottom of the viewport.
325
326
Args:
327
element: Which element to scroll into the viewport.
328
"""
329
self.w3c_actions.wheel_action.scroll(origin=element)
330
return self
331
332
def scroll_by_amount(self, delta_x: int, delta_y: int) -> ActionChains:
333
"""Scroll by a provided amount with the origin in the top left corner.
334
335
Scrolls by provided amounts with the origin in the top left corner
336
of the viewport.
337
338
Args:
339
delta_x: Distance along X axis to scroll using the wheel. A negative value scrolls left.
340
delta_y: Distance along Y axis to scroll using the wheel. A negative value scrolls up.
341
"""
342
self.w3c_actions.wheel_action.scroll(delta_x=delta_x, delta_y=delta_y)
343
return self
344
345
def scroll_from_origin(self, scroll_origin: ScrollOrigin, delta_x: int, delta_y: int) -> ActionChains:
346
"""Scroll by a provided amount based on a scroll origin (element or viewport).
347
348
The scroll origin is either the center of an element or the upper left of the
349
viewport plus any offsets. If the origin is an element, and the element
350
is not in the viewport, the bottom of the element will first be
351
scrolled to the bottom of the viewport.
352
353
Args:
354
scroll_origin: Where scroll originates (viewport or element center) plus provided offsets.
355
delta_x: Distance along X axis to scroll using the wheel. A negative value scrolls left.
356
delta_y: Distance along Y axis to scroll using the wheel. A negative value scrolls up.
357
358
Raises:
359
MoveTargetOutOfBoundsException: If the origin with offset is outside the viewport.
360
"""
361
if not isinstance(scroll_origin, ScrollOrigin):
362
raise TypeError(f"Expected object of type ScrollOrigin, got: {type(scroll_origin)}")
363
364
self.w3c_actions.wheel_action.scroll(
365
origin=scroll_origin.origin,
366
x=scroll_origin.x_offset,
367
y=scroll_origin.y_offset,
368
delta_x=delta_x,
369
delta_y=delta_y,
370
)
371
return self
372
373
# Context manager so ActionChains can be used in a 'with .. as' statements.
374
375
def __enter__(self) -> ActionChains:
376
return self # Return created instance of self.
377
378
def __exit__(self, _type, _value, _traceback) -> None:
379
pass # Do nothing, does not require additional cleanup.
380
381