How to Remove Popups and Modals from Website Screenshots in Playwright
Hide HTML popups for a single Playwright screenshot, stabilize visual snapshots, or mask an area. Learn which method fits and how to troubleshoot it.
To hide an HTML popup only in a single Playwright screenshot, pass CSS through the screenshot style option:
await page.screenshot({
path: 'page.png',
style: '[role="dialog"] { visibility: hidden !important; }',
});
Use stylePath with Playwright Test’s toHaveScreenshot() when you want a reusable stylesheet for visual snapshots. Use mask when covering the popup with a colored overlay is acceptable. If the test needs to verify that a visitor can dismiss the dialog, use its dismiss control instead. These approaches have different effects: screenshot CSS changes only the captured image, a mask covers an area, and a dismiss action changes the page state.
The selector above is only an example. Confirm the popup’s actual selector in the page DOM; not every modal uses role="dialog".
1. Choose the right method
| Method | What appears in the image | Use it when |
|---|---|---|
page.screenshot({ style }) |
The matching content is hidden or styled for that screenshot. | You need a one-off capture without changing the page’s regular state. |
toHaveScreenshot({ stylePath }) |
The matching content is styled for the visual snapshot. | You want repeatable visual comparisons with volatile content filtered out. |
mask |
A colored overlay covers each matched element’s bounding box. | Covering the region is acceptable and you want to avoid choosing a hiding style. |
| Click the dialog’s dismiss button | The dialog closes through the page’s normal interaction. | The test is about real user behavior or the resulting page state. |
Screenshot styles are intended for dynamic content and apply to Shadow DOM and inner frames. The screenshot style option and snapshot stylePath option were added in Playwright v1.41. On older versions, upgrade if you need these options or use a page-level style mechanism appropriate to your test. [Page API · Visual comparisons]
2. Hide a popup in a single Playwright screenshot
Here is a complete Node.js example using Playwright’s library. Replace the sample URL and selector with the page and popup you need to capture:
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
try {
await page.goto('https://example.com', { waitUntil: 'load' });
await page.screenshot({
path: 'page.png',
fullPage: true,
style: '[role="dialog"] { visibility: hidden !important; }',
});
} finally {
await browser.close();
}
})();
The style value is CSS text, not a path. For a site-specific class, for example, change the selector to .newsletter-modal. Prefer a narrowly scoped selector so unrelated dialogs or page elements are not hidden. A rule with !important can override many existing declarations; use it only as needed.
The screenshot option styles the capture; it does not remove the node from the DOM or close the dialog in the page’s normal interaction state. fullPage: true captures the full scrollable page, but it does not itself dismiss or hide an overlay. [Playwright Screenshots · Page API]
3. Filter dynamic content from visual snapshots
For Playwright Test, put screenshot-only CSS in a file and provide its path to the assertion. For example, save this as screenshot.css:
[role="dialog"],
.newsletter-modal,
.cookie-banner {
visibility: hidden !important;
}
Then use the stylesheet in a visual assertion:
import { test, expect } from '@playwright/test';
test('page visual snapshot without popups', async ({ page }) => {
await page.goto('https://example.com');
await expect(page).toHaveScreenshot({
fullPage: true,
stylePath: './screenshot.css',
});
});
Keep this filter limited to unstable elements that are irrelevant to the visual check. If a dialog is part of the feature being tested, hiding it could conceal a regression. The documented purpose of stylePath is to filter volatile or dynamic content and make screenshot comparisons more deterministic. [Playwright Visual comparisons]
4. Mask a popup instead of hiding it
Masking overlays the matched locator’s bounding box; it does not hide or delete the DOM element. The default mask color is pink (#FF00FF), and maskColor changes it.
await page.screenshot({
path: 'page-masked.png',
mask: [page.getByRole('dialog')],
maskColor: '#333333',
});
Use a locator that matches the popup. If a dialog is not exposed with an accessible role, use a verified CSS locator:
await page.screenshot({
path: 'page-masked.png',
mask: [page.locator('.newsletter-modal')],
maskColor: '#333333',
});
The resulting image contains a solid cover over the element’s bounds, which may also cover page content underneath. That visible block is the expected mask result, not evidence that the modal was removed. [Page API: screenshot mask options]
5. Dismiss the dialog when the test should exercise the page
If the intent is to test the page after a visitor closes the popup, locate the button by its role and accessible name, then click it before taking the screenshot:
const closeButton = page.getByRole('button', { name: 'Close' });
await closeButton.click();
await page.screenshot({ path: 'page-after-dismiss.png', fullPage: true });
Use the label actually shown by the site, such as “Accept”, “Continue”, or “No thanks”. Role and accessible-name locators describe how a user identifies controls and are generally more resilient than selectors tied to page structure. Confirm that the locator identifies the intended button; site markup and labels vary. [Playwright Locators]
This changes page state by activating a control. That is different from screenshot-only CSS, which affects the capture, and from masking, which paints over the element’s bounds. Avoid removing a node from the DOM merely to make a visual image clean if the test is intended to reflect user interaction.
6. Find a selector that matches the actual popup
- Inspect the rendered page and identify the popup container, its class or attribute, and whether it has an accessible dialog role.
- Use
[role="dialog"]only if the element actually has that role. Otherwise use the page’s specific, verified selector, such as.newsletter-modal. - Check whether the popup is inside an iframe. Screenshot-time
styleis documented to apply to inner frames, but page-level locator actions may need a frame locator to reach controls inside a frame. - Check whether the content is in Shadow DOM. Screenshot-time styles are documented to apply there as well.
- Revisit the selector when the site changes. CSS selectors tied to implementation details can become brittle; where possible, use user-facing roles and names for interaction locators.
Do not assume every consent banner, chat widget, or modal shares the same markup. A broad selector may hide unrelated content, while a selector that no longer matches will leave the popup visible.
7. Screenshot options that affect popup captures
| Option | Effect | Popup-related note |
|---|---|---|
path |
Writes the screenshot to a file. | Choose a path your process can write. |
fullPage |
Captures the full scrollable page. | It does not dismiss overlays; a fixed popup can still appear. |
style |
Applies CSS for a page screenshot. | Useful for hiding or restyling dynamic content for this capture; added in v1.41. |
mask, maskColor |
Covers locator bounds with an overlay of the selected color. | The default color is pink; masking is visually different from hiding. |
animations |
Controls finite and infinite animations during capture. | Can reduce animation-related variation, but does not remove the popup itself. |
scale |
Sets screenshot scale for supported screenshot output. | Does not change which elements are hidden or masked. |
Consult the Page API for the current screenshot option details and version compatibility. Avoid adding options that do not address the source of the unwanted content: full-page capture, for example, changes capture dimensions rather than popup visibility.
8. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Popup still appears | The selector does not match the rendered markup, or the popup is added after capture. | Inspect the DOM and verify the selector. If it is late content, wait for the relevant page state before capturing; then apply the screenshot style. |
| The wrong content disappears | The selector is too broad, such as a generic dialog rule. |
Scope the rule to the popup’s actual class or attribute and check the resulting capture. |
| Mask leaves a colored rectangle | That is how masking works: it covers the locator’s bounding box. | Use screenshot CSS if the goal is to make the element invisible in the image, or accept the cover as part of the expected snapshot. |
| Dismiss click cannot find a button | The accessible name differs, the control is not a button, or it is in a frame. | Inspect its role and accessible name, use the actual label, and target the correct frame where required. |
| Snapshot still changes between runs | Other volatile content, animations, timestamps, or changing layout remain. | Filter only the unstable regions with stylePath, and use screenshot options such as animation control where relevant. Keep meaningful UI visible. |
style or stylePath is rejected as unknown |
The installed Playwright version predates the option. | Use Playwright v1.41 or later for these documented options. |
| Stylesheet has no effect in the visual assertion | The path is wrong relative to the test process, or the rule’s selector does not match. | Verify the file location and CSS selector; use the option on toHaveScreenshot(). |
9. Performance, reliability, and cost
A screenshot-time stylesheet avoids writing a separate page mutation step, while a snapshot stylesheet is easy to keep alongside a visual test. A mask is also a direct capture option, but the image retains a visible covered region. A real dismiss click adds an interaction step and makes the resulting page state part of the flow.
For reliable captures, use a selector specific to the popup, wait for the page to reach the intended state, and avoid hiding content that should be covered by the test. Screenshot automation also depends on page load behavior, browser resources, and stable content; the options above do not guarantee that a site will load successfully or that its markup will remain unchanged. Playwright itself has no per-screenshot fee described in the cited documentation; browser compute and maintenance still have operational costs in your environment.
10. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. One GET request returns an image or PDF, and its screenshot API can handle consent cleanup without setting up a browser. The API supports site-specific capture options too; see the ScreenshotNeo documentation.
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}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await require('node:fs/promises').writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
ScreenshotNeo accepts cookie and consent banners as a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each cleanup step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are never billed, and response headers report the page verdict and billing status. Its MCP server offers take_screenshot, get_page_info, and capture_pdf for AI agents. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots. Every feature is available on every plan.
Get 1,000 free screenshots a month with no card.
11. FAQ
Does hiding a popup with screenshot CSS close it for the user?
No. It changes the screenshot styling for the capture; use the site’s dismiss control to change the page state.
Can I use a selector like [role="dialog"] for every popup?
No. It works only when the popup has that attribute. Inspect the rendered markup and use a selector that matches the specific site.
Does fullPage: true remove a fixed cookie banner?
No. It captures the full scrollable page and does not itself hide or dismiss overlays.
Is a JavaScript alert the same as an HTML modal?
No. Native browser dialogs such as alert, confirm, and prompt are different from HTML elements. The sources cited here cover HTML screenshots, styles, locators, and masks; they do not establish native-dialog handling instructions.


