Use page.$$eval() to turn every element matched by a selector into an HTML string:
const htmlByElement = await page.$$eval('.item', elements => elements.map(element => element.outerHTML));
The callback runs in the browser page, receives the matching elements as an array, and returns an array of serialized strings to Node.js. Use outerHTML when each string must include the selected element itself; use innerHTML for only its children.
Get the HTML for every matching element
Here is a complete Node.js example. It opens a page, selects every .item element, extracts its markup, prints the resulting array, and closes the browser.
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'networkidle0' });
const htmlByElement = await page.$$eval('.item', elements =>
elements.map(element => element.outerHTML)
);
console.log(htmlByElement);
await browser.close();
})();
If the page contains three matching elements, htmlByElement is an array with three strings. Each string contains the opening tag, attributes, descendants, and closing tag of one match.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
#1 Best Overall
- Read Before You Buy — No Video Output: These adapters support charging and USB 2.0 data transfer, but cannot transmit video signals. Except for standard USB webcams (which use USB data only), they are not compatible with HDMI/DisplayPort cables, video-capable USB-C hubs, or docking stations with video output.
- Convert USB-A Ports to USB-C: Designed to connect USB-C earphones, cables, flash drives, card readers, and other USB-C accessories to standard USB-A ports. Plug-and-play with no drivers or software required.
- Aluminum Alloy Housing: Built with a sturdy aluminum alloy shell that aids in heat dissipation and protects against daily wear and scratches. Designed to maintain a stable and secure connection.
- Compact & Travel-Friendly: The ultra-compact design allows the adapter to stay plugged into your device without blocking adjacent ports or adding bulk, reducing wear and tear on your original USB ports.
- 12-Month Warranty: Backed by a 12-month manufacturer warranty for peace of mind. Designed to meet strict quality control standards for reliable everyday performance.
Why $$eval is the direct fit
page.$$eval(selector, callback) finds all matches and passes them to the callback in page context. Mapping those elements to outerHTML keeps the extraction in one browser-side operation, then sends only serializable strings back to Node.js.
Understand the output scope before choosing an API
“HTML from a NodeList” can mean several different things. Choose the operation that matches the scope you actually need.
| Need | Puppeteer API | Result and no-match behavior |
|---|---|---|
| HTML strings for every match | page.$$eval() |
The callback receives all matching elements; return an array such as elements.map(element => element.outerHTML). |
| Element handles for every match | page.$$() |
Returns an array of ElementHandle objects. With no matches, the array is empty. |
| HTML for the first match only | page.$eval() |
The callback receives the first matching element. It throws when no element matches. |
| Only the selected element’s children | innerHTML inside $eval or $$eval |
Returns the markup inside the element, without the element’s own tag. |
| The complete page document | page.content() |
Returns the page’s full HTML contents, including the DOCTYPE. |
outerHTML versus innerHTML
Use outerHTML for complete element fragments
For an element such as <article class='item'>News</article>, outerHTML returns the entire fragment, including the article tag and its attributes. This is normally what you want when exporting a list of matched components.
const cards = await page.$$eval('.card', elements =>
elements.map(element => element.outerHTML)
);
Use innerHTML for descendants only
innerHTML omits the selected element’s own opening and closing tags. It is useful when you need to inject or compare only the children:
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 reinstallOutdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchconst cardContents = await page.$$eval('.card', elements =>
elements.map(element => element.innerHTML)
);
Work with a NodeList that already exists in page code
If your code has already called document.querySelectorAll() in the browser, convert that NodeList with Array.from and map it to outerHTML:
Rank #2
- 5-in-1 USB-C Hub: Experience comprehensive connectivity featuring a Power Delivery input, two USB-A 2.0 ports, a USB-A 3.0 port, and an HDMI port. (Note: The USB-C power delivery input port is only for connecting an external wall charger to power your laptop and cannot power peripheral devices.)
- 90W Pass-Through Charging: Achieve optimal charging with 90W pass-through power to your laptop, supported by a total input of 100W, with the hub reserving 10W for operational efficiency. (Note: Wall charger not included.)
- Quick Data Transfers: Accelerate your productivity with rapid data transfers using a high-speed 5Gbps USB 3.0 port and two 480Mbps USB 2.0 ports.
- 4K HDMI Display: Enhance your visual experience with a hub capable of delivering 4K resolution at 30Hz in both mirror and extend modes. Please note that this hub is compatible with MacBook (macOS 12 and newer), Windows 10 and 11, ChromeOS, and laptops equipped with DP Alt Mode and Power Delivery. Note: This device is not compatible with Linux.
- What You Get: Anker USB-C Hub (5-in-1, 4K HDMI), welcome guide, 18-month warranty, and our friendly customer service.
const htmlByElement = await page.evaluate(() => {
const nodeList = document.querySelectorAll('.item');
return Array.from(nodeList, node => node.outerHTML);
});
This is the same DOM-side operation as the $$eval example. With a selector supplied to Puppeteer, $$eval already performs the selection and passes the resulting array to your callback, so a separate querySelectorAll call is usually unnecessary.
Use $$ when you need handles instead of strings
page.$$() is appropriate when each match needs additional Puppeteer handle operations rather than immediate serialization:
const handles = await page.$$('.item');
for (const handle of handles) {
const text = await handle.evaluate(element => element.textContent);
console.log(text);
}
await Promise.all(handles.map(handle => handle.dispose()));
Choose $$eval when the final result is already known to be serializable, such as an array of HTML strings. Choose $$ when you need to keep references to individual elements for later work.
Keep evaluation callbacks self-contained
Puppeteer serializes the function you pass to evaluate, $eval, or $$eval and executes it in the browser page. Ordinary Node.js variables, imported helpers, and closures are not automatically available there. Put the extraction logic inside the callback, or pass required values as arguments.
const selector = '.item';
const htmlByElement = await page.$$eval(
selector,
(elements, attributeName) =>
elements.map(element => ({
html: element.outerHTML,
label: element.getAttribute(attributeName)
})),
'data-label'
);
In this example, attributeName is explicitly passed into the page context. A Node-side function such as formatResult would not be visible unless its logic were reproduced inside the callback or its result passed as an argument.
Rank #3
- Sleek 7-in-1 USB-C Hub: Features an HDMI port, two USB-A 3.0 ports, and a USB-C data port, each providing 5Gbps transfer speeds. It also includes a USB-C PD input port for charging up to 100W and dual SD and TF card slots, all in a compact design.
- Flawless 4K@60Hz Video with HDMI: Delivers exceptional clarity and smoothness with its 4K@60Hz HDMI port, making it ideal for high-definition presentations and entertainment. (Note: Only the HDMI port supports video projection; the USB-C port is for data transfer only.)
- Double Up on Efficiency: The two USB-A 3.0 ports and a USB-C port support a fast 5Gbps data rate, significantly boosting your transfer speeds and improving productivity.
- Fast and Reliable 85W Charging: Offers high-capacity, speedy charging for laptops up to 85W, so you spend less time tethered to an outlet and more time being productive.
- What You Get: Anker USB-C Hub (7-in-1), welcome guide, 18-month warranty, and our friendly customer service.
Handle empty results and missing elements
All matches with $$eval
When the selector matches nothing, the all-match operation gives the callback an empty collection, so mapping it produces []. You can treat that as a valid “no results” outcome:
const htmlByElement = await page.$$eval('.item', elements =>
elements.map(element => element.outerHTML)
);
if (htmlByElement.length === 0) {
console.log('No .item elements found');
}
One match with $eval
$eval is intentionally different: it throws if there is no matching element. Use it only when the element is required, or check first with page.$():
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Scan for outdated or missing drivers - takes under a minute3Repair Windows errors before they cause bigger problemsconst handle = await page.$('.item');
if (handle) {
const html = await handle.evaluate(element => element.outerHTML);
console.log(html);
await handle.dispose();
} else {
console.log('Required element was not found');
}
Extract at the right time
The selector is evaluated when the Puppeteer call runs. If the page has not yet produced the elements, the result can legitimately be empty. Navigate or perform the page actions that create the content before calling $$eval. For a static document, the complete runnable example is usually sufficient. For application pages that render after navigation, make your own readiness condition explicit before extraction and then run the selector.
Do not confuse an empty result with a malformed HTML string: an empty array means no elements matched at that moment, while each returned string is the browser’s current serialization of its element.
Performance and memory considerations
- Prefer one extraction call. Mapping all matches inside one
$$evalavoids a separate round trip for every element. - Return only what you need. Full
outerHTMLfor many large subtrees can consume substantial memory in both the browser and Node.js. If descendants are enough, returninnerHTML; if you need only metadata, return selected attributes or text instead. - Use handles deliberately.
page.$$()is useful for follow-up work but leaves you responsible for handle lifecycle. Dispose handles when they are no longer needed. - Keep the callback browser-safe. DOM properties such as
outerHTML,innerHTML, andtextContentare available in page context; Node.js modules are not.
Troubleshooting common failures
The returned array is empty
Check the selector spelling and whether the elements exist at the instant the call runs. Inspect the page state with a simple count:
Rank #4
- Dual Converters, Infinite Potential:Includes 2× USB C male to USB A female adapters and 2× USB A male to USB C female adapters. Perfect for a wide range of uses—tablets with Bluetooth keyboards, expand USB ports on macbook, and more. Two different converters for all your daily needs
- Next-Level 10Gbps & 3A Charging: No more slow 480Mbps, this usb to usb c adapter has a transfer speed of up to 10Gbps, allowing you to do more transferring in less time. This usb adapter fits both USB A and USB C charger, supporting up to 3A fast charging
- Upgraded Exquisite Craftsmanship: With an aluminum alloy housing and metal connector, the usbc to usb adapter is extremely durable and sturdy. Rigorously tested to withstand more than 10,000 times of plugging and unplugging, ensuring long-lasting performance
- Broad Compatible: The usb c to usb adapter widely supports all USB C/ USB A devices like laptops, tablets, cellphones, car chargers, and phone chargers. Such as compatible with MacBook Pro/Air 2023/2022, Thunderbolt 4/3 Devices,Apple MagSafe Watch 9/8/7/SE/Ultra, iPad Pro 2022/2021, Samsung Galaxy S23/S20/S10, and iPhone 17/16/15 Pro. Plug and play
- Please Note: To reach 10Gbps speed, keep the cable under 3.3 ft. For USB A Male to USB C adapters, try flipping the USB C connector. USB C Male to USB A adapters support bidirectional 10Gbps transfer within 3.3 ft
const count = await page.$$eval('.item', elements => elements.length);
console.log({ count });
If the count is zero, the problem is selection or timing, not HTML serialization.
$eval throws an element-not-found error
This is the documented behavior for a selector with no match. Switch to $$eval when zero matches are acceptable, or test with page.$() before using a single-element extraction.
The callback cannot find a Node.js variable
Move the needed logic into the callback or pass primitive data as an additional argument. Evaluation callbacks execute in the page context, not in the lexical scope of your Node.js module.
You received only the children, not the selected tag
Use outerHTML instead of innerHTML. The former includes the selected element; the latter deliberately excludes it.
You need the whole document, including its DOCTYPE
Do not assemble the document from selector matches. Call await page.content(), which returns the page’s full HTML contents.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Best Value
- 5-in-1 Connectivity: Equipped with a 4K HDMI port, a 5 Gbps USB-C data port, two 5 Gbps USB-A ports, and a USB C 100W PD-IN port. Note: The USB C 100W PD-IN port supports only charging and does not support data transfer devices such as headphones or speakers.
- Powerful Pass-Through Charging: Supports up to 85W pass-through charging so you can power up your laptop while you use the hub. Note: Pass-through charging requires a charger (not included). Note: To achieve full power for iPad, we recommend using a 45W wall charger.
- Transfer Files in Seconds: Move files to and from your laptop at speeds of up to 5 Gbps via the USB-C and USB-A data ports. Note: The USB C 5Gbps Data port does not support video output.
- HD Display: Connect to the HDMI port to stream or mirror content to an external monitor in resolutions of up to 4K@30Hz. Note: The USB-C ports do not support video output.
- What You Get: Anker 332 USB-C Hub (5-in-1), welcome guide, our worry-free 18-month warranty, and friendly customer service.
API-version note
The Puppeteer API reference pages consulted for these methods were labeled 25.9.0, 25.11.0, and 25.12.0. Those labels identify the versions shown on the respective reference pages, not a single synchronized release. Check the documentation that matches the Puppeteer version installed in your project before relying on version-specific signatures.
Or skip the browser setup
If your actual goal is a visual capture rather than HTML strings, ScreenshotNeo returns a website screenshot or PDF through one request. It does not replace $$eval for DOM extraction, but it removes the need to operate a browser for image capture.
Using the API documented at https://screenshotneo.com/docs/:
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 the shot; each cleanup step can be turned off.
- Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing. Response headers identify the page verdict and whether it was billed.
- An MCP server provides
take_screenshot,get_page_info, andcapture_pdftools for Claude, Cursor, and other MCP clients. - The Free plan includes 1,000 screenshots per month without a card. Paid plans start at $5 for 3,000 screenshots; every feature is available on every plan.
Create a free ScreenshotNeo account to try the 1,000 included screenshots.
Recommended Free Tools
Frequently Asked Questions
Does $$eval return a NodeList to Node.js?
No. Puppeteer supplies the matched elements to the browser-side callback, and Node.js receives whatever serializable value the callback returns. Mapping to outerHTML therefore produces an array of strings.
When should I use page.content() instead of a selector?
Use page.content() when you need the complete current document rather than fragments selected from it; its result includes the DOCTYPE.
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.

