Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

Handle a GDPR cookie popup according to the test’s purpose: use a new BrowserContext and interact with the banner when testing consent itself; use a reviewed storageState fixture or a supported API setup when testing a feature that should start with a known preference. Locate controls by accessible role and name, verify stored state and network behavior, and keep every test’s cookies isolated.

Choose the test purpose first

A cookie-banner test and a product-feature test need different setup. Mixing them is a common source of flaky tests and misleading privacy assertions.

Testing the consent experience

Create a fresh context for each test, open the page without saved state, wait for the banner, choose Accept, Reject, or a preferences option, and assert the resulting behavior. A fresh context prevents a previous test’s consent cookie or local storage from hiding the banner.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Testing a downstream feature

Do not make every feature test rediscover a third-party banner. Run a small, dedicated setup test that makes a reviewed choice, save its state, and load that state only in tests that intentionally require it. Playwright’s isolation model is designed so each test has independent cookies, storage, and session state.

Use resilient locators

Prefer what a user can perceive: an accessible dialog, button role, and accessible name. Generated CSS classes and long XPath chains often change when a consent-management platform updates its markup.

const banner = page.getByRole('dialog', { name: /cookie|privacy|consent/i });
await banner.getByRole('button', { name: /accept all/i }).click();

If the application does not expose a dialog role, scope to a stable container owned by your application and use getByRole, getByText, or a contract test id inside it. Keep button names aligned with the actual localized UI; “Allow all”, “Accept optional cookies”, and “Necessary only” are not interchangeable.

Wait for actionability instead of sleeping

Locator actions and assertions automatically wait for visibility and actionability. Use an assertion when presence is part of the test:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
await expect(banner).toBeVisible();
await banner.getByRole('button', { name: /reject all|necessary only/i }).click();
await expect(banner).toBeHidden();

A fixed timeout can pass on a fast run and fail on a slow one. If a banner is intentionally delayed, wait for the locator or a documented application event rather than guessing a number of milliseconds.

Deal with iframes and shadow DOM

Banner inside an iframe

Identify the frame by a stable URL, title, or another contract controlled by your team, then apply the same role-and-name strategy to its frame locator:

const consentFrame = page.frameLocator('iframe[title="Cookie preferences"]');
const frameBanner = consentFrame.getByRole('dialog', { name: /cookie|privacy|consent/i });
await frameBanner.getByRole('button', { name: /accept all/i }).click();

A vendor-generated frame selector can change. Prefer a stable title or URL contract and add a focused contract test if the integration is external.

Banner in a shadow root

Use normal Playwright locators where shadow-DOM support applies, then verify the component’s accessible name. If the component is closed or exposes no usable semantics, ask the application team for a supported test hook rather than reaching through implementation details.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Persist and reuse a deliberate consent choice

After a controlled interaction, save the context’s cookies and origin storage:

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

test('create rejected-consent state', async ({ browser }) => {
  const context = await browser.newContext();
  const page = await context.newPage();
  await page.goto('/');
  const banner = page.getByRole('dialog', { name: /cookie|privacy|consent/i });
  if (await banner.isVisible()) {
    await banner.getByRole('button', { name: /reject all|necessary only/i }).click();
  }
  await expect(banner).toBeHidden();
  await context.storageState({ path: 'playwright/.auth/consent-rejected.json' });
  await context.close();
});

test('feature starts with rejected consent', async ({ browser }) => {
  const context = await browser.newContext({
    storageState: 'playwright/.auth/consent-rejected.json'
  });
  const page = await context.newPage();
  await page.goto('/account');
  // Test the feature, not the banner setup.
  await context.close();
});

Storage state can contain cookies and headers capable of impersonation. Keep these files outside version control, restrict access in CI, and generate separate accept and reject fixtures when both paths matter.

Seed a cookie only when its contract is known

context.addCookies() accepts a URL or a domain/path plus attributes such as httpOnly, secure, sameSite, expiry, and partition keys:

await context.addCookies([{
  name: 'consent',
  value: 'necessary-only',
  url: 'https://app.example.test',
  path: '/',
  secure: true,
  sameSite: 'Lax'
}]);

Do not guess a consent-management platform’s cookie name or value. Obtain the exact contract from your application documentation or a controlled acceptance test. An incorrect seed can hide the banner while leaving optional tracking enabled.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use API setup when the application supports it

When your service exposes a supported preference or consent endpoint, prepare state through that API instead of clicking a vendor UI in every test. Playwright’s context-associated APIRequestContext shares the browser context’s cookie jar, so a request can establish state that the subsequent page uses.

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

test('prepare consent through the supported endpoint', async ({ browser }) => {
  const context = await browser.newContext();
  const request = await context.request;
  const response = await request.post('/api/privacy/consent', {
    data: { analytics: false, marketing: false }
  });
  expect(response.ok()).toBeTruthy();
  const page = await context.newPage();
  await page.goto('/');
  await expect(page.getByRole('dialog', { name: /cookie|privacy|consent/i })).toBeHidden();
  await context.close();
});

Use this only for an endpoint your application supports and documents. Keep one UI test that proves the banner produces the same state.

Verify behavior, not just the click

Accept path

  • Assert that the preference cookie or storage value is present and has the expected value.
  • Assert that optional scripts or requests appear only after acceptance.
  • Check that essential application functionality still works.

