ScreenshotNeo

BlogHow-to

How to Capture a Screenshot After Dismissing a Cookie Consent Dialog

Dismiss a cookie consent banner with browser automation, verify it is gone, then capture a viewport or full-page screenshot.

By the ScreenshotNeo team4 October 202610 min read

A basic screenshot API renders a URL; it does not necessarily interact with the page first. To capture a page after dismissing a cookie consent dialog, use a browser automation flow: navigate, wait for the page and consent UI, click the correct visible consent control, confirm the overlay has closed, and then take the screenshot. A supplied cookie is not a substitute for clicking: the cookie name and value are site-specific, and the banner may use a different mechanism.

This guide uses Playwright with Node.js. The same sequence applies to other browser automation tools. Adapt the consent selectors to the target site’s actual markup and consent choice. See the Playwright Page API and Browserless’s cookie consent guide.

1. Use a browser when capture needs interaction

A direct rendering endpoint can accept a URL and settings such as viewport dimensions, image format, delay, cookies, or headers. The reviewed Screenshot API documentation does not document a pre-capture click action. If the banner must be dismissed through its interface, use a browser session that can interact with the page before capturing it.

Do not assume that passing a cookie will dismiss the banner. A renderer’s cookie option sets browser state; it does not click the consent control, and a particular site’s consent implementation must be inspected and verified before relying on a cookie.

2. Install Playwright

Create a project and install Playwright. The browser install command downloads the browser binary used by Playwright.

mkdir cookie-shot
cd cookie-shot
npm init -y
npm install playwright
npx playwright install chromium

Save the following as capture.mjs. Set TARGET_URL to the page you are authorized to capture. The sample checks several common selectors, clicks only a visible candidate, waits for it to disappear, and treats the no-banner case as normal.

3. Navigate, dismiss, verify, and capture

import { chromium } from 'playwright';

const targetUrl = process.env.TARGET_URL ?? 'https://example.com';
const outputPath = process.env.OUTPUT_PATH ?? 'page.png';
const fullPage = process.env.FULL_PAGE === '1';

// These are starting points, not universal selectors. Inspect the target site's
// DOM/accessibility tree and replace or extend this list for production use.
const consentSelectors = [
  '#onetrust-accept-btn-handler',
  '.cc-accept',
  'button[id*="accept" i]',
  'button[class*="accept" i]',
  'button[aria-label*="accept" i]',
];

const browser = await chromium.launch({ headless: true });
try {
  const context = await browser.newContext({ viewport: { width: 1280, height: 800 } });
  const page = await context.newPage();

  await page.goto(targetUrl, { waitUntil: 'domcontentloaded', timeout: 30_000 });
  // Some consent interfaces appear after initial document parsing. Give the
  // page a bounded opportunity to finish loading without requiring every
  // analytics connection to become idle.
  await page.waitForLoadState('networkidle', { timeout: 8_000 }).catch(() => {});

  let dismissed = false;
  for (const selector of consentSelectors) {
    const candidate = page.locator(selector).first();
    if (await candidate.isVisible().catch(() => false)) {
      await candidate.click({ timeout: 3_000 });
      await candidate.waitFor({ state: 'hidden', timeout: 5_000 }).catch(() => {});
      dismissed = true;
      break;
    }
  }

  // Replace this with a site-specific assertion when possible. A generic
  // selector match is not proof that the correct consent option was chosen.
  console.log(dismissed ? 'Clicked a visible consent candidate.' : 'No known consent selector was visible.');
  await page.screenshot({ path: outputPath, fullPage });
  await context.close();
} finally {
  await browser.close();
}

Run it with:

TARGET_URL='https://example.com' OUTPUT_PATH='page.png' FULL_PAGE=1 node capture.mjs

The script uses domcontentloaded and then a bounded network-idle wait. Some sites keep connections open, so network idle may time out; the code continues after that bounded wait. For a site with a known consent component, wait for that component or a site-specific ready signal instead. A fixed delay can help with a known late animation, but it is not a guarantee that every banner will have appeared.

