ScreenshotNeo

BlogHow-to

How to Capture a Scheduled Screenshot After Accepting a Cookie Banner

Use Playwright to accept a site’s cookie banner, verify the page state, and capture a screenshot on a schedule. Includes storage, retries, and API options.

By the ScreenshotNeo team4 October 202610 min read

Direct answer: use a browser automation script that opens the page, accepts the site’s actual consent control, confirms the banner is gone or the expected post-consent state is visible, and then saves a screenshot. Run that script on a schedule provided by your host. With Playwright, the capture logic is portable; the scheduler and output storage depend on where you run it.

There is no universal cookie-banner selector or schedule configuration that works for every site. You need the target URL, the banner’s real accept control, a reliable post-consent condition, and a runtime that can launch a browser. This guide uses Node.js and Playwright for the do-it-yourself flow, then shows how to reuse consent state, choose screenshot scope, and handle recurring failures.

1. Build a Playwright capture script

Install Playwright and its Chromium browser in your project:

npm init -y
npm install playwright
npx playwright install chromium

Save this as capture.mjs. Replace the example URL, accept-button locator, and confirmation locator with selectors that match the target site. The example fails clearly if the expected banner or post-consent state does not appear.

import { chromium } from 'playwright';
import { mkdir } from 'node:fs/promises';

const targetUrl = process.env.TARGET_URL ?? 'https://example.com';
const outputDir = process.env.OUTPUT_DIR ?? 'screenshots';
const runAt = new Date().toISOString().replaceAll(':', '-');
const outputPath = `${outputDir}/page-${runAt}.png`;

const browser = await chromium.launch({ headless: true });
try {
  const context = await browser.newContext({
    viewport: { width: 1440, height: 1000 },
    // For a site that needs authentication, provide storageState here.
    // storageState: 'playwright/.auth/state.json',
  });
  const page = await context.newPage();
  await page.goto(targetUrl, { waitUntil: 'domcontentloaded', timeout: 45_000 });

  // Replace with a locator specific to the site's consent UI.
  const acceptButton = page.getByRole('button', {
    name: /accept all cookies|accept cookies|allow all/i,
  });
  await acceptButton.waitFor({ state: 'visible', timeout: 15_000 });
  await acceptButton.click();

  // Replace with a dependable post-consent condition for this page.
  // This can be a known page heading or the banner becoming hidden.
  await acceptButton.waitFor({ state: 'hidden', timeout: 10_000 });

  await page.locator('main').waitFor({ state: 'visible', timeout: 15_000 });
  await mkdir(outputDir, { recursive: true });
  await page.screenshot({ path: outputPath, fullPage: true, animations: 'disabled' });
  console.log(`Saved ${outputPath}`);
  await context.close();
} finally {
  await browser.close();
}

Run once manually before scheduling it:

TARGET_URL='https://example.com' node capture.mjs

Use a locator that expresses the visible meaning of the control when possible, such as an accessible button name. If the dialog is inside an iframe, locate the correct frame before selecting the control. If the site has separate “accept necessary” and “accept all” actions, choose the action that matches the consent you intend to record. Playwright’s Page API documents navigation, locators, interactions, and screenshots.

A successful click does not by itself prove that consent was saved or that the page has reached the state you want to record. Wait for an observable condition tied to the site:

  • Wait for the consent dialog or accept button to become hidden.
  • Wait for a known page heading, navigation item, or other post-consent content to appear.
  • If the site updates content asynchronously after acceptance, wait for that content rather than adding an arbitrary fixed delay.

The sample waits for both the accept button to disappear and the main content to become visible. Some sites keep the button in the DOM after the dialog closes, or use a different banner structure; inspect the page and choose a condition that actually changes after acceptance. Avoid simply hiding the banner with CSS: that removes it from the image without recording the user-facing consent action.

Timeouts should be long enough for the target and execution environment, but finite. A timeout turns a broken consent flow into a visible failed run instead of quietly saving a misleading image.

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

Pick the screenshot scope based on what the scheduled image must show:

Scope Playwright option Good fit Things to check
Visible viewport fullPage: false (the default) A fixed-size visual check or dashboard view Set a consistent viewport; content below the fold is omitted.
Full page fullPage: true A long article or whole-page archive Very tall pages can produce large images and may trigger lazy-loaded content only as the page is prepared for capture.
One element page.locator('selector').screenshot(...) A chart, card, or specific component Confirm the element exists, is visible, and is not clipped by an ancestor.

For an element image, replace the page screenshot call with:

const chart = page.locator('[data-testid="revenue-chart"]');
await chart.waitFor({ state: 'visible', timeout: 15_000 });
await chart.screenshot({ path: outputPath, animations: 'disabled' });

For a fixed viewport, set its width and height in browser.newContext. Use the same viewport, device scale factor, and capture scope between runs if you plan to compare images. Playwright also supports screenshot options and element screenshots; see its screenshots guide.

You can accept consent on every run, or save browser storage state after acceptance and load it for later runs. Reuse is convenient only when the site’s consent choice is represented by state that Playwright saves and the site continues to recognize it.

After a successful acceptance, save state:

await context.storageState({ path: 'playwright/.auth/consent-state.json' });

On later runs, load it while creating the context:

const context = await browser.newContext({
  viewport: { width: 1440, height: 1000 },
  storageState: 'playwright/.auth/consent-state.json',
});

Keep this state file private if it includes login cookies or other session data, and do not commit it to a public repository. A site’s consent implementation may store the choice in a cookie, local storage, or another mechanism. Playwright’s authentication guidance explains saving and reusing browser storage state; verify the target site’s behavior rather than assuming consent persists in a particular place. If stored state expires or stops working, return to accepting the banner during each run.

5. Schedule the script in the runtime you use

