Use Puppeteer’s XPath selector syntax with page.$$eval() when you want link data, or page.$$() with an awaited for...of loop when you need to interact with each link. The current prefixed selector form is ::-p-xpath(//a). For extraction, map each anchor to plain values such as its text and destination inside the page, then loop through the returned JavaScript objects in Node.js.
Choose extraction or interaction first
There are two useful patterns, and the choice depends on what the loop must do. If you only need text, URLs, or other attributes, extract those values with page.$$eval(). If you need to click, inspect, or otherwise act on actual page elements, get element handles with page.$$() and await each operation. Puppeteer documents both approaches in its Page interactions guide and Page API reference.
| Approach | Use it for | What you get | Trade-off |
|---|---|---|---|
page.$$eval() |
Collecting link text, destinations, or attributes | Serializable values such as strings and objects | The callback runs in the page context, so return data rather than Node.js objects. |
page.$$() plus for...of |
Clicking or inspecting each matched element | Element handles | Await each action; handles can become stale if the page changes. |
Extract all matching links with page.$$eval()
This complete example opens a page, selects anchor elements through XPath, returns their visible text and resolved destinations, and prints the results in Node.js:
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' });
const links = await page.$$eval(
'::-p-xpath(//a)',
anchors => anchors.map(anchor => ({
text: anchor.textContent?.trim() ?? '',
href: anchor.href,
})),
);
for (const link of links) {
console.log(link.text, link.href);
}
} finally {
await browser.close();
}
})();
Replace https://example.com with the page you are allowed to access. The selector ::-p-xpath(//a) matches anchor elements in the document. Use a narrower XPath when the page contains unrelated links; for example, ::-p-xpath(//nav//a) limits the match to anchors inside a navigation element, while ::-p-xpath(//a[@href]) selects anchors with an href attribute.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →#1 Best Overall
The mapping runs inside the browser page and returns an array of ordinary objects. anchor.href is the browser’s resolved URL, which is generally useful when you want a navigable destination. If you need the attribute exactly as written in the markup, use anchor.getAttribute('href') instead; it can be relative or absent. textContent includes descendant text, while trimming removes leading and trailing whitespace. Change the mapping to return only the fields your next step needs.
Interact with each matched anchor
When you need an element handle—for example, to click links one at a time—use page.$$(). Each action is awaited before moving to the next element:
const anchors = await page.$$('::-p-xpath(//a[@href])');
for (const anchor of anchors) {
const text = await anchor.evaluate(
element => element.textContent?.trim() ?? '',
);
console.log(text);
// Uncomment only if following each link is intended:
// await anchor.click();
}
page.$$() returns an array of matches and returns an empty array when nothing matches. This makes a no-match result an ordinary case to check, rather than an exception you must catch. Element handles are useful for interaction, but they are tied to the current page state. If navigation or substantial DOM replacement occurs, reacquire the elements before using them again. Puppeteer’s guide also notes that handles obtained through waiting APIs should be disposed when they are no longer needed.
Rank #2
Use the current XPath selector syntax
For current Puppeteer selector APIs, write XPath selectors in the prefixed form ::-p-xpath(...), as in ::-p-xpath(//a). The selector guide also documents the legacy form xpath///a, but recommends the documented modern syntax over prefixed selector syntax marked legacy. XPath selection uses the browser’s native Document.evaluate. The official guide and API references surfaced for this topic identify Puppeteer 25.12.0; related Frame references surfaced as 25.10.0. Your installed package may differ, so check the version in your project and consult the matching documentation if a selector behaves differently.
Windows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallCrashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteIn a project directory, inspect the installed dependency with npm ls puppeteer. If the selector string is rejected, confirm that the application is using the intended Puppeteer package and version, and compare the selector form with the documentation for that version.
Wait for links on a dynamic page
If the page adds links after initial navigation, wait for a matching element before extracting:
await page.waitForSelector('::-p-xpath(//a[@href])');
const links = await page.$$eval(
'::-p-xpath(//a[@href])',
anchors => anchors.map(anchor => ({
text: anchor.textContent?.trim() ?? '',
href: anchor.href,
})),
);
page.waitForSelector() waits for a matching element to appear. Its documented options include visible, hidden, timeout, and signal; the documented default timeout is 30 seconds. See the waitForSelector reference for option details.
One matching link does not establish that a dynamically populated list is complete. If completeness matters, wait for an application-specific ready condition, a known result count, or another state that signals the list has finished loading. A selector wait is a presence check, not a guarantee that a page’s data-fetching workflow has ended.
Handle empty results and page changes
An empty array from page.$$() or page.$$eval() means the selector found no matching elements at the time it ran. Decide explicitly whether that is acceptable for your task:
Rank #4
- If no links are a valid result, let the empty array pass through and avoid entering the loop.
- If links are required, wait for the correct selector or a stronger application-ready condition, then check the count and report a useful error if it remains zero.
- If the page navigates or replaces its relevant DOM during processing, reacquire the matches instead of relying on old handles.
For data collection, extracting a snapshot of values first also separates the result from later page mutations. For interaction, a changing page may make later handles unusable; structure the workflow around the page’s navigation or update behavior rather than assuming every original element remains available.
Common problems and fixes
- The XPath selector is rejected. Use the current form
::-p-xpath(//a), check spelling and parentheses, and confirm the installed Puppeteer version against its corresponding selector documentation. - The returned array is empty. Check whether the XPath matches the page’s actual markup, whether the intended content has loaded, and whether the links are inside a part of the page your selector does not cover. Wait for a matching element if it appears asynchronously.
- The loop sees only some of the eventual links. The query may run before the page finishes populating its list. Waiting for one anchor is not enough if the page appends more later; wait for a known completion state or expected result count.
- A destination differs from the HTML attribute.
anchor.hrefreturns the resolved browser URL. UsegetAttribute('href')when the literal attribute value is what you need. - An interaction fails after the page updates. The old handle may no longer refer to a live element. Re-query after navigation or DOM replacement, and await each interaction.
- Waiting times out. The selector may not match, the page may never reach the expected state, or its content may require a different readiness condition. Validate the XPath against the page and set a timeout appropriate to the workflow using the documented wait options.
Performance, reliability, and cost considerations
For extraction, one $$eval() call maps all matched elements in the page context and returns just the needed values. This avoids keeping a separate element handle for every anchor. Use the handle-based approach when actual element interaction is necessary, not merely to read a handful of properties.
Reliability depends on selecting the right page state. A syntactically valid selector can still return no results if the page has not rendered the target links, and waiting for one match can still leave an incomplete collection. Keep navigation and readiness conditions distinct from selector correctness, and decide how the program should treat a legitimately empty list.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Best Value
- Used Book in Good Condition
Puppeteer’s cited documentation establishes selector behavior and waiting options, not a universal runtime, request-cost, or success-rate figure. Browser setup and execution costs therefore depend on how and where you run your own automation; no general benchmark is implied here.
Or skip the browser setup
If your goal is a screenshot of the page rather than extracting link text and destinations, ScreenshotNeo provides a one-request screenshot API. It does not replace Puppeteer’s XPath extraction: use the code above when your application needs link data or per-element actions. For a screenshot, the following cURL request saves an image; 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://example.com -o shot.webp
- Cookie and consent banners are accepted before capture, and more than 60 known consent platforms, newsletter popups, and chat widgets can be removed; each step can be turned off.
- Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing; response headers report the page verdict and billing status.
- An MCP server provides
take_screenshot,get_page_info, andcapture_pdftools for AI agents, including Claude, Cursor, and other MCP clients. - The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots; every feature is available on every plan.
Sign up for ScreenshotNeo’s free plan to get 1,000 screenshots a month with no card.
Frequently Asked Questions
Does //a match every kind of clickable control?
No. It selects HTML anchor elements. Buttons and other clickable elements that are not anchors require a different selector.
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Does selecting an anchor navigate to its destination?
No. XPath selection finds elements; navigation happens only if your code explicitly clicks or otherwise follows a destination.
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.

