ScreenshotNeo

BlogHow-to

How to Capture Product Screenshots on Pages with Consent Popups in Playwright

Capture a consent popup as shown, or choose a consent option before taking a product screenshot. Includes runnable Playwright code and troubleshooting.

By the ScreenshotNeo team4 October 20268 min read

To capture a product page with a consent popup in Playwright, first determine whether the popup is an HTML overlay or a JavaScript browser dialog. For an HTML consent panel, use a locator to either leave it visible for the screenshot or activate the intended site control and capture the resulting state. Then call page.screenshot(). JavaScript alerts and prompts use Playwright’s dialog event instead.

The right sequence depends on the screenshot you need: document the consent notice, show the page after a choice, or reproduce a first visit. Consent controls and their labels vary by site; there is no universal cookie-banner selector.

1. Choose the screenshot state

Goal Sequence
Show the consent notice Navigate, wait for the panel to appear, then capture without clicking a choice.
Show the page after consent Find the actual accept, reject, or preference control, click it, verify the panel or expected page state changed, then capture.
Reproduce a first visit Use a fresh browser context and navigate to the page so the run does not inherit prior browsing state.

In an automated test, clicking a site’s consent control is an interaction with that UI; do not treat it as consent on behalf of a real user. Choose the control that matches the state your screenshot is intended to represent.

2. Install Playwright and capture the page

The following runnable Node.js script saves a screenshot with a consent panel left visible. Replace the URL and the example accessible name with values observed on your target page. It uses a fresh, non-persistent context for an isolated run.

npm init -y
npm install playwright
npx playwright install chromium

Save as capture-consent.cjs and run with node capture-consent.cjs:

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

(async () => {
  const browser = await chromium.launch();
  const context = await browser.newContext();
  const page = await context.newPage();

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

    // Replace this with a locator for the observed consent panel.
    const consentPanel = page.getByRole('dialog', { name: 'Cookie preferences' });
    await consentPanel.waitFor({ state: 'visible', timeout: 10000 });

    // Leave the consent controls untouched when the notice should be shown.
    await page.screenshot({ path: 'product-with-consent.png', fullPage: true });
  } finally {
    await context.close();
    await browser.close();
  }
})();

Some sites do not expose the panel with dialog semantics. Inspect the page and use an observed locator instead, such as a site-specific test ID, role, label, or CSS selector. Prefer accessible locators like getByRole() when the markup supports them. A generic selector guessed from another site is brittle.

To show the page after a choice, target the actual button by its observed accessible name. Wait for a meaningful result after clicking: for example, the panel becoming hidden or a page heading becoming visible.

const consentPanel = page.getByRole('dialog', { name: 'Cookie preferences' });
await consentPanel.waitFor({ state: 'visible' });

await consentPanel.getByRole('button', { name: 'Reject optional cookies' }).click();
await consentPanel.waitFor({ state: 'hidden' });

await page.screenshot({ path: 'product-after-consent.png', fullPage: true });

Replace the example name with the exact control and outcome appropriate to the page. If the panel remains visible by design after saving preferences, assert the site’s actual confirmation or expected content instead of waiting for it to disappear. For a preference center, set the relevant options before activating its save or confirm control.

4. Handle JavaScript dialogs separately

A browser dialog created by alert(), confirm(), prompt(), or a beforeunload event is not an HTML consent panel. Playwright automatically dismisses JavaScript dialogs when no dialog listener is installed. If you install a listener, it must accept or dismiss the dialog; a handler that only logs it can leave the page action waiting.

Register the handler before the action that triggers the dialog:

page.on('dialog', async dialog => {
  console.log(`Dialog type: ${dialog.type()}, message: ${dialog.message()}`);
  await dialog.dismiss(); // Use accept() when that is the intended behavior.
});

await page.getByRole('button', { name: 'Open product details' }).click();
await page.screenshot({ path: 'product.png' });

