ScreenshotNeo

BlogHow-to

How to Hover Over an Element with Puppeteer

Use Puppeteer’s Locator API to hover reliably, handle frames and hover-triggered UI, and troubleshoot common failures with runnable examples.

By the ScreenshotNeo team4 October 20266 min read

In current Puppeteer code, use a Locator to hover over an element:

await page.locator('button').hover();

Replace 'button' with a selector for the element you want. A Locator prepares the element for interaction by bringing it into the viewport, waiting for visibility, and waiting for its bounding box to stay stable across two animation frames. The page-level shorthand await page.hover('button') is also documented; it hovers the center of the first matching element after scrolling it into view. Puppeteer page interactions · Page.hover API

Hover over an element with Puppeteer

Here is a complete Node.js example using Puppeteer. It opens a page, hovers over a button, and waits for a menu that the page reveals on hover.

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch({ headless: true });
try {
  const page = await browser.newPage();
  await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });

  await page.locator('[data-testid="menu-trigger"]').hover();
  await page.locator('[data-testid="menu-panel"]').wait();
  console.log('The menu is visible');
} finally {
  await browser.close();
}

Use a selector that identifies the intended target. CSS selectors work by default; Puppeteer also supports selector forms for text, accessibility role and name, XPath, and queries through open shadow roots. See the selector guide for syntax.

Choose the right hover method

Method Use it when Behavior
page.locator(selector).hover() Ordinary element interaction in current code Locator performs hover readiness checks, including viewport placement, visibility, and stable geometry.
page.hover(selector) You want the concise page-level API Scrolls the first matching element into view if needed and moves to its center. Rejects when no element matches.
page.mouse You need pointer movement without selecting an element, or lower-level control Emits mouse events at coordinates you provide; you are responsible for finding the right coordinates and timing.

The Locator API is the recommended general pattern in the current interactions guide. The page-level method remains documented. Locator.hover API

Wait for the hover result

Hovering moves the pointer; your script should separately verify the resulting page state. Wait for the tooltip, submenu, or changed attribute that matters before proceeding. For example:

await page.locator('[data-testid="help-icon"]').hover();
await page.locator('[role="tooltip"]').wait();
const tooltipText = await page.locator('[role="tooltip"]').innerText();
console.log(tooltipText);

Choose a wait condition that reflects the behavior you need. A delay can help diagnose an animation, but waiting for a visible element or expected state is usually more dependable than guessing a fixed duration.

Hover inside a frame

A selector on the main page does not automatically target content inside a separate frame. Locate the relevant frame and use its own hover or Locator API:

const frame = page.frames().find(candidate => candidate.url().includes('widget'));
if (!frame) throw new Error('Widget frame was not found');

await frame.locator('[data-testid="trigger"]').hover();
// Or use the frame-level selector shorthand:
// await frame.hover('[data-testid="trigger"]');

Puppeteer documents both frame.hover(selector) and frame.locator(selector) on its Frame API. Match the frame using a stable property of your page rather than relying on its position in the frame list.

Use custom mouse movement when needed

For normal element hovering, the element APIs are simpler. If you need to move to a particular coordinate, use the page mouse API. This example gets the element’s bounding box and moves to its center:

const target = await page.$('[data-testid="menu-trigger"]');
if (!target) throw new Error('Target was not found');

try {
  const box = await target.boundingBox();
  if (!box) throw new Error('Target has no visible bounding box');
  await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
} finally {
  await target.dispose();
}

This lower-level approach requires you to handle missing elements, visibility, scrolling, and movement timing. Puppeteer describes Page.mouse as the mouse API for emitting events without first selecting an element.

Or skip the browser setup

If your goal is a screenshot after interacting with a page, ScreenshotNeo provides a screenshot API and MCP server. Its screenshot endpoint captures a URL in one request; it does not run a Puppeteer hover action, so use the DIY code above when the interaction itself is required.

Install the HTTP client for Python with python -m pip install requests. The following examples use https://stripe.com as the target; replace it with the page you need. See the ScreenshotNeo API documentation for options and setup.

cURL

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python

import requests

r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

Node.js

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));

ScreenshotNeo accepts cookie or consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits cost nothing, and response headers report the page verdict and billing status. Its MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots.

Sign up for 1,000 free screenshots a month, with no card.

Troubleshooting

Symptom Likely cause What to do
Hover rejects because no element matched The selector is wrong, the element has not been rendered, or it is inside a frame. Check the selector against the live page, wait for the target to appear, and query through the correct frame when applicable.
The hover call resolves but no menu appears The page may reveal the menu only after a different target is hovered, or the UI has not finished responding. Confirm the trigger selector and wait for the specific menu or tooltip state after hovering.
The target is covered or moves during interaction An overlay, animation, or layout shift can affect the target’s position. Wait for the overlay or animation to finish, then retry the Locator action. Check whether a consent dialog must be handled first.
Page selector cannot find a framed element The element belongs to a child frame, which has its own document. Find the relevant frame and use frame.locator() or frame.hover().
ElementHandle code accumulates resources Handles retained across many operations are not being released. Dispose of handles when finished, preferably in a finally block. For ordinary interactions, use a Locator where possible.

Puppeteer’s guide notes that waitForSelector() does not automatically retry a later action. If using it with an ElementHandle, handle the possibility that the element changes between waiting and interacting, and dispose of the handle when done. Page interactions guide

Reliability, speed, and cost

  • Reliability: Prefer Locators for standard interactions because they check readiness and retry when an element is not ready. Still wait for and verify the application state that should follow the hover.
  • Speed: Avoid unnecessary fixed delays. Wait for the target and resulting state. Reuse an open browser for multiple page operations where your application design allows it, and close it when work is complete.
  • Cost: Puppeteer’s API does not set a per-hover charge; your costs come from the environment running the browser, such as compute and any services your automation uses. No benchmark or runtime guarantee is implied here.
  • Screenshot path: For a screenshot without browser setup, ScreenshotNeo has a free tier of 1,000 shots per month, then paid plans from $5 for 3,000. Only clean shots are billed; response headers indicate verdict and billing.

FAQ

Does Puppeteer hover the center of an element?

page.hover(selector) uses the center of the first matching element. Use page.mouse if you need a custom pointer coordinate.

What happens if a selector matches several elements?

The page-level page.hover() method hovers the first match. Narrow the selector when a different matching element is intended.

Can I hover over an element in an open shadow root?

Puppeteer documents selector syntax for querying open shadow roots. Use a selector supported by the page’s shadow DOM structure and hover the resulting Locator.