or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

action-chains.mdbrowser-configuration.mdelement-interaction.mdindex.mdwaits-conditions.mdwebdriver-classes.md

action-chains.mddocs/

0

# ActionChains for Complex Interactions

1

2

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

3

4

## ActionChains Class

5

6

{ .api }

7

```python

8

from selenium.webdriver.common.action_chains import ActionChains

9

10

class ActionChains:

11

def __init__(

12

self,

13

driver: WebDriver,

14

duration: int = 250,

15

devices: Optional[List[AnyDevice]] = None

16

) -> None

17

```

18

19

**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.

20

21

**Parameters**:

22

- `driver`: The WebDriver instance which performs user actions

23

- `duration`: Override the default 250 msecs of DEFAULT_MOVE_DURATION in PointerInput

24

- `devices`: Optional list of input devices (PointerInput, KeyInput, WheelInput)

25

26

**Usage Patterns**:

27

28

1. **Method Chaining Pattern**:

29

```python

30

menu = driver.find_element(By.CSS_SELECTOR, ".nav")

31

hidden_submenu = driver.find_element(By.CSS_SELECTOR, ".nav #submenu1")

32

33

ActionChains(driver).move_to_element(menu).click(hidden_submenu).perform()

34

```

35

36

2. **Sequential Actions Pattern**:

37

```python

38

actions = ActionChains(driver)

39

actions.move_to_element(menu)

40

actions.click(hidden_submenu)

41

actions.perform()

42

```

43

44

## Core ActionChains Methods

45

46

### Action Execution

47

48

{ .api }

49

```python

50

def perform(self) -> None

51

```

52

53

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

54

55

{ .api }

56

```python

57

def reset_actions(self) -> None

58

```

59

60

**Description**: Clears actions that are already stored locally and on the remote end.

61

62

**Example**:

63

```python

64

actions = ActionChains(driver)

65

actions.click(element1)

66

actions.click(element2)

67

actions.perform() # Execute both clicks

68

69

actions.reset_actions() # Clear the action queue

70

```

71

72

## Mouse Actions

73

74

### Click Actions

75

76

{ .api }

77

```python

78

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

79

```

80

81

**Description**: Clicks an element.

82

83

**Parameters**:

84

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

85

86

**Example**:

87

```python

88

# Click on specific element

89

ActionChains(driver).click(button_element).perform()

90

91

# Click at current mouse position

92

ActionChains(driver).move_to_element(element).click().perform()

93

```

94

95

{ .api }

96

```python

97

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

98

```

99

100

**Description**: Double-clicks an element.

101

102

**Parameters**:

103

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

104

105

**Example**:

106

```python

107

# Double-click to select word or open item

108

text_element = driver.find_element(By.ID, "editable-text")

109

ActionChains(driver).double_click(text_element).perform()

110

```

111

112

{ .api }

113

```python

114

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

115

```

116

117

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

118

119

**Parameters**:

120

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

121

122

**Example**:

123

```python

124

# Right-click to open context menu

125

image = driver.find_element(By.ID, "photo")

126

ActionChains(driver).context_click(image).perform()

127

128

# Handle context menu

129

context_menu = driver.find_element(By.CLASS_NAME, "context-menu")

130

save_option = context_menu.find_element(By.TEXT, "Save Image")

131

save_option.click()

132

```

133

134

### Click and Hold Operations

135

136

{ .api }

137

```python

138

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

139

```

140

141

**Description**: Holds down the left mouse button on an element.

142

143

**Parameters**:

144

- `on_element`: The element to mouse down. If None, holds at current mouse position

145

146

{ .api }

147

```python

148

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

149

```

150

151

**Description**: Releases a held mouse button on an element.

152

153

**Parameters**:

154

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

155

156

**Example**:

157

```python

158

# Manual drag operation

159

source = driver.find_element(By.ID, "draggable")

160

target = driver.find_element(By.ID, "droppable")

161

162

ActionChains(driver)\

163

.click_and_hold(source)\

164

.move_to_element(target)\

165

.release()\

166

.perform()

167

```

168

169

### Mouse Movement

170

171

{ .api }

172

```python

173

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

174

```

175

176

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

177

178

**Parameters**:

179

- `to_element`: The WebElement to move to

180

181

**Example**:

182

