Use page.$$eval() with a CSS class selector, then call remove() on every matched element:
await page.$$eval('.target-class', elements => {
elements.forEach(element => element.remove());
});
The leading dot is required for a class selector. Use $eval() instead when you intentionally want only the first match.
The basic operation
Puppeteer evaluates the callback in the page context, where the callback receives an array of all elements matching the selector. The browser then detaches each element from its parent node.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({headless: true});
const page = await browser.newPage();
await page.goto('https://example.com', {waitUntil: 'networkidle2'});
await page.$$eval('.target-class', elements => {
elements.forEach(element => element.remove());
});
await page.screenshot({path: 'clean-page.png', fullPage: true});
await browser.close();
$$eval() is the right choice for “remove every element with this class.” If there are no matches, Puppeteer supplies an empty array, so forEach() simply performs no work.
#1 Best Overall
Remove one element or every matching element
Remove the first match
page.$eval() runs against the first element matching the selector:
await page.$eval('.target-class', element => element.remove());
This is useful when the class identifies a single banner or when the first match has a defined meaning. If no element matches, $eval() throws an error, so use a presence check or an explicit wait when the element is optional.
Remove all matches
page.$$eval() passes every match to the callback:
await page.$$eval('.target-class', elements => {
for (const element of elements) element.remove();
});
Use this for repeated cards, notices, overlays, advertisements, or any other collection where every matching node should disappear.
Require more than one class
Concatenate class selectors to require that the same element has both classes:
await page.$$eval('.notice.active', elements => {
elements.forEach(element => element.remove());
});
By contrast, .notice .active means an element with class active inside an ancestor with class notice; it does not mean two classes on one element.
Restrict the element type
Add a type selector when a class is reused on different HTML elements:
await page.$$eval('div.notice', elements => {
elements.forEach(element => element.remove());
});
Make sure the target exists before removing it
DOM removal is immediate. It does not wait for a framework to render a component, an API response to arrive, or a consent banner to be inserted. Choose a wait strategy based on the page.
Wait for a required element
await page.goto('https://example.com', {waitUntil: 'domcontentloaded'});
await page.waitForSelector('.target-class');
await page.$$eval('.target-class', elements => {
elements.forEach(element => element.remove());
});
waitForSelector() rejects if the selector does not appear before its timeout. That is appropriate when the page is expected to contain the element and its absence indicates a failed page state.
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Crashes, 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 minuteHandle an optional element without failing
const removed = await page.$$eval('.optional-banner', elements => {
elements.forEach(element => element.remove());
return elements.length;
});
console.log(`Removed ${removed} element(s)`);
Because $$eval() returns an empty array to the callback when nothing matches, this pattern is safe for optional content and also gives you a count for logging.
Rank #2
Remove after a known page update
If clicking a control causes the target to render, perform the click first, wait for the update, and then evaluate the removal:
await page.click('[data-action="open-settings"]');
await page.waitForSelector('.settings-ad');
await page.$$eval('.settings-ad', elements => {
elements.forEach(element => element.remove());
});
Puppeteer’s locator API is the recommended interaction layer when you need waiting and action preconditions. For a direct mutation over elements that are already present, $$eval() remains the straightforward API. The current interactions guide displayed version 25.12.0 when consulted on September 29, 2026; verify the guide for the Puppeteer version installed in your project.
Understand what remove() changes
Element.remove() removes the node from its parent. It returns undefined. Calling it on a node that no longer has a parent does nothing.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
The operation changes the current document only. It is not a CSS rule, event listener, or permanent ban. A site script can create another matching node later, and a client-side framework can re-render the component you removed.
Remove repeatedly when the page keeps reinserting nodes
If you know exactly when the page updates, call the same removal code after each update. For unpredictable updates, install a mutation observer in the page and remove new matches as they are added:
await page.evaluate(() => {
const removeTargets = () => {
document.querySelectorAll('.target-class').forEach(element => element.remove());
};
removeTargets();
const observer = new MutationObserver(removeTargets);
observer.observe(document.documentElement, {
childList: true,
subtree: true
});
});
This observer runs in the page until you disconnect it. Keep the observation scope as narrow as practical; watching the entire document can add work on pages that perform many DOM mutations.
Selector details that prevent silent mistakes
Start a class selector with a dot
.notice matches an element whose class list includes notice. Writing notice without the dot searches for an element named <notice>, which normally matches nothing.
Recommended Free Tools
Escape unusual class names
Class names containing characters that are not valid in a CSS identifier must be escaped before they are used in a selector. Use CSS.escape() when the class name comes from data rather than being a hard-coded selector:
const className = 'promo:summer';
const selector = `.${CSS.escape(className)}`;
await page.$$eval(selector, elements => {
elements.forEach(element => element.remove());
});
Keep the selector construction outside the page callback. The callback receives the matched elements, not the selector string.
Use a stable attribute when classes are generated
Frameworks sometimes generate classes that change between builds or sessions. If the page provides a stable attribute, target that instead:
await page.$$eval('[data-testid="newsletter-modal"]', elements => {
elements.forEach(element => element.remove());
});
This article’s technique is still the same: query a set, then call remove(); only the selector changes.
Shadow DOM boundaries
A normal CSS query does not automatically descend into a shadow root. Therefore, .target-class on the main document will not find an element inside a component’s shadow tree.
For open shadow roots, Puppeteer documents deep selectors such as:
await page.$$eval('my-widget >>> .target-class', elements => {
elements.forEach(element => element.remove());
});
Deep combinators depend on an open shadow root. They do not provide access to a closed shadow root. If the component is closed, you need a supported application hook or must remove the host component instead.
Rank #4
Common failures and fixes
“It removed nothing”
- Check the dot: use
.target-class, nottarget-class. - Log the count by returning
elements.lengthfrom$$eval(). - Wait for client-rendered content with
waitForSelector()before evaluating. - Check whether the element is inside an open or closed shadow root.
- Confirm that the class is on the element you intend;
.a .band.a.bselect different structures.
“Cannot read properties of null” or a missing-element error
This usually comes from $eval() when no element matches. Use $$eval() for an optional collection, or wait for the required selector before calling $eval().
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Fix the driver behind crashes, sound loss and screen glitches3Repair Windows errors before they cause bigger problemsThe element comes back
A later script, route transition, or framework render inserted it again. Move the removal after the update that creates it, or use a mutation observer for repeated insertions. Removing a node once does not establish a lasting rule.
The screenshot still contains the element
Capture only after the removal promise resolves. If the target is reinserted during layout or navigation, wait for the final page state and run the removal immediately before the screenshot.
await page.waitForSelector('.target-class');
await page.$$eval('.target-class', elements => {
elements.forEach(element => element.remove());
});
await page.screenshot({path: 'result.png', fullPage: true});
Removing the node leaves behavior behind
remove() detaches the element; it does not undo application state or server-side effects. If the page registered global listeners or changed application state, those effects may remain. Remove only the DOM node when that is the intended outcome, and use the site’s own controls when you need a complete application-level dismissal.
Performance and reliability considerations
Prefer one page-context evaluation
Calling $$eval() once lets the browser query all matches and remove them in one page-context operation. Avoid looping in Node.js and making a separate evaluation call for every element unless each element needs different logic.
Keep selectors specific
A narrow selector reduces accidental removals and the amount of DOM Puppeteer must inspect. Combine a class with an element type, ancestor, or second class when the class is reused widely.
Decide whether failure should stop the job
For required content, wait and let a timeout fail the job so an incomplete capture is visible. For optional banners, use $$eval(), record the returned count, and continue when zero is an acceptable result.
Run the mutation as late as necessary
Removing too early allows later navigation or rendering to replace the node. Removing immediately before extraction or screenshotting minimizes the chance that the target returns, while still leaving the page’s other content intact.
Best Value
- Used Book in Good Condition
Or skip the browser setup
If your goal is a clean image or PDF rather than browser-automation code, ScreenshotNeo provides a one-call website screenshot API. Its cleanup step accepts cookie and consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each cleanup step can be turned off.
The Tool Desk
Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Only clean shots are billed. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and each response identifies the result with X-Page-Verdict and X-Billed headers. ScreenshotNeo also offers an MCP server with take_screenshot, get_page_info, and capture_pdf tools for 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 shots. See the ScreenshotNeo documentation for request options and response details.
cURL
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
Node.js
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const buffer = Buffer.from(await res.arrayBuffer());
require('fs').writeFileSync('shot.webp', buffer);
When you need Puppeteer’s full control over arbitrary page logic, use the DOM method above. When you simply need a cleaned capture without maintaining browser launch, navigation, wait, and popup-removal code, create a free ScreenshotNeo account with 1,000 screenshots a month and no card.
FAQ
Does remove() delete the element permanently from the website?
No. It changes the current document in the browser instance. Reloading the page or a later script can create the element again.
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 →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Can I remove elements by class inside an iframe?
Not from the top-level page context. Obtain the iframe’s Puppeteer frame and run the same $$eval() call on that frame after its document has loaded.
What does an empty $$eval() result mean?
It means the selector matched no elements at the moment the query ran. Treat that as success for optional content, or add an explicit wait when the element is required.
Frequently Asked Questions
Can I remove elements by class inside an iframe?
Obtain the iframe’s Puppeteer frame and run the same $$eval() selector in that frame after its document has loaded; the top-level page context cannot query across the frame boundary.
What does an empty $$eval() result mean?
The selector matched no elements at query time. That is safe for optional content, but a required element needs an explicit wait and failure handling.
Free tools Windows power users keep installed
One-click scans. No signup required.
Will removing a node undo its network request or application state?
No. remove() detaches the DOM node only; requests already made and application-level state can remain.
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.