Cookie dialogs often offer different choices, such as accepting all, rejecting optional cookies, or opening preferences. Select the action that matches the purpose of the capture and your requirements. Do not blindly click the first button containing “accept” if the intended outcome is to reject optional tracking.

There is no universal banner selector. Common selectors such as an ID or class containing accept, a vendor-specific accept button ID, or .cc-accept are useful starting points, not guarantees. For reliable automation:

  1. Inspect the target page’s DOM or accessibility tree in a browser.
  2. Prefer a stable, site-specific ID, role, accessible name, or consent vendor selector.
  3. Confirm the control is visible and enabled before clicking.
  4. Wait for the dialog or backdrop to become hidden, or assert that a known page element is unobstructed.
  5. Capture only after the post-click state is established.

Prefer role and accessible name locators when the site’s labels are clear. For example, replace the CSS loop with a locator grounded in the actual consent text:

const accept = page.getByRole('button', { name: 'Accept all cookies', exact: true });
if (await accept.isVisible().catch(() => false)) {
  await accept.click();
  await accept.waitFor({ state: 'hidden', timeout: 5_000 });
}

Use a timeout on the wait so a changed interface does not hang the entire capture job. If the consent UI is inside an iframe, locate the frame and its button with a frame locator. If the site uses a custom control without button semantics, inspect the markup and use a precise selector rather than a broad text match.

5. Viewport or full-page output

Use a viewport screenshot when you need the visible browser area, such as a hero section or above-the-fold check. Set the browser context viewport to the desired CSS width and height. Use fullPage: true when the content of interest extends below the fold. Lazy-loaded images or content may require scrolling through the page before capture so they load.

// Viewport only
await page.screenshot({ path: 'viewport.png' });

// Entire scrollable page
await page.screenshot({ path: 'full-page.png', fullPage: true });

A very long page can produce a large image and consume more memory. If the destination has output limits, capture a specific element or several viewport sections rather than one enormous bitmap.

6. Handle JavaScript dialogs separately

A cookie consent panel is usually ordinary HTML inside the page. Browser-managed JavaScript dialogs such as alert, confirm, prompt, and beforeunload use a different mechanism. Playwright automatically dismisses JavaScript dialogs when no dialog listener is installed. If you add a listener, it must accept or dismiss each dialog, or the page can remain blocked.

page.on('dialog', async dialog => {
  // Choose accept() only when that is the intended behavior.
  await dialog.dismiss();
});

Do not add a generic dialog handler to dismiss an HTML cookie banner; it will not interact with that banner.

7. Direct screenshot API settings and their limits

A direct screenshot API is useful when the page needs no interaction before rendering. The reviewed Screenshot API documentation describes an HTTP GET that returns image bytes and settings for viewport size, full-page output, format, quality, scale, dark mode, delay, cookies, headers, basic authentication, and timeout. It does not document a consent-button click setting.

Setting Documented behavior in the reviewed API When it helps
Viewport width and height Defaults: 1280 by 800 CSS pixels. Maximum width: 3840; maximum height: 4320. Match the layout breakpoint or desired browser viewport.
Full page Full-page output height is capped at 4320 pixels. Capture content below the fold within the documented limit.
Delay Defaults to 0 ms; documented maximum 10,000 ms. Allow a known late animation or client render to settle; it does not dismiss a banner.
Timeout Defaults to 25 seconds; documented range 1–30 seconds. Bound the render wait for slow pages.
Cookies Accepts cookie values in name=value form for the target host. Restore known site state only when the site’s cookie behavior is understood.
Output Image format and quality/scale settings are documented. Choose output size and format for your downstream use.

These are vendor settings, not independent performance measurements, and may change. Check the current API documentation before relying on a limit. The key distinction remains: cookies set browser state; they do not perform the site’s consent interaction for you.

8. Troubleshooting

