ScreenshotNeo

BlogHow-to

How to Hide Cookie Banners in Puppeteer Screenshots

Hide a known consent banner in Puppeteer with targeted CSS, handle late-loading banners, and capture clean screenshots without changing consent state.

By the ScreenshotNeo team4 October 20268 min read

To hide a known cookie banner in a Puppeteer screenshot, add a narrow CSS rule with page.addStyleTag() after navigation and before capture. Replace the example selector below with one verified on the page. This hides the banner visually while leaving its DOM node in place; it does not record a consent choice.

const puppeteer = require('puppeteer');

(async () => {
  const url = 'https://example.com';
  const browser = await puppeteer.launch({ headless: true });
  try {
    const page = await browser.newPage();
    await page.goto(url, { waitUntil: 'networkidle2', timeout: 45000 });
    await page.addStyleTag({
      content: `
        .cookie-banner-selector {
          display: none !important;
        }
      `,
    });
    await page.screenshot({ path: 'page.png', fullPage: true });
  } finally {
    await browser.close();
  }
})();

Install Puppeteer in a Node project with npm install puppeteer. The official Puppeteer screenshot guide covers page.screenshot(); see also the API references for addStyleTag(), evaluate(), evaluateOnNewDocument(), and browser cookies.

1. Find a precise selector

Inspect the target page’s DOM in browser developer tools and identify a selector that matches the consent banner itself. Prefer an ID or distinctive class tied to that component. Avoid broad rules such as [class*="cookie"] or div[class*="consent"]: these can hide unrelated page content, including policy notices and preference controls elsewhere on the page.

Check whether the banner lives inside an iframe or shadow root. A CSS rule added to the main document will not reach into an iframe; inspect its frame and apply the rule in that frame, or locate the element using Puppeteer’s frame APIs. Shadow DOM contents also need to be addressed through the component’s shadow root rather than a document-level selector.

2. Choose the right intervention

Method Use it when Tradeoff
addStyleTag() The banner selector is known and visual hiding is enough. Keeps the node and page structure; a site script may override or recreate it.
evaluate() You need to remove or alter a specific node after it appears. The site can recreate a removed node; removal changes the DOM.
evaluateOnNewDocument() A startup script inserts the banner before your post-navigation code runs. Runs early but does not identify the correct selector for you.
Use the consent UI or documented preference mechanism The screenshot must represent a genuine accepted, rejected, or customized state. Requires making and preserving the intended choice; hiding alone does not do this.

Remove a banner node after it appears

const selector = '.cookie-banner-selector';
await page.waitForSelector(selector, { timeout: 10000 }).catch(() => {});
const removed = await page.evaluate((sel) => {
  const node = document.querySelector(sel);
  if (!node) return false;
  node.remove();
  return true;
}, selector);
console.log({ removed });

Waiting is useful when client-side code inserts the banner after initial navigation. If the selector times out, decide whether the page genuinely has no banner or whether its markup differs; do not silently assume it was removed. If the site recreates the node, a narrowly scoped style rule may be more stable, or you may need to observe the relevant page state and remove it after insertion.

Install an early style rule

When timing matters, install a style element before page scripts run. The example below applies the rule when the document element becomes available. Keep the selector specific to the site.

const browser = await puppeteer.launch({ headless: true });
try {
  const page = await browser.newPage();
  await page.evaluateOnNewDocument(() => {
    const selector = '.cookie-banner-selector';
    const addRule = () => {
      if (!document.documentElement || document.getElementById('capture-hide-consent')) return;
      const style = document.createElement('style');
      style.id = 'capture-hide-consent';
      style.textContent = `${selector} { display: none !important; }`;
      document.documentElement.appendChild(style);
    };
    addRule();
    new MutationObserver(addRule).observe(document, { childList: true, subtree: true });
  });
  await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
  await page.screenshot({ path: 'page.png', fullPage: true });
} finally {
  await browser.close();
}

This observer is useful only for the targeted element and should not be expanded into a broad page-wide removal rule. For pages with strict Content Security Policy, injected styles can behave differently; verify the actual output. A post-navigation addStyleTag() is simpler when it works.

3. Wait for the intended page state

Navigation completion does not guarantee that client-rendered content or a delayed consent component is ready. Use a wait condition tied to the content you need when possible. networkidle2 is a practical navigation option, as in the first example, but pages that maintain network connections may never become idle. In those cases, wait for a stable content selector or a known application state rather than increasing an arbitrary sleep.

await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 45000 });
await page.waitForSelector('main article', { timeout: 15000 });
await page.addStyleTag({ content: '.cookie-banner-selector { display:none !important; }' });
await page.screenshot({ path: 'article.png', fullPage: true });

4. Select screenshot dimensions and format

Puppeteer can save a full-page image, capture a viewport or clipped region, and choose image format and output path. Use options that fit the consuming workflow:

  • fullPage: true captures beyond the current viewport.
  • clip limits the screenshot to a rectangle; it cannot be combined with fullPage.
  • type selects png, jpeg, or webp where supported by the installed Puppeteer/browser version.
  • quality applies to JPEG and WebP output, not PNG.
  • path writes the image to a file; omit it when you want the screenshot bytes returned to your program.
// Viewport PNG
await page.screenshot({ path: 'viewport.png', type: 'png' });

// Full-page JPEG at a chosen quality
await page.screenshot({ path: 'full.jpg', fullPage: true, type: 'jpeg', quality: 82 });

// A clipped region (x, y, width and height are CSS pixels)
await page.screenshot({
  path: 'region.png',
  clip: { x: 0, y: 0, width: 1200, height: 800 },
});

