How to Hide an Element Before Taking a Puppeteer Screenshot
Hide banners, modals, and widgets before Puppeteer captures a page, with reliable CSS, DOM, wait, full-page, and troubleshooting patterns.
Direct answer: hide the element before the capture call. The most reliable pattern is to inject a temporary CSS rule with page.addStyleTag(), or remove or change the node with page.evaluate(), await that operation, and then call page.screenshot().
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.addStyleTag({
content: '.cookie-banner, #promo-modal { display: none !important; }'
});
await page.waitForSelector('.cookie-banner', {hidden: true});
await page.screenshot({path: 'page.png', fullPage: true});
await browser.close();
This uses Puppeteer’s documented Page API and screenshot guide. Use a narrow selector, decide whether the layout should collapse, and await the hide operation before capturing.
1. Install Puppeteer and create a reproducible capture
Install Puppeteer in a new project, then run the script with Node.js:
mkdir hidden-shot && cd hidden-shot
npm init -y
npm install puppeteer
node capture.mjs
Save this as capture.mjs. It waits for navigation, injects the rule, verifies the hidden state, and writes a full-page PNG.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({headless: true});
try {
const page = await browser.newPage();
await page.setViewport({width: 1440, height: 900, deviceScaleFactor: 1});
await page.goto('https://example.com', {
waitUntil: 'networkidle2',
timeout: 60000
});
await page.addStyleTag({
content: `
.cookie-banner,
#promo-modal,
.sticky-chat-widget {
display: none !important;
}
`
});
await page.waitForSelector('.cookie-banner', {hidden: true});
await page.screenshot({path: 'page.png', fullPage: true, type: 'png'});
} finally {
await browser.close();
}
2. Hide with CSS, change the node, or remove it
Inject a temporary rule
addStyleTag adds a style element to the page. It is usually the best default because the rule remains effective if the application re-renders the matching node.
await page.addStyleTag({
content: '.cookie-banner { display: none !important; }'
});
Use !important when site CSS would otherwise win. A site’s inline !important declaration or script that repeatedly changes styles can still override it.
Set a style or remove the node
await page.evaluate(() => {
const element = document.querySelector('.cookie-banner');
if (element) element.style.display = 'none';
});
await page.evaluate(() => {
document.querySelector('.cookie-banner')?.remove();
});
page.evaluate runs in the page context. Removing the node is useful when it should no longer exist in the captured DOM; setting display preserves the node for later code.
Choose the layout effect
| Technique | Layout space | Use it when |
|---|---|---|
display: none |
Collapses | The screenshot should close the gap around the element. |
visibility: hidden |
Preserves | Surrounding geometry must stay in the same position. |
remove() |
Collapses | The element itself should be gone from the DOM. |
opacity: 0 |
Usually preserves | Rarely appropriate; transparent elements can still occupy space and affect interaction. |
3. Wait for asynchronous banners and widgets
Consent dialogs and chat widgets often appear after the first response. Wait for the target, hide it, then verify the hidden state:
await page.waitForSelector('.cookie-banner', {visible: true, timeout: 15000});
await page.addStyleTag({
content: '.cookie-banner { display: none !important; }'
});
await page.waitForSelector('.cookie-banner', {hidden: true});
await page.screenshot({path: 'without-banner.png'});
Puppeteer treats a selector as hidden when it is absent, has display: none, or has visibility: hidden. Therefore this also works when the banner never appears:
await page.addStyleTag({content: '.cookie-banner { display: none !important; }'});
await page.waitForSelector('.cookie-banner', {hidden: true, timeout: 5000});
If a script recreates the element, inject a persistent matching rule after navigation and immediately before the screenshot. For a one-off node, remove it at the last possible moment.
4. Capture the correct region
Hiding an element and choosing the screenshot region are separate decisions. Puppeteer documents fullPage and clip in its ScreenshotOptions.
// Viewport only
await page.screenshot({path: 'viewport.png'});
// Entire document after the hide operation
await page.screenshot({path: 'full.png', fullPage: true});
// A calculated rectangle
await page.screenshot({
path: 'clip.png',
clip: {x: 0, y: 120, width: 900, height: 600}
});
For one component, capture an element handle. You can hide a child first, then capture its parent:
const card = await page.waitForSelector('[data-card]');
await card.screenshot({path: 'card.png'});
Other useful options include type: 'png' | 'jpeg' | 'webp', quality for JPEG or WebP, path, and omitBackground for transparency where supported.
5. Complete reusable helper
import puppeteer from 'puppeteer';
async function screenshotWithout(page, selectors, options = {}) {
const css = selectors.map((s) => `${s} { display: none !important; }`).join('\n');
await page.addStyleTag({content: css});
for (const selector of selectors) {
await page.waitForSelector(selector, {hidden: true, timeout: options.verifyTimeout ?? 5000});
}
return page.screenshot({
path: options.path ?? 'page.png',
fullPage: options.fullPage ?? true,
type: options.type ?? 'png'
});
}
const browser = await puppeteer.launch({headless: true});
try {
const page = await browser.newPage();
await page.goto('https://example.com', {waitUntil: 'networkidle2', timeout: 60000});
await screenshotWithout(page, ['.cookie-banner', '#newsletter-modal'], {
path: 'clean.png', fullPage: true
});
} finally {
await browser.close();
}
6. Common failures and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Banner still visible | Selector does not match, rule was not awaited, or inline styles win. | Inspect the selector, await addStyleTag or evaluate, add !important, and verify with waitForSelector(..., {hidden: true}). |
| Content jumps upward | display: none removes layout space. |
Use visibility: hidden when geometry must remain stable. |
| Element returns | Framework code recreated or restyled it. | Use a persistent injected rule and apply it after the element’s insertion, or remove it immediately before capture. |
waitForSelector times out |
The selector is wrong, the page is cross-origin framed, or the element never appears. | Check the DOM in the correct frame; if absence is acceptable, use a hidden wait with a shorter timeout or skip the visible wait. |
| Screenshot is blank or partial | Navigation, lazy content, or a clip rectangle completed too early. | Wait for the required selector or network state, set an adequate viewport, and validate clip coordinates. |
| Only the iframe content is unwanted | document.querySelector searched the top document. |
Get the frame with page.frames(), then run frame.evaluate or inject CSS in that frame. |
7. Reliability, performance, and cost considerations
- Reliability: use stable data attributes where possible, wait for the page state you actually need, and keep hide selectors specific. Log the URL, selector list, and screenshot options for reproducibility.
- Performance: one style tag containing several selectors avoids repeated DOM scans. Avoid arbitrary long sleeps; wait for a selector or network state instead. Full-page screenshots require more rasterization and memory than a viewport or clip.
- Visual stability: set the viewport and device scale factor explicitly. If fonts or images matter, wait for their relevant selectors before capture.
- Cost: self-hosted Puppeteer cost is your browser runtime, CPU, memory, and storage. Reusing a browser process while creating fresh pages can reduce startup overhead, but close pages and browsers in
finallyblocks.
8. Or skip the browser setup
ScreenshotNeo accepts one GET request and returns a PNG, JPEG, WebP, or PDF. Its cleanup step accepts cookie or consent banners and removes 60+ known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. It also provides an MCP server for AI agents with take_screenshot, get_page_info, and capture_pdf.
See the ScreenshotNeo API docs for all options. A direct call is:
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}`);
There are 1,000 screenshots a month free with no card. Paid plans start at $5 for 3,000 shots, and every feature is on every plan. Create a free ScreenshotNeo account.
9. FAQ
Does hiding an element change the website for other users?
No. The CSS or DOM change runs in your isolated browser page and affects only that capture.
Should I click “Accept” instead of hiding a consent banner?
Clicking can change cookies and page state. Hide or remove the banner when the goal is only a clean image; click when your test specifically needs consent behavior.
Can I hide an element without changing layout?
Yes. Use visibility: hidden; unlike display: none, it preserves the layout box.
Can Puppeteer hide an element inside a cross-origin iframe?
Only code running in that frame can access its DOM. Select the frame and evaluate there; browser same-origin rules still apply.
Which source documents these APIs?
The official Page API, Screenshots guide, and ScreenshotOptions document the methods and options used here.