Symptom Likely cause Fix
The banner still appears in the screenshot The selector did not match, the click targeted the wrong control, or the banner returned after navigation. Inspect the rendered DOM and accessible name, use a site-specific locator, and wait for the actual dialog/backdrop to be hidden before capture.
Click times out or reports that the element is not actionable The element is hidden, covered, disabled, or not yet attached. Check visibility and enabled state; wait for the correct element; avoid using force unless you have established why normal interaction is blocked.
No consent control is found The banner uses different markup, is inside an iframe, appears later, or does not appear for this region/session. Inspect the page after navigation, check frames, and make “no banner” a valid path. Add a bounded wait for the known banner only if it appears asynchronously.
The page hangs waiting for network idle Analytics, streaming, or long-polling connections keep network activity open. Use a bounded wait as in the sample, or wait for a specific page or consent readiness condition.
The banner closes, then returns The consent choice has not persisted, or the site reloads or re-renders the interface. Wait for the post-click update and inspect the site’s documented state. If a verified consent cookie is available, seed it in the same browser context for later navigations.
Screenshot is blank or the page is incomplete Navigation failed, content renders after the chosen readiness point, or the destination blocks the browser session. Check navigation errors and response state, wait for a meaningful page locator, and capture diagnostics before increasing timeouts indiscriminately.
Unexpected browser dialog blocks capture The page opened a JavaScript dialog, not an HTML consent overlay. Register a Playwright dialog handler that explicitly accepts or dismisses it, according to the intended flow.
Full-page capture omits content Lazy-loaded content has not been triggered, or the tool has a full-page height ceiling. Scroll through the page before capture, wait for images/content, or capture sections. Check the renderer’s current maximum height.

9. Reliability, speed, and cost considerations

Browser automation gives control over clicks and verification, but it adds a browser binary, runtime, and selector maintenance to your job. Keep the flow deterministic: use a fresh context when isolation matters, bound navigation and action waits, log whether a consent control was found, and save an error screenshot or page state when capture fails. A no-banner result should usually proceed to capture rather than fail the entire job.

Choose readiness signals based on the target page. Network idle can be a useful hint, but it can also be delayed indefinitely by background requests. Fixed waits are simple but can waste time and still miss late UI. A site-specific locator or state check is usually more reliable. Full-page screenshots take more resources than viewport captures, especially for tall pages.

Cost depends on where the browser runs and the service terms; the reviewed sources do not establish a comparative price or speed claim for Browserless and the direct-render API. Compare whether the service supports pre-capture interaction, conditional selectors, post-click verification, the output modes and limits you need, and its current commercial terms.

10. Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. It accepts a URL in one GET request and returns a PNG, JPEG, WebP, or PDF. Its capture flow accepts cookie and consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before the shot; each cleanup step can be turned off. This is useful when you want a clean page capture without maintaining browser launch and selector code.

Here is the one-call request in cURL, Python, and Node.js. Replace YOUR_API_KEY with your ScreenshotNeo key and change the target URL as needed. See the ScreenshotNeo API documentation for parameters 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,
)
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(`ScreenshotNeo returned HTTP ${res.status}`);
await Bun.write('shot.webp', new Uint8Array(await res.arrayBuffer()));

Use Node.js 18 or newer for built-in fetch; the example writes the returned bytes with Bun. In a Node-only project, save the response bytes with Node’s node:fs/promises writeFile instead.

ScreenshotNeo removes cookie banners, popups, and chat widgets before the shot. Bot checks, blank pages, and failed loads are never billed; the response identifies the page verdict and billing status with X-Page-Verdict and X-Billed headers. Its MCP server includes take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots. Every feature is on every plan.

Create a free ScreenshotNeo account for 1,000 screenshots a month, with no card required.

11. FAQ

Can I dismiss the banner with a screenshot URL parameter?

Only if that screenshot service explicitly supports pre-capture interaction or cleanup. A standard URL render plus cookies is not equivalent to clicking the page’s consent control.

Should I accept or reject cookies?

Choose the site’s actual option that matches the purpose and policy of your capture. The automation should not silently turn a preference into a different consent choice.

Will a generic “accept” selector work across sites?

No. Consent interfaces vary in markup, labels, timing, and frame placement. Treat generic selectors as fallbacks and validate against each target site.

Use full-page output when below-the-fold content matters. Use a viewport shot when the visible initial screen is the required artifact.

Sources