For a specific element, locate it and capture its handle rather than using a page-wide screenshot:

const element = await page.waitForSelector('main article');
await element.screenshot({ path: 'article.png' });

Removing or hiding a banner changes what is visible in the screenshot. It does not accept or reject cookies, update a consent-management platform, or create a browser cookie. If the capture must reflect a real consent state, use the site’s consent controls or documented preference mechanism, then preserve the resulting browser state as needed. Puppeteer’s browser cookie APIs are separate from page styling and DOM evaluation.

Use an isolated browser context for independent captures when state must not leak between jobs. Reuse a browser process for throughput when appropriate, but create a fresh page or context for each capture’s distinct state and close pages and browsers reliably.

6. Complete runnable capture with error handling

This CommonJS example checks whether the selector was found, applies the visual rule, and always closes the browser. Replace both the URL and selector with the target values.

const puppeteer = require('puppeteer');

async function capture(url, bannerSelector) {
  const browser = await puppeteer.launch({ headless: true });
  try {
    const page = await browser.newPage();
    page.setDefaultNavigationTimeout(45000);
    await page.goto(url, { waitUntil: 'domcontentloaded' });
    await page.waitForSelector('body');

    const bannerPresent = await page.$(bannerSelector);
    if (bannerPresent) {
      await page.addStyleTag({
        content: `${bannerSelector} { display: none !important; }`,
      });
    } else {
      console.warn(`Banner selector not found: ${bannerSelector}`);
    }

    await page.screenshot({ path: 'capture.png', fullPage: true });
  } finally {
    await browser.close();
  }
}

capture('https://example.com', '.cookie-banner-selector').catch((error) => {
  console.error('Screenshot capture failed:', error);
  process.exitCode = 1;
});

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server from ScreenshotNeo. One GET request returns an image or PDF. Its capture flow accepts cookie and consent banners like a visitor and removes 60+ known consent platforms, newsletter popups, and chat widgets; each cleanup step can be turned off. Only clean shots are billed: bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and the response identifies the page verdict and billing status in headers. An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents. The free plan includes 1,000 shots a month with no card; paid plans start at $5 for 3,000.

For the full parameter list and behavior, see the ScreenshotNeo API documentation.

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,
)
r.raise_for_status()
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}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);

Use YOUR_API_KEY from your account and change the target URL. Puppeteer remains useful when you need custom browser code or a genuine site-specific consent workflow; the API avoids managing browser installation and capture infrastructure. Sign up for 1,000 free screenshots a month with no card.

Troubleshooting

Symptom Likely cause Fix
Banner is still visible Wrong selector, late insertion, iframe or shadow DOM, or a competing style. Inspect the rendered DOM, target the correct frame/root, wait for insertion, and use a specific rule with !important.
Page content disappears too The selector is too broad. Use a distinctive component selector and inspect the screenshot before scaling the job.
Banner returns after removal Site JavaScript recreated the node. Prefer a targeted CSS rule or apply removal after the node is inserted.
Navigation times out The page keeps connections open or loads slowly. Try domcontentloaded, set an appropriate timeout, then explicitly wait for the content needed.
Screenshot is blank or incomplete Capture ran before app content rendered, or lazy content had not loaded. Wait for the main content selector and the page state required by the capture; inspect the returned image.
Style injection fails or has no effect Wrong execution context, restrictive page behavior, or a selector mismatch. Confirm the selector and frame; try the early injection approach when startup timing is the issue.
Consent still appears on the next job The browser context retains cookies or storage. Use a fresh context when jobs require isolated state, or deliberately persist consent state if that is the desired workflow.
Target closed or browser disconnect The page/browser closed during work or the process ran out of resources. Await each operation, avoid closing the browser before screenshot completion, and close pages/contexts after the capture.

Performance, reliability, and cost

  • Keep selectors narrow. A small targeted style rule has little setup overhead and avoids expensive or fragile page-wide DOM scans.
  • Wait on conditions. Prefer a relevant content or banner condition over a fixed delay. Set navigation and selector timeouts so a failed page does not hold a worker indefinitely.
  • Manage browser lifetime. Reusing a browser can avoid repeated startup work in a capture service; isolate per-job pages or contexts and close resources in finally blocks. Monitor memory when capturing many large full-page images.
  • Choose output intentionally. Full-page images use more memory and bandwidth than viewport or element captures. JPEG/WebP can reduce output size when their quality tradeoff is acceptable; PNG preserves lossless output.
  • Account for operational work. Self-hosted Puppeteer requires browser installation, updates, compute, storage, and handling failed destinations. There is no per-shot Puppeteer API charge, but infrastructure and engineering time still have cost.
  • Hosted API option. ScreenshotNeo lists Free at 1,000 shots/month, Starter $5 for 3,000, Growth $15 for 15,000, Pro $39 for 60,000, Scale $99 for 250,000, and Business $249 for 1,000,000; yearly billing gives two months free. Every feature is on every plan. Check the docs for current request options before integrating.

FAQ

Does hiding a banner accept cookies?

No. CSS only changes visibility. Use the page’s consent workflow when a recorded preference matters.

There is no reliable universal selector across sites. Inspect each target and keep rules specific to its markup.

Can I capture only the page content beneath the banner?

Yes. Hide the banner, then use fullPage, clip, or an element handle according to the desired region.

Will the CSS rule appear in the saved screenshot?

No. The screenshot shows the rendered page after the rule is applied; the style tag itself is not visible content.