ScreenshotNeo

BlogHow-to

How to Handle Popups and Overlays in Website Screenshots

Choose whether to capture, dismiss, or mask a popup, then handle it reliably with Playwright or Selenium.

By the ScreenshotNeo team4 October 202611 min read

To handle a popup in a website screenshot, first decide whether the image should show what a visitor saw or show the page without the obstruction. Capture it unchanged for a visitor record. For a clean layout or regression image, dismiss a predictable in-page popup through its intended control, or mask a specific element during capture. Browser-native JavaScript dialogs such as alert() and page-built modals are different: use the browser automation framework’s dialog API for the former and normal page interaction for the latter.

1. Decide what the screenshot should show

Write down the purpose before changing the page state. Removing an overlay changes the rendered result, so the image should not be presented as an untouched view if you dismissed, masked, or styled away part of the page.

Goal Recommended handling
Record what a visitor saw, including an obstruction Capture the popup as it appeared.
Compare page layout or content without a predictable popup Use the popup’s normal close, reject, or other appropriate control before capture.
Keep the page state but omit a specific region from a visual comparison Mask a narrowly selected element in the screenshot and note that it was masked.
Test the popup itself Capture it deliberately and assert or record its visible state.

Do not automatically accept every consent prompt or close every dialog. Choose the action that matches the test objective and the control’s meaning. The page’s cookie or consent requirements can vary by jurisdiction; this guide does not make legal recommendations.

2. Identify the kind of popup

Browser-native JavaScript dialogs

alert(), confirm(), prompt(), and some beforeunload dialogs are browser dialogs, not elements in the page DOM. They need the automation framework’s dialog API. Playwright automatically dismisses dialogs when there is no dialog listener. If you register a listener, it must accept or dismiss every dialog; otherwise the action that triggered it can stall. Selenium’s WebDriver alerts API can read dialog text, accept or dismiss alerts and confirmations, and enter text into prompts.

Page-built overlays

Cookie notices, newsletter prompts, sign-up modals, sticky banners, and chat widgets are ordinary page UI. Locate a stable, meaningful control such as a button named “Close” or “Reject,” then interact with that control. Avoid broad selectors such as div patterns that may also match legitimate content, and avoid fixed sleeps as the sole synchronization method. Playwright recommends explicitly handling a predictable overlay in the normal test flow; its locator handler is useful when an overlay appears unpredictably.

3. Handle a predictable page overlay with Playwright

The following Node.js example uses Playwright’s test runner. It navigates, waits for a consent dialog, activates the intended rejection control, confirms the dialog is gone, and saves a viewport screenshot. Replace the URL and accessible names with those used by your site. Install Playwright and its browser as described in the official Playwright getting started guide.

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

test('capture the page after handling its consent dialog', async ({ page }) => {
  await page.goto('https://example.com');

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

  // Choose the control that matches the test objective and site behavior.
  await consent.getByRole('button', { name: /reject|decline/i }).click();
  await expect(consent).toBeHidden();

  await page.screenshot({ path: 'page.png' });
});

If the site does not expose the popup as an accessible dialog, inspect its accessible name and controls and prefer a role, label, or other stable locator. If the prompt appears only sometimes, make the test reflect that expected behavior rather than assuming it always exists. A narrowly scoped conditional is appropriate only when either state is valid:

const closeButton = page.getByRole('button', { name: /close newsletter/i });
if (await closeButton.isVisible().catch(() => false)) {
  await closeButton.click();
}

Do not hide a real test failure by swallowing arbitrary locator errors. If the popup is required for the scenario, assert that it appears; if it is optional, document that and handle only the known optional state.

4. Handle an intermittent overlay with a locator handler

For an overlay that can appear unpredictably while later actions are running, Playwright provides page.addLocatorHandler(). The handler is checked before actions or assertions that require actionability; it does not run just because an overlay appears while the script performs no action. Keep the handler targeted and make its response match the scenario.

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

test('capture despite an occasional newsletter prompt', async ({ page }) => {
  await page.goto('https://example.com');

  const prompt = page.getByRole('dialog', { name: /newsletter/i });
  await page.addLocatorHandler(prompt, async () => {
    await prompt.getByRole('button', { name: /close/i }).click();
  });

  // An action/assertion causes Playwright to check and handle the obstruction.
  await expect(page.getByRole('main')).toBeVisible();
  await page.screenshot({ path: 'page.png' });
});

