iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more
Use Puppeteer’s locator API to click a link, and use page.$$eval() or page.$eval() to read link destinations. If the click may navigate, start page.waitForNavigation() and the click together with Promise.all() so the wait cannot miss the navigation.
Find link URLs without clicking
To collect every anchor’s resolved destination, evaluate in the page and return serializable strings:
const links = await page.$$eval('a', anchors =>
anchors.map(anchor => ({
text: anchor.innerText.trim(),
href: anchor.href,
}))
);
console.log(links);
anchor.href is the browser-resolved URL, so a relative value such as /about becomes an absolute URL. If you need the literal attribute as written in the markup instead, use anchor.getAttribute('href'). The returned objects contain strings rather than DOM nodes, which can be serialized out of the page context.
Recommended Free Tools
To read just the first match:
const href = await page.$eval('a.next-page', anchor => anchor.href);
page.$eval() passes the first matching element to its callback and throws if no element matches. Use page.$$eval() when you need zero or more matches and want to handle an empty result as an empty array.
#1 Best Overall
Click a link with a locator
For interaction, Puppeteer recommends locators. A locator waits for an element to be available and checks action preconditions such as visibility, enabled state, viewport position, and a stable bounding box before acting.
await page.locator('a.next-page').click();
Use a selector that identifies the intended link, preferably one tied to stable page structure or an accessible name. Puppeteer accepts CSS selectors and also documents selector extensions for text, accessibility, XPath, and shadow-root traversal. For example, a text selector can target a link by its visible label:
Rank #2
- HTML CSS Design and Build Web Sites
- Comes with secure packaging
- It can be a gift option
await page.locator('a').filter(anchor => anchor.innerText === 'Next').click();
Confirm selector syntax and behavior against the installed Puppeteer version and the page you are automating. When a selector matches multiple links, make it more specific rather than relying on an accidental first match.
Wait for navigation after clicking
When clicking is expected to navigate, create the navigation wait before the click can trigger it. Starting both operations in Promise.all() avoids the common race in which the click completes before the wait is listening.
Rank #3
const [response] = await Promise.all([
page.waitForNavigation(),
page.locator('a.next-page').click(),
]);
console.log('Current URL:', page.url());
console.log('Navigation response:', response);
A navigation response is not guaranteed: Puppeteer treats History API URL changes as navigation, and hash or History API navigation can resolve waitForNavigation() with null. A click can therefore change the URL or application state without producing a new document response.
For single-page applications
If success means that a particular view or result has appeared, wait for that outcome rather than assuming a full-document navigation:
Rank #4
- Brand: Wiley
- Set of 2 Volumes
- A handy two-book set that uniquely combines related technologies Highly visual format and accessible language makes these books highly effective learning tools Perfect for beginning web designers and front-end developers
await Promise.all([
page.locator('a.next-page').click(),
page.locator('[data-page="2"]').wait(),
]);
Use a selector that represents the actual destination state on the target site. If the expected result is a URL change, use a URL condition instead. This is often more meaningful for a single-page application than waiting for a document response.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Choose the right selection method
| Need | Use | Behavior to account for |
|---|---|---|
| Click one link as a user would | page.locator(selector).click() |
Locator waits for readiness and checks action preconditions. |
| Read one matching destination | page.$eval(selector, fn) |
Uses the first match and throws if none exists. |
| Read many destinations | page.$$eval(selector, fn) |
Returns the values produced for matching elements; return strings or other serializable data. |
| Use a lower-level click | page.click(selector) |
Selects the first match, scrolls it into view, and clicks its center; throws when nothing matches. |
waitForSelector() is a lower-level option for waiting for an element, but it does not automatically retry an action that later fails. Prefer locators for ordinary interactions; use lower-level element or handle flows when you need custom control.
Best Value
Troubleshoot links that are not found or do not navigate
- The selector times out or finds nothing: the link may not have rendered yet, the selector may be wrong, or the match may be in a frame or shadow root. Verify the page state, use a locator, select the correct frame, or use supported shadow-root selector syntax.
- The selector matches the wrong link: several anchors may share the same selector. Narrow it using a stable parent, attribute, text, or accessible name.
- The click succeeds but there is no response: the site may have changed a hash or used the History API. A
nullnavigation response is possible; checkpage.url()or wait for the application’s destination element. - The navigation wait occasionally misses the transition: do not await the click before creating the wait. Put
waitForNavigation()and the click in the samePromise.all(). - A lower-level click fails:
page.click(selector)throws if no element matches. Check the selector and render timing, or switch to a locator that waits for action readiness. - Extracted URLs differ from source markup:
anchor.hrefreturns the resolved browser URL. UsegetAttribute('href')when you need the literal attribute value.
Or skip the browser setup
If you need a screenshot of the destination rather than browser interaction, ScreenshotNeo provides a one-request screenshot API. Its API can return PNG, JPEG, WebP, or PDF; the following cURL request saves a WebP screenshot of the target URL:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
See the ScreenshotNeo API documentation for request options. It accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; those steps can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and each response identifies the page verdict and billing status. Its MCP server offers take_screenshot, get_page_info, and capture_pdf for AI agents. The Free plan includes 1,000 screenshots per month without a card; paid plans start at $5 for 3,000 shots.
Sign up for ScreenshotNeo’s free plan to get 1,000 screenshots a month with no card.
Free tools Windows power users keep installed
One-click scans. No signup required.
Version note
Puppeteer’s API and recommendations can change. The official documentation surfaced version 25.12.0 for several references reviewed for this guide; check the current interaction guide and your installed package when version-specific behavior matters.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

