CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-selenium

Python bindings for Selenium WebDriver providing automated browser control for multiple browsers including Chrome, Firefox, Edge, Safari, and Internet Explorer

Pending
Overview
Eval results
Files

action-chains.mddocs/

ActionChains for Complex Interactions

This document covers ActionChains in Python Selenium WebDriver, which enable complex user interactions like mouse movements, drag and drop, keyboard combinations, and scrolling actions.

ActionChains Class

{ .api }

from selenium.webdriver.common.action_chains import ActionChains

class ActionChains:
    def __init__(
        self,
        driver: WebDriver,
        duration: int = 250,
        devices: Optional[List[AnyDevice]] = None
    ) -> None

Description: 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 actions
  • duration: Override the default 250 msecs of DEFAULT_MOVE_DURATION in PointerInput
  • devices: Optional list of input devices (PointerInput, KeyInput, WheelInput)

Usage Patterns:

  1. Method Chaining Pattern:
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()
  1. Sequential Actions Pattern:
actions = ActionChains(driver)
actions.move_to_element(menu)
actions.click(hidden_submenu)
actions.perform()

Core ActionChains Methods

Action Execution

{ .api }

def perform(self) -> None

Description: Performs all stored actions in the order they were added.

{ .api }

def reset_actions(self) -> None

Description: 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

Mouse Actions

Click Actions

{ .api }

def click(self, on_element: Optional[WebElement] = None) -> ActionChains

Description: Clicks an element.

Parameters:

  • on_element: The element to click. If None, clicks on current mouse position

Example:

# 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) -> ActionChains

Description: Double-clicks an element.

Parameters:

  • on_element: The element to double-click. If None, clicks on current mouse position

Example:

# 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) -> ActionChains

Description: Performs a context-click (right click) on an element.

Parameters:

  • on_element: The element to context-click. If None, clicks on current mouse position

Example:

# 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()

Click and Hold Operations

{ .api }

def click_and_hold(self, on_element: Optional[WebElement] = None) -> ActionChains

Description: 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) -> ActionChains

Description: Releases a held mouse button on an element.

Parameters:

  • on_element: The element to mouse up. If None, releases at current mouse position

Example:

# 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()

Mouse Movement

{ .api }

def move_to_element(self, to_element: WebElement) -> ActionChains

Description: Moves the mouse to the middle of an element.

Parameters:

  • to_element: The WebElement to move to

Example:

# 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
) -> ActionChains

Description: 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 to
  • xoffset: X offset to move to, as a positive or negative integer
  • yoffset: Y offset to move to, as a positive or negative integer

Example:

# 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) -> ActionChains

Description: Moves the mouse to an offset from current mouse position.

Parameters:

  • xoffset: X offset to move to, as a positive or negative integer
  • yoffset: Y offset to move to, as a positive or negative integer

Example:

# 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()

Drag and Drop Actions

{ .api }

def drag_and_drop(self, source: WebElement, target: WebElement) -> ActionChains

Description: 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 from
  • target: The element to drop to

Example:

# 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
) -> ActionChains

Description: 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 from
  • xoffset: X offset to move to
  • yoffset: Y offset to move to

Example:

# 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()

Keyboard Actions

Key Press and Release

{ .api }

def key_down(self, value: str, element: Optional[WebElement] = None) -> ActionChains

Description: 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 class
  • element: The element to send keys to. If None, sends to currently focused element

{ .api }

def key_up(self, value: str, element: Optional[WebElement] = None) -> ActionChains

Description: Releases a modifier key.

Parameters:

  • value: The modifier key to release. Values are defined in Keys class
  • element: The element to send keys to. If None, sends to currently focused element

Example:

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()

Text Input

{ .api }

def send_keys(self, *keys_to_send: str) -> ActionChains

Description: Sends keys to current focused element.

Parameters:

  • *keys_to_send: The keys to send. Modifier keys constants can be found in the Keys class

Example:

# 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) -> ActionChains

Description: 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 class

Example:

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()

Keys Class Constants

{ .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()

Scrolling Actions

Element-based Scrolling

{ .api }

def scroll_to_element(self, element: WebElement) -> ActionChains

Description: 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 viewport

Example:

footer = driver.find_element(By.ID, "footer")
ActionChains(driver).scroll_to_element(footer).perform()

Amount-based Scrolling

{ .api }

def scroll_by_amount(self, delta_x: int, delta_y: int) -> ActionChains

Description: 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 left
  • delta_y: Distance along Y axis to scroll. A negative value scrolls up

Example:

# 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()

Origin-based Scrolling

{ .api }

def scroll_from_origin(
    self,
    scroll_origin: ScrollOrigin,
    delta_x: int,
    delta_y: int
) -> ActionChains

Description: 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 offsets
  • delta_x: Distance along X axis to scroll. A negative value scrolls left
  • delta_y: Distance along Y axis to scroll. A negative value scrolls up

ScrollOrigin Class

{ .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 offset
  • from_viewport(): Create origin from viewport corner with optional offset

Example:

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()

Timing Control

{ .api }

def pause(self, seconds: Union[float, int]) -> ActionChains

Description: 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()

Context Manager Support

ActionChains can be used as a context manager:

{ .api }

def __enter__(self) -> ActionChains
def __exit__(self, _type, _value, _traceback) -> None

Example:

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 block

Complex Interaction Examples

Multi-Step Drag and Drop with Hover Effects

def 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()

Advanced Text Editing

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()

Interactive Drawing

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()

Keyboard Navigation

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()

Best Practices

1. Always Call perform()

# ✅ Correct - actions are executed
ActionChains(driver).click(element).perform()

# ❌ Incorrect - actions are queued but never executed
ActionChains(driver).click(element)

2. Use Method Chaining for Related Actions

# ✅ 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()

3. Reset Actions for Reusable ActionChains

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()

4. Handle Element State Before Actions

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()

5. Use Pauses for User Experience

# 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

docs

action-chains.md

browser-configuration.md

element-interaction.md

index.md

waits-conditions.md

webdriver-classes.md

tile.json