Playwright Screenshot with Cookie Consent Banner Dismissed
Dismiss a site’s cookie banner with Playwright before taking a screenshot. Includes JavaScript and Python examples, visual-only alternatives, and troubleshooting.
To take a Playwright screenshot after dismissing a cookie consent banner, identify the consent control used by the target site, wait for it to appear, click it, verify that the banner is gone, and then capture the page. There is no universal cookie-banner selector or consent action: use the site’s actual reject, accept, or close control according to the behavior your automation is meant to test.
This example uses Playwright’s JavaScript API. It assumes the target page has a button whose accessible name is “Reject optional cookies.” Replace that locator with one that matches the site you are automating.
1. Install Playwright and take a screenshot
For a one-off script, install Playwright and its Chromium browser:
npm init -y
npm install playwright
npx playwright install chromium
Save the following as screenshot.js. Set TARGET_URL to the page under test, and update the button name and banner locator to match that site.
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } });
try {
await page.goto(process.env.TARGET_URL || 'https://example.com', {
waitUntil: 'domcontentloaded',
timeout: 30_000,
});
// Match the visible consent action on this particular site.
const rejectButton = page.getByRole('button', {
name: 'Reject optional cookies',
exact: true,
});
// Some pages have no banner because consent is already stored or the
// banner is not shown in this region. Wait briefly, then proceed if absent.
if (await rejectButton.isVisible({ timeout: 5_000 }).catch(() => false)) {
await rejectButton.click();
// Prefer checking a site-specific banner container if it is available.
await page.locator('#cookie-consent-banner').waitFor({
state: 'hidden',
timeout: 5_000,
}).catch(async () => {
// If the site has no stable container selector, at minimum wait for
// the button to disappear after the click.
await rejectButton.waitFor({ state: 'hidden', timeout: 5_000 });
});
}
// Viewport screenshot. Use fullPage: true to include content below the fold.
await page.screenshot({ path: 'page.png', fullPage: false });
} finally {
await browser.close();
}
})().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Run it with TARGET_URL=https://your-site.example node screenshot.js. The optional banner check handles pages where consent is already persisted or a banner is not shown. For a strict test, treat a missing banner as a test failure instead of proceeding.
2. Choose the right consent interaction
Dismissal is a real page interaction. Choose the control that matches the scenario you want to represent: a reject control for a rejection flow, an accept control for an acceptance flow, or a close control if the site provides one and the test is about closing the notice. Do not click “accept all” merely to make the banner disappear if that is not the intended behavior.
For a predictable overlay, Playwright recommends explicitly waiting for it and dismissing it as part of the normal test flow. Use a locator based on the site’s accessible role and name when possible, or a stable test id or site-specific selector. Avoid relying on generic selectors such as the first button on the page.
Make the locator specific
- Accessible role and name:
page.getByRole('button', { name: 'Reject optional cookies' })is readable and usually resilient to layout changes. - Test id:
page.getByTestId('reject-cookies')works well when the site exposes a stable test id. - CSS selector:
page.locator('.consent-banner button.reject')can be appropriate when the site’s markup is stable, but is coupled to its implementation. - Scope to the banner: locate the banner container first, then find the button inside it when the page has similarly named controls elsewhere.
If the banner is rendered in an iframe, locate the relevant frame and interact there. If the banner appears only after a delay, wait for its control rather than sleeping for a fixed interval. A fixed delay is useful only when the site’s behavior requires it and the duration is known.
3. Python example
Install the Python package and browser:
python -m pip install playwright
python -m playwright install chromium
Save as screenshot.py:
import os
from playwright.sync_api import sync_playwright, TimeoutError as PlaywrightTimeoutError
TARGET_URL = os.environ.get("TARGET_URL", "https://example.com")
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page(viewport={"width": 1440, "height": 1000})
try:
page.goto(TARGET_URL, wait_until="domcontentloaded", timeout=30_000)
reject_button = page.get_by_role(
"button", name="Reject optional cookies", exact=True
)
try:
reject_button.wait_for(state="visible", timeout=5_000)
except PlaywrightTimeoutError:
# No banner appeared; decide whether that is acceptable for your job.
pass
else:
reject_button.click()
banner = page.locator("#cookie-consent-banner")
try:
banner.wait_for(state="hidden", timeout=5_000)
except PlaywrightTimeoutError:
reject_button.wait_for(state="hidden", timeout=5_000)
page.screenshot(path="page.png", full_page=False)
finally:
browser.close()
Run it with TARGET_URL=https://your-site.example python screenshot.py. The locator and banner selector are examples; substitute the actual site’s controls.
4. Hide the banner only in the image
If your goal is presentation and you do not need to exercise or record the consent flow, a screenshot stylesheet or mask can hide the overlay in the output. This does not click a consent control, record a consent choice, or prove that the banner’s behavior was handled.
A capture-only stylesheet can hide a known element:
await page.screenshot({
path: 'page.png',
fullPage: true,
style: '#cookie-consent-banner { display: none !important; }',
});
Or mask the banner while capturing:
await page.screenshot({
path: 'page.png',
mask: [page.locator('#cookie-consent-banner')],
maskColor: '#777777',
});
Use a selector that actually identifies the banner. A mask covers its region in the image; a stylesheet changes its appearance during capture. Neither approach is a substitute for interacting with the page when the test concerns consent behavior.
5. Screenshot scope and output options
| Need | Option or approach |
|---|---|
| Capture the visible viewport | Default screenshot behavior; set the viewport explicitly for repeatability. |
| Include content below the fold | fullPage: true. |
| Save JPEG or WebP | Set type: 'jpeg' or type: 'webp' where supported by the installed browser; use quality for lossy formats. |
| Capture a particular element | Call locator.screenshot({ path: 'element.png' }) on the target element. |
| Capture at device scale | Set the browser context’s deviceScaleFactor when creating the context or page. |
| Reduce animation differences | Use the screenshot API’s animations option, such as animations: 'disabled', for a stable capture. |
| Hide or cover content just for capture | Use the screenshot style option or mask and maskColor. |
For example, a full-page WebP capture can be written as:
await page.screenshot({
path: 'page.webp',
type: 'webp',
quality: 85,
fullPage: true,
animations: 'disabled',
});
Check the documentation for the installed Playwright version for exact option availability and behavior. Playwright’s Page API documents screenshot configuration and locator masking.
6. Use screenshots in visual regression tests
For a test-runner visual assertion, dismiss the banner in the test flow and then use Playwright Test’s toHaveScreenshot(). This assertion waits for two consecutive screenshots to produce the same result before comparing the final capture.
import { test, expect } from '@playwright/test';
test('page screenshot without the consent banner', async ({ page }) => {
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
const rejectButton = page.getByRole('button', {
name: 'Reject optional cookies',
exact: true,
});
await rejectButton.waitFor({ state: 'visible', timeout: 5_000 });
await rejectButton.click();
await page.locator('#cookie-consent-banner').waitFor({ state: 'hidden' });
await expect(page).toHaveScreenshot('page-without-consent-banner.png', {
fullPage: true,
animations: 'disabled',
});
});
Keep the browser, browser version, operating system, viewport, device scale, and relevant rendering settings consistent between baseline generation and comparison. Rendering can vary across operating systems, browser versions, hardware, power settings, and headless versus headed mode. Avoid updating reference screenshots just to silence a failure until you have checked whether the page or environment changed.
7. cURL, Python, and Node.js options for hosted capture
If you want a hosted screenshot endpoint instead of managing browser installation and execution, ScreenshotNeo is a website screenshot API and MCP server. It supports the same common screenshot parameter names used by other screenshot APIs, which can make switching easier. See the ScreenshotNeo API documentation for request options.
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(`Screenshot request failed: ${res.status}`);
await require('node:fs/promises').writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
Or skip the browser setup
ScreenshotNeo takes a screenshot with one GET request. Cookie banners are accepted like a visitor and more than 60 known consent platforms, newsletter popups, and chat widgets are removed before the shot; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and the response identifies the page verdict and billing status in headers. 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.
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 API documentation and get 1,000 screenshots a month free with no card.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Timeout waiting for the button | The banner did not appear, the control name differs, or it is inside an iframe. | Inspect the page and confirm the accessible name and frame. Decide whether a missing banner is acceptable or should fail the job. |
| Strict mode reports multiple matches | The same button name appears in more than one place. | Scope the locator to the banner container or use a more specific accessible name or test id. |
| Click succeeds but the banner remains | The chosen control did not close it, another step is required, or the banner reappeared. | Use the intended site control and wait for the banner container to become hidden. Check whether a preference panel or nested dialog opened. |
| Screenshot still includes the banner | The capture ran before dismissal completed, or the banner selector used for cleanup is wrong. | Wait for the banner to be hidden after clicking; verify the selector against the current DOM. |
| Page appears blank or incomplete | Navigation returned before client-side content rendered, or the site is still loading data. | Wait for a meaningful page element or a site-specific readiness condition before capture. Avoid relying on a fixed sleep when a selector can express readiness. |
| Visual test changes across runs | Browser or host rendering differs, animations or dynamic content vary, or the page itself changed. | Pin and reuse the same browser and execution environment, disable animations, and stabilize dynamic content where possible. |
| Page hangs after a JavaScript dialog | A real alert, confirm, or prompt is open and a dialog handler is waiting for a response. |
Handle the browser dialog using Playwright’s dialog API. An HTML consent banner is a separate page element and does not use this mechanism. |
Performance, reliability, and cost
- Wait for conditions, not arbitrary time: wait for the consent control, its disappearance, and any content needed in the screenshot. This avoids unnecessary idle time while preserving the required sequence.
- Reuse browser processes for batches: launch a browser once and create a fresh page or context per job as appropriate. Close pages and browsers in cleanup paths so failures do not leak processes.
- Set timeouts deliberately: navigation, consent appearance, dismissal, and page readiness have different failure points. Use finite limits and report which step timed out.
- Keep capture conditions stable: explicitly set viewport and browser version for repeatable output. Full-page capture can take longer and create larger files than viewport capture.
- Manage local costs: self-hosted Playwright has no per-screenshot API charge, but it consumes compute, storage, maintenance, and browser runtime. Hosted capture trades browser operations for the service’s plan limits and request behavior; ScreenshotNeo’s free tier is 1,000 shots per month, and its paid plans start at $5 for 3,000.
FAQ
Can I use one selector for every cookie banner?
No. Sites use different markup, labels, frames, and consent tools. Inspect the target and use a locator that matches its actual control.
Does hiding a banner in the screenshot mean consent was dismissed?
No. A stylesheet or mask changes the capture output. To test a consent flow, click the site’s normal control and verify the resulting page state.
Should I accept or reject cookies?
Choose the action that matches the test scenario and intended user behavior. Playwright does not prescribe a consent choice.
How do I capture the page after the banner is gone?
Click the chosen control, wait for the banner or control to become hidden, and then call page.screenshot() or the relevant visual assertion.
References
- Playwright Page API — screenshot options and guidance for predictable overlays.
- Playwright PageAssertions — screenshot stabilization and visual assertions.
- Playwright Dialogs — browser JavaScript dialog handling.
- Playwright visual comparisons documentation source — environment variation in screenshot rendering.


