ScreenshotNeo

BlogHow-to

Why does Selenium screenshot capture a cookie popup on every page?

Selenium captures the page as it appears at screenshot time. Learn why consent banners return, how to inspect and persist the right state, and when to wait before capturing.

By the ScreenshotNeo team4 October 20269 min read

Selenium captures the browser window in its current state. It does not automatically accept or dismiss a consent banner. If the popup appears on every screenshot, the run may be starting with fresh browser storage, the site may not be persisting consent where you expect, the pages may have different cookie scopes, or the screenshot may happen before the page finishes its JavaScript updates.

The right fix depends on the site and on what the screenshot is meant to prove. For a first-visit test, the banner should appear. For a post-consent screenshot, establish the consent state the site actually recognizes, then wait until the dialog is gone before capturing. Do not assume a cookie name or local-storage key.

1. Why the popup appears in Selenium screenshots

A screenshot records the visible browser window at the instant Selenium takes it. Selenium does not infer that a consent banner should be dismissed. Several causes can make the banner appear repeatedly:

  • Each run starts with a clean profile. A new WebDriver session or temporary browser profile may not contain consent saved by an earlier manual session.
  • The site stores consent differently than expected. It may use a cookie, browser storage, a consent platform, or another mechanism. The implementation is site-specific.
  • The pages have different origins or scopes. Scheme, host, subdomain, or a separate consent service can affect which state applies.
  • The capture happens too early. Document readiness does not guarantee that later JavaScript changes have finished. A banner can still be present when the screenshot is taken.
  • The intended test state is a first visit. A fresh state is correct when testing what a new visitor sees.

Selenium cookie commands work in the current browsing context, and adding a cookie requires first navigating to a domain where that cookie is valid. See the Selenium guide to working with cookies. Selenium’s waiting strategies also explain why page loading alone may not mean the application is ready.

2. Decide which browser state you need

Before changing the script, decide whether the capture should show a first visit or a visit after consent. These are different test cases.

Goal Expected state What to verify
Test the consent experience Fresh browser state; banner visible The run starts without prior consent and the banner is present.
Capture the page after consent Site-recognized consent state; banner absent The site records the change and the same relevant origin uses it on the next page.
Compare both experiences Two deliberate states Run a clean-state capture and a separate post-consent capture.

Keeping these states explicit prevents a test from accidentally hiding a banner it is supposed to cover, or capturing a popup that is irrelevant to a post-consent page image.

  1. Open the target page in a browser and record the exact page origin: scheme, host, and port if present.
  2. Inspect cookies before accepting the banner, accept it through the site’s UI, then inspect cookies again.
  3. Check the site’s browser storage and consent code if no relevant cookie changed. Do not guess a cookie name or set an arbitrary value.
  4. Repeat the check on the other pages or subdomains you plan to capture. Confirm whether they share the same consent state.
  5. Compare state before and after a WebDriver run boundary. A new session may not reuse the previous session’s profile.

Chrome DevTools exposes cookies by origin under Application > Storage > Cookies; see Chrome’s cookie inspection guide. Selenium’s cookie API documents retrieval and mutation, including the need to be on the appropriate domain before adding a cookie.

4. Runnable Python example: accept the real banner and wait before capture

This example uses Selenium’s actual UI interaction path. Replace the URL and the example selector with selectors from the target site. It checks that the consent control is clickable, clicks it, waits for the dialog to disappear, waits for target content, and then writes a screenshot. The selectors are intentionally placeholders because consent markup differs by website.

from pathlib import Path

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

url = "https://example.com"
consent_button = (By.CSS_SELECTOR, "REPLACE_WITH_SITE_CONSENT_BUTTON_SELECTOR")
consent_dialog = (By.CSS_SELECTOR, "REPLACE_WITH_SITE_DIALOG_SELECTOR")
page_ready = (By.CSS_SELECTOR, "REPLACE_WITH_PAGE_CONTENT_SELECTOR")

options = webdriver.ChromeOptions()
# Keep this as a fresh profile when the test should reproduce a first visit.
# For repeatable runs, use a dedicated persistent profile directory instead.
driver = webdriver.Chrome(options=options)
wait = WebDriverWait(driver, 15)

try:
    driver.get(url)

    # Interact with the site's actual consent control.
    wait.until(EC.element_to_be_clickable(consent_button)).click()

    # Confirm the intended post-consent state before capturing.
    wait.until(EC.invisibility_of_element_located(consent_dialog))
    wait.until(EC.presence_of_element_located(page_ready))

    Path("page.png").write_bytes(driver.get_screenshot_as_png())
finally:
    driver.quit()

If the consent dialog is in an iframe, switch into that frame before locating and clicking its controls. If the site renders the control inside a shadow root, use the site’s DOM structure to access it. A click intercepted by an overlay or a disabled control should be diagnosed from the live page rather than worked around with a guessed storage value.

A persistent profile can preserve browser state between runs, but it should be dedicated to the test and used consistently. It is not equivalent to explicitly verifying consent: the profile may contain stale state, and browser versions or test environments may use different storage locations. Do not run concurrent browser instances against the same profile directory.

from pathlib import Path
from selenium import webdriver

profile_dir = Path("./selenium-consent-profile").resolve()
profile_dir.mkdir(parents=True, exist_ok=True)

options = webdriver.ChromeOptions()
options.add_argument(f"--user-data-dir={profile_dir}")

driver = webdriver.Chrome(options=options)
try:
    driver.get("https://example.com")
    # On the first run, accept consent through the real site UI.
    # On a later run, verify the site recognizes the saved state.
    driver.save_screenshot("page.png")
