How to Click Elements with Puppeteer
Use Puppeteer locators to click elements reliably, handle navigation and async pages, choose selectors, and fix common click errors.
For current Puppeteer, use a locator: await page.locator('button').click();. It waits for the target to be in the viewport, visible, enabled, and stable before clicking. For existing code, await page.click('#submit') remains documented and clicks the first matching element after scrolling it into view if needed.
1. Set up a runnable Puppeteer example
Install Puppeteer in a Node.js project, then save this as click.js. The example opens a page, clicks a button by locator, and closes the browser even if the interaction fails.
npm install puppeteer
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.locator('button').click();
console.log('Clicked the button');
} finally {
await browser.close();
}
})();
Replace the example URL and selector with the page and target you need. In production, use a selector tied to the intended control rather than a broad selector like button if the page has multiple buttons.
2. Use locators for new interaction code
Puppeteer’s page-interactions guide recommends locators for selecting and interacting with elements. A locator can retry when an action fails because the target is not ready. Before clicking, its documented checks include viewport presence, visibility, enabled state, and a stable bounding box across two consecutive animation frames. See the official page interactions guide and Locator.click() reference.
await page.locator('#submit').click();
Locators also support Puppeteer selector syntax for text and accessibility queries, in addition to CSS. For example:
await page.locator('::-p-aria(Submit)').click();
await page.locator('div ::-p-text(Checkout)').click();
Use an accessibility selector when the accessible name is the clearest way to identify a control; use a specific CSS selector when the page structure gives you a stable target. Puppeteer also documents XPath and queries through open shadow roots in its selector guide.
3. Click with the legacy page API
page.click(selector) remains available and can be useful in existing scripts or when you need its lower-level behavior. It fetches the matching element, scrolls it into view when necessary, then clicks its center using the page mouse. If multiple elements match, it clicks the first; if none match, it throws. See the Page.click() API reference.
await page.click('#submit');
Because this method picks the first match, inspect selectors that may match repeated cards, menu items, or buttons. Narrow them to a unique target instead of relying on document order.
4. Click an element that triggers navigation
Start waiting for navigation and perform the click together. This sets up the wait before the click can trigger navigation and avoids a race:
const [response] = await Promise.all([
page.waitForNavigation(),
page.locator('a.next').click(),
]);
console.log('Navigation finished', response?.url());
The documented example uses page.click() in the same pattern. The important part is to create the navigation wait at the same time as the click, rather than awaiting the click first. See Page.click() navigation guidance.
5. Wait for an element that appears asynchronously
A locator click is often sufficient because the locator waits for click preconditions. For an explicit lower-level wait, use waitForSelector before interacting:
await page.waitForSelector('#continue', {
visible: true,
timeout: 10000,
});
await page.locator('#continue').click();
waitForSelector can wait for presence, visibility, or a hidden state. Its documented default timeout is 30 seconds, and you can set a different timeout. Unlike locator actions, this wait only establishes the selector condition; it does not itself retry the later click. See Page.waitForSelector().
6. Locator versus Page.click
| Situation | Use | What to know |
|---|---|---|
| New interaction code | page.locator(selector).click() |
Recommended by the interactions guide; waits for documented readiness checks. |
| Existing code or a lower-level call | page.click(selector) |
Scrolls into view and clicks the center of the first match. |
| Click causes navigation | Combine navigation wait and click with Promise.all |
Prevents the wait from starting too late. |
| Need an explicit selector wait | page.waitForSelector() |
Can wait for visibility or hidden state; it does not retry the subsequent click. |
7. Configure waits and click behavior
Timeouts
Locator actions inherit the page timeout and can also set an individual timeout. If Puppeteer cannot find the target or satisfy the required preconditions in time, the action throws a TimeoutError. Set a per-action timeout when one interaction has a different expected wait than the rest of the page:
await page.locator('#submit').setTimeout(10000).click();
For selector waits, pass timeout to waitForSelector; its documented default is 30 seconds. Choose limits based on the expected page behavior, and keep waits bounded so a broken page does not stall a whole job.
Readiness checks
Locator configuration can relax particular checks, including whether the target is in the viewport, visible, enabled, or has a stable bounding box. Keep the default checks unless the interaction genuinely requires different preconditions. Relaxing a check can make a click happen at a time or position the page cannot handle reliably. See the Locator class reference.
ElementHandle workflow
ElementHandle is a lower-level alternative when you need to retain a reference to a specific DOM element. The interactions guide describes it as an alternative to locators; dispose of a returned handle when finished to release its resources. Prefer locators for ordinary selection and clicking.
8. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
page.click() rejects because no element matched |
The selector is wrong, the page is in an unexpected state, or the element has not been added yet. | Confirm the current page and selector; wait for the expected element with a locator or waitForSelector. |
| Locator times out | The element did not appear, or it remained hidden, disabled, outside the viewport, or unstable until the timeout. | Check page state and selector. Increase the timeout only when the page legitimately needs longer; otherwise fix the page assumption or selector. |
| The wrong repeated control is clicked | page.click() clicks the first matching element. |
Use a selector that scopes to the intended card or container, or use a more specific locator. |
| The click appears to do nothing | The target may not be enabled or stable, or the click may have triggered navigation that the script did not await. | Use the locator readiness checks and, for navigation, await the click and navigation together with Promise.all. |
| Explicit wait succeeds but click fails | waitForSelector only waits for its selector condition; it does not guarantee all locator click preconditions. |
Use a locator click or inspect whether the target is enabled, visible, in view, and stable. |
9. Performance, reliability, and cost
Clicking is usually a small part of an automation job; page loading and application behavior determine how long the whole flow takes. Avoid stacking long fixed delays on top of locator waits. Prefer a condition tied to the target or the navigation you expect, with a bounded timeout.
For reliability, use selectors that identify the intended control, keep locator readiness checks enabled by default, and coordinate navigation waits with triggering clicks. Puppeteer’s documented behavior does not provide a universal success-rate or timing guarantee, so allow for site-specific loading and state changes.
Running Puppeteer yourself means managing the browser process and its execution environment as part of your application. If the task is only to obtain a screenshot, a screenshot API can avoid setting up browser automation; the ScreenshotNeo option below provides that alternative. Its published pricing is listed below and in its documentation.
10. Or skip the browser setup
If you need a screenshot rather than an interactive Puppeteer flow, ScreenshotNeo is a website screenshot API and MCP server. One GET request returns PNG, JPEG, WebP, or PDF. Its API docs cover the 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 and consent banners, newsletter popups, and chat widgets are removed before capture; each cleanup step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the page verdict and billing status in headers. Its MCP server offers take_screenshot, get_page_info, and capture_pdf for AI agents. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots. Create a free account and get 1,000 screenshots a month with no card.
11. Frequently asked questions
Does Puppeteer click the center of an element?
page.click(selector) clicks the element center. Locator clicks follow locator action behavior and its documented readiness checks.
Can I click by visible text?
Yes. Puppeteer documents text selector syntax, including ::-p-text(...); it also supports accessibility selectors such as ::-p-aria(...).
Which Puppeteer version do these examples target?
The reviewed official documentation covers Puppeteer 25.10.0 to 25.12.0. Check the API docs for the version installed in your project because behavior and APIs can change between releases.


