ScreenshotNeo

BlogHow-to

How to Capture a Screenshot of a Web Page After Dismissing Its Modal

Dismiss the page overlay or JavaScript dialog before capturing. This guide shows how to do it with Playwright and when to use viewport, full-page, or element screenshots.

By the ScreenshotNeo team4 October 20269 min read

To capture a web page after dismissing its modal, first identify whether it is a page overlay or a browser JavaScript dialog. For a page overlay, wait for its locator, click the site’s close or dismiss control, confirm the overlay is hidden, and then take the screenshot. Playwright automatically dismisses JavaScript dialogs by default; if you install a dialog listener, it must call accept() or dismiss().

The examples below use Playwright with Node.js. They cover viewport, full-page, and element captures, plus common timing and dialog problems. If you only need an image rather than browser automation, see ScreenshotNeo.

1. Identify which kind of modal you have

The word “modal” can refer to two different things in browser automation:

  • Page overlay: HTML rendered as part of the site, such as a newsletter prompt, consent dialog, or sign-in modal. It usually has a close button and a selector you can locate. Use normal page locators to dismiss it.
  • JavaScript dialog: A browser-managed alert(), confirm(), or prompt(). It is not an HTML element, so a CSS selector cannot find it. Handle it with Playwright’s dialog API.

Inspect the page or the automation error to tell them apart. If the dialog is part of the rendered page, locate its controls. If the browser reports a JavaScript dialog, use a dialog handler. Some pages show both kinds, so handle each that appears before capture.

2. Dismiss a predictable page overlay with Playwright

When you know an overlay appears during a flow, handle it explicitly at the point it is expected. Playwright recommends this over relying on a locator handler for a predictable overlay. Replace the example URL and selectors with those for your page.

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch({ headless: true });
  const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });

  try {
    await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });

    const modal = page.locator('[role="dialog"]');
    const closeButton = modal.getByRole('button', { name: /close|dismiss|no thanks/i });

    // Wait for the overlay only if this page is expected to show it.
    await modal.waitFor({ state: 'visible', timeout: 10000 });
    await closeButton.click();
    await modal.waitFor({ state: 'hidden', timeout: 10000 });

    // Use fullPage: true for the complete scrollable page.
    await page.screenshot({ path: 'page.png', fullPage: true });
  } finally {
    await browser.close();
  }
})();

Install Playwright in your project with npm install playwright. If the site requires a browser binary, install one with npx playwright install chromium. The sample’s accessible role and button name are examples: inspect the actual page and use its real dialog and close-button selectors.

Waiting for the dialog to become hidden after clicking is important. A click can begin an animation or trigger asynchronous page work; capturing immediately may preserve the overlay or a partially transitioned state.

3. Handle an unpredictable overlay with a locator handler

If an overlay may appear at an uncertain point, a Playwright locator handler can dismiss it when an action or auto-waiting assertion checks the page. It does not run just because the overlay appears while the script is idle. Keep the handler focused and make sure it does not accidentally dismiss a different dialog.

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch({ headless: true });
  const page = await browser.newPage();

  try {
    await page.addLocatorHandler(
      page.locator('[role="dialog"]'),
      async dialog => {
        const close = dialog.getByRole('button', { name: /close|dismiss|no thanks/i });
        await close.click();
      }
    );

    await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
    // This action/assertion check is an opportunity for the handler to run.
    await page.getByRole('heading', { name: 'Example Domain' }).waitFor();
    await page.screenshot({ path: 'page.png' });
  } finally {
    await browser.close();
  }
})();

Use explicit dismissal instead when the overlay is part of a known sequence; handlers are useful when timing is uncertain, but they are not background watchers.

4. Handle JavaScript alert, confirm, and prompt dialogs

Playwright automatically dismisses JavaScript dialogs if no dialog listener is registered. If you register a listener, you take responsibility for resolving each dialog. Leaving it open can block the page and stall later actions.

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch({ headless: true });
  const page = await browser.newPage();

  // Register before navigation or the action that might trigger the dialog.
  page.on('dialog', async dialog => {
    await dialog.dismiss();
  });

  try {
    await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
    await page.screenshot({ path: 'page.png' });
  } finally {
    await browser.close();
  }
})();

For a confirmation flow that requires the positive choice, call await dialog.accept() instead. For a prompt that needs a value, pass it to accept(value). A JavaScript dialog has no page locator to wait for; the dialog event is the relevant signal.

5. Choose the screenshot scope

Capture Playwright example Use it when
Viewport await page.screenshot({ path: 'page.png' }) You need the visible browser area at the current viewport size.
Full page await page.screenshot({ path: 'page.png', fullPage: true }) You need the whole scrollable page in one image.
Element await page.locator('main').screenshot({ path: 'main.png' }) You need a component or region rather than the whole page.

A locator screenshot scrolls the target into view. Another element can still cover it, so verify that the target is unobstructed as well as the modal being gone. Playwright also supports choosing CSS-pixel or device-pixel screenshot scale; device-pixel output can be larger on high-DPI displays.

