How to Use the Puppeteer Mouse API for Browser Automation
Learn when to use Puppeteer’s coordinate-based mouse API, how to click, drag, and scroll, and when a locator or DOM selection is a better fit.
Puppeteer’s page.mouse API sends pointer input to viewport coordinates. Use it when the pointer’s position or movement path matters; use a locator when you want to act on a particular page element. A mouse click is a move, press, and release sequence, while a drag can be built from those lower-level operations.
This guide covers coordinate-based clicks, button options, movement, dragging, wheel input, navigation waits, the limits of synthetic mouse events, and troubleshooting. The official Puppeteer documentation checked for this guide showed version 25.12.0 on key pages; method page version labels were not fully synchronized. Check the signatures against the Puppeteer version installed in your project.
1. Set up a runnable Puppeteer example
Install Puppeteer in a Node.js project:
npm install puppeteer
Save the following as mouse-demo.js and run it with node mouse-demo.js. It opens a page, prints its viewport size, clicks a coordinate, moves the pointer, presses and releases a button, and sends wheel input. The example uses a public page; replace the URL and coordinates with ones appropriate to your target page.
const puppeteer = require('puppeteer');
async function main() {
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage({
viewport: { width: 1280, height: 800 },
});
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
console.log('Viewport:', await page.evaluate(() => ({
width: window.innerWidth,
height: window.innerHeight,
})));
// Coordinates are viewport-relative CSS pixels.
await page.mouse.click(120, 80);
// Low-level press, move, release sequence.
await page.mouse.move(150, 150);
await page.mouse.down();
await page.mouse.move(250, 200, { steps: 8 });
await page.mouse.up();
// Wheel input is sent at the current pointer position.
await page.mouse.move(400, 300);
await page.mouse.wheel({ deltaY: 300 });
} finally {
await browser.close();
}
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
For production automation, choose coordinates only after the page has reached the expected state. A hard-coded point can hit a different target if the viewport, layout, scroll position, or page content changes.
2. Understand coordinates and mouse state
Each Puppeteer Page has its own mouse instance at page.mouse. Coordinates are main-frame CSS pixels measured from the top-left corner of the viewport. They are not document coordinates: scrolling changes which content appears at a given viewport point. The Mouse constructor is internal; use the instance supplied by the page.
| Operation | What it does | Typical use |
|---|---|---|
move(x, y, options?) |
Moves the pointer to a viewport coordinate. | Position before pressing, clicking, or sending wheel input. |
down(options?) |
Presses a mouse button. | Begin a press-and-move interaction. |
up(options?) |
Releases a mouse button. | Finish a press or drag sequence. |
click(x, y, options?) |
Moves to the point, presses, and releases. | Click a known coordinate. |
wheel(options) |
Dispatches wheel input at the pointer position. | Exercise a page’s wheel handler or scroll behavior. |
Documented button names are left, right, middle, back, and forward; left is the default. For example, await page.mouse.click(300, 200, { button: 'right' }) requests a right-button click. Use the API’s documented options for the Puppeteer version in your project.
3. Click a coordinate
Call click(x, y) when you deliberately want to target a point rather than identify an element:
await page.mouse.click(120, 80);
The call is a shortcut for moving to the coordinate, pressing, and releasing. Make sure the point is inside the current viewport and that the page layout is stable before clicking. For ordinary buttons and links, a locator usually expresses intent more clearly and avoids maintaining fragile coordinates.
4. Move with intermediate steps
move returns a promise. Its optional movement settings include steps, the number of movements between the previous and new positions; the default is 1. More steps can be useful when the application reacts to pointer movement along a path.
await page.mouse.move(100, 100);
await page.mouse.move(300, 180, { steps: 10 });
More steps mean more input events and work. They do not make the input equivalent to a physical mouse or guarantee that a particular page animation or handler will finish. Wait for the application state you need before continuing.
5. Press, move, and release
Build a press-and-move sequence with the lower-level methods:
await page.mouse.move(100, 100);
await page.mouse.down();
await page.mouse.move(250, 180, { steps: 8 });
await page.mouse.up();
This is appropriate for pointer interactions such as drawing or dragging a control when the target behavior is coordinate-based. Ensure the release runs even if an intermediate step fails, so a pressed button is not left active for later actions:
await page.mouse.move(100, 100);
await page.mouse.down();
try {
await page.mouse.move(250, 180, { steps: 8 });
} finally {
await page.mouse.up();
}
Puppeteer also documents drag and drop sequence methods, including drag, drag-and-drop, drag-enter, drag-over, and drop. Use those when their purpose-built sequence matches the interaction. Consult the reference for the installed version’s signatures.
6. Send wheel input
Move the pointer over the area that should receive wheel input, then call wheel:
await page.mouse.move(400, 300);
await page.mouse.wheel({ deltaY: 300 });
The method dispatches a mousewheel event. The page’s handlers and browser behavior determine the result; a wheel event does not guarantee ordinary document scrolling. For example, an element may handle the event itself, or page code may prevent the default behavior. Check the resulting scroll position or application state if the next step depends on it.
7. Choose between mouse coordinates and locators
Use a locator when the task is “click this button” and a coordinate when the task depends on a particular pointer position or path. Puppeteer’s page-interactions guide recommends locators for finding and interacting with elements. Before acting, a locator checks that the element is in the viewport, visible, enabled, and stable across consecutive animation frames.
| Need | Prefer | Reason |
|---|---|---|
| Click a button or link by its page identity | page.locator(...) |
Targets an element and performs documented readiness checks. |
| Move to exact viewport points or follow a custom path | page.mouse |
Provides low-level coordinate control. |
| Click a selector for compatibility with existing code | page.click(selector) |
Resolves the first matching element, scrolls it into view if needed, then clicks its center using the page mouse. |
For example:
await page.locator('button').click();
page.click(selector) rejects if no matching element is found. For a click expected to navigate, start the navigation wait and click together to avoid a race:
const [response] = await Promise.all([
page.waitForNavigation(),
page.click('a.next'),
]);
Select the appropriate navigation wait options for the application. If a locator can express the desired action, prefer it over reproducing the click through coordinates.
8. Know the synthetic-event limitation
Puppeteer’s Mouse API says its input triggers synthetic MouseEvents and does not fully replicate a normal physical mouse. One explicit limitation is that dragging with page.mouse cannot select text. Do not rely on coordinate dragging to create a browser text selection.
For selecting text between DOM nodes, use the DOM Selection API with a Range. That is a DOM selection operation, not a mouse gesture. If you then need to copy selected content, Puppeteer’s documentation points to the clipboard API; clipboard permissions and tab focus matter.
9. Troubleshoot common problems
| Symptom | Likely cause | What to do |
|---|---|---|
| The click hits the wrong thing or nothing. | The coordinates are outside the intended element, the viewport changed, or the page layout moved. | Confirm the viewport dimensions and current scroll position; wait for the layout to settle. Use a locator for an element target. |
| The target is below the visible area. | Mouse coordinates address the viewport, not the full document. | Scroll the page or use a locator that can bring the element into view, then act. |
| A drag leaves later actions behaving as if a button is pressed. | The sequence failed before up() ran. |
Put page.mouse.up() in a finally block after a successful press. |
| Wheel input does not scroll the document. | The pointer is over the wrong area, a page handler consumes the event, or default scrolling is prevented. | Move to the intended area and inspect the page’s resulting scroll or application state. |
| Text will not become selected during a drag. | Text selection by dragging is a documented limitation of page.mouse. |
Use a DOM Range and Selection API for DOM text selection. |
| The script continues before navigation completes. | The click and navigation wait were not coordinated. | Start waitForNavigation() and the click in the same Promise.all. |
| A method option or signature does not match examples. | The installed Puppeteer version differs from the documentation page version. | Check the API reference for the version installed in the project and adjust the call accordingly. |
10. Performance, reliability, and cost
Coordinate input itself is a small part of an automation workflow, but every awaited browser action and every extra movement step adds work. Use only as many movement steps as the interaction requires. For routine element actions, locators reduce coordinate maintenance by identifying the target and checking its readiness.
Reliability depends on matching the input method to the task and waiting for the state the next step needs. Coordinates are sensitive to viewport and layout changes; selectors and locators depend on the page’s element structure. Navigation-triggering clicks need a coordinated navigation wait. Synthetic input has the documented fidelity limits described above.
Puppeteer is the browser automation approach covered here; the research dossier establishes no cost or performance benchmark for it. Your runtime and hosting costs depend on your own execution environment and workload.
11. Skip browser setup for screenshots
If your goal is a website screenshot rather than browser interaction, ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. It returns a PNG, JPEG, WebP, or PDF from one GET request. See the ScreenshotNeo API documentation 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}`);
Cookie banners are accepted and removed before capture, along with known consent platforms, newsletter popups, and chat widgets. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Sign up for 1,000 free screenshots a month, with no card.
12. FAQ
Can I create a Mouse instance directly?
No. The Mouse constructor is internal; use page.mouse from the Page you are automating.
Does click(x, y) scroll the page to that point?
No. The coordinates are relative to the viewport. Scroll or bring the target into view before using a coordinate.
Can wheel input be used to zoom?
It sends wheel input. Whether that zooms, scrolls, or triggers another action depends on the page’s event handlers and browser behavior.
Where should I check exact method options?
Use the official API reference for the Puppeteer version installed in your project. The living documentation pages used here showed mixed version labels.


