Actions Class
driver.findElement(...).click() covers simple clicks, but real UIs have gestures that don't map to a single method call: hovering to reveal a menu, dragging an item to reorder it, holding Ctrl while clicking multiple items, or pressing Ctrl+A to select all text in a field. Selenium exposes all of these through the Actions class — a builder that lets you chain low-level mouse and keyboard events into one composite gesture.
Building an Actions Chain
Actions works like a builder: you chain calls describing a sequence of input events, and nothing actually happens until you call .perform().
WebDriver driver = new ChromeDriver();
Actions actions = new Actions(driver);
actions.moveToElement(someElement)
.click()
.build()
.perform();
Mouse Hover: moveToElement
Say RecipeBox reveals an "Edit" / "Delete" icon pair only when you hover over a recipe card. A plain click() can't trigger a CSS :hover state — you need to actually move the simulated mouse there.
WebElement recipeCard = driver.findElement(By.cssSelector(".recipe-card:nth-child(1)"));
new Actions(driver)
.moveToElement(recipeCard)
.perform();
// Now the hover-revealed icons exist/are visible in the DOM
WebElement editIcon = driver.findElement(By.cssSelector(".recipe-card:nth-child(1) .edit-icon"));
editIcon.click();
Drag-and-Drop
Selenium offers a convenience method, dragAndDrop(source, target), and a manual three-step version using clickAndHold + moveByOffset + release. The manual version gives you more control — some drag implementations (especially ones built with JS libraries) need intermediate mouse-move events to register the drag at all, which the one-call convenience method doesn't always produce.
// Convenience method — works for simple HTML5/native drag targets
WebElement source = driver.findElement(By.id("ingredient-2"));
WebElement target = driver.findElement(By.id("ingredient-slot-0"));
new Actions(driver)
.dragAndDrop(source, target)
.perform();
// Manual version — clickAndHold + moveByOffset + release
WebElement source = driver.findElement(By.id("ingredient-2"));
new Actions(driver)
.clickAndHold(source)
.moveByOffset(0, -80) // move up by 80px, onto the slot above
.pause(Duration.ofMillis(200))
.release()
.build()
.perform();
Keyboard Combinations: keyDown / keyUp
To simulate holding a modifier key (Ctrl, Shift) while pressing another key, use keyDown() before the key and keyUp() after it — otherwise the modifier is released before the other key registers.
WebElement titleField = driver.findElement(By.id("recipeTitle"));
titleField.sendKeys("Paneer Butter Masala");
// Select all text in the field (Ctrl+A), then replace it
new Actions(driver)
.click(titleField)
.keyDown(Keys.CONTROL)
.sendKeys("a")
.keyUp(Keys.CONTROL)
.sendKeys("Chicken Tikka Masala")
.build()
.perform();
Worked Example: Reordering RecipeBox Ingredients
RecipeBox's "Add Recipe" page lets you reorder the ingredient list by dragging rows up and down. Each ingredient row has an id like ingredient-0, ingredient-1, etc., in DOM order. The worked example below drags the third ingredient (index 2) to the top of the list, then verifies the reorder actually happened by reading the list's text order back out.
public class ReorderIngredientsTest {
private WebDriver driver;
@BeforeMethod
public void setUp() {
driver = new ChromeDriver();
driver.get("https://recipebox.example.com/recipes/add");
}
@Test
public void dragThirdIngredientToTop() {
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("ingredient-2")));
WebElement thirdIngredient = driver.findElement(By.id("ingredient-2"));
WebElement firstSlot = driver.findElement(By.id("ingredient-0"));
new Actions(driver)
.clickAndHold(thirdIngredient)
.moveToElement(firstSlot)
.pause(Duration.ofMillis(300))
.release()
.build()
.perform();
List<WebElement> rows = driver.findElements(By.className("ingredient-row"));
Assert.assertEquals(rows.get(0).getText(), "Paneer, 250g",
"The dragged ingredient should now be first in the list");
}
@AfterMethod
public void tearDown() {
if (driver != null) driver.quit();
}
}