// Viewport at CSS-pixel scale
await page.screenshot({ path: 'viewport.png', scale: 'css' });

// Full page at device-pixel scale
await page.screenshot({ path: 'full-page.png', fullPage: true, scale: 'device' });

// One element
await page.locator('.product-card').screenshot({ path: 'card.png' });

6. Make capture timing reliable

  1. Navigate to the page and wait for the condition your page needs. domcontentloaded is a starting point, not proof that client-rendered content is ready.
  2. Wait for the expected overlay, then click its actual close control.
  3. Wait for the overlay to be hidden. If the page animates its dismissal, this avoids capturing the transition.
  4. Wait for any content that should appear after dismissal, such as a lazy-loaded section or a page heading.
  5. Capture the viewport, full page, or target element that matches the deliverable.

Prefer a condition tied to the page, such as a selector becoming visible or hidden, over a fixed delay. A delay can be too short on a slow run and waste time on a fast one. If the page continues making background network requests, waiting for network idle may also be unsuitable; wait for the specific content needed in the screenshot.

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

Playwright is useful when you need to interact with a specific site’s modal, because your script can inspect and click the site’s controls. If you want a screenshot API to load a public page and return an image, ScreenshotNeo provides a one-request capture workflow. Its consent-banner handling can accept the banner and remove known consent platforms, newsletter popups, and chat widgets before the shot; each step can be turned off. That is useful when those overlays are the obstruction, though it cannot click every arbitrary site-specific modal.

Use your ScreenshotNeo API key in place of YOUR_API_KEY. The API base is https://api.screenshotneo.com/v1/shot. See the ScreenshotNeo API documentation for options and configuration.

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

response = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
    timeout=90,
)
response.raise_for_status()
with open("shot.webp", "wb") as image_file:
    image_file.write(response.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}`);
const bytes = new Uint8Array(await res.arrayBuffer());
await (await import('node:fs/promises')).writeFile('shot.webp', bytes);

For the specific task of dismissing a custom modal, first check whether it is one of the consent or popup elements ScreenshotNeo handles. If it is a site-specific modal with custom behavior, use the Playwright method above or configure the API with the relevant supported options documented by ScreenshotNeo. Do not assume a generic screenshot request can interact with every arbitrary overlay.

8. Troubleshooting

Symptom Likely cause Fix
Click times out The modal did not appear, the selector is wrong, or the button is not actionable. Inspect the rendered page, use the actual dialog and button names, and only wait for a modal that this page is expected to show.
Screenshot still contains the overlay The script captured before dismissal completed, or the overlay selector matched the wrong element. Wait for the correct overlay to reach hidden after clicking, then capture.
Actions stall after a JavaScript dialog A registered dialog listener did not accept or dismiss the dialog. Resolve every dialog in the listener with accept() or dismiss().
Locator handler seems not to run No action or auto-waiting assertion checked the page after the overlay appeared. Use explicit dismissal at a known point, or perform an appropriate action/assertion that checks the page.
Element screenshot is covered A different sticky header, popup, or overlay covers the target. Dismiss or hide the covering element and verify the target before taking its locator screenshot.
Screenshot is blank or incomplete The page is client-rendered, the content is lazy-loaded, or navigation was considered ready too early. Wait for the actual content selector or state needed before capture; use full-page capture only when below-the-fold content is required.
Screenshot API returns an error The key, URL, network access, or request configuration may be invalid. Check the API key and encoded target URL, inspect the response status and headers, and consult the API docs.

9. Performance, reliability, and cost

In Playwright, the main reliability cost is waiting for the right page state: explicit visibility and hidden-state checks prevent many timing errors, while unbounded fixed sleeps slow runs and still do not guarantee readiness. Full-page and device-scale captures can produce larger images than viewport or CSS-scale captures. Choose the smallest capture scope and scale that meets the output requirement.

For ScreenshotNeo, the free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; higher plans are Starter $5 for 3,000, Growth $15 for 15,000, Pro $39 for 60,000, Scale $99 for 250,000, and Business $249 for 1,000,000. Yearly billing gives two months free, and every feature is on every plan. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed; the response identifies the page verdict and billing state in X-Page-Verdict and X-Billed headers.

10. Or skip the browser setup

For a public page that does not need a custom click sequence, ScreenshotNeo can return the screenshot from one GET request. 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 use take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots a month with no card, and 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://example.com \
  -o shot.webp

See the API docs, then sign up free for 1,000 screenshots a month with no card.

Frequently asked questions

Can I dismiss a modal with Escape?

Only if that page’s modal supports Escape. Prefer the modal’s actual close control and verify the overlay is hidden before capture.

Does a locator handler watch continuously for overlays?

No. It runs when an action or auto-waiting assertion checks the page, so it will not respond while the script is simply idle.

Can a screenshot include the closed modal for debugging?

Yes. Capture once before dismissal and again after it, using distinct output paths, if you need both states for a test or report.

Will ScreenshotNeo close every custom modal?

No. Its documented clean-shot behavior covers known consent platforms, newsletter popups, and chat widgets. A site-specific modal may require browser automation that clicks that page’s own control.