How to capture a website screenshot after dismissing a newsletter popup
Use Playwright to dismiss a newsletter overlay, wait for it to close, and capture the page. Learn how to handle native JavaScript dialogs and common failures.
To capture a page after a newsletter popup is dismissed, identify whether it is an in-page overlay or a browser-native JavaScript dialog. For a typical HTML overlay, use its real close or decline control, wait until the overlay is hidden, then take the screenshot. For alert(), confirm(), or prompt(), handle Playwright’s dialog event instead. The examples below use Playwright with JavaScript; replace the example popup name and button label with the accessible names used by your target page.
1. Identify the kind of popup
A newsletter signup usually appears as ordinary page content: a dialog or panel rendered in the document. You can inspect and interact with it using locators. A JavaScript alert, confirmation, or prompt is browser-native; it is not an HTML element and cannot be found with getByRole('dialog').
Also decide whether the popup is expected or intermittent. If it appears at a known point in the flow, explicitly wait for and dismiss it there. Playwright recommends this normal-flow approach for predictable overlays rather than relying on page.addLocatorHandler(). A locator handler can help with genuinely unpredictable overlays, but it runs around actions and auto-waiting assertions; it is not a background watcher that runs independently.
2. Set up a runnable Playwright script
Install Playwright and its Chromium browser in a new Node.js project:
npm init -y
npm install --save-dev playwright
npx playwright install chromium
Save the following as screenshot-after-popup.js. Set TARGET_URL to the page you are allowed to access. The locator names are illustrative: use the actual dialog and dismissal control exposed by your page.
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,
});
// Replace these accessible names with the target site's actual names.
const popup = page.getByRole('dialog', { name: 'Newsletter signup' });
await popup.waitFor({ state: 'visible', timeout: 10_000 });
await popup.getByRole('button', { name: 'No thanks' }).click();
await popup.waitFor({ state: 'hidden', timeout: 10_000 });
// Viewport screenshot. Set fullPage to true for the whole scrollable document.
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://example.com' node screenshot-after-popup.js
3. Choose and verify the dismissal locator
Newsletter widgets vary. Their dialog may have another accessible name, the decline control may be a link, or the widget may be rendered inside an iframe. Inspect the rendered accessibility tree or page markup and target the specific control a visitor would use. Prefer a locator scoped to the popup so a page-level button with the same label cannot be clicked by mistake.
Use the control’s real action, such as “Close,” “No thanks,” or “Continue without subscribing,” when the goal is to represent a visitor dismissing the prompt. Hiding the popup with CSS, removing its DOM node, or masking it in the screenshot produces a different result: it does not exercise the site’s dismissal flow and may leave scroll locking or other page state in place.
The click and the dismissal are separate conditions. Waiting for hidden after clicking verifies that the overlay is no longer visible; if the site removes it from the DOM, waiting for detached can be more specific. If the popup is optional and may not appear, first check whether it is visible, and only click it when present. Do not wait indefinitely for an overlay that the site might not show.
4. Capture the right area
page.screenshot() captures the current viewport by default. Pass fullPage: true to capture the entire scrollable document as a tall image. Use a viewport capture for a first-screen preview or a full-page capture when the deliverable needs all page content. Full-page capture can produce a very large image on long pages.
await page.screenshot({ path: 'viewport.png' });
await page.screenshot({ path: 'full-page.png', fullPage: true });
For other needs, consult the screenshot API for your installed Playwright version. It documents format and other capture options; do not assume options behave identically across versions.
5. Handle a browser-native JavaScript dialog
If the page triggers a native JavaScript dialog, register a handler before the action that can trigger it. The handler must accept or dismiss the dialog. If you install a listener that only logs the event, the page can stay blocked and subsequent actions may stall. When no page or context dialog listener is installed, Playwright automatically dismisses these dialogs.
page.on('dialog', async (dialog) => {
console.log(`Dialog type: ${dialog.type()}`);
await dialog.dismiss();
});
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
// Perform the action that triggers the dialog here.
await page.screenshot({ path: 'page.png' });
Use dialog.accept() when the intended flow is to accept or confirm; use dialog.dismiss() when it should be declined. For a native prompt, acceptance may require a prompt value. A newsletter modal built from HTML still needs a DOM locator and its own dismissal control.
6. Handle an unpredictable in-page overlay
When an overlay appears unpredictably and blocks an action, a locator handler can be appropriate. Keep its action self-contained, and make sure it targets only the unexpected overlay. Playwright checks locator handlers around actions or auto-waiting assertions, so code that performs low-level mouse steps can be affected by handler timing.
await page.addLocatorHandler(
page.getByRole('dialog', { name: 'Newsletter signup' }),
async (dialog) => {
await dialog.getByRole('button', { name: 'No thanks' }).click();
}
);
// The handler is checked around actions/assertions; it is not a background poller.
await page.getByRole('main').waitFor({ state: 'visible' });
await page.screenshot({ path: 'page.png' });
If the prompt is predictable, prefer the explicit wait, click, and hidden-state check shown earlier. That makes the expected behavior and failure point clearer.
7. Troubleshoot common failures
| Symptom | Likely cause | Fix |
|---|---|---|
| Dialog locator times out | The widget uses a different role or accessible name, has not appeared yet, or is inside an iframe. | Inspect the page’s accessible tree, wait for the actual condition, and use a frame locator if the widget is inside a frame. |
| Button locator finds nothing | The close control is a link, icon with another accessible name, or unlabeled element. | Inspect the control and choose a locator matching its actual role and accessible name. Avoid guessing a universal selector. |
| Click succeeds but popup remains | The click triggered a transition, the button was not the true dismiss action, or the widget reopened. | Wait for the dialog to become hidden or detached and verify the chosen control’s behavior. |
| Page actions hang after a JavaScript dialog | A dialog listener was installed but did not accept or dismiss the dialog. | Call accept() or dismiss() from the listener, or remove the listener if automatic dismissal is appropriate. |
| Screenshot still contains the popup | The capture happened before the dismissal transition completed, or the popup is a different overlay. | Wait for the specific overlay to be hidden, then capture. Check for multiple prompts or a second widget. |
| Screenshot is cut off | The code captured only the viewport. | Use fullPage: true if the complete scrollable page is required. |
| Overlay handler did not run | No action or auto-waiting assertion reached the point where Playwright checks the handler. | Use explicit dismissal for predictable overlays; for unpredictable ones, ensure the flow performs a relevant action or assertion. |
8. Reliability, runtime, and cost
Give navigation, popup appearance, and dismissal waits explicit timeouts so a missing widget fails clearly instead of hanging. Keep browser cleanup in a finally block, as in the example, to close Chromium even when navigation or capture fails. Use the least broad navigation wait that suits the page: waiting for every network request to finish can be unsuitable for pages with long-lived connections. The sample waits for DOM content, then waits specifically for the popup.
Screenshot size and capture time depend on the page, viewport, and whether the whole document is captured. A full-page image can use more memory and take longer than a viewport capture. If the page loads content lazily, scrolling or full-page capture behavior may affect what has loaded; confirm that the content required by your deliverable is present before saving.
Playwright is browser automation software; this workflow has no per-screenshot service charge, but you operate the browser runtime and its compute environment. If you need a hosted one-call capture instead, ScreenshotNeo provides a screenshot API with a free tier and paid plans described below.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. Its API accepts a URL in one GET request and returns an image or PDF. The example below captures the target URL; see the ScreenshotNeo API documentation for request options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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 accepts cookie and consent banners, then removes more than 60 known consent platforms, newsletter popups, and chat widgets before the capture; each of those steps can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and whether the request was billed. Its MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.
The free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots; yearly billing gives two months free, and every feature is available on every plan. Sign up for 1,000 free screenshots a month, with no card required.
FAQ
Will this work if the popup appears only once per browser session?
It depends on the site’s storage and session behavior. A fresh browser context may show it again, while a reused context may retain the site’s prior choice. Choose the context behavior that matches the screenshot scenario you need.
Should I accept or decline a newsletter prompt?
Use the interaction that matches the intended visitor flow. For a clean page screenshot, a visible decline or close control is usually the relevant dismissal action; accepting may submit data or change page state.
Can I just block the popup with CSS?
You can alter the rendered page, but that does not dismiss the prompt through its normal control. Use CSS hiding only when the goal is specifically to produce a modified rendering rather than represent a visitor dismissing it.


