ScreenshotNeo

BlogHow-to

How to capture a web page screenshot after a GDPR consent choice is saved

Use Playwright to make and verify a consent choice, preserve browser state when needed, and capture the resulting page reliably.

By the ScreenshotNeo team4 October 202610 min read

To capture a page after a GDPR consent choice is saved, use the site’s actual consent controls, verify that the selected state took effect, then take the screenshot. With Playwright, the core capture call is await page.screenshot({ path: 'screenshot.png' }). If the choice must survive into a later run, save and restore the browser context’s storage state, and check whether the site also relies on session storage or another mechanism.

A screenshot records the page’s rendered appearance at a point in time. By itself, it does not prove that a site handled consent lawfully or show every relevant processing action. For reproducible or investigative work, retain appropriate browser-state and network evidence as well.

1. Choose the state you need to document

Decide whether the screenshot should show a first visit, the page after accepting, the page after rejecting, or a particular set of purposes. These are different test conditions. A fresh browser context helps keep an earlier run’s cookies and storage from silently changing a first-visit test; a context loaded from saved state represents a returning visitor.

Goal Starting point What to capture
Document the first-visit banner New isolated context without prior site state Viewport or element containing the banner
Document the page after a choice New context; make the choice in the site UI Page after confirming the choice took effect
Repeat a returning-visitor scenario Context initialized from saved state Resulting page, with the state provenance recorded
Compare page rendering Same saved state and stable browser environment Consistent screenshot assertion or baseline

Do not assume a universal button label, cookie name, or local-storage key. Consent interfaces and persistence mechanisms vary by site. Prefer the visible interface the site actually renders, because injecting an assumed storage value can bypass site logic and misrepresent the user interaction.

2. Install Playwright and prepare a project

The example uses Node.js and Playwright. Create a project and install the package and browser:

mkdir consent-screenshot
cd consent-screenshot
npm init -y
npm install playwright
npx playwright install chromium

Save the script below as capture-consented-page.mjs. It accepts the target URL, an optional accessible button name, and an optional output path. The button-name argument is deliberately site-specific: inspect the target page and pass the exact accessible name exposed by its consent interface.

3. Make the choice, verify it, and capture

import { chromium } from 'playwright';

const [url, choiceName, output = 'screenshot.png'] = process.argv.slice(2);
if (!url || !choiceName) {
  console.error('Usage: node capture-consented-page.mjs <url> <accessible-choice-name> [output.png]');
  process.exit(2);
}

const browser = await chromium.launch({ headless: true });
// A new context starts without cookies or local storage from another run.
const context = await browser.newContext({ viewport: { width: 1440, height: 1000 } });
const page = await context.newPage();

try {
  await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 45000 });

  // Site-specific interaction: use the actual accessible name, such as the
  // target site's displayed “Reject optional” or “Accept all” action.
  await page.getByRole('button', { name: choiceName, exact: true }).click({ timeout: 15000 });

  // Replace this with a site-specific confirmation that the choice took effect.
  // This generic check only confirms that the clicked control is no longer visible;
  // it does not prove persistence or legal compliance.
  await page.getByRole('button', { name: choiceName, exact: true }).waitFor({ state: 'hidden', timeout: 10000 });

  // Save cookies and local storage for a later run. Review this file before sharing.
  await context.storageState({ path: 'consent-state.json' });

  // Capture the post-choice page. Set fullPage: true when the whole scrollable
  // document is required; omit it for a viewport image.
  await page.screenshot({ path: output, fullPage: true });
  console.log(`Saved ${output}; browser state saved to consent-state.json`);
} finally {
  await context.close();
  await browser.close();
}

Run it with a URL and the real accessible name of the action you intend to choose:

node capture-consented-page.mjs 'https://example.com' 'Reject optional' 'after-reject.png'

example.com and Reject optional are placeholders, not a claim about a particular site’s interface. Inspect the target’s accessible roles and names, then use a locator matching its actual control. If the choice is made in a preferences dialog, interact with the relevant purpose controls and the dialog’s save or confirm action. Do not silently substitute “accept all” for a different intended choice.

Make the verification meaningful

The example’s “button becomes hidden” check is only a simple placeholder. A stronger, site-specific check can confirm a preference summary, a visible settings indicator, or expected storage state after the interaction. For example, inspect cookies and local storage for the target origin after the page has applied its choice. The exact key and value must come from that site’s implementation or documentation; there is no universal consent key.

const state = await context.storageState();
console.log(state.cookies.map(({ name, domain }) => ({ name, domain })));
console.log(await page.evaluate(() => Object.keys(localStorage)));

Inspect values only when necessary, and avoid printing session or authentication material into shared logs. Browser storage demonstrates persistence of browser data; it is not by itself proof of what requests or processing occurred.

4. Save and reuse the selected browser state

Playwright browser contexts are isolated sessions. The storageState API saves cookies and local storage for reuse; the current API also supports optional IndexedDB capture. Check the target site’s behavior before relying on a saved state, because some sites use session storage or another mechanism. Playwright’s authentication guide describes session-storage handling separately.

To open a later context from the saved file, use:

import { chromium } from 'playwright';

const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({
  storageState: 'consent-state.json',
  viewport: { width: 1440, height: 1000 }
});
const page = await context.newPage();
try {
  await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
  // Add a target-specific assertion that the expected returning-visitor state is active.
  await page.screenshot({ path: 'returning-visitor.png', fullPage: true });
} finally {
  await context.close();
  await browser.close();
}

Saved browser-state files can contain cookies or other material that grants access to an account. Keep them out of source control, restrict access, and inspect them before sharing. A consent-only run may contain less sensitive data, but check rather than assume.

Session storage and other state

