ScreenshotNeo

BlogHow-to

How to Handle Cookie Warnings in Websites and Automated Tests

Test cookie banners as part of consent behavior: start with clean state, check storage before and after each choice, and keep unrelated browser tests reproducible.

By the ScreenshotNeo team4 October 202611 min read

Handle cookie warnings by testing them as part of the website’s behavior. For a first-visit test, use a fresh browser context or profile, verify the banner appears, and check that consent-requiring cookies and other covered storage are absent before a choice. Then test acceptance, rejection, preferences, persistence, and withdrawal. For unrelated end-to-end tests, initialize a documented consent state as setup while keeping dedicated tests for the banner and its effects.

A banner disappearing is not proof that consent works correctly. Inspect browser storage and, where relevant, verify that optional scripts or network requests do not start before consent. Cookie checks alone are not a complete privacy audit because relevant storage and access technologies extend beyond cookies.

1. Define what each test should prove

Start by separating consent behavior from workflows that merely happen to encounter the banner. The first group verifies the consent system; the second verifies other product behavior under a known state.

Scenario Starting state Evidence to collect
First visit Fresh isolated context or a complete application reset Banner is visible; required service still works; optional storage and behavior are not active before a choice where prior consent applies.
Accept Fresh state User-like accept action changes banner state and enables only the expected categories.
Reject Fresh state Choice is retained; optional categories remain disabled.
Preferences Fresh state Each category can be selected separately and the selected behavior matches that category.
Return visit Saved consent state The saved choice is respected and the banner behaves as intended.
Change or withdraw Previously saved choice Settings are reachable, the choice changes, and storage or optional behavior responds.
Unrelated workflow Known, documented consent setup The product workflow is tested without accidentally claiming that the first-visit flow works.

Consent requirements depend on jurisdiction, purpose, exemptions, and the facts. EU business guidance gives strictly necessary cookies used to provide a service explicitly requested by the user as an example of cookies that may be exempt. Analytics, behavioral advertising, and other optional tracking often require consent, depending on the applicable rules. Do not call something necessary only because it is convenient to the operator. The EU guidance says information about cookies and their purposes should be provided, choices can be specific by purpose, and withdrawal should be as easy as acceptance. UK ICO guidance describes consent as an informed positive action and says non-essential cookies must not be set before consent under its guidance. These are jurisdiction-specific examples, not a universal compliance conclusion. EU business guidance on cookies; ICO cookies and similar technologies; see also the ICO’s storage and access technologies guidance, finalised 29 April 2026.

2. Start from a genuinely clean state

Browser state persists unless the test deliberately isolates or resets it. A stale consent cookie can hide the banner and make a first-visit test pass without exercising the first visit.

Playwright: create a fresh context

A new non-persistent browser context is isolated and does not write browsing data to disk. Use one context per test when consent state must not leak between tests. Playwright documents both isolated contexts and cookie inspection and clearing in its BrowserContext API.

import { test, expect } from '@playwright/test';

test('first visit shows the banner and does not pre-set optional cookies', async ({ browser }) => {
  const context = await browser.newContext();
  const page = await context.newPage();

  await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
  await expect(page.getByRole('dialog', { name: /cookie|privacy|consent/i })).toBeVisible();

  const cookiesBeforeChoice = await context.cookies('https://example.com');
  const optionalCookieNames = new Set(['analytics_id', 'ad_tracking']);
  expect(cookiesBeforeChoice.some(cookie => optionalCookieNames.has(cookie.name))).toBe(false);

  await context.close();
});

Replace the example URL, accessible dialog locator, and optional cookie names with the application’s actual contract. A banner may not use a dialog role; prefer an accessible role and name when available, or a stable test identifier owned by the application. Avoid selectors tied to generated CSS classes.

Playwright: clear cookies in an existing context

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

Clearing cookies is narrower than resetting a browser profile. It does not necessarily clear local storage, session storage, IndexedDB, service-worker state, or a server-side preference. If the application stores consent elsewhere, use a test fixture that resets those stores too or create a wholly new context/profile. For tests where storage must be preloaded intentionally, Playwright authentication guidance shows using a clean storage state, including storageState: undefined when creating a context: Playwright authentication.

Selenium: delete cookies before the first-visit check

Selenium’s cookie APIs operate on the current browsing context and domain. Navigate to the site before deleting its cookies. For stronger isolation, start a fresh WebDriver session/profile per test instead of sharing a long-lived session. See Selenium’s cookie documentation.

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

options = webdriver.ChromeOptions()
options.add_argument('--headless=new')
driver = webdriver.Chrome(options=options)

