ScreenshotNeo

BlogAI agents

How to Make an AI Agent Screenshot a Page Behind a Login

Use an authorized Playwright session to log in, verify access, and capture a protected page. Learn how to reuse session state and keep screenshots private.

By the ScreenshotNeo team4 October 20269 min read

To make an AI agent screenshot a page behind a login, give it an authorized authenticated browser session, verify that login succeeded, navigate to the protected page, and then capture the viewport, an element, or the full page. A screenshot API call by itself cannot sign in. The example below uses Playwright with a normal sign-in flow and saves reusable browser state only after checking for proof of authentication.

Use an account you are authorized to access. Treat both the saved session state and the screenshot as sensitive: session files can enable account impersonation, and screenshots may reveal private page or account data.

1. Set up an isolated Playwright browser

This runnable Node.js example uses a dedicated browser context. It reads credentials from environment variables, signs in through the site’s ordinary login page, checks the final URL and a signed-in marker, saves storage state, opens the protected page, verifies a page-specific marker, and captures a full-page PNG.

npm install playwright
npx playwright install chromium

Save the following as screenshot-protected.mjs. Replace the example URLs and selectors with those for a site and account you are authorized to use.

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

const baseUrl = process.env.BASE_URL;
const loginUrl = process.env.LOGIN_URL;
const protectedUrl = process.env.PROTECTED_URL;
const email = process.env.LOGIN_EMAIL;
const password = process.env.LOGIN_PASSWORD;

if (!baseUrl || !loginUrl || !protectedUrl || !email || !password) {
  throw new Error('Set BASE_URL, LOGIN_URL, PROTECTED_URL, LOGIN_EMAIL, and LOGIN_PASSWORD.');
}

const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({
  viewport: { width: 1440, height: 1000 },
  // Set deviceScaleFactor: 2 for a higher-resolution image when needed.
});
const page = await context.newPage();

try {
  await page.goto(loginUrl, { waitUntil: 'domcontentloaded' });
  await page.locator('input[name="email"]').fill(email);
  await page.locator('input[name="password"]').fill(password);
  await page.locator('button[type="submit"]').click();

  // Use the site's real post-login URL or a stable signed-in UI marker.
  await page.waitForURL(url => url.href.startsWith(baseUrl + '/dashboard'), {
    timeout: 30_000,
  });
  await page.locator('[data-testid="signed-in-profile"]').waitFor({
    state: 'visible',
    timeout: 15_000,
  });

  // Keep this file private. It contains reusable authentication credentials.
  await mkdir('private', { recursive: true, mode: 0o700 });
  await context.storageState({ path: 'private/auth-state.json', indexedDB: true });

  await page.goto(protectedUrl, { waitUntil: 'domcontentloaded' });
  await page.locator('[data-testid="protected-content"]').waitFor({
    state: 'visible',
    timeout: 30_000,
  });
  await page.screenshot({ path: 'protected-page.png', fullPage: true });
  console.log('Saved protected-page.png');
} finally {
  await context.close();
  await browser.close();
}

Run it with environment variables rather than putting credentials in the source file or command history. For example, set them through your shell’s secret manager or a local, untracked environment file, then run node screenshot-protected.mjs. Add private/ and any secret files to .gitignore.

2. Prove login worked before capturing

A successful submit-button click does not mean the account is signed in. Login may involve several redirects, delayed cookie writes, multi-factor authentication, or an error page. Wait for a final URL, a stable profile control, or another marker that is only present to signed-in users. Then navigate to the protected URL and check for content unique to that page.

Use selectors that match the actual application. A URL check alone can be insufficient when a service returns an access-denied page at the expected URL. A page marker alone can be insufficient if the selector is too generic. Combining the expected URL and a page-specific marker is more reliable.

3. Choose what to capture

Playwright supports viewport screenshots, selected-element screenshots, and full-page screenshots. Use the viewport for the visible state, a locator for one component, or fullPage for the scrollable document.

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

// One element, such as a report or dashboard panel
await page.locator('[data-testid="report"]').screenshot({ path: 'report.png' });

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

// JPEG output with a chosen quality
await page.screenshot({ path: 'viewport.jpg', type: 'jpeg', quality: 85 });

Screenshot scale can be css (CSS pixels) or device (device pixels). The latter can produce a higher-resolution image when the context uses a device scale factor. Large full-page or high-resolution captures take more memory and produce larger files.

Wait for the relevant content before capture. If the page has lazy-loaded images or data that appears after navigation, wait for a specific locator or an application-specific ready signal. A fixed delay can help with a known animation, but it is less reliable than waiting for the content that matters.

4. Reuse authentication state across browser contexts

For repeatable runs, create a new context from the state saved after a verified login:

import { chromium } from 'playwright';

const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({
  storageState: 'private/auth-state.json',
});
const page = await context.newPage();

try {
  await page.goto(process.env.PROTECTED_URL, { waitUntil: 'domcontentloaded' });
  await page.locator('[data-testid="protected-content"]').waitFor({
    state: 'visible',
    timeout: 30_000,
  });
  await page.screenshot({ path: 'protected-page.png', fullPage: true });
} finally {
  await context.close();
  await browser.close();
}