```python

183

# Hover to reveal dropdown menu

184

menu_item = driver.find_element(By.CLASS_NAME, "dropdown-trigger")

185

ActionChains(driver).move_to_element(menu_item).perform()

186

187

# Wait for dropdown to appear

188

dropdown = WebDriverWait(driver, 5).until(

189

EC.visibility_of_element_located((By.CLASS_NAME, "dropdown-menu"))

190

)

191

```

192

193

{ .api }

194

```python

195

def move_to_element_with_offset(

196

self,

197

to_element: WebElement,

198

xoffset: int,

199

yoffset: int

200

) -> ActionChains

201

```

202

203

**Description**: Move the mouse by an offset of the specified element. Offsets are relative to the in-view center point of the element.

204

205

**Parameters**:

206

- `to_element`: The WebElement to move to

207

- `xoffset`: X offset to move to, as a positive or negative integer

208

- `yoffset`: Y offset to move to, as a positive or negative integer

209

210

**Example**:

211

```python

212

# Move to specific position within element

213

canvas = driver.find_element(By.ID, "drawing-canvas")

214

ActionChains(driver)\

215

.move_to_element_with_offset(canvas, 100, 50)\

216

.click()\

217

.perform()

218

```

219

220

{ .api }

221

```python

222

def move_by_offset(self, xoffset: int, yoffset: int) -> ActionChains

223

```

224

225

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

226

227

**Parameters**:

228

- `xoffset`: X offset to move to, as a positive or negative integer

229

- `yoffset`: Y offset to move to, as a positive or negative integer

230

231

**Example**:

232

```python

233

# Draw a simple shape by moving mouse

234

ActionChains(driver)\

235

.click_and_hold()\

236

.move_by_offset(100, 0)\

237

.move_by_offset(0, 100)\

238

.move_by_offset(-100, 0)\

239

.move_by_offset(0, -100)\

240

.release()\

241

.perform()

242

```

243

244

## Drag and Drop Actions

245

246

{ .api }

247

```python

248

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

249

```

250

251

**Description**: Holds down the left mouse button on the source element, then moves to the target element and releases the mouse button.

252

253

**Parameters**:

254

- `source`: The element to drag from

255

- `target`: The element to drop to

256

257

**Example**:

258

```python

259

# Simple drag and drop

260

source = driver.find_element(By.ID, "item1")

261

target = driver.find_element(By.ID, "basket")

262

263

ActionChains(driver).drag_and_drop(source, target).perform()

264

```

265

266

{ .api }

267

```python

268

def drag_and_drop_by_offset(

269

self,

270

source: WebElement,

271

xoffset: int,

272

yoffset: int

273

) -> ActionChains

274

```

275

276

**Description**: Holds down the left mouse button on the source element, then moves to the target offset and releases the mouse button.

277

278

**Parameters**:

279

- `source`: The element to drag from

280

- `xoffset`: X offset to move to

281

- `yoffset`: Y offset to move to

282

283

**Example**:

284

```python

285

# Drag element to specific position

286

slider = driver.find_element(By.CLASS_NAME, "slider-handle")

287

288

# Move slider 200 pixels to the right

289

ActionChains(driver)\

290

.drag_and_drop_by_offset(slider, 200, 0)\

291

.perform()

292

```

293

294

## Keyboard Actions

295

296

### Key Press and Release

297

298

{ .api }

299

```python

300

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

301

```

302

303

**Description**: Sends a key press only, without releasing it. Should only be used with modifier keys (Control, Alt and Shift).

304

305

**Parameters**:

306

- `value`: The modifier key to send. Values are defined in Keys class

307

- `element`: The element to send keys to. If None, sends to currently focused element

308

309

{ .api }

310

```python

311

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

312

```

313

314

**Description**: Releases a modifier key.

315

316

**Parameters**:

317

- `value`: The modifier key to release. Values are defined in Keys class

318

- `element`: The element to send keys to. If None, sends to currently focused element

319

320

**Example**:

321

```python

322

from selenium.webdriver.common.keys import Keys

323

324

# Ctrl+C (Copy)

325

ActionChains(driver)\

326

.key_down(Keys.CONTROL)\

327

.send_keys('c')\

328

.key_up(Keys.CONTROL)\

329

.perform()

330

331

# Ctrl+A (Select All) then type new text

332

text_area = driver.find_element(By.ID, "editor")

333

ActionChains(driver)\

334

.click(text_area)\

335

.key_down(Keys.CONTROL)\

336

.send_keys('a')\

337

.key_up(Keys.CONTROL)\

338

.send_keys("New content")\

339

.perform()

340

```