finally:
    driver.quit()

Use a fresh profile for first-visit tests. Use a dedicated persistent profile only when the test specifically needs browser state to carry over, and verify that the target site still recognizes it.

When inspection confirms that the site’s consent mechanism is a cookie and you know its valid attributes and value, Selenium can add it after navigating to the correct domain. The example below deliberately uses placeholders; it is not a universal consent cookie recipe.

from selenium import webdriver

options = webdriver.ChromeOptions()
driver = webdriver.Chrome(options=options)

try:
    driver.get("https://example.com")  # Must be on the cookie's applicable domain first.
    driver.add_cookie({
        "name": "REPLACE_WITH_VERIFIED_COOKIE_NAME",
        "value": "REPLACE_WITH_SITE_ACCEPTED_VALUE",
        "path": "/",
        # Set domain, expiry, secure, and sameSite only as the site requires.
    })
    driver.refresh()
    # Wait for the intended page state before capturing.
    driver.save_screenshot("page.png")
finally:
    driver.quit()

Cookie attributes such as domain, path, expiry, secure, and same-site policy matter. A cookie for one host may not apply to another. Some sites do not use a cookie as their source of truth, so adding one may have no effect.

7. Wait for the state you intend to capture

Prefer an explicit wait for a meaningful condition: the consent dialog becomes invisible, a page heading appears, or a known loading indicator disappears. A fixed sleep can sometimes help diagnose timing, but it is slower than necessary on fast runs and still too short on slow ones.

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

wait = WebDriverWait(driver, 20)
wait.until(EC.invisibility_of_element_located(
    (By.CSS_SELECTOR, "REPLACE_WITH_SITE_DIALOG_SELECTOR")
))
wait.until(EC.visibility_of_element_located(
    (By.CSS_SELECTOR, "REPLACE_WITH_PAGE_READY_SELECTOR")
))
driver.save_screenshot("page.png")

Selenium’s wait guidance notes that JavaScript can continue modifying a page after the document reaches its readiness state. Waiting for the application condition you care about is more reliable than assuming that navigation completion means the page is settled.

Symptom Likely cause Fix and confirmation
Banner returns at the start of every run Each run gets a new session or clean profile. Use a dedicated persistent profile if persistence is required, or establish consent in every run. Compare storage across the run boundary.
Banner returns while navigating within one run Pages use different origins, subdomains, or consent scopes. Compare scheme and host, inspect cookie domain and path, and check the consent behavior on each origin.
Banner flashes in the screenshot and disappears later The capture happens before JavaScript completes the consent or page update. Wait for the dialog to become invisible and for the relevant page content to appear.
Click fails with an intercepted or not clickable error The control is covered, outside the viewport, not yet visible, or inside a frame. Wait for clickability, inspect overlays and frames, and switch into the correct frame where needed.
Adding a cookie has no effect The cookie name/value or attributes are wrong, the browser is on the wrong domain, or the site uses another storage mechanism. Inspect the actual state change and cookie attributes; navigate to the valid origin before adding it.
Dialog is absent but screenshot still looks wrong A different overlay, newsletter popup, or chat widget remains, or the page has not settled. Inspect the screenshot and DOM for the remaining element, then wait for the intended visual state.
Consent persists locally but not in CI CI may create a fresh profile or run in a different environment. Make state setup part of the test; do not depend on a developer’s local browser profile.

9. Reliability, speed, and cost considerations

  • Reliability: State the desired first-visit or post-consent condition in the test and assert it before capturing. This makes failures explainable and avoids relying on accidental profile history.
  • Speed: Use explicit waits tied to the page condition. Broad fixed delays slow every run, while short delays can still fail on slower pages.
  • Parallel runs: Give concurrent browser jobs separate profile directories. Shared mutable profile state can make consent behavior inconsistent.
  • Privacy and test isolation: Treat a persistent profile as test data. Keep it scoped to the intended site and test, and clear it when you need a genuine first-visit state.
  • Cost: Selenium’s direct costs depend on where and how you run the browser infrastructure; the source material provides no general price or benchmark. Account for browser compute and maintenance in your environment.

10. Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. Its API can return a screenshot in one GET request, without setting up a Selenium browser for each capture. Cookie banners are accepted like a visitor, and 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 response headers report the page verdict and billing status. An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents.

See the ScreenshotNeo API documentation for parameters and configuration.

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

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
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 Bun.write('shot.webp', res);

The Python example uses requests. The Node.js example uses the built-in fetch API and Bun’s file writer; in Node.js, save the response bytes with writeFile from node:fs/promises. ScreenshotNeo offers 1,000 shots per month free with no card; paid plans start at $5 for 3,000 shots. Sign up for 1,000 free screenshots a month, with no card required.

11. Frequently asked questions

No. It captures the current browser window. Your test must leave the banner visible, interact with it, or establish a state the site recognizes.

Should I save cookies and reuse them?

Only if the test needs a persisted post-consent state and you have verified the site’s mechanism. A dedicated browser profile may be more appropriate when the site relies on multiple storage values, but verify it across runs.

Can I hide the banner with CSS?

You can alter page presentation in a test, but hiding the element does not prove that consent was recorded. Use that approach only when the test is explicitly about a clean visual composition rather than consent behavior.

Why does the banner appear on one subdomain but not another?

Consent state can be scoped by domain or origin, and sites may implement separate consent behavior. Inspect the pages and storage to determine their actual relationship.