Cookies and local storage are the ordinary saved-state case. Session storage is scoped differently and may not be included in the saved context file. If the target uses it, explicitly save and restore the necessary values with a site-aware approach, then verify the resulting page. Avoid copying all storage blindly: retain only what the test needs, and do not transfer authentication data into an artifact intended for publication.

5. Choose viewport, full-page, or element capture

Use the smallest capture that answers the documentation question. A viewport screenshot records what a visitor sees without scrolling. A full-page screenshot records the entire scrollable document, which is useful when below-the-fold content matters. An element screenshot can isolate a banner, confirmation, or page section.

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

// Entire scrollable page
await page.screenshot({ path: 'full-page.png', fullPage: true });

// One relevant element
await page.getByRole('main').screenshot({ path: 'main-content.png' });

Use a banner-specific locator for a banner capture, if the site exposes one. Full-page capture can be less suitable when the evidence is specifically about the banner or a small post-choice confirmation. For a visual regression test, use Playwright’s screenshot assertions and keep the browser and rendering setup stable.

6. Keep the capture repeatable

  • Fix the browser engine and version, viewport dimensions, device scale factor, and color scheme for comparisons.
  • Use the same operating system and headless or headed mode where practical. Rendering can vary with host OS, browser version, settings, hardware, power source, and headless mode.
  • Wait for the target-specific post-choice confirmation and any content needed in the screenshot. A navigation event alone does not prove the page is ready.
  • For lazy-loaded content, scroll or otherwise trigger the content before a full-page capture if the page requires it.
  • Record the URL, choice made, time, viewport, browser version, and whether the image is viewport, full-page, or element capture when the artifact needs to be reproduced.

A screenshot assertion is useful when the goal is to detect a change against a baseline; a one-time screenshot merely captures an image. See Playwright’s documentation on screenshots, visual comparisons, authentication and saved state, and the BrowserContext API.

7. Understand what the screenshot can establish

The screenshot can show the rendered interface after a choice at a particular time. It cannot alone establish that consent was freely given, sufficiently specific, informed, or lawfully implemented, and it cannot show every network request or subsequent processing action. The European Commission’s business guidance says consent-required cookies should not be set when a page is first opened and describes purpose-specific choice and withdrawal. Its citizen guidance lists information to provide before a decision. These are general public guidance points, not a legal conclusion about an unidentified site.

The European Data Protection Board’s Guidelines 05/2020 on consent and its cookie-banner taskforce report provide further official context. The taskforce report discusses the boundary between ePrivacy rules for storing or accessing information on terminal equipment and the GDPR framework for subsequent processing; it should not be treated as a ruling on every banner design.

When a broader record is needed, consider retaining a storage snapshot and network traffic alongside the screenshot. The EDPS Website Evidence Collector describes collecting page screenshots, visited-page information, HTML5 local storage, and HTTP traffic as HAR, among other artifacts. No particular artifact set is sufficient in every situation.

8. Common problems and fixes

Symptom Likely cause Fix
Consent control not found The accessible name or role differs, the banner is inside a frame, or it has not rendered yet. Inspect the actual page and accessible tree, wait for the site-specific control, and use a locator matching the rendered interface. Check frames when applicable.
Click times out or is intercepted An animation, overlay, or another element blocks the action. Wait for the dialog to settle, target the visible intended control, and diagnose overlays. Avoid forced clicks that can bypass the user-facing interaction.
Banner returns on the next run The choice was not persisted, the site uses session storage, or the later context did not load the saved state. Verify the choice and storage after interaction; restore the saved state; handle session storage separately if the site uses it.
Screenshot still shows the banner Capture occurred before the choice was applied, or the verification condition was too weak. Wait for a site-specific confirmation, then capture. Do not equate a click completing with a saved choice.
Screenshot is blank or incomplete Navigation failed, content loads later, or content is lazy-loaded. Check the page URL and load errors, wait for the needed content or selector, and trigger lazy content before full-page capture.
Image differs across runs Browser, OS, viewport, device scale, fonts, animation, or dynamic content changed. Pin the rendering environment and dimensions; disable or wait out animations where appropriate; use visual assertions with suitable tolerances.
Saved state cannot be shared safely The state file contains session or authentication cookies. Restrict access, do not commit it, and create a minimal consent-only state when possible.
Clicking a control does not prove the intended purposes The interface may have multiple purpose toggles or a separate save action. Set the intended options in the site’s real preferences UI, submit its confirmation, then verify the resulting state.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. Its API takes a URL in one GET request and returns an image or PDF. See the API documentation for parameters and response details.

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}`);
  • Cookie banners are accepted as a visitor and more than 60 known consent platforms, newsletter popups, and chat widgets are removed before the shot; each step can be turned off.
  • Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing; response headers report the page verdict and billing status.
  • An MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.
  • The free plan includes 1,000 shots per month with no card. Paid plans start at $5 for 3,000 shots; every feature is on every plan.

This API call captures a URL and does not make or preserve your particular GDPR choice. Use browser automation when you need to choose a specific preference through a site’s interface and carry that state forward. Sign up for 1,000 free screenshots a month with no card.

FAQ

Does a saved screenshot prove GDPR compliance?

No. It records visible appearance at capture time. Compliance depends on the site’s behavior and context, which a screenshot alone does not establish.

Only if you have verified the site’s implementation and the test specifically calls for restoring that state. For a first-visit or choice-making test, use the actual interface so the site’s own logic runs.

Should I capture the whole page?

Only when below-the-fold content matters. Use a viewport or element capture when the evidence concerns a banner or confirmation.

What if the site stores the choice in session storage?

Handle that storage separately and verify the resulting page. Ordinary saved browser state covers cookies and local storage, while session storage may require custom save and restore logic.

Sources