341

342

### Text Input

343

344

{ .api }

345

```python

346

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

347

```

348

349

**Description**: Sends keys to current focused element.

350

351

**Parameters**:

352

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

353

354

**Example**:

355

```python

356

# Type text to focused element

357

ActionChains(driver)\

358

.send_keys("Hello World")\

359

.send_keys(Keys.ENTER)\

360

.perform()

361

```

362

363

{ .api }

364

```python

365

def send_keys_to_element(self, element: WebElement, *keys_to_send: str) -> ActionChains

366

```

367

368

**Description**: Sends keys to a specific element.

369

370

**Parameters**:

371

- `element`: The element to send keys to

372

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

373

374

**Example**:

375

```python

376

username_field = driver.find_element(By.NAME, "username")

377

password_field = driver.find_element(By.NAME, "password")

378

379

ActionChains(driver)\

380

.send_keys_to_element(username_field, "testuser")\

381

.send_keys_to_element(password_field, "password123")\

382

.send_keys_to_element(password_field, Keys.ENTER)\

383

.perform()

384

```

385

386

## Keys Class Constants

387

388

{ .api }

389

```python

390

from selenium.webdriver.common.keys import Keys

391

392

class Keys:

393

# Modifier Keys

394

CONTROL = "\ue009"

395

ALT = "\ue00a"

396

SHIFT = "\ue008"

397

META = "\ue03d"

398

COMMAND = "\ue03d" # Same as META

399

400

# Navigation Keys

401

ENTER = "\ue007"

402

RETURN = "\ue006"

403

TAB = "\ue004"

404

SPACE = "\ue00d"

405

BACKSPACE = "\ue003"

406

DELETE = "\ue017"

407

ESCAPE = "\ue00c"

408

409

# Arrow Keys

410

LEFT = "\ue012"

411

UP = "\ue013"

412

RIGHT = "\ue014"

413

DOWN = "\ue015"

414

ARROW_LEFT = LEFT

415

ARROW_UP = UP

416

ARROW_RIGHT = RIGHT

417

ARROW_DOWN = DOWN

418

419

# Page Navigation

420

PAGE_UP = "\ue00e"

421

PAGE_DOWN = "\ue00f"

422

HOME = "\ue011"

423

END = "\ue010"

424

425

# Function Keys

426

F1 = "\ue031"

427

F2 = "\ue032"

428

# ... F3 through F12

429

F12 = "\ue03c"

430

431

# Numpad Keys

432

NUMPAD0 = "\ue01a"

433

NUMPAD1 = "\ue01b"

434

# ... NUMPAD2 through NUMPAD9

435

NUMPAD9 = "\ue023"

436

ADD = "\ue025"

437

SUBTRACT = "\ue027"

438

MULTIPLY = "\ue024"

439

DIVIDE = "\ue029"

440

```

441

442

**Description**: Set of special keys codes for use with ActionChains and send_keys methods.

443

444

**Common Key Combinations**:

445

```python

446

from selenium.webdriver.common.keys import Keys

447

448

# Copy (Ctrl+C)

449

ActionChains(driver)\

450

.key_down(Keys.CONTROL)\

451

.send_keys('c')\

452

.key_up(Keys.CONTROL)\

453

.perform()

454

455

# Select All (Ctrl+A)

456

ActionChains(driver)\

457

.key_down(Keys.CONTROL)\

458

.send_keys('a')\

459

.key_up(Keys.CONTROL)\

460

.perform()

461

462

# Undo (Ctrl+Z)

463

ActionChains(driver)\

464

.key_down(Keys.CONTROL)\

465

.send_keys('z')\

466

.key_up(Keys.CONTROL)\

467

.perform()

468

469

# New Tab (Ctrl+T)

470

ActionChains(driver)\

471

.key_down(Keys.CONTROL)\

472

.send_keys('t')\

473

.key_up(Keys.CONTROL)\

474

.perform()

475

```

476

477

## Scrolling Actions

478

479

### Element-based Scrolling

480

481

{ .api }

482

```python

483

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

484

```

485

