ScreenshotNeo

BlogHow-to

Puppeteer Screenshot of an Indian Website Keeps Showing the Consent Language Selector

Find out why a language or consent selector returns in Puppeteer screenshots, then diagnose browser state and capture the page you need.

By the ScreenshotNeo team4 October 202610 min read

If a language or consent selector keeps appearing in a Puppeteer screenshot, Puppeteer is capturing what the website rendered. page.screenshot() does not dismiss the selector or decide whether the site should show it. The cause depends on the site: it may be showing a first-visit choice, responding to stored browser state, or reacting to a language or regional signal. There is no universal Puppeteer setting or selector that reliably removes it.

To find the cause, compare the same page in the same browser context and a fresh context, inspect storage before and after making a choice, then vary one locale or region signal at a time. First decide whether you need to document the initial selector or capture the page after a visitor has made a choice.

What Puppeteer is capturing

Puppeteer’s screenshot method captures the page. Page interaction and browser-context methods let you change the page or its surrounding state, but screenshot capture itself does not suppress website UI. See the Puppeteer screenshot API and its Page API.

A selector that appears over the page is part of the site’s current rendered state. Its recurrence could relate to cookies or other storage, but the title alone does not identify the site, its storage key, its consent manager, or its region-detection rules. Treat each possible trigger as a hypothesis to check, not a guaranteed explanation.

Language choice can also be an intentional entry feature. Government of India guidance for government websites and apps recommends offering a preferred-language choice before entry. That guidance applies to its government-site context; it does not establish a technical rule for every Indian website. See the Guidelines for Indian Government Websites and apps.

Diagnose the selector systematically

  1. Choose the state you want to capture. A first-visit screenshot should preserve the selector. If you need the page after a choice, make that choice through the site’s real control and verify what changes.
  2. Compare a repeat visit with a fresh context. Keep the URL, viewport, network route, and wait conditions the same. A changed result points toward state associated with the existing context, but does not by itself prove which storage mechanism is responsible.
  3. Inspect storage before and after the choice. Check cookies and relevant browser storage. Puppeteer documents cookie operations in its cookie guide. Do not assume dismissing a consent banner also saves the language choice; the site may treat them separately.
  4. Change one regional signal per run. Test browser language or locale, timezone, geolocation emulation, and network egress separately. These are distinct inputs, and a particular site may ignore some or all of them.
  5. Record the conditions with the image. Note whether the context was fresh, any prior consent or language choice, the locale, timezone, geolocation, network route, and relevant wait condition.

A government portal’s cookie information, for example, describes persistent preferences that can include language settings. That shows one possible implementation, not proof that an unidentified site uses a cookie or the same mechanism. See the National Portal of India cookie information.

Runnable Puppeteer diagnostic

This Node.js example compares a new browser context with a second page in the same context. It records cookies and local storage, waits for the page to reach a usable state, and saves screenshots both before and after an optional manual choice. Set URL to the page you are diagnosing. The example deliberately does not guess a selector: inspect the target page and use its actual control if you need to automate the choice.

import puppeteer from 'puppeteer';

const url = process.env.URL;
if (!url) throw new Error('Set URL to the page you are diagnosing');

const browser = await puppeteer.launch({ headless: true });

async function capture(page, label) {
  await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 });
  // Allow client-rendered UI to appear. Replace with a site-specific wait
  // when you have identified a reliable page or selector condition.
  await new Promise(resolve => setTimeout(resolve, 1500));

  const cookies = await page.cookies();
  const localStorage = await page.evaluate(() => {
    return Object.fromEntries(
      Array.from({ length: window.localStorage.length }, (_, i) => {
        const key = window.localStorage.key(i);
        return [key, window.localStorage.getItem(key)];
      })
    );
  });

  console.log(JSON.stringify({ label, cookies, localStorage }, null, 2));
  await page.screenshot({ path: `${label}.png`, fullPage: true });
}