When the popup is predictable, explicit handling in the normal flow is usually clearer because it makes the expected state and chosen action visible at the point they occur. Use a handler for genuine unpredictability, not as a blanket rule that closes every modal.

5. Handle a native dialog with Playwright

Register a listener before the action that triggers the dialog. Resolve it explicitly. In this example, the test expects a confirmation dialog and accepts it; choose dismiss() instead if the test needs the cancel path.

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

test('resolve a native confirmation dialog', async ({ page }) => {
  await page.goto('https://example.com');

  page.once('dialog', async dialog => {
    expect(dialog.type()).toBe('confirm');
    expect(dialog.message()).toContain('Continue');
    await dialog.accept();
  });

  await page.getByRole('button', { name: /continue/i }).click();
  await expect(page.getByText(/continued/i)).toBeVisible();
  await page.screenshot({ path: 'after-confirm.png' });
});

Use dialog.accept('text') for a prompt that needs input, or dialog.dismiss() to cancel it. A native dialog blocks normal page interaction until it is resolved, so do not wait for a page locator that cannot become actionable while the dialog is open.

6. Handle a native alert with Selenium in Python

Selenium exposes native browser alerts through switch_to.alert. This runnable example uses Selenium 4 and Chrome; install the Selenium package and have a compatible browser and driver setup for your environment.

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

with webdriver.Chrome() as driver:
    driver.get('https://example.com')
    driver.find_element(By.ID, 'show-alert').click()

    alert = WebDriverWait(driver, 10).until(EC.alert_is_present())
    print(alert.text)
    alert.accept()  # Use alert.dismiss() for the cancel path.

    WebDriverWait(driver, 10).until(
        EC.visibility_of_element_located((By.ID, 'result'))
    )
    driver.save_screenshot('after-alert.png')

For a prompt, call alert.send_keys('answer') before accepting. For a confirmation, test both accept and dismiss if both outcomes matter. Selenium documents these operations in its JavaScript alerts, prompts and confirmations guide.

7. Mask an overlay or apply capture-only CSS

If a specific popup should remain in the page state but should not affect a visual comparison, Playwright can mask matching locators in the screenshot. It can also inject CSS styles for the capture. These are image alterations: record the mask or style in the test or report, and select the narrowest possible target.

await page.screenshot({
  path: 'page-masked.png',
  fullPage: true,
  mask: [page.getByRole('dialog', { name: /newsletter/i })],
  maskColor: '#888888'
});
await page.screenshot({
  path: 'page-with-capture-style.png',
  style: '.newsletter-modal { visibility: hidden !important; }'
});

A mask obscures the selected region in the resulting image; capture-time CSS changes how the target is rendered for the screenshot. Neither method is a substitute for testing the popup’s actual behavior. Do not use a generic rule that hides all fixed-position elements: that can remove navigation, sticky controls, or other content relevant to the screenshot.

8. Choose viewport or full-page capture

A normal screenshot captures the current viewport. A full-page screenshot includes content below the fold, which is useful for page-level layout checks but can change how fixed and sticky elements are represented. Confirm the chosen mode answers the question the image is meant to answer.

// Current viewport
await page.screenshot({ path: 'viewport.png' });

// Full page, including content below the fold
await page.screenshot({ path: 'full-page.png', fullPage: true });

After dismissal or masking, verify that the relevant page content is still present and that the overlay has not reappeared at a later point. For repeatable visual comparisons, keep viewport size, browser, page state, and screenshot mode consistent.

9. cURL, Python, and Node.js with ScreenshotNeo

For a capture that does not require you to install or operate a browser, ScreenshotNeo provides a website screenshot API and an MCP server for developers. A GET request takes a URL and returns an image or PDF. Its capture process accepts cookie and consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Responses identify page verdict and billing status through X-Page-Verdict and X-Billed headers. See the ScreenshotNeo website and API documentation.

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,
)
r.raise_for_status()
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 Bun.write('shot.webp', res);

