ScreenshotNeo

BlogHow-to

How to Move the Mouse to an Element or Coordinate With Puppeteer

Use Puppeteer locators to hover an element, or move the mouse to viewport coordinates. See runnable examples, options, troubleshooting, and when screenshots need no browser setup.

By the ScreenshotNeo team4 October 20268 min read

Use await page.locator(selector).hover() to move the mouse over a DOM element. Use await page.mouse.move(x, y) when you need to move to a specific point. Puppeteer coordinates are CSS pixels measured from the top-left of the main-frame viewport.

This guide uses Puppeteer’s JavaScript API. The examples assume a recent Puppeteer version and an already launched browser page. Check the documentation for your installed version if an API differs: page interactions.

1. Hover over an element with a locator

For an element identified by a selector, use a locator. Locators are Puppeteer’s recommended way to select and interact with page elements. Locator hover brings the element into the viewport, waits for it to be visible, and waits for its bounding box to stay stable across two animation frames before acting.

const button = page.locator('button.submit');
await button.hover();

Use a selector that identifies the intended element. For example:

await page.locator('[data-testid="menu-trigger"]').hover();
await page.locator('#account-menu').hover();
await page.locator('nav a[href="/pricing"]').hover();

Hovering can trigger CSS states, menus, tooltips, or page event handlers. It does not click the element. If the page responds asynchronously to hover, wait for the resulting state explicitly:

await page.locator('[data-testid="menu-trigger"]').hover();
await page.locator('[data-testid="menu-panel"]').wait();

Locator actions retry when an action fails because the target is not ready. That makes locator hover a good default when the target is an element rather than a point. See the Puppeteer interactions guide.

2. Hover a selector with page.hover()

page.hover(selector) is a page-level shortcut. Puppeteer finds a matching element, scrolls it into view if necessary, and hovers over its center. If multiple elements match, it uses the first match; if none match, the call rejects.

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

Use this when the selector is sufficient and center-point hovering is what you want. Prefer a locator when you want the recommended selection-and-interaction pattern and its readiness checks.

try {
  await page.hover('[data-testid="menu-trigger"]');
} catch (error) {
  console.error('Could not hover the menu trigger:', error);
}

API reference: Page.hover().

3. Move to a coordinate

Call page.mouse.move(x, y) to move to a specific viewport point. The first argument is horizontal position, the second is vertical position; both use CSS pixels relative to the viewport’s top-left corner.

await page.mouse.move(250, 120);

To move through intermediate positions, pass steps. It defaults to 1:

await page.mouse.move(250, 120, { steps: 10 });

More steps can be useful when page behavior depends on the mouse path. They do not turn automation input into physical hardware input: Puppeteer dispatches synthetic mouse events, which do not reproduce every property of a real mouse. See Mouse.move() and the Mouse class documentation.

4. Calculate a point inside an element

If you need low-level mouse movement but the target is an element, get its bounding box and derive a point. The example moves to the center:

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

const box = await element.boundingBox();
if (!box) {
  throw new Error('Element has no layout box');
}

await page.mouse.move(
  box.x + box.width / 2,
  box.y + box.height / 2,
);

boundingBox() returns null if the element is not part of layout, such as when it has display: none. Bounding-box coordinates are relative to the main frame. See ElementHandle.boundingBox().

Choose a point inside the element when its center is obstructed or has different behavior. For instance, move to a point one quarter across and halfway down:

await page.mouse.move(
  box.x + box.width * 0.25,
  box.y + box.height * 0.5,
);

5. Pick the right operation

Need Use What to account for
Hover a DOM element page.locator(selector).hover() Waits for viewport presence, visibility, and a stable bounding box.
Hover the first match and its center page.hover(selector) Scrolls into view; rejects if there is no match; first match wins if several match.
Move to a known point page.mouse.move(x, y) Coordinates are main-frame viewport CSS pixels; readiness is your responsibility.
Move to a point within an element boundingBox() then mouse.move() Check for a missing element and a null bounding box.

6. Complete runnable example

Install Puppeteer in a Node.js project with npm install puppeteer. This standalone script opens a page, hovers a locator, moves to a viewport coordinate, and closes the browser even if an operation fails.

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({ headless: true });
  try {
    const page = await browser.newPage();
    await page.setViewport({ width: 1280, height: 800 });
    await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });

    // Element-based hover (replace with a selector on your page).
    await page.locator('h1').hover();

    // Point-based movement in viewport CSS pixels.
    await page.mouse.move(250, 120, { steps: 5 });

    console.log('Mouse movement completed');
  } finally {
    await browser.close();
  }
})().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

