A screenshot of a European website taken by a fresh browser shows the consent banner first: a dialog over half the page, a dimmed background and, often, scrolling switched off. For a thumbnail, a monitoring archive or a page in a report, that is the wrong picture. This page shows the two ways to get rid of it — accept or hide — with Puppeteer code, and what came out when we ran both on 12 real sites.
How we tested. On 2026-11-08 we captured the home pages of The Guardian, Le Monde, Der Spiegel, IKEA Germany, Booking.com, Cookiebot, Marca, Bild, Zalando, BBC, Wirtualna Polska and Onet at 1280×800, from a machine in Poland, so every site treated us as an EU visitor. Each site went through our API twice (banner option off and on) and once through the Puppeteer script below. Banners differ by country, by day and by A/B test; treat the counts as one snapshot, not a ranking of sites.
Accept or hide?
There are two ways to remove a banner from a capture:
- Click "accept". The consent platform runs its own code: it stores the choice, removes the dialog and the dark overlay, and unlocks scrolling. The page then looks as it does for a visitor who has agreed — including the ads and trackers that consent switches on (after accepting on Marca, a game ad filled the top of the page).
- Hide it with CSS.
display: noneon the banner's container. Nothing else changes, but an overlay or aoverflow: hiddenon<body>may stay behind, and a full-page capture may then stop at the first screen.
In practice you do both: click where you can, hide what is left. Clicking "reject" works too where a site offers it on the first layer, but many sites put it one screen deeper, and a "pay or accept" site (Le Monde, Spiegel, Bild, Marca and The Guardian all had one) offers no free reject at all.
Doing it yourself in Puppeteer
The script below tries the "accept all" buttons of the common consent platforms in every frame, falls back to matching button text, then injects CSS for whatever stays. It ran with Puppeteer 25 on 2026-11-08:
import puppeteer from 'puppeteer';
// "Accept all" buttons of common consent platforms.
const ACCEPT = [
'#onetrust-accept-btn-handler', // OneTrust
'#CybotCookiebotDialogBodyLevelButtonLevelOptinAllowAll', // Cookiebot
'#CybotCookiebotDialogBodyButtonAccept',
'#didomi-notice-agree-button', // Didomi
'[data-testid="uc-accept-all-button"]', // Usercentrics
'.qc-cmp2-summary-buttons button[mode="primary"]', // Quantcast
'button.sp_choice_type_11', // Sourcepoint (in an iframe)
];
// Text fallback for in-house banners.
const TEXT = /^(accept all( cookies)?|allow all|i agree|agree|alle akzeptieren|tout accepter|accepter et continuer|aceptar todo|acepto)/i;
// What to hide if clicking did not work.
const HIDE = '#onetrust-consent-sdk, #CybotCookiebotDialog, #didomi-host, #usercentrics-root, .qc-cmp2-container, [id^="sp_message_container"] { display: none !important; } html, body { overflow: auto !important; }';
async function acceptCookies(page) {
for (const frame of page.frames()) {
const clicked = await frame.evaluate((selectors, text) => {
const visible = (el) => el && el.getBoundingClientRect().height > 0;
for (const s of selectors) {
const el = document.querySelector(s);
if (visible(el)) { el.click(); return s; }
}
const button = [...document.querySelectorAll('button, a[role="button"]')]
.find((b) => visible(b) && new RegExp(text, 'i').test(b.textContent.trim()));
if (button) { button.click(); return button.textContent.trim(); }
return null;
}, ACCEPT, TEXT.source).catch(() => null);
if (clicked) return clicked;
}
return null;
}
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setViewport({ width: 1280, height: 800 });
await page.goto('https://www.theguardian.com/europe', { waitUntil: 'load' });
await new Promise((r) => setTimeout(r, 2000)); // banners arrive late
await acceptCookies(page);
await new Promise((r) => setTimeout(r, 1000)); // let the dialog close
await page.addStyleTag({ content: HIDE });
await page.screenshot({ path: 'guardian.jpg', type: 'jpeg' });
await browser.close();
Three details matter. Frames: Sourcepoint (The Guardian, BBC, Bild, Spiegel) draws its dialog in a cross-origin iframe, so a selector in the main frame finds nothing — page.frames() reaches it. Timing: most banners appear after load, so a fixed wait or a waitForSelector with a short timeout is needed. Text: the button text is in the page's language.
What this script got on the same 11 sites that showed a banner:
| Result | Sites |
|---|---|
| Banner gone | The Guardian, Le Monde, IKEA, Cookiebot, Marca, Bild, Der Spiegel (7) |
| Dialog gone, a cookie bar left at the bottom | BBC |
| Banner left: button text not in the list | Wirtualna Polska ("Akceptuję i przechodzę do serwisu"), Onet ("Przejdź do serwisu") |
| No page: the site answered "Site can't be reached right now" to default headless Chrome | Zalando |
None of these is hard to fix one by one — add the Polish texts, the BBC bar's selector, a normal user agent. That is the real cost of doing it yourself: the list grows with every site you capture, and vendors change their markup. Two shortcuts exist. The EasyList Cookie List is a maintained filter list of banner elements, usable through an ad-blocking library. DuckDuckGo's autoconsent (MPL-2.0) is a library of per-platform rules that click through consent pop-ups, choosing opt-in or opt-out, and runs in a Chrome extension or a Playwright-driven headless browser; DuckDuckGo's own browser apps use it.
The same sites through our API
Our screenshot API does the same in one option. block_cookie_banners first clicks "accept" for the consent platforms it knows (OneTrust, Cookiebot, Didomi, Usercentrics, Quantcast, TrustArc, Sourcepoint) in every frame, then tries "accept" buttons by their text (English, German, French, Spanish, Italian, Polish, Dutch, Swedish, Portuguese, Czech and Hungarian) inside elements that call themselves cookie or consent banners, and hides what is left using the EasyList Cookie List, unlocking scrolling afterwards:
curl -X POST "https://shareglint.com/v1/screenshots?wait=true" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://www.theguardian.com/europe", "block_cookie_banners": true, "format": "jpeg"}'
What came out on the 12 sites:
| Without the option | With block_cookie_banners |
|
|---|---|---|
| Sites showing a cookie banner | 11 of 12 (Booking.com showed none to us) | 1 of 12 in the first run (Marca), 0 after the fix below |
| Median render time | 5.0 s | 9.4 s |
| Other pop-ups on the picture | — | Spiegel: a subscription offer; Booking.com: a sign-in box |
Marca's banner is a "pay or accept" box whose button reads "Acepto y continúo gratis", which the text matching did not recognise in the first run. Clicking it ourselves fixed the capture: "click_selector": ["button[action-name=\"agreeAll\"]"]. We added the phrase to the text matching the same day, and a second capture of Marca with only block_cookie_banners came out clean. That is the general workaround for any banner the option misses: name its button in click_selector. The subscription offer and the sign-in box are not consent banners and are not touched by the option; hide them with hide_selectors once you know their selector (right-click → Inspect in Chrome shows it).
The extra four seconds are the price of waiting: the capture watches for consent-platform traffic and waits for late banners and for the page to settle after the click. Sites without any banner pay a little of it too (example.com: 1.8 s without the option, 6.0 s with).
Options that go with it
block_adshides ads and blocks ad requests; on its own it also turns onblock_cookie_banners. Useful together with "accept", which switches ads on.block_trackersblocks analytics and tracking requests (EasyPrivacy).hide_selectorstakes a list of CSS selectors to hide;click_selectorone selector or a list, clicked in order before the capture.full_pagewithlazy_loadfor the whole page; after an "accept" the page scrolls again.
A viewport screenshot is 1 render of the quota, a full page 2, and the same URL with the same options within 24 hours is returned from cache for free. The option list is in the screenshot API docs; bulk screenshots shows how to run it over a list of URLs.
Limits, honestly
- Banners change. A site that is clean today may ship a new dialog tomorrow; the filter lists catch up, but not the same day.
- Country matters. We captured as a visitor from Poland. Outside the EU many of these sites show a smaller banner or none, so the same URL can give a different picture from another country.
- Accepting is accepting. The capture session agrees to the site's cookies, so the picture shows the consented page. Nothing is stored between captures: each one connects to a fresh browser, so the banner is accepted again every time.
- Pages behind a login are out of reach for the API; for those, run your own Puppeteer with the session cookies.