ScreenshotNeo

BlogHow-to

How to Test a Website’s Cookie Banner Across Screenshot Baselines

Build repeatable visual tests for cookie banners by controlling consent state, pinning the browser environment, and checking behavior alongside screenshots.

By the ScreenshotNeo team4 October 202610 min read

To test a cookie banner across screenshot baselines, make each test start from an explicit consent state, render it in a pinned browser environment, compare it with a reviewed reference screenshot, and assert the controls’ behavior separately. A screenshot can show that the banner looks right; it cannot prove that a choice works or persists.

This guide uses Playwright Test. It covers first visit, preferences, accept, reject, and returning-visitor states, with runnable TypeScript, baseline review, environment stability, troubleshooting, and an optional screenshot API workflow.

Consent state is test data. Do not let a test inherit cookies or storage from another test. Decide which states your actual product supports, then give each one an independent setup.

State What to capture What to assert separately
First visit, no saved choice Banner, copy, and available actions Expected actions are visible and accessible
Preferences opened Settings panel and category controls Controls can be changed and saved
Reject or decline Resulting banner or confirmation state Choice is recorded and reflected on the next visit
Accept Resulting banner or confirmation state Choice is recorded as intended
Returning visit Initial page after a saved choice Banner behavior matches the product design
Reopen or withdraw Reopened settings, if supported Entry point works and updated choice takes effect

Implement only the states your site supports. If the banner differs by locale, viewport, or route, treat each supported variation as a separate test case when it changes the expected design or behavior.

2. Install Playwright Test and configure the project

For a new project, install the test runner and its browser binaries:

npm init -y
npm install --save-dev @playwright/test
npx playwright install

Add a script to package.json:

{
  "scripts": {
    "test:e2e": "playwright test"
  }
}

Create playwright.config.ts. The base URL, viewport, browser project, and snapshot location should be deliberate and consistent across baseline creation and comparison.

import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  fullyParallel: true,
  use: {
    baseURL: 'http://127.0.0.1:3000',
    viewport: { width: 1440, height: 900 },
    locale: 'en-US',
    colorScheme: 'light',
    timezoneId: 'UTC',
    ...devices['Desktop Chrome'],
  },
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
  ],
  webServer: {
    command: 'npm run dev',
    url: 'http://127.0.0.1:3000',
    reuseExistingServer: !process.env.CI,
  },
});

Adjust the server command and URL to your app. Pin the Playwright version, browser version, operating system, and fonts in the environment that creates and checks snapshots. Playwright notes that rendering can vary with the OS, browser version, settings, hardware, power source, and headless mode. Its guidance is to run comparisons in the same environment used to generate the reference images: Playwright visual comparisons.

3. Write isolated screenshot and behavior tests

The example below assumes the app exposes an accessible button named “Reject optional cookies,” a button named “Accept all cookies,” a button named “Cookie settings,” a preferences dialog, and a status element with data-testid="consent-status". Replace those selectors and the storage key with the real interface and state used by your application.

The setup clears cookies and origin storage before each test. It then visits the page and captures the banner or settings region. Playwright’s screenshot assertions wait for two consecutive screenshots to match before comparing with the expectation. The tests still need to make dynamic application content deterministic.

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

const banner = '[data-testid="cookie-banner"]';
const status = '[data-testid="consent-status"]';

test.beforeEach(async ({ context, page }) => {
  await context.clearCookies();
  await page.goto('/');
  await page.evaluate(() => {
    localStorage.clear();
    sessionStorage.clear();
  });
  await page.reload();
});

test('first visit shows the expected cookie banner', async ({ page }) => {
  await expect(page.getByRole('button', { name: 'Accept all cookies' })).toBeVisible();
  await expect(page.getByRole('button', { name: 'Reject optional cookies' })).toBeVisible();
  await expect(page.locator(banner)).toHaveScreenshot('cookie-banner-first-visit.png', {
    animations: 'disabled',
    caret: 'hide',
  });
});

test('preferences can be opened and captured', async ({ page }) => {
  await page.getByRole('button', { name: 'Cookie settings' }).click();
  const dialog = page.getByRole('dialog', { name: 'Cookie preferences' });
  await expect(dialog).toBeVisible();
  await expect(dialog).toHaveScreenshot('cookie-preferences-open.png', {
    animations: 'disabled',
    caret: 'hide',
  });
});