example.com is only a runnable navigation target; replace the selectors and coordinates with ones appropriate to the page you automate. The page must be loaded and the target must be present for element interactions to succeed.

7. Coordinates, viewport, and edge cases

  • Viewport origin: coordinate (0, 0) is the viewport’s top-left, not the full document’s top-left.
  • CSS pixels: use viewport CSS pixels. Do not multiply coordinates by a device scale factor just because a screenshot is retina-sized.
  • Scrolling: locator hover and page.hover() bring an off-screen element into view. A raw coordinate move does not select an element or scroll to it.
  • Frames: coordinates and the documented bounding box are relative to the main frame. For a target inside an iframe, interact with its frame context and take care to map any point to the relevant coordinate space.
  • Moving layouts: animation, responsive changes, and delayed content can shift a target. Locator hover waits for a stable box; manually calculated coordinates can become stale if the page changes after measurement.
  • Overlapping elements: a point may land on an overlay or a different element. Check the layout and choose another point or dismiss the obstruction.
  • Hidden elements: an element with no layout box cannot be targeted using its bounding box. Make it visible or choose a visible target.

8. Troubleshooting

Symptom Likely cause Fix
page.hover() rejects because no element was found The selector matches nothing at call time, or the page has not loaded the target yet. Check the selector and wait for the element to appear before hovering.
The wrong matching element is hovered page.hover() uses the first match when several elements match. Make the selector more specific or use a locator scoped to the intended target.
Bounding box is null The element is not in layout, for example it is display: none. Wait for or cause the element to become visible, then request its box again.
Coordinate movement does not trigger the expected hover The point is outside the viewport, stale, covered, or calculated in the wrong coordinate system. Confirm viewport dimensions, recalculate after layout settles, and verify the target is unobstructed.
The target is off-screen Raw mouse.move() accepts a point; it does not locate or scroll to an element. Use locator hover or page.hover(), or scroll the page before calculating coordinates.
Hover works but a menu or tooltip does not appear The page may show it after an event handler, animation, or asynchronous request. Wait for the resulting selector or state instead of assuming it appears immediately.
Behavior differs from a person’s physical mouse Puppeteer emits synthetic mouse events, which do not reproduce all hardware behavior. Test against the event behavior the page actually relies on; do not assume physical-device equivalence.

9. Performance and reliability

Use locator hover for ordinary element interaction: it avoids hand-maintaining coordinates and includes readiness checks. Raw mouse movement is appropriate for point-specific behavior, but the caller must ensure the page is at the expected viewport, the target position is current, and the point is not blocked.

Keep selectors stable, wait for a meaningful page state before interacting, and avoid fixed delays when a selector or other explicit condition can indicate readiness. For coordinate workflows, set the viewport deliberately and calculate positions close to the moment of movement. Add intermediate steps only when the interaction requires a path; the default single step is less work.

There is no universal timing or performance figure for these operations: page complexity, rendering, network activity, and the page’s own event handlers affect completion time. For robust automation, close the browser in a finally block and surface failures rather than silently continuing with a missed hover.

10. Or skip the browser setup

If you only need an image or PDF of a web page, you may not need to launch Puppeteer or manage a browser. ScreenshotNeo is a website screenshot API and MCP server for developers. One GET request returns a PNG, JPEG, WebP, or PDF. See the ScreenshotNeo API docs for request options.

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://stripe.com \
  -o shot.webp
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)
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 require('node:fs/promises').writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
  • Cookie banners, newsletter popups, and chat widgets are removed before the shot; each cleanup step can be turned off.
  • Bot checks, blank pages, timeouts, failed loads, and cache hits are never billed. Response headers report the page verdict and billing outcome.
  • An MCP server lets AI agents, including Claude, Cursor, and other MCP clients, take screenshots.
  • 1,000 screenshots a month are free with no card. Paid plans start at $5 for 3,000 shots; every feature is available on every plan.

Sign up for ScreenshotNeo and get 1,000 free screenshots a month, with no card required.

11. FAQ

Does moving the mouse click the element?

No. Hover and mouse movement dispatch movement behavior; use a click operation when you intend to activate an element.

Should I use a locator or coordinates?

Use a locator when the target is an element. Use coordinates when the interaction is defined by a point or needs low-level mouse control.

Can I hover the center of an element without calculating it?

Yes. page.hover(selector) hovers the first match at its center, and locator hover provides element-oriented interaction with readiness checks.

What do the movement steps change?

steps sets how many intermediate moves Puppeteer makes between the current point and destination. It defaults to one.

12. Sources