Python bindings for Selenium WebDriver providing automated browser control for multiple browsers including Chrome, Firefox, Edge, Safari, and Internet Explorer
—
This document covers ActionChains in Python Selenium WebDriver, which enable complex user interactions like mouse movements, drag and drop, keyboard combinations, and scrolling actions.
{ .api }
from selenium.webdriver.common.action_chains import ActionChains
class ActionChains:
def __init__(
self,
driver: WebDriver,
duration: int = 250,
devices: Optional[List[AnyDevice]] = None
) -> NoneDescription: ActionChains are a way to automate low level interactions such as mouse movements, mouse button actions, key press, and context menu interactions. Actions are stored in a queue and executed when perform() is called.
Parameters:
driver: The WebDriver instance which performs user actionsduration: Override the default 250 msecs of DEFAULT_MOVE_DURATION in PointerInputdevices: Optional list of input devices (PointerInput, KeyInput, WheelInput)Usage Patterns:
menu = driver.find_element(By.CSS_SELECTOR, ".nav")
hidden_submenu = driver.find_element(By.CSS_SELECTOR, ".nav #submenu1")
ActionChains(driver).move_to_element(menu).click(hidden_submenu).perform()actions = ActionChains(driver)
actions.move_to_element(menu)
actions.click(hidden_submenu)
actions.perform(){ .api }
def perform(self) -> NoneDescription: Performs all stored actions in the order they were added.
{ .api }
def reset_actions(self) -> NoneDescription: Clears actions that are already stored locally and on the remote end.
Example:
actions = ActionChains(driver)
actions.click(element1)
actions.click(element2)
actions.perform() # Execute both clicks
actions.reset_actions() # Clear the action queue{ .api }
def click(self, on_element: Optional[WebElement] = None) -> ActionChainsDescription: Clicks an element.
Parameters:
on_element: The element to click. If None, clicks on current mouse positionExample:
# Click on specific element
ActionChains(driver).click(button_element).perform()
# Click at current mouse position
ActionChains(driver).move_to_element(element).click().perform(){ .api }
def double_click(self, on_element: Optional[WebElement] = None) -> ActionChainsDescription: Double-clicks an element.
Parameters:
on_element: The element to double-click. If None, clicks on current mouse positionExample:
# Double-click to select word or open item
text_element = driver.find_element(By.ID, "editable-text")
ActionChains(driver).double_click(text_element).perform(){ .api }
def context_click(self, on_element: Optional[WebElement] = None) -> ActionChainsDescription: Performs a context-click (right click) on an element.
Parameters:
on_element: The element to context-click. If None, clicks on current mouse positionExample:
# Right-click to open context menu
image = driver.find_element(By.ID, "photo")
ActionChains(driver).context_click(image).perform()
# Handle context menu
context_menu = driver.find_element(By.CLASS_NAME, "context-menu")
save_option = context_menu.find_element(By.TEXT, "Save Image")
save_option.click(){ .api }
def click_and_hold(self, on_element: Optional[WebElement] = None) -> ActionChainsDescription: Holds down the left mouse button on an element.
Parameters:
on_element: The element to mouse down. If None, holds at current mouse position{ .api }
def release(self, on_element: Optional[WebElement] = None) -> ActionChainsDescription: Releases a held mouse button on an element.
Parameters:
on_element: The element to mouse up. If None, releases at current mouse positionExample:
# Manual drag operation
source = driver.find_element(By.ID, "draggable")
target = driver.find_element(By.ID, "droppable")
ActionChains(driver)\
.click_and_hold(source)\
.move_to_element(target)\
.release()\
.perform(){ .api }
def move_to_element(self, to_element: WebElement) -> ActionChainsDescription: Moves the mouse to the middle of an element.
Parameters:
to_element: The WebElement to move toExample:
# Hover to reveal dropdown menu
menu_item = driver.find_element(By.CLASS_NAME, "dropdown-trigger")
ActionChains(driver).move_to_element(menu_item).perform()
# Wait for dropdown to appear
dropdown = WebDriverWait(driver, 5).until(
EC.visibility_of_element_located((By.CLASS_NAME, "dropdown-menu"))
){ .api }
def move_to_element_with_offset(
self,
to_element: WebElement,
xoffset: int,
yoffset: int
) -> ActionChainsDescription: Move the mouse by an offset of the specified element. Offsets are relative to the in-view center point of the element.
Parameters:
to_element: The WebElement to move toxoffset: X offset to move to, as a positive or negative integeryoffset: Y offset to move to, as a positive or negative integerExample:
# Move to specific position within element
canvas = driver.find_element(By.ID, "drawing-canvas")
ActionChains(driver)\
.move_to_element_with_offset(canvas, 100, 50)\
.click()\
.perform(){ .api }
def move_by_offset(self, xoffset: int, yoffset: int) -> ActionChainsDescription: Moves the mouse to an offset from current mouse position.
Parameters:
xoffset: X offset to move to, as a positive or negative integeryoffset: Y offset to move to, as a positive or negative integerExample:
# Draw a simple shape by moving mouse
ActionChains(driver)\
.click_and_hold()\
.move_by_offset(100, 0)\
.move_by_offset(0, 100)\
.move_by_offset(-100, 0)\
.move_by_offset(0, -100)\
.release()\
.perform(){ .api }
def drag_and_drop(self, source: WebElement, target: WebElement) -> ActionChainsDescription: Holds down the left mouse button on the source element, then moves to the target element and releases the mouse button.
Parameters:
source: The element to drag fromtarget: The element to drop toExample:
# Simple drag and drop
source = driver.find_element(By.ID, "item1")
target = driver.find_element(By.ID, "basket")
ActionChains(driver).drag_and_drop(source, target).perform(){ .api }
def drag_and_drop_by_offset(
self,
source: WebElement,
xoffset: int,
yoffset: int
) -> ActionChainsDescription: Holds down the left mouse button on the source element, then moves to the target offset and releases the mouse button.
Parameters:
source: The element to drag fromxoffset: X offset to move toyoffset: Y offset to move toExample:
# Drag element to specific position
slider = driver.find_element(By.CLASS_NAME, "slider-handle")
# Move slider 200 pixels to the right
ActionChains(driver)\
.drag_and_drop_by_offset(slider, 200, 0)\
.perform(){ .api }
def key_down(self, value: str, element: Optional[WebElement] = None) -> ActionChainsDescription: Sends a key press only, without releasing it. Should only be used with modifier keys (Control, Alt and Shift).
Parameters:
value: The modifier key to send. Values are defined in Keys classelement: The element to send keys to. If None, sends to currently focused element{ .api }
def key_up(self, value: str, element: Optional[WebElement] = None) -> ActionChainsDescription: Releases a modifier key.
Parameters:
value: The modifier key to release. Values are defined in Keys classelement: The element to send keys to. If None, sends to currently focused elementExample:
from selenium.webdriver.common.keys import Keys
# Ctrl+C (Copy)
ActionChains(driver)\
.key_down(Keys.CONTROL)\
.send_keys('c')\
.key_up(Keys.CONTROL)\
.perform()
# Ctrl+A (Select All) then type new text
text_area = driver.find_element(By.ID, "editor")
ActionChains(driver)\
.click(text_area)\
.key_down(Keys.CONTROL)\
.send_keys('a')\
.key_up(Keys.CONTROL)\
.send_keys("New content")\
.perform(){ .api }
def send_keys(self, *keys_to_send: str) -> ActionChainsDescription: Sends keys to current focused element.
Parameters:
*keys_to_send: The keys to send. Modifier keys constants can be found in the Keys classExample:
# Type text to focused element
ActionChains(driver)\
.send_keys("Hello World")\
.send_keys(Keys.ENTER)\
.perform(){ .api }
def send_keys_to_element(self, element: WebElement, *keys_to_send: str) -> ActionChainsDescription: Sends keys to a specific element.
Parameters:
element: The element to send keys to*keys_to_send: The keys to send. Modifier keys constants can be found in the Keys classExample:
username_field = driver.find_element(By.NAME, "username")
password_field = driver.find_element(By.NAME, "password")
ActionChains(driver)\
.send_keys_to_element(username_field, "testuser")\
.send_keys_to_element(password_field, "password123")\
.send_keys_to_element(password_field, Keys.ENTER)\
.perform(){ .api }
from selenium.webdriver.common.keys import Keys
class Keys:
# Modifier Keys
CONTROL = "\ue009"
ALT = "\ue00a"
SHIFT = "\ue008"
META = "\ue03d"
COMMAND = "\ue03d" # Same as META
# Navigation Keys
ENTER = "\ue007"
RETURN = "\ue006"
TAB = "\ue004"
SPACE = "\ue00d"
BACKSPACE = "\ue003"
DELETE = "\ue017"
ESCAPE = "\ue00c"
# Arrow Keys
LEFT = "\ue012"
UP = "\ue013"
RIGHT = "\ue014"
DOWN = "\ue015"
ARROW_LEFT = LEFT
ARROW_UP = UP
ARROW_RIGHT = RIGHT
ARROW_DOWN = DOWN
# Page Navigation
PAGE_UP = "\ue00e"
PAGE_DOWN = "\ue00f"
HOME = "\ue011"
END = "\ue010"
# Function Keys
F1 = "\ue031"
F2 = "\ue032"
# ... F3 through F12
F12 = "\ue03c"
# Numpad Keys
NUMPAD0 = "\ue01a"
NUMPAD1 = "\ue01b"
# ... NUMPAD2 through NUMPAD9
NUMPAD9 = "\ue023"
ADD = "\ue025"
SUBTRACT = "\ue027"
MULTIPLY = "\ue024"
DIVIDE = "\ue029"Description: Set of special keys codes for use with ActionChains and send_keys methods.
Common Key Combinations:
from selenium.webdriver.common.keys import Keys
# Copy (Ctrl+C)
ActionChains(driver)\
.key_down(Keys.CONTROL)\
.send_keys('c')\
.key_up(Keys.CONTROL)\
.perform()
# Select All (Ctrl+A)
ActionChains(driver)\
.key_down(Keys.CONTROL)\
.send_keys('a')\
.key_up(Keys.CONTROL)\
.perform()
# Undo (Ctrl+Z)
ActionChains(driver)\
.key_down(Keys.CONTROL)\
.send_keys('z')\
.key_up(Keys.CONTROL)\
.perform()
# New Tab (Ctrl+T)
ActionChains(driver)\
.key_down(Keys.CONTROL)\
.send_keys('t')\
.key_up(Keys.CONTROL)\
.perform(){ .api }
def scroll_to_element(self, element: WebElement) -> ActionChainsDescription: If the element is outside the viewport, scrolls the bottom of the element to the bottom of the viewport.
Parameters:
element: Which element to scroll into the viewportExample:
footer = driver.find_element(By.ID, "footer")
ActionChains(driver).scroll_to_element(footer).perform(){ .api }
def scroll_by_amount(self, delta_x: int, delta_y: int) -> ActionChainsDescription: Scrolls by provided amounts with the origin in the top left corner of the viewport.
Parameters:
delta_x: Distance along X axis to scroll. A negative value scrolls leftdelta_y: Distance along Y axis to scroll. A negative value scrolls upExample:
# Scroll down 500 pixels
ActionChains(driver).scroll_by_amount(0, 500).perform()
# Scroll left 200 pixels and up 300 pixels
ActionChains(driver).scroll_by_amount(-200, -300).perform(){ .api }
def scroll_from_origin(
self,
scroll_origin: ScrollOrigin,
delta_x: int,
delta_y: int
) -> ActionChainsDescription: Scrolls by provided amount based on a provided origin. The scroll origin is either the center of an element or the upper left of the viewport plus any offsets.
Parameters:
scroll_origin: Where scroll originates (viewport or element center) plus provided offsetsdelta_x: Distance along X axis to scroll. A negative value scrolls leftdelta_y: Distance along Y axis to scroll. A negative value scrolls up{ .api }
from selenium.webdriver.common.actions.wheel_input import ScrollOrigin
class ScrollOrigin:
def __init__(self, origin: Union[str, WebElement], x_offset: int, y_offset: int) -> None
@classmethod
def from_element(cls, element: WebElement, x_offset: int = 0, y_offset: int = 0)
@classmethod
def from_viewport(cls, x_offset: int = 0, y_offset: int = 0)Description: Represents a scroll origin point for scrolling operations.
Factory Methods:
from_element(): Create origin from element center with optional offsetfrom_viewport(): Create origin from viewport corner with optional offsetExample:
from selenium.webdriver.common.actions.wheel_input import ScrollOrigin
# Scroll from element center
element = driver.find_element(By.ID, "content")
scroll_origin = ScrollOrigin.from_element(element)
ActionChains(driver)\
.scroll_from_origin(scroll_origin, 0, 200)\
.perform()
# Scroll from element with offset
scroll_origin = ScrollOrigin.from_element(element, 100, 50)
ActionChains(driver)\
.scroll_from_origin(scroll_origin, 0, -100)\
.perform()
# Scroll from viewport
scroll_origin = ScrollOrigin.from_viewport(200, 100)
ActionChains(driver)\
.scroll_from_origin(scroll_origin, 0, 300)\
.perform(){ .api }
def pause(self, seconds: Union[float, int]) -> ActionChainsDescription: Pause all inputs for the specified duration in seconds.
Parameters:
seconds: Duration to pause in seconds (can be fractional)Example:
# Add pauses between actions for better visual feedback
button1 = driver.find_element(By.ID, "btn1")
button2 = driver.find_element(By.ID, "btn2")
ActionChains(driver)\
.click(button1)\
.pause(1.5)\
.click(button2)\
.pause(0.5)\
.perform()ActionChains can be used as a context manager:
{ .api }
def __enter__(self) -> ActionChains
def __exit__(self, _type, _value, _traceback) -> NoneExample:
with ActionChains(driver) as actions:
actions.move_to_element(menu_item)
actions.click(submenu_item)
# perform() is automatically called at the end of the with blockdef complex_drag_drop():
source = driver.find_element(By.ID, "draggable-item")
intermediate = driver.find_element(By.ID, "hover-zone")
target = driver.find_element(By.ID, "drop-zone")
ActionChains(driver)\
.click_and_hold(source)\
.pause(0.5)\
.move_to_element(intermediate)\
.pause(1.0)\
.move_to_element(target)\
.pause(0.5)\
.release()\
.perform()def advanced_text_editing():
text_area = driver.find_element(By.ID, "editor")
ActionChains(driver)\
.click(text_area)\
.key_down(Keys.CONTROL)\
.send_keys('a')\
.key_up(Keys.CONTROL)\
.send_keys("New content here")\
.key_down(Keys.SHIFT)\
.send_keys(Keys.LEFT, Keys.LEFT, Keys.LEFT, Keys.LEFT)\
.key_up(Keys.SHIFT)\
.key_down(Keys.CONTROL)\
.send_keys('b')\
.key_up(Keys.CONTROL)\
.perform()def draw_signature():
canvas = driver.find_element(By.ID, "signature-pad")
# Draw a simple signature
ActionChains(driver)\
.move_to_element_with_offset(canvas, -100, 0)\
.click_and_hold()\
.move_by_offset(50, -20)\
.move_by_offset(30, 40)\
.move_by_offset(40, -30)\
.move_by_offset(30, 20)\
.release()\
.perform()def navigate_table_with_keyboard():
table = driver.find_element(By.ID, "data-table")
first_cell = table.find_element(By.CSS_SELECTOR, "tr:first-child td:first-child")
# Navigate and edit table cells
ActionChains(driver)\
.click(first_cell)\
.send_keys("Cell 1 Value")\
.send_keys(Keys.TAB)\
.send_keys("Cell 2 Value")\
.send_keys(Keys.ENTER)\
.send_keys("Next Row Value")\
.perform()# ✅ Correct - actions are executed
ActionChains(driver).click(element).perform()
# ❌ Incorrect - actions are queued but never executed
ActionChains(driver).click(element)# ✅ Good - related actions chained together
ActionChains(driver)\
.move_to_element(menu)\
.click(submenu)\
.perform()
# ✅ Also good - sequential building for complex logic
actions = ActionChains(driver)
actions.move_to_element(menu)
if condition:
actions.pause(1.0)
actions.click(submenu)
actions.perform()actions = ActionChains(driver)
# First set of actions
actions.click(button1).perform()
actions.reset_actions() # Clear previous actions
# Second set of actions
actions.click(button2).perform()from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# Wait for element to be clickable before action
element = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.ID, "interactive-button"))
)
ActionChains(driver).click(element).perform()# Add natural pauses to mimic human interaction
ActionChains(driver)\
.move_to_element(dropdown_trigger)\
.pause(0.5)\
.click()\
.pause(1.0)\
.click(dropdown_item)\
.perform()Install with Tessl CLI
npx tessl i tessl/pypi-selenium