ScreenshotNeo

BlogHow-to

How to Dismiss Cookie Consent Before Taking a Playwright Screenshot

Dismiss a cookie banner before capturing a Playwright screenshot by choosing the right UI control, verifying it is gone, and handling site-specific consent state.

By the ScreenshotNeo team4 October 20269 min read

For an ordinary cookie banner or modal, navigate to the page, locate the site’s actual consent control, click the choice you intend to make, verify the banner is gone, and then call page.screenshot(). For a JavaScript alert, confirm, prompt, or beforeunload dialog, use Playwright’s dialog event instead. Those are different problems.

The button name and consent storage are site-specific. The example below uses an illustrative accessible label; inspect the target page and choose its actual accept, reject, or preferences control. Do not accept by default if the screenshot needs to represent another choice.

This runnable Node.js example launches Chromium, uses a fresh browser context, waits for the page to load, clicks an example consent button only if it is visible, verifies it is hidden, and captures the page. Install Playwright first with npm install playwright; install its browser with npx playwright install chromium.

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  const context = await browser.newContext();
  const page = await context.newPage();

  try {
    await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });

    // Replace this example label with the site's actual consent control.
    const consentButton = page.getByRole('button', {
      name: /accept all cookies/i,
    });

    if (await consentButton.isVisible().catch(() => false)) {
      await consentButton.click();
      await consentButton.waitFor({ state: 'hidden', timeout: 5000 });
    }

    // Prefer checking the actual banner container as well, if you can identify it.
    await page.screenshot({ path: 'page.png', fullPage: true });
  } finally {
    await context.close();
    await browser.close();
  }
})();

fullPage: true is optional. Remove it for a viewport screenshot. If the button is inside an iframe, locate the correct frame first; page-level locators target the main frame by default. Use the site’s real accessible name or a stable selector, not the sample label blindly.

Click the real control when the interaction matters

Clicking and checking the resulting page is the clearest approach when a test should exercise the consent flow. Choose the actual accept, reject, or preferences control. If the preference dialog has several steps, complete those steps and verify the resulting state before capture. Playwright’s screenshot guide documents page.screenshot(); its Page API documents page-level behavior.

If inspection shows that the site represents consent with a cookie, you can set the correct site-specific cookie in a fresh context before navigating. The name, value, domain or URL, path, and other attributes depend on the site; Playwright does not define a universal consent cookie.

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  const context = await browser.newContext();
  await context.addCookies([
    {
      name: 'CONSENT_COOKIE_NAME',
      value: 'SITE_SPECIFIC_CONSENT_VALUE',
      domain: 'example.com',
      path: '/',
      // Add secure, sameSite, expires, or other attributes if required.
    },
  ]);
  const page = await context.newPage();

  try {
    await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
    await page.screenshot({ path: 'page.png', fullPage: true });
  } finally {
    await context.close();
    await browser.close();
  }
})();

Replace the placeholders only after confirming what the target site expects. A cookie that merely has a plausible name may be ignored or may encode a different preference. For repeatable runs, browser contexts isolate cookies and other browser storage; see Playwright’s browser context guide and BrowserContext API.

Use an initialization script only when you know the site’s storage state

Some sites keep a preference in local storage or another browser-side store. Playwright’s context.addInitScript() runs after a document is created but before page scripts run, making it suitable for known site-specific setup. It does not discover or dismiss a banner automatically.

await context.addInitScript(() => {
  // Example only: replace with the exact key and value used by the site.
  localStorage.setItem('SITE_CONSENT_KEY', 'SITE_CONSENT_VALUE');
});

await page.goto('https://example.com');

Initialization scripts apply to pages and newly attached or navigated frames in the context. If multiple context and page initialization scripts are registered, their evaluation order is not defined. Use this method only when the site’s storage behavior is known. Playwright’s authentication guide explains storage-state workflows.

Hide the prompt in the screenshot only when visual omission is the goal

Playwright’s screenshot style option can apply CSS to the captured image, including elements in shadow DOM and inner frames. This changes what appears in the image; it does not make or persist a consent choice.

await page.screenshot({
  path: 'page.png',
  fullPage: true,
  style: '.cookie-banner, #consent-modal { display: none !important; }',
});

Use selectors that match the actual site. This is appropriate for a visual-only capture, not a test claiming that a visitor accepted or rejected consent.

3. Handle JavaScript dialogs separately

A JavaScript dialog is created by browser APIs such as alert(), confirm(), and prompt(). It is not an HTML cookie banner. Playwright automatically dismisses these dialogs if no dialog listener is registered. If you add a listener, it must accept or dismiss the dialog; otherwise the page can remain blocked.

page.on('dialog', async dialog => {
  console.log(`Dialog type: ${dialog.type()}`);
  await dialog.dismiss();
});

await page.goto('https://example.com');
await page.screenshot({ path: 'page.png' });

Use dialog.accept() instead if the test explicitly needs to accept the dialog. Register the handler before the action that might trigger it. See Playwright’s dialog guide and Dialog API.