test('rejecting optional cookies updates and persists the choice', async ({ page }) => {
  await page.getByRole('button', { name: 'Reject optional cookies' }).click();
  await expect(page.locator(status)).toHaveText('Optional cookies rejected');
  await page.reload();
  await expect(page.locator(status)).toHaveText('Optional cookies rejected');
});

test('accepting cookies updates and persists the choice', async ({ page }) => {
  await page.getByRole('button', { name: 'Accept all cookies' }).click();
  await expect(page.locator(status)).toHaveText('All cookies accepted');
  await page.reload();
  await expect(page.locator(status)).toHaveText('All cookies accepted');
});

test('a returning visitor sees the intended saved-choice state', async ({ page }) => {
  await page.getByRole('button', { name: 'Reject optional cookies' }).click();
  await page.reload();
  await expect(page.locator(banner)).toBeHidden();
  await expect(page.locator(status)).toHaveText('Optional cookies rejected');
});

If your app stores consent in a cookie, use the UI to establish the saved choice in the test, as above, or set the precise test cookie with the browser context before navigation. Avoid guessing the production cookie format: use the application’s actual schema. For setup that requires state before the first page render, Playwright’s isolated browser contexts and storage-state facilities can provide separate test data; see browser contexts and authentication and storage state.

Use role and accessible-name locators for controls where possible. This checks that the test can find the same user-facing actions assistive technology can identify. A screenshot assertion on the banner alone can be narrower and less noisy than comparing the whole page; capture the whole page as well when the banner’s position or overlay affects surrounding layout.

4. Create and review the baseline

  1. Run the tests once in the pinned environment. Playwright creates expected screenshots for new snapshot assertions.
  2. Open each generated image and check that it represents the intended state, viewport, locale, and design.
  3. Commit the reviewed baseline with the test code. Keep the platform and browser context that produced it documented in the project configuration or CI setup.
  4. On later runs, inspect the actual image and diff whenever an assertion fails. Determine whether the cause is a real design change, state leakage, uncontrolled content, or environment drift.
  5. Update a baseline only after reviewing and accepting the visual change. Do not refresh snapshots automatically just to make a failing run pass.

Playwright creates a reference screenshot on first run and compares later runs against it. Separate baselines are appropriate for browser or platform variants that are intentionally in your support matrix. A Chromium baseline does not establish that Firefox or WebKit renders identically.

5. Control visual noise without hiding regressions

Screenshot assertions support capture controls such as animation handling, caret hiding, masks, stylesheets, and visual-difference thresholds. Apply them only to known sources of noise. Never mask or hide the banner’s text, choices, or state indicator when those are the subject of the test.

  • Animations: use animations: 'disabled' to avoid transition timing differences. Ensure disabling animation does not remove the state you intend to verify.
  • Caret: hide the text caret if a focused input would otherwise produce a blinking difference.
  • Mask: mask a known, unrelated dynamic region only if it is outside the assertion. A broad mask can conceal layout overlap.
  • Stylesheet: a capture-only stylesheet can suppress known volatile decoration, but avoid changing the banner or its layout.
  • Thresholds: pixel or color tolerances can account for minor rendering variation. Keep them small and explainable; there is no universal safe threshold.

See the Playwright PageAssertions API for the options available in the installed version. The assertion’s stability wait helps with settling but cannot make live content, network responses, or application data deterministic.

6. Verify what the screenshot cannot prove

After each interaction, assert the visible outcome and the resulting application-controlled state. For a consent-sensitive flow, also check the relevant requests or application behavior under controlled test conditions. For example, if optional analytics must not load after rejection, observe the relevant request in the test and assert that it was not sent. The exact check depends on how the site loads scripts and records consent; there is no universal consent-manager implementation.

Visual correctness and functional correctness are separate. The EDPB Cookie Banner Taskforce report discusses reject-option availability and says pre-ticked opt-in boxes do not lead to valid consent. It quotes GDPR recital 32: “Silence, pre-ticked boxes or inactivity should not therefore constitute consent.” Treat that report as regulatory context, not a complete statement of every jurisdiction’s law; test the requirements that apply to your service and seek appropriate legal review. EDPB Cookie Banner Taskforce report.

