Recommended Free Tools
Use page.$$eval() to collect the markup for every element matching a selector: await page.$$eval('.item', elements => elements.map(element => element.outerHTML)). The result is an array of HTML strings returned to Node.js. Use outerHTML when you want each element including its own tag; use innerHTML for only its children, and page.content() for the whole document.
Get the HTML for every matching element
In Puppeteer, page.$$eval(selector, callback) finds all elements matching the CSS selector, passes them to a callback in the browser page, and returns the callback’s serializable result to Node.js. Map those elements to outerHTML to get one string per match:
const htmlByElement = await page.$$eval('.item', elements =>
elements.map(element => element.outerHTML)
);
console.log(htmlByElement);
For example, if the page contains two elements matching .item, htmlByElement is an array with two strings, each containing one element’s markup. The array is empty when there are no matches. This is usually simpler than first obtaining a NodeList yourself: $$eval already supplies the matching elements to its callback.
The returned values cross from the page context into Node.js. Strings and arrays of strings are suitable results; return the HTML strings rather than DOM elements, which are page objects rather than ordinary serializable data.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
#1 Best Overall
Runnable Node.js example
This example starts a browser, opens a page, waits for matching elements to exist, extracts their markup, and closes the browser even if extraction fails. Install Puppeteer in your project first with 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.waitForSelector('.item');
const htmlByElement = await page.$$eval('.item', elements =>
elements.map(element => element.outerHTML)
);
console.log(htmlByElement);
} finally {
await browser.close();
}
})().catch(error => {
console.error(error);
process.exitCode = 1;
});
Replace https://example.com and .item with the page and selector you need. waitForSelector makes the example wait for at least one match; omit it if an empty array is an acceptable result. Puppeteer’s API reference pages retrieved for $$eval, $eval, evaluate, and $$ identified different versions (25.9.0, 25.12.0, and 25.11.0). Check the API reference corresponding to your installed Puppeteer version before relying on version-specific signature details.
Choose between outerHTML, innerHTML, and the full document
“HTML from a NodeList” can mean several different scopes. Choose based on exactly what markup you need, not just how many elements you select.
| Need | Puppeteer API or property | What you get |
|---|---|---|
| Markup for all selector matches | page.$$eval(selector, elements => elements.map(element => element.outerHTML)) |
An array of strings, one for each match; an empty array if none match. |
| Markup for the first match | page.$eval(selector, element => element.outerHTML) |
A single string. $eval throws if nothing matches. |
| Only the contents inside an element | page.$eval(selector, element => element.innerHTML) |
A string of the element’s child markup, without the element’s own opening and closing tags. |
| The entire page | page.content() |
The page’s full HTML contents, including the DOCTYPE. |
| Element handles for later Puppeteer operations | page.$$(selector) |
An array of ElementHandle objects; an empty array if there are no matches. |
All matches: outerHTML
An element’s outerHTML includes the element itself and its descendants. For example, extracting a matched <article> returns the article tag along with its nested markup. When the requested result is the markup for each selected element, this is generally the property to use.
Free tools Windows power users keep installed
One-click scans. No signup required.
Rank #2
const articles = await page.$$eval('article', elements =>
elements.map(element => element.outerHTML)
);
Children only: innerHTML
innerHTML excludes the selected element’s own tag. If you only need the contents inside a matched container, use:
const contents = await page.$$eval('.content', elements =>
elements.map(element => element.innerHTML)
);
This still produces an array for all matches. For a single container, use page.$eval('.content', element => element.innerHTML).
The first match or the whole page
Use $eval when you want one match, not a collection. It evaluates the callback with the first matching element and throws when the selector finds none, so handle that case if the selector is optional. Use page.content() when your target is the complete page rather than a selected element.
Use a NodeList you already have
If your code is already running in the browser page and has a DOM NodeList, convert it to an array and map it. Array.from accepts a mapping callback, so it can produce the HTML strings directly:
Rank #3
const htmlByElement = Array.from(
document.querySelectorAll('.item'),
element => element.outerHTML
);
Within Puppeteer, you can do the equivalent inside evaluate:
const htmlByElement = await page.evaluate(() =>
Array.from(
document.querySelectorAll('.item'),
element => element.outerHTML
)
);
For this selector-based task, $$eval is the direct alternative: it supplies the matching elements to the callback, so a separate document.querySelectorAll() call is unnecessary.
Understand where the callback runs
Puppeteer evaluates the callback in the browser page context, not in the Node.js process. The function is converted and evaluated in that context. It cannot automatically access ordinary Node-side variables, imported modules, or helper functions from the surrounding file.
Keep the callback self-contained, as in elements => elements.map(element => element.outerHTML). If the callback needs a value calculated in Node.js, pass it as an argument supported by the relevant evaluation method rather than assuming a lexical variable is visible in the page. This distinction matters when an expression works in your Node.js file but fails after being sent to the browser.
Rank #4
Use $$ or $$eval?
Choose based on whether you need HTML data or live element handles:
- Use
page.$$eval()when you want to calculate and return serializable results, such as an array of markup strings, in one evaluation. - Use
page.$$('selector')when you need an array ofElementHandleobjects for follow-up Puppeteer operations on individual elements. It resolves to an empty array when there are no matches.
A handle is useful when subsequent work requires Puppeteer to interact with the actual page element. If all you need is a string of HTML, $$eval avoids managing a collection of handles.
Wait for dynamic content when necessary
A selector may not match immediately after navigation if the page adds content asynchronously. Decide whether an empty result is valid. If it is, call $$eval directly and accept an empty array. If at least one matching element must appear, wait for the selector first:
await page.waitForSelector('.item');
const htmlByElement = await page.$$eval('.item', elements =>
elements.map(element => element.outerHTML)
);
This wait establishes that a match appeared; it does not guarantee that a site will never update the element’s content afterward. Choose a wait condition that matches the page behavior you need, and use a site-specific readiness signal if the content has additional asynchronous steps.
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 problemsCommon problems and fixes
- The result is empty. The selector may not match the current document, or the elements may not have been added yet. Check the selector and the page state; if a match is required, wait with
waitForSelector. $evalthrows because no element exists. That API expects one match. Use$$evalif zero matches should produce an empty array, or wait for the single element before using$eval.- The string omits the selected tag. You likely read
innerHTML, which contains only the children. UseouterHTMLto include the selected element itself. - You received the whole document instead of separate strings.
page.content()returns the complete page markup. Use$$evalwith a map over the matching elements for one string per match. - A Node.js variable or helper is undefined in the callback. The callback runs in the page context. Put the needed logic inside it or pass required values as arguments; do not rely on Node-side lexical scope.
- You need to interact with each match after selecting it. Return handles with
page.$$()rather than strings with$$eval. The former yields an array of element handles and is empty when there are no matches.
Or skip the browser setup
If your actual goal is a screenshot or PDF rather than HTML strings, ScreenshotNeo can return one with a single GET request. This does not return a NodeList or HTML; it is an alternative for capturing a page visually.
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
See the ScreenshotNeo API documentation for the request options and response details. ScreenshotNeo accepts cookie or consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each of those steps can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server gives AI agents tools for screenshots, page information, and PDF capture. The Free plan includes 1,000 shots per month with no 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.
FAQ
Does outerHTML include the selected element’s attributes?
Yes. It represents the element’s markup, including its own tag and attributes, as well as its descendants.
Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Can I return the selected DOM elements to Node.js instead of their HTML?
DOM elements belong to the browser page context. Return serializable data such as strings when you need a value in Node.js, or use Puppeteer element handles when you need to continue operating on the page elements.
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.