Use await dialog.accept() to accept a dialog, or await dialog.dismiss() to dismiss it. For a prompt that needs text, pass it to accept(promptText). The handler’s choice should match the scenario being captured. See the [Playwright Dialogs guide](https://playwright.dev/docs/dialogs).

5. Make the capture repeatable

Fresh session or saved state

A new non-persistent browser context isolates a run from other pages and does not write browsing data to disk. This is useful when the screenshot should reproduce a first visit and show a banner that is otherwise suppressed by saved preferences. If the intended image depends on an already-chosen preference, deliberately load the corresponding storage state instead. Do not reuse a context accidentally when consistency depends on a clean session.

Wait for the UI you need

Wait for the relevant panel, button, confirmation, or page heading with a locator assertion or waitFor(). A fixed sleep can be too short on a slow run and waste time on a fast one. Playwright discourages using networkidle as a blanket testing-readiness strategy; background analytics or other ongoing requests can make it a poor signal. The readiness check should describe the state the screenshot requires. See the [Page API](https://playwright.dev/docs/api/class-page).

Capture only the intended area

  • fullPage: true captures the full scrollable page; omit it for the current viewport.
  • mask: [locator] visually covers matching content in the screenshot. Masking does not dismiss a banner, save a preference, or change page state.
  • For an element-only image, use the target element locator’s screenshot method, such as await page.locator('[data-testid="product-card"]').screenshot({ path: 'card.png' }).

Choose full-page capture, viewport capture, or masking based on what the image is meant to communicate. When the consent panel itself is the subject, do not mask it.

Handle a new tab or window

If a click opens a new page, wait for the popup before clicking and take the screenshot from the returned page. That popup belongs to the opener’s browser context.

const popupPromise = page.waitForEvent('popup');
await page.getByRole('link', { name: 'Open product page' }).click();
const popup = await popupPromise;
await popup.waitForLoadState('domcontentloaded');
await popup.screenshot({ path: 'opened-product.png' });

6. Troubleshooting

Symptom Likely cause Fix
“Locator resolved to 0 elements” or a timeout waiting for the panel The assumed selector, role, or name does not match this site, the banner has not appeared, or prior state suppressed it. Inspect the rendered page and use its real locator. For a first-visit screenshot, start with a fresh context.
Click times out because another element intercepts it A visible overlay covers the target, or the chosen locator matches a hidden duplicate. Locate the consent panel and its button specifically. If the overlay is known and expected, handle it explicitly before the blocked action.
The page action hangs after a dialog appears A dialog listener was added but did not resolve the dialog. Call accept() or dismiss() in the handler. If no deliberate handling is needed, remove the listener and Playwright auto-dismisses dialogs.
The consent panel is missing from the screenshot A reused context retained a previous choice, or the script clicked a control before capture. Use a fresh context and do not activate a choice when the notice should remain visible.
The screenshot is blank, incomplete, or taken too early Navigation completion did not mean the target UI was ready, or the site rendered content later. Wait for the specific panel or page content needed before capture. Avoid relying on a guessed delay.
The output unexpectedly hides the consent panel A screenshot mask covers it. Remove the mask or narrow it to the intended locator. A mask alters pixels only; it does not handle consent.
Popup page is not captured The screenshot is taken from the opener instead of the new page, or the popup event was not awaited. Wait for page.waitForEvent('popup') before the click, then capture the returned page.

7. Performance, reliability, and cost

For reliable captures, wait on the specific UI state rather than adding long sleeps or waiting for every network connection to stop. Close contexts and browsers in a finally block so they are cleaned up when navigation, waiting, or screenshot writing fails. Keep the session strategy explicit: a fresh context gives isolation, while saved state reproduces a returning visitor.

Full-page screenshots and large pages can take longer and produce larger files than viewport captures. Capture the smallest area that meets the requirement, and avoid repeating browser launches unnecessarily in a larger capture job; contexts can be isolated within a browser process. Playwright itself is the browser-automation route shown here, so resource costs depend on where and how you run the browser. There is no task-specific benchmark or fixed cost figure in the Playwright documentation.

Or skip the browser setup

If your goal is a clean website screenshot without maintaining a browser script, [ScreenshotNeo](https://screenshotneo.com) provides a screenshot API and MCP server. The one-call API returns an image or PDF; see the [ScreenshotNeo API docs](https://screenshotneo.com/docs/) for its parameters.

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

Python:

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)

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
  • 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; paid plans start at $5 for 3,000.

Start with 1,000 free screenshots a month, no card required.

FAQ

Yes. Wait for the panel to be visible and take the screenshot without activating a choice control.

Usually the visible banner is page content. Inspect the page; use locators for an HTML panel and the dialog event for browser-native JavaScript dialogs.

Does masking remove a popup from the page?

No. It covers a locator in the resulting image and leaves the page state unchanged.

How do I make the banner appear on every run?

Start from a fresh, isolated context so an earlier stored preference does not carry over.

Official Playwright references