try:
    driver.get('https://example.com')
    driver.delete_all_cookies()
    driver.get('https://example.com')

    banner = WebDriverWait(driver, 10).until(
        EC.visibility_of_element_located((By.CSS_SELECTOR, '[role="dialog"]'))
    )
    assert banner.is_displayed()

    cookie_names = {cookie['name'] for cookie in driver.get_cookies()}
    optional_cookie_names = {'analytics_id', 'ad_tracking'}
    assert cookie_names.isdisjoint(optional_cookie_names)
finally:
    driver.quit()

The explicit second navigation ensures the page is loaded after clearing state. A new driver session is preferable for repeatable first-visit coverage because deletion of cookies alone does not reset every browser store.

Use the banner’s visible controls, not direct cookie injection, to test the decision flow. Directly setting a consent cookie is appropriate for setup in an unrelated workflow test; it does not verify the user interface, save operation, or downstream behavior.

import { test, expect } from '@playwright/test';

test('rejecting optional cookies retains the choice', async ({ browser }) => {
  const context = await browser.newContext();
  const page = await context.newPage();
  await page.goto('https://example.com');

  await page.getByRole('button', { name: /reject optional|reject all/i }).click();
  await expect(page.getByRole('dialog', { name: /cookie|privacy|consent/i })).toBeHidden();

  const cookiesAfterReject = await context.cookies('https://example.com');
  expect(cookiesAfterReject.some(cookie => cookie.name === 'analytics_id')).toBe(false);

  await page.reload();
  await expect(page.getByRole('dialog', { name: /cookie|privacy|consent/i })).toBeHidden();

  await context.close();
});

For an accept case, click the explicit accept control and assert the expected categories appear or activate. For granular preferences, open settings, change one category at a time, save, and check that only the matching behavior changes. For withdrawal, reopen settings from the persistent privacy control, change the choice, then inspect both the browser state and relevant optional requests or scripts.

Define the expected cookie names, purposes, and category mapping from the application’s implementation. Some preferences are represented in local storage or a server-side profile rather than a cookie. Assert those stores through application-owned test hooks where possible, and inspect network events when the requirement is that optional tracking requests do not happen before consent. Keep such assertions targeted: third-party scripts can make noisy requests unrelated to the category being tested.

4. Make waits deterministic

Consent managers may load asynchronously, but fixed sleeps create slow and flaky tests. Wait for a meaningful state: the banner becomes visible, a named control becomes enabled, a saved-state indicator appears, or a specific request completes. If a vendor’s live configuration, geolocation, or experiment assignment makes the banner nondeterministic, configure a test-specific consent environment or stub the integration at a stable boundary.

Use accessible roles and visible names for controls where possible. Give parallel tests independent contexts or profiles so one test’s acceptance cannot hide another test’s first visit. When investigating a failure, record the URL, browser version, relevant storage state, console errors, and the banner’s accessible structure.

5. Verify storage beyond cookies

A cookie-only assertion can miss consent state in local storage, session storage, IndexedDB, service workers, server-side account preferences, or optional scripts that have already run. Inventory the mechanisms the application actually uses, then define checks for the relevant ones.

For a web application you own, a narrowly scoped diagnostic can inspect browser-visible stores:

const storageSnapshot = await page.evaluate(async () => ({
  localStorage: Object.fromEntries(
    Array.from({ length: localStorage.length }, (_, i) => {
      const key = localStorage.key(i);
      return [key, localStorage.getItem(key)];
    })
  ),
  sessionStorage: Object.fromEntries(
    Array.from({ length: sessionStorage.length }, (_, i) => {
      const key = sessionStorage.key(i);
      return [key, sessionStorage.getItem(key)];
    })
  ),
  databases: typeof indexedDB.databases === 'function' ? await indexedDB.databases() : 'enumeration unavailable'
}));
console.log(storageSnapshot);

Use this for diagnosis, not as a universal compliance scanner. Browser APIs and visibility differ, and a test cannot infer legal purpose or compliance from a storage dump alone. The ICO’s current storage and access guidance covers technologies beyond cookies. GOV.UK service guidance advises minimizing the number of cookies and stored information; that page was last updated 8 February 2021, so treat its technical advice as dated service guidance and verify current security requirements. GOV.UK: working with cookies and similar technologies.

6. Keep unrelated end-to-end tests fast and repeatable

Most checkout, dashboard, or content workflow tests do not need to re-test the consent banner. Set up a known consent choice with application-supported test fixtures or a deliberate saved state, and document exactly what it means. Retain separate tests for first visit, accept, reject, preferences, return visits, and withdrawal.