486

**Description**: If the element is outside the viewport, scrolls the bottom of the element to the bottom of the viewport.

487

488

**Parameters**:

489

- `element`: Which element to scroll into the viewport

490

491

**Example**:

492

```python

493

footer = driver.find_element(By.ID, "footer")

494

ActionChains(driver).scroll_to_element(footer).perform()

495

```

496

497

### Amount-based Scrolling

498

499

{ .api }

500

```python

501

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

502

```

503

504

**Description**: Scrolls by provided amounts with the origin in the top left corner of the viewport.

505

506

**Parameters**:

507

- `delta_x`: Distance along X axis to scroll. A negative value scrolls left

508

- `delta_y`: Distance along Y axis to scroll. A negative value scrolls up

509

510

**Example**:

511

```python

512

# Scroll down 500 pixels

513

ActionChains(driver).scroll_by_amount(0, 500).perform()

514

515

# Scroll left 200 pixels and up 300 pixels

516

ActionChains(driver).scroll_by_amount(-200, -300).perform()

517

```

518

519

### Origin-based Scrolling

520

521

{ .api }

522

```python

523

def scroll_from_origin(

524

self,

525

scroll_origin: ScrollOrigin,

526

delta_x: int,

527

delta_y: int

528

) -> ActionChains

529

```

530

531

**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.

532

533

**Parameters**:

534

- `scroll_origin`: Where scroll originates (viewport or element center) plus provided offsets

535

- `delta_x`: Distance along X axis to scroll. A negative value scrolls left

536

- `delta_y`: Distance along Y axis to scroll. A negative value scrolls up

537

538

## ScrollOrigin Class

539

540

{ .api }

541

```python

542

from selenium.webdriver.common.actions.wheel_input import ScrollOrigin

543

544

class ScrollOrigin:

545

def __init__(self, origin: Union[str, WebElement], x_offset: int, y_offset: int) -> None

546

547

@classmethod

548

def from_element(cls, element: WebElement, x_offset: int = 0, y_offset: int = 0)

549

550

@classmethod

551

def from_viewport(cls, x_offset: int = 0, y_offset: int = 0)

552

```

553

554

**Description**: Represents a scroll origin point for scrolling operations.

555

556

**Factory Methods**:

557

- `from_element()`: Create origin from element center with optional offset

558

- `from_viewport()`: Create origin from viewport corner with optional offset

559

560

**Example**:

561

```python

562

from selenium.webdriver.common.actions.wheel_input import ScrollOrigin

563

564

# Scroll from element center

565

element = driver.find_element(By.ID, "content")

566

scroll_origin = ScrollOrigin.from_element(element)

567

ActionChains(driver)\

568

.scroll_from_origin(scroll_origin, 0, 200)\

569

.perform()

570

571

# Scroll from element with offset

572

scroll_origin = ScrollOrigin.from_element(element, 100, 50)

573

ActionChains(driver)\

574

.scroll_from_origin(scroll_origin, 0, -100)\

575

.perform()

576

577

# Scroll from viewport

578

scroll_origin = ScrollOrigin.from_viewport(200, 100)

579

ActionChains(driver)\

580

.scroll_from_origin(scroll_origin, 0, 300)\

581

.perform()

582

```

583

584

## Timing Control

585

586

{ .api }

587

```python

588

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

589

```

590

591

**Description**: Pause all inputs for the specified duration in seconds.

592

593

**Parameters**:

594

- `seconds`: Duration to pause in seconds (can be fractional)

595

596

**Example**:

597

```python

598

# Add pauses between actions for better visual feedback

599

button1 = driver.find_element(By.ID, "btn1")

600

button2 = driver.find_element(By.ID, "btn2")

601

602

ActionChains(driver)\

603

.click(button1)\

604

.pause(1.5)\

605

.click(button2)\

606

.pause(0.5)\

607

.perform()

608

```

609

610

## Context Manager Support

611

612

ActionChains can be used as a context manager:

613

614

{ .api }

615

```python

616

def __enter__(self) -> ActionChains

617

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

618

```

619

620

**Example**:

621

```python

622

with ActionChains(driver) as actions:

623

actions.move_to_element(menu_item)

624

actions.click(submenu_item)

625

# perform() is automatically called at the end of the with block

626

```

627

628

## Complex Interaction Examples

629

630