Reject path

  • Assert that optional tracking remains disabled.
  • Assert that required functionality remains available.
  • Confirm the banner disappears and the rejection choice persists after reload.

For your own services, inspect requests and responses or use controlled routes to prove the gate. Avoid making tests depend on live third-party analytics or advertising endpoints; those systems can be unavailable, reordered, or changed without notice.

test('reject blocks optional collection', async ({ page }) => {
  await page.goto('/');
  const banner = page.getByRole('dialog', { name: /cookie|privacy|consent/i });
  await banner.getByRole('button', { name: /reject all|necessary only/i }).click();
  await expect(page).toHaveURL(///);
  const cookies = await page.context().cookies();
  const consent = cookies.find(cookie => cookie.name === 'consent');
  expect(consent?.value).toBe('necessary-only');
});

Build a reusable fixture

A fixture keeps setup in one place while preserving a clear lifetime for the context and saved state:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import { test as base } from '@playwright/test';

export const test = base.extend<{ consentContext: void }>({
  consentContext: async ({ browser }, use) => {
    const context = await browser.newContext();
    const page = await context.newPage();
    await page.goto('/');
    const banner = page.getByRole('dialog', { name: /cookie|privacy|consent/i });
    if (await banner.isVisible()) {
      await banner.getByRole('button', { name: /reject all|necessary only/i }).click();
    }
    await context.storageState({ path: 'playwright/.auth/consent-rejected.json' });
    await context.close();
    await use();
  }
});

Adapt the names and structure to your accessible UI. This pattern tests browser behavior; it is not proof that a banner is legally compliant.

Compliance boundaries you must test separately

The UK Information Commissioner’s Office says organisations should tell people cookies are present, explain what they do and why, and obtain consent for cookies outside a strictly necessary exception. It also recommends a way for people to control non-essential cookies and keeping consent records. CNIL states that consent for non-exempt trackers must generally come before their deposit or reading and must be free, specific, informed, and unambiguous.

Jurisdiction, tracker type, and enforcement expectations differ. A passing Playwright click is not legal evidence. Involve the privacy counsel or regulator guidance applicable to your deployment, and retain the stored preference and network assertions that demonstrate the implemented behavior.

Troubleshooting flaky cookie tests

The banner is not found

Check whether a previous context or persisted profile already contains consent. Confirm the actual role, accessible name, locale, and whether the banner is in an iframe or shadow root. Capture the page accessibility snapshot during diagnosis instead of switching immediately to XPath.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The click is intercepted or times out

The banner may be covered by an animation, another overlay, or a delayed frame. Wait for the scoped locator to be visible and actionable; remove animation only through a test-controlled style if your application supports that contract. Do not force-click a different element, because that can produce a false pass.

The choice disappears after reload

Inspect cookies and origin storage after the click. Verify domain, path, secure and same-site attributes, expiration, and whether the application writes state asynchronously. Wait for the application’s stored-state signal before reloading.

Optional requests happen before consent

That is an application defect or an incorrectly isolated test, not a selector problem. Start from a new context, record early requests with a controlled route, and fix initialization so non-essential trackers are gated before deposit or reading.

Tests pass locally but fail in CI

Use independent contexts, avoid live third-party dependencies, and make iframe or locale contracts explicit. Ensure saved state is generated for the same origin and deployment configuration used by CI, and never share a mutable profile between workers.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Performance, reliability, and maintenance

  • Run the banner UI flow once per relevant locale and consent outcome; reuse reviewed state for broad feature coverage.
  • Keep accept and reject fixtures separate, versioned by application contract, and regenerated when consent behavior changes.
  • Prefer a small number of semantic locators over one giant selector that encodes vendor markup.
  • Review Playwright APIs, consent-management markup, cookie names, and regulator guidance when upgrading dependencies or changing regions.
  • Separate essential functionality assertions from optional-tracking assertions so a third-party outage does not fail unrelated tests.

Or skip the browser setup

If your goal is a clean visual capture rather than testing the banner interaction, ScreenshotNeo accepts the consent banner like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before the shot. It bills only clean shots: bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and each response identifies the page verdict and billing result in headers. Its MCP server gives Claude, Cursor, and other MCP clients take_screenshot, get_page_info, and capture_pdf tools.

One request is enough:

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

See the complete parameter reference in the ScreenshotNeo documentation. Equivalent clients:

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}`);

ScreenshotNeo includes full-page and element capture, device and viewport controls, custom CSS and JavaScript, cookie and header settings, waits, blocking rules, PDFs, caching, signed links, asynchronous webhooks, bulk capture, and an OpenAPI spec. Every plan includes every feature: 1,000 shots per month are free with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.

Frequently Asked Questions

Should I accept cookies automatically in every Playwright test?

No. Interact with the banner in tests that validate consent. For unrelated feature tests, load a reviewed state fixture or use a supported application API.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Can I use a vendor’s cookie name in a fixture?

Only when that name and value are part of your application’s documented contract. Otherwise create state through the real UI or a supported endpoint.

Does a successful Reject button click prove GDPR compliance?

No. You must also verify persistence, blocking of non-essential tracking before consent, required functionality, records, and the rules applicable to the deployment jurisdiction.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.