Prefer the application’s consent setup helper or a controlled test account over hard-coding a vendor-specific cookie. If a cookie must be seeded, it must match the site’s actual name, value, domain/path scope, and format. Be careful with HTTP-only cookies, secure cookies on non-HTTPS test origins, and partitioned cookies: not every cookie can be created through page JavaScript, and browser scope rules still apply. Never use a preloaded “accept” state as evidence that the initial consent flow works.

7. Troubleshooting

Symptom Likely cause Fix
Banner does not appear in the first-visit test Stale consent state remains in cookies, another browser store, profile data, or the server-side account. Use a fresh context/profile and reset application-owned state; check where the consent manager persists its choice.
Banner appears intermittently Asynchronous third-party loading, region or geolocation rules, A/B tests, or a fixed sleep. Use deterministic test configuration and wait for the banner/control condition rather than sleeping for a fixed duration.
Cookie is absent but tracking still occurs The identifier or preference may live in another store, or the script/request can run without the cookie assertion noticing. Check local/session storage, IndexedDB, service-worker behavior, and targeted network events before and after the choice.
Cookie appears before the test can inspect it Application code or a tag initializes before consent, or the check runs after an action that already accepted. Capture state immediately after initial navigation and before interacting; observe relevant requests from page creation.
“Strictly necessary” cookie causes a failed assertion The test’s denylist treats every cookie as optional. Maintain an explicit inventory by purpose. Assert only the cookies and behavior that should be blocked in that scenario.
Selenium cannot delete or read the expected cookie The driver is not on the cookie’s domain, the cookie belongs to another host/path, or state was reset in a different context. Navigate to the relevant origin, inspect cookie domain and path, and use a fresh driver/profile for isolation.
Preloaded consent does not persist The state is malformed, scoped to the wrong origin, expires, or is overwritten by application initialization. Use the app’s supported test setup, verify the storage format and scope, and reload to confirm persistence.
Parallel tests occasionally hide the prompt Tests share a browser profile, account, or server-side preference. Give tests isolated contexts and distinct test data; share state only when persistence itself is under test.

8. Reliability, runtime, and maintenance

  • Isolation: A fresh context/profile costs some setup time but prevents order-dependent consent state. Reuse contexts only when shared state is part of the scenario.
  • Runtime: Avoid repeating the full consent flow in every unrelated test. Keep a focused consent suite and use a known state for broad workflow coverage.
  • Stability: Prefer stable accessible controls, deterministic configuration, and condition-based waits over generated selectors and fixed delays.
  • Maintenance: Keep a purpose-to-storage inventory alongside the application’s consent configuration. Update assertions when vendors, categories, or persistence mechanisms change.
  • Evidence: Preserve failure artifacts that show the initial banner, selected choice, relevant storage, and targeted network events. Avoid logging sensitive cookie values unnecessarily.

For production behavior, minimize stored information and retention, scope cookies to the intended domain, and apply secure cookie attributes in line with current platform and security requirements. A test can show observed behavior for its browser and test conditions; it cannot determine whether a particular implementation complies with every applicable law.

9. Or skip the browser setup

If your task is to capture a page for visual review rather than test its consent logic, ScreenshotNeo is a website screenshot API and MCP server for developers. One GET request returns a PNG, JPEG, WebP, or PDF; its clean-capture flow accepts cookie/consent banners like a visitor and removes 60+ known consent platforms, newsletter popups, and chat widgets before capture. Each cleanup step can be turned off. Bot checks/CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and billing status. Its MCP server gives AI agents tools for screenshots, page info, and PDF capture.

cURL:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp

Python:

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
    timeout=90,
)
open("shot.webp", "wb").write(r.content)

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await require('node:fs/promises').writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

See the ScreenshotNeo API documentation for options. Cookie banners, popups, and chat widgets are removed before the shot; bot checks, blank pages, and failed loads are never billed; an MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000. Sign up for 1,000 free screenshots a month, no card required.

FAQ

Should every automated test click “Accept”?

No. Test accept, reject, preferences, persistence, and withdrawal in dedicated cases. Use a known state only as setup for unrelated workflows.

Does clearing cookies guarantee a first-visit state?

No. Consent can persist in other browser storage or on the server. Use a fresh context/profile or reset every state location the application uses.

No. It provides evidence about observed browser behavior. Applicable requirements depend on jurisdiction, purpose, exemptions, and current law; consult the relevant official guidance for the jurisdiction and date.

Should screenshots dismiss the banner?

For a visual capture, that depends on what the screenshot is meant to show. Keep consent-flow tests in a real browser automation suite; a screenshot service is for capturing the resulting page, not proving the underlying consent implementation.