Why the Actions Class Exists
Selenium's WebElement.click() and sendKeys() only cover the two most common interactions. Real UIs rely on richer mouse and keyboard gestures — hovering to reveal a dropdown, holding a mouse button to drag a card, or pressing Shift while clicking to multi-select — none of which a single method call can express. The Actions class (org.openqa.selenium.interactions.Actions) solves this by letting you compose a sequence of low-level input events (moveToElement, clickAndHold, keyDown, pause) into one chain, then fire them together with perform(), mirroring how a real user's hand moves across the screen over time.
Cricket analogy: Just as a fielder doesn't just 'catch the ball' but goes through a sequence — sighting it, positioning the body, cushioning the catch — Actions breaks a UI gesture into the same kind of ordered micro-steps like moveToElement then clickAndHold.
Simulating Mouse Hover
Many navigation menus only render their dropdown when the browser fires a genuine mouseover event on the parent element — clicking it does nothing because no click handler exists. new Actions(driver).moveToElement(menuElement).perform() moves the simulated cursor to the center of that element, which triggers the same mouseover/mouseenter listeners a real user's mouse would. Because the dropdown's items are often not present in the DOM (or not interactable) until that hover fires, you should call perform() first, then locate and act on the newly visible submenu item in a separate step, otherwise you'll hit a NoSuchElementException or ElementNotInteractableException.
Cricket analogy: A spinner drifting the ball in flight before it pitches changes what the batter sees just before contact; moveToElement changes what's rendered on screen just before your next findElement call, so ordering matters.
WebDriver driver = new ChromeDriver();
driver.get("https://example.com/shop");
WebElement menu = driver.findElement(By.id("categories-menu"));
Actions actions = new Actions(driver);
// Hover to reveal the dropdown, then act on the newly visible submenu item
actions.moveToElement(menu).pause(Duration.ofMillis(300)).perform();
WebElement submenuItem = driver.findElement(By.linkText("Laptops"));
submenuItem.click();
Drag and Drop
Selenium exposes a one-shot dragAndDrop(source, target) convenience method, but it only fires the native HTML5 drag events (dragstart, dragover, drop) and often fails against sortable lists or Kanban boards built with JavaScript libraries like SortableJS or react-beautiful-dnd, which listen for raw mousedown/mousemove/mouseup instead. For those, you build the gesture manually: clickAndHold(source), a moveToElement(target) or several moveByOffset() calls to simulate intermediate mouse movement (some libraries only start tracking after a minimum drag distance), a short pause, then release(). Adding pause() steps between moves is often the difference between a flaky drag that silently does nothing and one that reliably reorders the list.
Cricket analogy: A well-executed run-out relay throw isn't one motion but pick-up, transfer, and release in sequence, and rushing any step fumbles it; a JS-based drag-drop needs the same clickAndHold-move-pause-release sequence or the library never registers the gesture.
// Simple HTML5 drag-and-drop
WebElement source = driver.findElement(By.id("drag-item"));
WebElement target = driver.findElement(By.id("drop-zone"));
new Actions(driver).dragAndDrop(source, target).perform();
// Manual sequence for JS-based sortable lists (e.g. SortableJS, react-beautiful-dnd)
WebElement card = driver.findElement(By.id("task-card-3"));
WebElement dropZone = driver.findElement(By.id("in-progress-column"));
new Actions(driver)
.clickAndHold(card)
.moveByOffset(10, 0) // small nudge to trigger drag-detection threshold
.pause(Duration.ofMillis(200))
.moveToElement(dropZone)
.pause(Duration.ofMillis(200))
.release()
.build()
.perform();
clickAndHold and moveToElement operate on WebElement references captured before the drag begins. If the drag reorders the DOM (common in Kanban boards), those references can become stale mid-chain and throw StaleElementReferenceException. Re-locate elements after any drag operation before interacting with them again, and never reuse the same WebElement across a drag and a subsequent assertion.
Building and Reusing Action Chains
Calling .build() compiles the queued events into a CompositeAction and .perform() executes it immediately; in modern Selenium (4.x) you can skip build() and call perform() directly, since it builds implicitly, but build() is still useful when you want to construct a chain once and store the resulting Action object to replay later. Each call to actions.moveToElement() etc. mutates and returns the same Actions instance, so chain calls fluently rather than reassigning; calling build() a second time on an Actions object that already had actions queued and performed can lead to unexpected re-execution of earlier steps if you don't instantiate a fresh Actions object per gesture.
Cricket analogy: A bowling machine can be pre-loaded with a specific line-and-length setting and then fired repeatedly for throwdowns without resetting it each time; Actions.build() similarly compiles a reusable Action object you can perform() more than once.
Always instantiate a fresh new Actions(driver) for each distinct gesture rather than reusing one Actions object across unrelated interactions in a test — reusing it can silently queue and replay stale actions alongside new ones, producing intermittent, hard-to-debug failures.
- The Actions class composes low-level mouse and keyboard events (moveToElement, clickAndHold, keyDown) into one chain via build() and perform().
- moveToElement() simulates a real mouseover, needed to reveal CSS/JS-driven dropdown menus before their items can be located and clicked.
- dragAndDrop(source, target) only works for native HTML5 drag events; JS libraries like SortableJS need a manual clickAndHold-moveByOffset-pause-release sequence.
- Intermediate moveByOffset() calls matter because many drag libraries only register a drag once a minimum movement threshold is crossed.
- In Selenium 4.x, perform() implicitly calls build(), but build() remains useful for constructing a reusable Action object.
- WebElement references can go stale mid-drag if the DOM reorders; re-locate elements after any drag-and-drop before further interaction.
- Instantiate a fresh Actions object per gesture to avoid replaying stale queued actions from a previous interaction.
Practice what you learned
1. Why does clicking a navigation menu item sometimes fail unless you first call moveToElement() on the parent menu?
2. Why might Actions.dragAndDrop(source, target) fail on a React-based Kanban board using react-beautiful-dnd?
3. What is the purpose of inserting moveByOffset() calls with small increments during a manual drag sequence?
4. In Selenium 4.x, what happens if you call perform() without first calling build()?
5. Why is it risky to reuse the same WebElement reference for an assertion immediately after a drag-and-drop operation?
Was this page helpful?
You May Also Like
Handling Alerts and Popups
Understand how to detect, read, and respond to native JavaScript alert, confirm, and prompt dialogs, and how to tell them apart from ordinary DOM-based popups.
Working with Frames and Multiple Windows
Learn how to switch WebDriver's context into iframes and nested frames, and how to detect, switch between, and close multiple browser windows and tabs.
JavaScriptExecutor for Tricky Elements
Learn how JavascriptExecutor lets Selenium run arbitrary JavaScript to work around interactability limitations, and when relying on it can mask real bugs.