### Multi-Step Drag and Drop with Hover Effects

631

632

```python

633

def complex_drag_drop():

634

source = driver.find_element(By.ID, "draggable-item")

635

intermediate = driver.find_element(By.ID, "hover-zone")

636

target = driver.find_element(By.ID, "drop-zone")

637

638

ActionChains(driver)\

639

.click_and_hold(source)\

640

.pause(0.5)\

641

.move_to_element(intermediate)\

642

.pause(1.0)\

643

.move_to_element(target)\

644

.pause(0.5)\

645

.release()\

646

.perform()

647

```

648

649

### Advanced Text Editing

650

651

```python

652

def advanced_text_editing():

653

text_area = driver.find_element(By.ID, "editor")

654

655

ActionChains(driver)\

656

.click(text_area)\

657

.key_down(Keys.CONTROL)\

658

.send_keys('a')\

659

.key_up(Keys.CONTROL)\

660

.send_keys("New content here")\

661

.key_down(Keys.SHIFT)\

662

.send_keys(Keys.LEFT, Keys.LEFT, Keys.LEFT, Keys.LEFT)\

663

.key_up(Keys.SHIFT)\

664

.key_down(Keys.CONTROL)\

665

.send_keys('b')\

666

.key_up(Keys.CONTROL)\

667

.perform()

668

```

669

670

### Interactive Drawing

671

672

```python

673

def draw_signature():

674

canvas = driver.find_element(By.ID, "signature-pad")

675

676

# Draw a simple signature

677

ActionChains(driver)\

678

.move_to_element_with_offset(canvas, -100, 0)\

679

.click_and_hold()\

680

.move_by_offset(50, -20)\

681

.move_by_offset(30, 40)\

682

.move_by_offset(40, -30)\

683

.move_by_offset(30, 20)\

684

.release()\

685

.perform()

686

```

687

688

### Keyboard Navigation

689

690

```python

691

def navigate_table_with_keyboard():

692

table = driver.find_element(By.ID, "data-table")

693

first_cell = table.find_element(By.CSS_SELECTOR, "tr:first-child td:first-child")

694

695

# Navigate and edit table cells

696

ActionChains(driver)\

697

.click(first_cell)\

698

.send_keys("Cell 1 Value")\

699

.send_keys(Keys.TAB)\

700

.send_keys("Cell 2 Value")\

701

.send_keys(Keys.ENTER)\

702

.send_keys("Next Row Value")\

703

.perform()

704

```

705

706

## Best Practices

707

708

### 1. Always Call perform()

709

710

```python

711

# ✅ Correct - actions are executed

712

ActionChains(driver).click(element).perform()

713

714

# ❌ Incorrect - actions are queued but never executed

715

ActionChains(driver).click(element)

716

```

717

718

### 2. Use Method Chaining for Related Actions

719

720

```python

721

# ✅ Good - related actions chained together

722

ActionChains(driver)\

723

.move_to_element(menu)\

724

.click(submenu)\

725

.perform()

726

727

# ✅ Also good - sequential building for complex logic

728

actions = ActionChains(driver)

729

actions.move_to_element(menu)

730

if condition:

731

actions.pause(1.0)

732

actions.click(submenu)

733

actions.perform()

734

```

735

736

### 3. Reset Actions for Reusable ActionChains

737

738

```python

739

actions = ActionChains(driver)

740

741

# First set of actions

742

actions.click(button1).perform()

743

actions.reset_actions() # Clear previous actions

744

745

# Second set of actions

746

actions.click(button2).perform()

747

```

748

749

### 4. Handle Element State Before Actions

750

751

```python

752

from selenium.webdriver.support.ui import WebDriverWait

753

from selenium.webdriver.support import expected_conditions as EC

754

755

# Wait for element to be clickable before action

756

element = WebDriverWait(driver, 10).until(

757

EC.element_to_be_clickable((By.ID, "interactive-button"))

758

)

759

760

ActionChains(driver).click(element).perform()

761

```

762

763

### 5. Use Pauses for User Experience

764

765

```python

766

# Add natural pauses to mimic human interaction

767

ActionChains(driver)\

768

.move_to_element(dropdown_trigger)\

769

.pause(0.5)\

770

.click()\

771

.pause(1.0)\

772

.click(dropdown_item)\

773

.perform()

774

```