Cookie Consent Banner: Why It Ruins Screenshots

A cookie consent banner asks visitors to accept or reject tracking. Why every headless capture shows one, and three tested ways to remove it.

By Simon Chiu · Updated

A cookie consent banner is the notice a website shows on a first visit to ask for permission to set non-essential cookies, typically with Accept and Reject buttons, and it stays on screen until the visitor makes a choice that the site stores in a cookie or local storage.

For screenshots, the banner is the most common reason a capture looks wrong: it covers the bottom of the page or, as a modal, dims all of it. This entry is part of the screenshot glossary.

What it means in plain terms

Privacy laws such as the GDPR and the ePrivacy rules in Europe require consent before most analytics and advertising cookies are set. Sites show a banner, often built with a consent management platform (CMP) such as OneTrust, Cookiebot or Didomi. Many CMPs follow the IAB Transparency and Consent Framework and store the choice in a cookie (for TCF, commonly euconsent-v2) plus their own cookie.

A real visitor clicks once and never sees the banner again. A headless capture starts with an empty profile, so it is a first visit on every run. Many sites also show the banner only to visitors from some regions, so a capture can show it from one server and not from another.

The same page, before and after

A test page with the heading Article and a dark consent bar across the bottom that reads We use cookies, with Accept all and Reject buttons The same test page with only the heading Article and no consent bar, because the consent cookie was set before the page loaded
Our test page at 640×240. Top: a fresh browser context, so the banner appears after 300 ms. Bottom: the same page with the site's consent cookie set before navigation. Captured with Puppeteer 24.43.1 and chrome-headless-shell 148 on 2026-10-15. These are local renders, not API renders.

How it works technically

flowchart TD
  A[Page loads] --> B[CMP script runs]
  B --> C{"Consent cookie or<br/>storage entry present?"}
  C -->|yes| D["No banner; apply the stored choice"]
  C -->|no| E{"Visitor in a region<br/>that needs consent?"}
  E -->|no| D
  E -->|yes| F[Show banner, often after a delay]
  F --> G[Visitor clicks] --> H[Store choice] --> D

That flow gives four places to intervene: provide the stored choice (seed the cookie), make the click, hide the banner element, or stop the CMP script from loading. Each has a cost:

Method Works when Watch out for
Seed the consent cookie You know the site's cookie name and value Each CMP and site stores it differently
Click Accept or Reject The button has stable text or a role Text changes with language; banner in an iframe
Hide with CSS Banner is a normal element Page may stay scroll-locked or dimmed
Block the CMP script Page works without it Some sites hide content until consent

Removing it in Puppeteer, Playwright and RenderScreenshot

Puppeteer, with the first two methods. The ::-p-text() selector matches by visible text, and the locator waits for the button:

import puppeteer from 'puppeteer';

const url = process.env.TARGET ?? 'http://127.0.0.1:8079/';
const browser = await puppeteer.launch();
const bannerVisible = (page) => page.$eval('#consent', (b) => !b.hidden);
try {
  // Option A: seed the site's own consent cookie before the page loads.
  const context = await browser.createBrowserContext();
  await context.setCookie({ name: 'consent', value: 'all', domain: new URL(url).hostname, path: '/' });
  const seeded = await context.newPage();
  await seeded.goto(url);
  await new Promise((r) => setTimeout(r, 500)); // give the banner script time to decide
  console.log('cookie seeded, banner visible:', await bannerVisible(seeded));
  await context.close();

  // Option B: click "Accept all" when it appears.
  const page = await browser.newPage();
  await page.goto(url);
  await page.locator('#consent ::-p-text(Accept all)').click();
  console.log('after click, banner visible:', await bannerVisible(page));
  await page.screenshot({ path: 'clicked.png' });
} finally {
  await browser.close();
}

Both lines printed false. Playwright, with the click by accessible name, which survives class-name changes:

import { chromium } from 'playwright';

const url = process.env.TARGET ?? 'http://127.0.0.1:8079/';
const browser = await chromium.launch();
try {
  const context = await browser.newContext();
  await context.addCookies([{ name: 'consent', value: 'all', url }]); // Option A
  const page = await context.newPage();
  await page.goto(url);
  await page.waitForLoadState('networkidle');
  console.log('cookie seeded, banner visible:', await page.locator('#consent').isVisible());
  await context.close();

  const clickPage = await browser.newPage(); // Option B
  await clickPage.goto(url);
  await clickPage.getByRole('button', { name: /accept all/i }).click();
  await clickPage.locator('#consent').waitFor({ state: 'hidden' });
  await clickPage.screenshot({ path: 'after-click.png' });
} finally {
  await browser.close();
}

RenderScreenshot has a switch for the common case. block_cookies=true (POST: block.cookie_banners) auto-clicks consent buttons and hides common banner elements:

curl -G https://api.renderscreenshot.com/v1/screenshot \
  -H "Authorization: Bearer $RS_API_KEY" \
  --data-urlencode "url=https://example.com" \
  -d block_cookies=true \
  --output no-banner.png

For a banner that survives, combine the same fallbacks as above in one POST: seed the cookie with network.cookies, click with page.click, or remove the element with page.remove:

{
  "url": "https://example.com",
  "block": { "cookie_banners": true, "trackers": true },
  "network": { "cookies": [{ "name": "consent", "value": "all", "domain": "example.com" }] },
  "page": { "remove": ["#consent"] }
}

The block cookie banners guide has a table of common CMPs and a script that renders your URLs with and without blocking. The options are in blocking.

Tested on 2026-10-15 on macOS 27 (arm64) with Node 22.18.0, [email protected] (Chrome 148.0.7778.97, headless: 'shell') and [email protected], against a local test page whose banner checks a consent cookie. The API requests were not run for this page.

Common mistakes

  • Waiting a fixed time, then capturing. Many CMPs show the banner after a delay. Wait for the button, or for the banner to be hidden, as the scripts above do.
  • Hiding the banner but not the overlay. Modal CMPs add a dimmed backdrop and lock scrolling on <body>. Remove both, or use the click.
  • Selecting by text in one language. "Accept all" becomes "Alle akzeptieren" on a German page. Pin the locale, or select by role and a stable attribute.
  • Missing banners inside iframes. Some CMPs render in a cross-origin iframe. Selectors in the main page do not reach it; click inside the frame, or block the CMP script.

To capture clean pages with one parameter, get a free API key.

Frequently asked questions

Why do my screenshots show a cookie banner when my browser does not?
Your browser stored your choice in a cookie or in local storage the first time you clicked. A headless capture usually starts with an empty profile, so to the site it is a first visit, every time.
Is it legal to auto-accept cookie banners in automated captures?
Consent rules protect people. A capture bot is not a person browsing, and you are making the choice for your own automated session. If you would rather not let trackers run during the capture, block them as well as the banner. This is not legal advice.
Should I click Accept or Reject?
Reject (or seeding a rejected-consent cookie) keeps trackers off, but on some sites it shows a second dialog or a reduced page. Accept gives the page most visitors see. Pick one and use it for all captures, so images stay comparable.

Related in this topic

Try RenderScreenshot free

150 free credits every month, no credit card required.