7. Keep tests repeatable in CI

  • Run baseline generation and comparison on the same pinned operating system, browser version, fonts, locale, timezone, viewport, and color scheme.
  • Use an independent browser context and explicit consent setup for every test. Do not share mutable browser storage between parallel cases.
  • Control data and responses that affect the rendered page. Playwright recommends testing what you control and using network routing to supply test responses when needed: Playwright best practices.
  • Keep browser projects intentional. Add separate snapshots for additional browsers or platforms when those are part of the support matrix.
  • On a diff, inspect the actual image, expected image, and test state before deciding whether to change code or update a baseline.

8. Troubleshooting common failures

Symptom Likely cause Fix
Banner is missing on a first-visit test A cookie or local-storage value survived setup, or the page was captured before the app initialized. Clear cookies and origin storage in a fresh context, reload, and wait for the banner locator to become visible. Check whether state is also stored in IndexedDB or server-side.
Every run produces a screenshot diff Browser, OS, fonts, viewport, animation, or live content differs from the baseline environment. Pin the rendering environment and test data. Disable relevant animations and control external responses; do not widen the threshold before identifying the source.
Only CI fails CI uses a different browser build, OS image, font set, or headless configuration. Generate and compare baselines in the same CI image, and ensure the Playwright browser binaries are installed for the locked package version.
Accept/reject test passes visually but state is wrong The test checks only pixels, or the app updates the banner without persisting the choice. Add assertions for the resulting status and reload the page to verify persistence. Check relevant application behavior separately.
Snapshot updates unexpectedly A test ran in snapshot-update mode, or a baseline was refreshed without review. Use normal comparison mode in CI, review diffs, and update expectations only for an intentional design change.
Locator times out for a banner action Accessible name differs, the control is inside a frame, or another state is showing. Inspect the rendered state and accessible name, use the correct frame locator if needed, and establish the intended consent state before interacting.
Parallel tests interfere with one another Tests share a browser context, account, or server-side consent record. Use independent contexts and unique test identities or controlled fixtures for shared server state.

9. Performance, reliability, and maintenance

Focused banner snapshots usually compare less content and produce easier-to-review diffs than whole-page screenshots. Whole-page captures are useful when the banner changes scrolling, overlays content, or shifts page layout. Choose the capture area to match the regression risk rather than capturing everything by default.

Running several browser projects and states increases execution time and baseline files. Keep the matrix tied to supported behavior and rendering environments. Stable fixtures and controlled network responses reduce retries and false failures. A retry can help diagnose intermittent infrastructure problems, but it should not substitute for understanding a flaky test.

Visual regression tests have ongoing maintenance cost: reviewed screenshots must change when the design intentionally changes, and browser upgrades can alter rasterization. Record why a baseline was updated in the associated change review. Avoid overly permissive thresholds because they can let meaningful changes to consent copy, control visibility, or layout pass unnoticed.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. A single request captures a URL as PNG, JPEG, WebP, or PDF. For a browser-driven baseline suite, keep your Playwright tests; for repeatable captures of a page or for an existing API workflow, the screenshot call is a simpler capture step. See the ScreenshotNeo documentation.

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(`Screenshot request failed: ${res.status}`);
const bytes = new Uint8Array(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', bytes));

Cookie banners, newsletter popups, and chat widgets are removed before the shot; each cleanup step can be turned off. Bot checks, blank pages, and failed loads are never billed, and responses identify page verdict and billing status. An MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 shots.

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

FAQ

Should I test the banner in every browser?

Test each browser and platform that your product intentionally supports. Keep separate, reviewed baselines where rendering environments differ.

No. It shows visual similarity for the captured state. Test behavior and review the requirements that apply to your service separately.

Should I snapshot the whole page or only the banner?

Snapshot the banner for focused appearance checks. Include the whole page when you need to catch effects on overlays, page position, or surrounding content.

Can I update snapshots automatically in CI?

CI can generate candidate images, but a person should review intentional changes before replacing committed expectations.