4. Make the capture deterministic

  1. Start with a clean context. Create a new context for each independent run when you need predictable cookies and storage.
  2. Navigate, then act. Wait for the page state you need. domcontentloaded can be a useful starting point, but some sites render the banner later.
  3. Use a meaningful locator. Prefer the actual accessible role and name or a stable site-specific selector. Handle iframes explicitly.
  4. Wait on state, not an arbitrary pause. Check that the relevant banner is hidden or removed before capture. A fixed sleep can be too short on a slow run and waste time on a fast one.
  5. Capture after the intended choice has taken effect. If the page updates or reloads after the choice, wait for the resulting state before taking the screenshot.
  6. Keep the choice explicit. A screenshot that represents rejection or customized preferences should use that control and state, not an accept button.

For example, if the banner has a known container, wait for that container to disappear after clicking:

const banner = page.locator('#cookie-consent');
const rejectButton = page.getByRole('button', { name: /reject optional/i });

if (await banner.isVisible().catch(() => false)) {
  await rejectButton.click();
  await banner.waitFor({ state: 'hidden', timeout: 5000 });
}

await page.screenshot({ path: 'page.png' });

The selector and label are examples; inspect the page to identify the real elements. A hidden element and a removed element both satisfy the hidden wait state.

5. Troubleshooting

Symptom Likely cause Fix
The banner returns on every run Each run starts with a fresh context, or consent was not persisted in the state being reused. For a flow test, make the choice during each run. For a known repeatable state, seed the verified cookie or storage value in each fresh context.
Adding a cookie has no effect The site uses another cookie, local storage, server-side state, or different cookie attributes. Inspect the site’s behavior and verify the cookie name, value, domain or URL, path, expiry, and security attributes. Do not assume a universal value.
The locator cannot find the button The accessible name differs, the prompt has not appeared yet, or the UI is in an iframe or shadow root. Inspect the rendered page, use its actual accessible name or stable selector, wait for the relevant state, and target the correct frame when needed.
The screenshot still contains a late banner The capture happened before a delayed script displayed the prompt. Wait for the banner’s actual visibility and then for it to become hidden after the choice. Avoid relying on a guessed sleep duration.
Page actions hang after adding a dialog listener The listener did not resolve the JavaScript dialog. Call dialog.accept() or dialog.dismiss() in the handler. A dialog handler does not remove an HTML banner.
The banner is absent from the image but consent is unchanged Screenshot styling hid pixels without changing site state. Use the real control or verified stored state if the consent outcome matters. Treat CSS hiding as visual-only.
The click succeeds but the page still shows the prompt The clicked control may open a preferences panel, or the site may require a second confirmation. Follow the actual interaction through its final step and verify the banner or dialog container’s final state.

6. Performance, reliability, and cost

A fresh browser context makes runs easier to reason about, while reusing an already-consented context can avoid repeating the UI flow when that matches the task. The tradeoff is that reused state can make tests depend on earlier actions. For reliable screenshots, make the consent state explicit and wait on observable page state rather than a long fixed delay. Browser launch, navigation, site scripts, and image loading usually matter more to elapsed time than the final screenshot call; the required wait depends on the page.

Playwright itself is browser automation software, so the relevant costs are the compute and browser infrastructure used to run it. There is no universal duration or cost figure for this workflow; it depends on the page, browser environment, and capture frequency. A hosted screenshot API can remove the need to manage browser infrastructure, but compare its billing rules and consent handling to the behavior you need.

7. Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. One GET request returns a PNG, JPEG, WebP, or PDF. Its cleanup accepts cookie and consent banners like a visitor and removes 60+ known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Only clean shots are billed: bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and responses include X-Page-Verdict and X-Billed headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.

Use the API when you want a screenshot without setting up Playwright and its browser on your own infrastructure. The API accepts the parameter names other screenshot APIs use, which can make switching easier. See the ScreenshotNeo documentation for request options.

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}`);
if (!res.ok) throw new Error(`ScreenshotNeo request failed: ${res.status}`);
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));

The Python example requires requests. The Node.js example uses the built-in fetch and filesystem modules. ScreenshotNeo includes full-page capture with lazy images loaded, CSS selector capture, device presets and custom viewports, dark mode, PDF settings, custom CSS and JavaScript, click-before-capture, wait conditions, request blocking, custom headers and cookies, user agent, timezone and geolocation, caching, signed image links, async jobs with signed webhooks, bulk capture up to 100 URLs per call, a usage API, and an OpenAPI spec. These options are available on every plan.

Plans include 1,000 shots per month free with no card; Starter is $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. For a do-it-yourself browser flow, keep using Playwright; for a managed one-call capture, sign up for 1,000 free screenshots a month with no card.

8. FAQ

Not necessarily. Verify the site-specific behavior or stored state; hiding a banner in the screenshot alone does not save a preference.

Yes. Use the site’s corresponding control and complete any follow-up steps before capture. The implementation should match the outcome the screenshot is meant to represent.

No. A dialog handler handles JavaScript browser dialogs. An HTML banner must be handled as page content or represented through known site storage.

No. Cookie names and values are defined by each site. Confirm the site’s own consent behavior before seeding state.