In Node.js environments without Bun, write the response body using the runtime’s file APIs. Keep API keys on a server or in a secret manager; do not expose them in public browser code. ScreenshotNeo supports PNG, JPEG, WebP, or PDF output, and many capture controls including viewport and device presets, full-page capture, selector capture, custom CSS and JavaScript, waits, request blocking, headers, cookies, user agent, caching, async jobs, and bulk capture. Check the docs for the parameter names and supported values. ScreenshotNeo also supports parameter names used by other screenshot APIs, which can make switching easier.

Or skip the browser setup

Use the one-call API when you want an image without setting up browser automation:

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

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; 1,000 screenshots a month are free with no card, and paid plans start at $5 for 3,000.

Sign up free for 1,000 screenshots a month, no card required.

10. Troubleshooting

Symptom Likely cause Fix
The click times out because the button is covered A modal, backdrop, or consent layer is intercepting input. Locate the visible overlay and operate its intended control before the blocked action; assert it is hidden afterward.
The screenshot still contains the popup The close action did not run, targeted the wrong control, or the prompt appeared again. Wait for the correct dialog/control, check the resulting state, and inspect whether the site reopens it after navigation or a delay.
A test stalls after a JavaScript dialog appears A registered Playwright dialog listener did not accept or dismiss the dialog. Resolve every dialog in the listener, or remove the listener if automatic dismissal is the intended behavior.
A locator handler appears ineffective Playwright checks handlers before relevant actions/assertions, not continuously while idle. Use explicit handling at a known point, or ensure an action/assertion occurs after the unexpected overlay appears.
A broad hide rule removes page content The selector matched unrelated fixed, sticky, or modal elements. Use a specific locator for the exact popup and document capture-only CSS or masking.
The prompt is absent in headless or CI runs Different viewport, timing, session state, consent cookie, or targeting may change whether the site presents it. Set a consistent viewport and browser state; wait for an expected prompt rather than sleeping an arbitrary duration.
Full-page output differs from the viewport image Full-page capture includes below-fold content and can affect sticky/fixed element rendering. Use the capture mode that matches the comparison and keep it consistent between runs.
Selenium reports no alert The obstruction is an HTML element, not a native browser dialog, or the alert has not appeared yet. Use a WebDriver wait for native alerts; for page UI, locate and interact with its DOM control instead.

11. Performance, reliability, and cost

  • Prefer conditions over delays. Waiting for a locator or dialog state avoids capturing too early while limiting unnecessary idle time. Fixed waits can be too short on a slow run and waste time on a fast one.
  • Keep state repeatable. Use the same viewport, browser version, session/cookie state, and capture mode when comparing screenshots. A consent choice stored between runs can change whether the banner appears.
  • Use the smallest intervention. Dismissing one known popup or masking one region is easier to audit than applying global CSS or deleting broad classes of elements.
  • Capture evidence when the overlay is the subject. If diagnosing a popup, save the pre-action state or assert its visible content before dismissing it.
  • Account for browser operations. Browser automation requires a browser process, page navigation, synchronization, and image storage. For a single capture, a screenshot API can avoid maintaining that browser setup; for interactive test flows, browser automation gives direct control over page actions.
  • Know the billing state. ScreenshotNeo bills only clean shots; bot checks/CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing. Inspect X-Page-Verdict and X-Billed to distinguish outcomes. Plans are Free: 1,000 shots/month; Starter: $5 for 3,000; Growth: $15 for 15,000; Pro: $39 for 60,000; Scale: $99 for 250,000; Business: $249 for 1,000,000. Yearly billing gives two months free, and every feature is on every plan.

12. FAQ

Choose the action that matches what the test is intended to represent. For a visitor-state record, preserve the popup. For a page comparison, use the site’s relevant control and record that choice.

Can I remove a popup from an existing screenshot?

These workflows change or mask the page before capture. Editing an already captured image is a separate image-editing operation and should be disclosed if the image is evidence.

Why does a popup appear only in some runs?

Session state, stored consent, viewport, timing, or site behavior can change whether it appears. Control the inputs that matter and explicitly model optional versus required popup behavior.

Does full-page capture always include a popup?

It captures the page beyond the viewport, but fixed and sticky UI may behave differently in full-page mode. Check the output and select the mode appropriate to the question.

When should I use ScreenshotNeo instead of Playwright?

Use Playwright when the workflow needs browser interaction or test assertions. Use ScreenshotNeo when you want a screenshot API call or an MCP tool for an AI agent without maintaining browser capture code.

Sources