// First visit in a fresh context.
const context = await browser.createBrowserContext();
const first = await context.newPage();
await capture(first, 'fresh-before-choice');

// Inspect the screenshot or page and interact with the real site control here.
// If automating, replace this comment with a verified locator, for example:
// await first.locator('SITE_SPECIFIC_SELECTOR').click();
// Then wait for the site to save the preference before recording state.

const afterChoiceCookies = await first.cookies();
const afterChoiceStorage = await first.evaluate(() => ({
  localStorage: Object.fromEntries(
    Array.from({ length: window.localStorage.length }, (_, i) => {
      const key = window.localStorage.key(i);
      return [key, window.localStorage.getItem(key)];
    })
  ),
  sessionStorage: Object.fromEntries(
    Array.from({ length: window.sessionStorage.length }, (_, i) => {
      const key = window.sessionStorage.key(i);
      return [key, window.sessionStorage.getItem(key)];
    })
  )
}));
console.log(JSON.stringify({ label: 'after-choice', afterChoiceCookies, afterChoiceStorage }, null, 2));
await first.screenshot({ path: 'after-choice.png', fullPage: true });

// Repeat in the same context to see whether the site retained a preference.
const repeat = await context.newPage();
await capture(repeat, 'same-context-repeat');

// Compare with a separate fresh context. It has independent browser state.
const freshContext = await browser.createBrowserContext();
const fresh = await freshContext.newPage();
await capture(fresh, 'new-context');

await browser.close();

Run it with URL='https://example.com/' node diagnose.mjs after installing Puppeteer in your project. Replace the example domain with the target URL. The optional interaction is intentionally left site-specific: use a locator confirmed from the page’s DOM, and wait for the site’s resulting state before taking the post-choice screenshot. Store credentials and URLs with sensitive query parameters outside shared logs.

Automate a known choice

Once you have inspected the page and confirmed the correct control, use a stable locator and verify the outcome rather than clicking a guessed class or text. For example, if the site exposes an accessible button name that you have confirmed:

const choice = first.getByRole('button', { name: 'English', exact: true });
await choice.click();
// Wait for a site-specific signal that the choice took effect.
await first.waitForFunction(() => !document.querySelector('CONFIRMED_SELECTOR'));
await first.screenshot({ path: 'english-selected.png', fullPage: true });

Replace both the accessible name and CONFIRMED_SELECTOR with details verified on the target page. A language menu may be a link, dialog, custom widget, or page-entry step rather than a button. If the control navigates to another URL, wait for that navigation or for the destination page’s meaningful content.

Locale and region signals to isolate

When the page’s behavior seems regional, change one input per run and record it. Do not treat these settings as substitutes for one another.

Signal What to vary What the comparison can tell you
Browser language Set the page’s Accept-Language header or browser locale before navigation. Whether the site appears to respond to language preferences sent by the browser.
Timezone Set the context timezone before loading the page. Whether time or timezone-dependent logic changes the rendered state.
Geolocation Emulate coordinates and grant geolocation permission if the page requests it. Whether client-side location behavior appears to affect the selector.
Network egress Repeat through a different network route, keeping browser settings fixed. Whether behavior correlates with the network’s apparent location.
Stored preference Compare the same context before and after a real choice with a new context. Whether the existing browser context appears to retain a choice.

Browser locale and timezone can be configured before navigating. Geolocation may require permissions as well as coordinates. A network route is controlled outside the page context. If you change several of these together, you will not know which one corresponds with a different result.

// Example context setup: apply settings before opening the page.
const context = await browser.createBrowserContext();
await context.overridePermissions('https://TARGET_HOST', ['geolocation']);
const page = await context.newPage();
await page.setExtraHTTPHeaders({ 'Accept-Language': 'en-IN,en;q=0.9' });
await page.emulateTimezone('Asia/Kolkata');
await page.setGeolocation({ latitude: 28.6139, longitude: 77.2090 });
await page.goto(url, { waitUntil: 'domcontentloaded' });

