Skip to main content
SeleniumDecoded

Hover a Menu and Click a Submenu Item

Open a hover-driven dropdown with the Actions API and click an item that only exists while the pointer is over the trigger.

Medium Verified against Selenium 4.48.0

Problem

The “Account” menu opens on mouseover and its items are removed from the DOM (or hidden) as soon as the pointer leaves. A plain click() on the submenu item fails with NoSuchElement or ElementNotInteractable.

Solution

Move the pointer to the trigger with Actions, wait for the item to be visible, then move to and click the item within the same interaction so the pointer never leaves the menu.

Hover, wait, click
Selenium 4 Medium
Actions actions = new Actions(driver);
WebElement trigger = driver.findElement(By.cssSelector("[data-testid='account-menu']"));
actions.moveToElement(trigger).perform();
WebElement item = new WebDriverWait(driver, Duration.ofSeconds(5)).until(
ExpectedConditions.visibilityOfElementLocated(By.linkText("Billing")));
actions.moveToElement(item).click().perform();
actions = ActionChains(driver)
trigger = driver.find_element(By.CSS_SELECTOR, "[data-testid='account-menu']")
actions.move_to_element(trigger).perform()
item = WebDriverWait(driver, 5).until(EC.visibility_of_element_located((By.LINK_TEXT, "Billing")))
ActionChains(driver).move_to_element(item).click().perform()
const trigger = await driver.findElement(By.css("[data-testid='account-menu']"));
await driver.actions({ async: true }).move({ origin: trigger }).perform();
const item = await driver.wait(until.elementLocated(By.linkText('Billing')), 5000);
await driver.wait(until.elementIsVisible(item), 5000);
await driver.actions({ async: true }).move({ origin: item }).click().perform();
var actions = new Actions(driver);
var trigger = driver.FindElement(By.CssSelector("[data-testid='account-menu']"));
actions.MoveToElement(trigger).Perform();
var item = new WebDriverWait(driver, TimeSpan.FromSeconds(5))
.Until(d => { var e = d.FindElement(By.LinkText("Billing")); return e.Displayed ? e : null; });
new Actions(driver).MoveToElement(item).Click().Perform();

Why It Works

moveToElement sends real pointer-move events, so mouseenter handlers fire and the menu opens. Moving to the item before clicking keeps the pointer path inside the menu; a direct item.click() would first move the pointer in a straight line that may leave the trigger and close the menu.

Gotchas

  • In Python, build a fresh ActionChains for the second step; a reused chain replays the earlier moves.
  • Menus with a close delay may need the click to happen quickly after the hover; the wait timeout of 5 seconds is a maximum, not a delay.
  • Keyboard-accessible menus can often be opened with sendKeys(Keys.ENTER) on the trigger, which is more robust than hover and also tests accessibility.
  • Headless browsers have no real cursor but still process synthetic pointer events, so this works headless.

Learn the theory