Playwright storage state covers cookies, local storage, IndexedDB, and passkey-based authentication. It does not automatically preserve sessionStorage. If the application depends on session storage, use an authorized, domain-scoped save-and-restore step, such as capturing the relevant values from the authenticated page and installing an initialization script before application code runs in the new context. Avoid copying unrelated origin data.

State can expire when the site expires a session, revokes it, or requires a fresh sign-in. Refresh the state through the normal login process when that happens. For tests that change server-side state or use browser-specific authentication, use separate accounts or sessions where appropriate instead of sharing one account across parallel jobs.

5. Choose how the agent gets an authenticated page

Approach Best fit Considerations
Log in in a controlled Playwright context Automated runs with an authorized test or service account You control the browser lifecycle. Store credentials securely and verify login before capture.
Save and restore Playwright state Repeat runs that need to survive context restarts The state file is sensitive; it may expire. Session storage needs separate handling.
Share an already signed-in browser page A tool supports explicit sharing and the task needs the user’s existing session Do not assume an agent-created page inherits regular browser cookies. Confirm what the tool shares and revoke access when finished.
Use a hosted browser capability The workflow needs a managed browser session Confirm its authentication, data retention, availability, and access controls for your use case.

For example, Visual Studio Code documents that an agent-opened page is isolated unless the user shares a page. Cloudflare documents a Browser Run session option for agents. Those are tool-specific behaviors, not a general guarantee that browser cookies are shared.

6. Or skip the browser setup

For pages that are publicly reachable, ScreenshotNeo can return an image or PDF with one request. It cannot use the authenticated browser session in the Playwright example; use the browser workflow above for a page protected by your account login. ScreenshotNeo is a website screenshot API and MCP server by ScreenshotNeo, with [API documentation](https://screenshotneo.com/docs/).

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,
)
r.raise_for_status()
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}`);
const bytes = new Uint8Array(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', bytes));

ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before capture; bot checks, blank pages, failed loads, timeouts, and cache hits are never billed. Its MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Start with 1,000 free screenshots a month, no card required.

7. Protect session files and screenshots

  • Keep storage-state files out of version control and restrict access to the runner and authorized operators.
  • Use environment variables or a secrets manager for credentials; do not print credentials, cookies, storage state, or sensitive page content in logs.
  • Delete or refresh saved state when it expires or is no longer needed.
  • Limit screenshot access and retention to the people and systems authorized to view the page.
  • Use a dedicated account with only the access needed for the capture task when the service supports it.

A screenshot is a visual inspection artifact, not a substitute for a structured accessibility snapshot when an agent needs to understand page structure or text.

8. Troubleshooting

Symptom Likely cause Fix
Capture shows a login page Login did not complete, state was not restored, or the session expired Check the final login URL and signed-in marker. Re-authenticate through the normal flow and verify the protected-page marker.
Login click times out or redirects repeatedly Selector mismatch, multi-factor challenge, consent step, or delayed redirect Inspect the expected login flow, use stable selectors, and handle required authorized steps. Wait for the final URL or signed-in control.
Storage state loads but access is denied Expired/revoked state, account restrictions, or site-bound session behavior Sign in again and save fresh state. Check the site’s rules and avoid assuming a state file is permanent.
Works in the current context, fails in a new one Authentication depends on session storage or another context-specific value Identify the site’s required storage mechanism. Playwright’s ordinary state file does not persist session storage; add a narrowly scoped restore step if authorized.
Screenshot is blank or incomplete Capture ran before content rendered, lazy content was not loaded, or the wrong page was reached Wait for a page-specific marker, confirm the URL, and trigger or wait for needed content before capture.
Full-page screenshot is unexpectedly large Long document or high device-pixel scale Capture a viewport or specific element, reduce the viewport/device scale as suitable, or use JPEG quality when a lossy image is acceptable.
State file appears in a commit Secret file was not ignored or was staged before ignore rules were added Remove it from tracking, rotate/revoke the affected session if needed, and prevent future commits with repository ignore rules and secret handling.

9. Reliability, performance, and cost

Browser automation has setup and runtime costs: launching a browser, completing authentication, loading the page, waiting for dynamic content, and encoding the image. Reusing state can avoid repeated login steps, but it introduces expiry and secret-management work. Keep captures focused on the required viewport or element when full-page output is unnecessary; large pages and high-resolution images use more memory, time, and storage.

For reliable jobs, make authentication and page readiness explicit, use bounded timeouts, capture only after content checks pass, and report failures without logging secrets or page contents. Retry only transient failures; repeatedly submitting credentials or retrying an access-denied page will not fix invalid authentication. Cost depends on the browser infrastructure and execution environment you choose; the sources here do not establish a universal per-capture cost.

10. Frequently asked questions

Can an agent use the cookies in my normal browser automatically?

Do not assume so. Browser tools commonly isolate agent sessions. Use an explicitly supported page-sharing mechanism or sign in within the controlled context.

Can I use this for a page I cannot access?

No. The workflow requires an account and access you are authorized to use. It does not bypass authentication or access controls.

Does a screenshot prove the page is accessible to everyone?

No. It records what the authenticated browser rendered in that session. It does not establish public access or replace authorization checks.

Should an AI agent inspect a screenshot or page structure?

Use a screenshot for visual layout and rendering issues. Use accessibility or structured page information when the task depends on semantics, controls, or text relationships.

Sources