Replace TARGET_HOST with the target origin and use coordinates appropriate to the scenario you intend to document. A site may not request geolocation, and these settings do not prove what location the site infers from its network connection.

Common errors and fixes

Symptom Likely explanation What to do
The selector returns after a click. The click may dismiss a panel without saving a preference, or the site may store consent and language separately. Inspect cookies and local or session storage before and after; verify the page state after reload.
A fresh context shows the selector, but the old one does not. The existing context may have retained state that the fresh context lacks. Compare storage and cookies; identify which state correlates with the change before deciding what to preserve.
The selector is missing from the first screenshot but appears later. It may be injected after initial navigation or after a client-side request. Wait for a verified page condition or selector, then capture. Avoid assuming domcontentloaded means all UI has appeared.
A guessed locator times out. The target control, accessible name, or selector differs from the assumption, or it is inside a frame or shadow root. Inspect the live DOM and frames; use a locator verified against the actual page and wait for its visibility.
Changing browser locale has no effect. The site may ignore that signal or use stored preferences, network location, or its own entry flow. Keep locale fixed and test other variables separately. Do not infer a specific mechanism from one negative result.
Geolocation emulation has no effect. The page may not request location, may not use it for language selection, or may rely on another signal. Check permission requests and test only when the page has a reason to use geolocation.
The screenshot captures a loading overlay or incomplete page. The chosen wait condition may finish before the relevant client rendering or assets. Wait for the target content or a site-specific readiness condition; use a bounded timeout to avoid hanging on perpetual network activity.
The prompt differs across runs with the same script. Context freshness, timing, network conditions, or remote site behavior may differ. Record those conditions, save diagnostic storage snapshots, and compare repeat and fresh-context runs.

Reliability, performance, and cost

  • Make state explicit. Choose a fresh context for first-visit captures, or deliberately reuse a context when you need retained state. Record which one you used.
  • Use bounded waits. Waiting for a specific page condition is usually more dependable than an arbitrary long delay. A short delay can help diagnose late UI, but should not be mistaken for a stable readiness signal.
  • Keep diagnostic runs comparable. Fix the viewport, URL, network route, and timing while comparing context or locale. Save screenshots and storage snapshots with labels that describe the conditions.
  • Manage browser resources. Close pages and contexts when the run is complete. Reuse a browser process for a batch of captures when appropriate, while creating separate contexts where state must be isolated.
  • Budget for the work your page requires. Local Puppeteer has no screenshot API charge in this workflow, but your runtime, browser hosting, bandwidth, and maintenance still have costs. Retries and long waits consume resources even when the output is not useful.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. For a direct capture, send a GET request with the target URL. Check the ScreenshotNeo API documentation for the request options and response details.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.in -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://example.in"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.in' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);

ScreenshotNeo can accept cookie and consent banners and remove more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each of these steps can be turned off. That may help when the intended output is a clean page image, but a language selector that is unique to a site may need its own handling. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed; response headers identify the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots per month with no card, and paid plans start at $5 for 3,000 screenshots.

Sign up free for 1,000 screenshots a month, with no card required.

FAQ

Pages in the same browser context can share browser state. Whether a particular site remembers consent, and where it stores that choice, depends on the site. Compare the context’s state before and after the choice.

No. It may be a language entry step, a consent interface, or another site-specific control. Inspect the page and its storage instead of assuming the selector’s purpose from its appearance.

Can I force an Indian language using Puppeteer?

You can set browser language preferences and test other regional inputs, but the site decides whether to honor them. A browser locale does not guarantee a particular language or remove a selector.

What should I save for a reproducible screenshot?

Save the URL, screenshot, context freshness, prior choices, storage observations, viewport, locale, timezone, geolocation settings, network conditions, and wait condition.