The scheduler belongs to the host environment. The Playwright capture documentation covers browser operations, but does not select a scheduler or hosting service for an unspecified setup. Configure your host’s scheduler to run the script at the desired recurrence, ensure it has Node.js and the installed browser available, and direct output to storage that persists between executions if the runtime’s local disk is temporary.

Before relying on a recurring job, confirm these settings in the chosen host:

  1. Command and working directory: invoke node capture.mjs from the project directory, or use absolute paths.
  2. Environment: provide TARGET_URL and any credentials or output settings through the host’s secret and environment configuration.
  3. Browser dependencies: install Chromium and any operating-system libraries required by the runtime.
  4. Persistent output: choose durable storage or upload each result; local files may not remain after a job ends.
  5. Overlap behavior: avoid concurrent runs writing the same filename or overwriting one another.
  6. Failure visibility: retain logs and configure the host to report a nonzero exit status or retry according to your needs.

The timestamped filename in the example distinguishes runs. For a fixed “latest” image as well, save or copy the successful result to a stable path after the timestamped file has been written.

6. Make scheduled captures reliable

Recurring jobs expose transient failures that a one-off run may not reveal. Use an explicit navigation timeout, explicit consent and content conditions, and a clear failure signal. For flaky sites, a bounded retry can help with temporary network errors; do not retry indefinitely or treat a bot check, CAPTCHA, or unexpected page as a valid screenshot.

Keep the screenshot step after the consent and readiness checks. If the page contains animations or video, disable animations for the capture where appropriate or wait for a specific stable state. A fixed sleep may be useful when a known delayed element has no observable readiness condition, but it can make jobs slower and remains fragile if load time varies.

Playwright’s screenshot stability feature often cited in this context belongs to its test runner’s screenshot assertion: the assertion waits until consecutive screenshots match before comparing to an expectation. That behavior is not an automatic stability guarantee for this standalone script. The PageAssertions reference describes the test assertion behavior; a scheduled capture script should define its own ready condition.

7. Troubleshoot common failures

Symptom Likely cause Fix
“Timeout” while waiting for the accept button The site uses different wording, a different element, or an iframe; the dialog may also be absent because consent state was already saved. Inspect the rendered page, update the locator, handle the correct frame, or make the consent step conditional on the dialog being visible.
Click succeeds but the banner remains The click hit the wrong control, a second dialog appeared, or the site did not persist consent. Check the chosen action and wait for a site-specific confirmation. Handle any follow-up dialog explicitly.
Screenshot still contains the banner The script captures before the dismissal completes, or the locator is not tied to the full dialog. Wait for the dialog itself to become hidden and verify the resulting page state before capture.
Blank or incomplete page Navigation resolved before the application rendered, a resource failed, or the page requires more readiness conditions. Wait for a meaningful page element, inspect logs and network behavior, and fail the run if expected content never appears.
Consent returns on every run The site expires consent or stores it somewhere outside the reused storage state. Accept on each run, or investigate the site’s documented persistence behavior and confirm the saved state is loaded.
Browser fails to launch on the host Chromium or required system libraries are missing, or the runtime cannot run the installed browser. Install Playwright’s browser for the deployment environment and its required dependencies; verify the job uses the same runtime where installation occurred.
Output file is missing after the job The scheduler uses a different working directory or ephemeral local storage. Use an absolute output path or upload the file to persistent storage as part of the job.
Runs overwrite one another They share an output filename or execute concurrently. Use a unique run identifier or timestamp and configure concurrency limits if the host supports them.
Unexpected CAPTCHA or bot-check page The destination served a challenge instead of the intended page. Record the run as unsuccessful, follow the destination’s access requirements, and do not label the challenge screenshot as a successful capture.

8. Performance, reliability, and cost

Browser startup and page loading usually dominate a single scheduled capture; full-page images and large resources can add time and storage. Keep one browser process per job and create a fresh context for the run when session isolation matters. Reusing saved state can avoid repeating the consent interaction, but it does not eliminate navigation or rendering work and may become stale.

Set practical navigation and locator timeouts, avoid unnecessary fixed waits, and capture only the scope you need. A lower viewport size or element screenshot can reduce output size compared with a very tall full-page image. The browser script itself does not prescribe a hosting price: cost depends on the scheduler, runtime, and storage you choose. Include browser execution, output retention, and retry volume in your estimate.

9. Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server for developers. It handles the recurring scheduler separately: call the API from your existing scheduled job, or let an AI agent use its MCP tools. A single GET request captures a URL as PNG, JPEG, WebP, or PDF. The ScreenshotNeo API documentation lists the request options and parameter names.

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 and removed before capture; newsletter popups and chat widgets are removed too. Each of these steps can be turned off.
  • Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing. Response headers report the page verdict and whether the capture was billed.
  • The MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, or another MCP client.
  • The Free plan includes 1,000 screenshots each month with no card; paid plans start at $5 for 3,000 screenshots. Every feature is on every plan.

Schedule the API call using your host’s scheduler and choose a filename or output destination for each run. Create a free account for 1,000 screenshots a month with no card.

10. Frequently asked questions

Can I accept a banner once and use that decision forever?

Only if the site continues to recognize the saved consent state. Consent can expire or change when the site changes its storage behavior, so keep a fallback that handles the banner again.

Should I use a screenshot assertion for a scheduled capture?

Use a test-runner screenshot assertion when you are checking a screenshot against an expectation. For a saved recurring image, define page-specific readiness checks in the capture script.

Can the same job capture several URLs?

Yes. Put the URLs in a list and run the navigation, consent handling, readiness check, and screenshot steps for each URL. Use per-site consent logic where their banners differ, and give each output a distinct name.

Does the scheduler come from Playwright?

No. Playwright automates the browser. Your host or deployment environment starts the script on the recurrence you configure.