ScreenshotNeo

BlogHow-to

How to Capture a Website After Login

Capture authenticated pages once in a browser or automate repeatable screenshots with Playwright, saved auth state, and ScreenshotNeo.

By the ScreenshotNeo team1 October 20269 min read

To capture a website after login, sign in first, open the protected page, then use a full-page, viewport, or element screenshot command. For one-off work, Firefox Developer Tools is enough. For repeatable captures, Playwright can save authenticated browser state and reuse it in later runs.

This guide covers manual captures, Playwright automation, authentication state, MFA and session edge cases, lazy-loaded content, protected assets, troubleshooting, and a managed alternative with ScreenshotNeo.

Choose the right capture method

Situation Best method Why
One screenshot now Firefox Developer Tools Sign in normally and capture the current page without writing code.
Recurring screenshots in a test or job Playwright with saved authentication state Repeat the same navigation and capture steps.
Chrome or Firefox automation already uses Puppeteer Puppeteer It supports browser interaction, screenshots, PDFs, and visual snapshots.
AI agent or API workflow ScreenshotNeo One request handles capture options and can use an MCP server for AI agents.

A full-page capture includes the complete scrollable document. A viewport capture includes only what is visible. An element capture isolates one DOM element such as a table or panel. Playwright documents all three scopes and can return an image buffer instead of writing a file. Read the Playwright screenshot documentation.

One-off capture in Firefox

  1. Sign in to the site and complete MFA, consent, or device verification.
  2. Navigate to the exact protected page.
  3. Open Firefox Developer Tools with F12 or Ctrl+Shift+I (Cmd+Option+I on macOS).
  4. Open Developer Tools settings and enable Take a screenshot of the entire page under Available Toolbox Buttons.
  5. Click the camera button to save the complete page to Downloads.
  6. For one component, open Inspector, right-click the element, and choose Screenshot Node.

Firefox’s official documentation describes both the entire-page button and the Inspector element capture. See Mozilla’s screenshot instructions.

Automate an authenticated capture with Playwright

Install Playwright and its browser binaries:

npm init -y
npm install -D playwright
npx playwright install

The following script performs an interactive login once, saves the authenticated state, opens a protected page, waits for its main content, and writes a full-page PNG.

import { chromium } from 'playwright';

const browser = await chromium.launch({ headless: false });
const context = await browser.newContext();
const page = await context.newPage();

await page.goto('https://example.com/login', { waitUntil: 'domcontentloaded' });
await page.getByLabel('Email').fill(process.env.LOGIN_EMAIL);
await page.getByLabel('Password').fill(process.env.LOGIN_PASSWORD);
await page.getByRole('button', { name: 'Sign in' }).click();

// Complete MFA or a device check here if the site requires it.
await page.waitForURL('**/dashboard');
await context.storageState({ path: 'auth-state.json' });
await browser.close();

const browser2 = await chromium.launch();
const authenticated = await browser2.newContext({ storageState: 'auth-state.json' });
const protectedPage = await authenticated.newPage();
await protectedPage.goto('https://example.com/protected-page', { waitUntil: 'networkidle' });
await protectedPage.locator('main').waitFor();
await protectedPage.screenshot({ path: 'protected-page.png', fullPage: true });
await browser2.close();

Replace the URLs, labels, and selectors with those used by the target site. Playwright’s authentication guide documents saving state with browserContext.storageState and creating later contexts with that file. It also states that session storage is specific to a domain and is not persisted across page loads. Read the Playwright authentication guide.

Use a setup project for repeatable test runs

For a test suite, perform login in a setup project and have dependent tests load the state file. Keep the state file outside source control:

// playwright.config.js
import { defineConfig } from '@playwright/test';

export default defineConfig({
  projects: [
    { name: 'setup', testMatch: /.*\.setup\.js/ },
    { name: 'authenticated', dependencies: ['setup'], use: { storageState: 'playwright/.auth/user.json' } }
  ]
});
// auth.setup.js
import { test as setup } from '@playwright/test';

setup('authenticate', async ({ page }) => {
  await page.goto('https://example.com/login');
  await page.getByLabel('Email').fill(process.env.LOGIN_EMAIL);
  await page.getByLabel('Password').fill(process.env.LOGIN_PASSWORD);
  await page.getByRole('button', { name: 'Sign in' }).click();
  await page.waitForURL('**/dashboard');
  await page.context().storageState({ path: 'playwright/.auth/user.json' });
});

Control the screenshot scope and rendering

Viewport, full page, and element

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

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

// One table or panel
await page.locator('[data-testid="report"]').screenshot({ path: 'report.png' });

Use fullPage: true for a document, and an element locator when the useful evidence is a single component. Very tall documents can produce large images; element captures are often easier to review and store.

Wait for content before capturing

Authentication only proves that the request is authorized. The page may still be rendering charts, images, or data after navigation. Wait for a stable application marker, then optionally wait for images:

await page.goto('https://example.com/protected-page', { waitUntil: 'domcontentloaded' });
await page.locator('[data-testid="report-ready"]').waitFor({ state: 'visible' });
await page.waitForFunction(() => [...document.images].every(image => image.complete));
await page.screenshot({ path: 'report.png', fullPage: true });

Prefer a site-specific ready selector over an arbitrary sleep. If a chart appears only after an API response, wait for the chart selector or the relevant response.

Hide sensitive or distracting elements

await page.addStyleTag({ content: `
  .sticky-header, .cookie-banner, .chat-widget { display: none !important; }
` });
await page.screenshot({ path: 'clean.png', fullPage: true });

Review every capture for personal data, tokens, customer records, redacted fields, sticky headers repeated on every scroll, and blank lazy-loaded regions.

Authentication details and edge cases

Cookies, local storage, and IndexedDB

Web apps can store authenticated state in cookies, local storage, IndexedDB, or passkeys. A saved Playwright state file may contain reusable credentials. Restrict its permissions, store it outside the repository, add it to .gitignore, and rotate the account if it is exposed.

# .gitignore
playwright/.auth/
auth-state.json

Session storage

Session storage is not persisted across page loads. If the site keeps its login only there, a saved storageState file will not restore the session. Run the interactive login in the same context, or use the application’s supported test or service account flow.

MFA, CAPTCHA, and device checks

Complete MFA interactively during setup when allowed. CAPTCHA, passkeys, expiring cookies, IP allowlists, and device fingerprints may require a fresh interactive step. Do not attempt to bypass access controls; use an authorized account and the site’s supported automation policy.

Cross-domain login

If the login provider and application use different domains, wait for the final application URL before saving state. Confirm that the saved cookies cover every domain needed to load the protected page and its authenticated assets.

Protected images and API data

A page can show a signed-in shell while image or chart requests fail separately. In Playwright, inspect failed responses and console errors, then ensure the authenticated context can reach the asset host:

page.on('response', response => {
  if (response.status() >= 400) console.log(response.status(), response.url());
});
page.on('console', message => console.log(message.type(), message.text()));

Python and cURL options

Playwright’s primary API is available in multiple languages. A Python example using the synchronous API:

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()
    context = browser.new_context(storage_state='auth-state.json')
    page = context.new_page()
    page.goto('https://example.com/protected-page', wait_until='networkidle')
    page.locator('[data-testid="report-ready"]').wait_for()
    page.screenshot(path='protected-page.png', full_page=True)
    browser.close()

cURL alone cannot execute a browser login flow or render a page. It can fetch an already authorized image endpoint when you have a supported cookie or bearer token, but it will not replace a browser for JavaScript-rendered pages:

curl -L 'https://example.com/protected-image.png' \
  -H 'Authorization: Bearer YOUR_TOKEN' \
  -o protected-image.png

Use the site’s documented authentication method and protect any exported cookies or tokens.

Or skip the browser setup

ScreenshotNeo provides a website screenshot API and MCP server. The call below captures a URL; see the ScreenshotNeo documentation for authentication and options.

cURL

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)
r.raise_for_status()
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}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
await Bun.write('shot.webp', res);

For an authenticated page, pass the site’s supported custom headers, cookies, user agent, or Authorization value through the corresponding ScreenshotNeo options documented at the API docs. ScreenshotNeo can capture full pages, one CSS-selected element, PDFs, custom CSS and JavaScript, waits, blocked resources, device presets, viewport sizes, retina scale, timezone, geolocation, and more.

  • Cookie banners, newsletter popups, and chat widgets are removed before the shot.
  • Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed; response headers identify the page verdict and billing result.
  • An MCP server lets Claude, Cursor, and other MCP clients use take_screenshot, get_page_info, and capture_pdf.
  • The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots.

Create a free ScreenshotNeo account and start with 1,000 screenshots a month without a card.

Troubleshooting

Symptom Likely cause Fix
Redirected to login Expired cookies, wrong domain, or missing state Re-run setup, save state after the final redirect, and verify cookie domains.
Screenshot shows a loading spinner Capture ran before application data rendered Wait for a stable content selector or specific API response.
Lazy images are blank Images load only after scrolling or intersection events Scroll through the page, wait for image completion, then capture.
Charts are missing Canvas or data request is still pending Wait for the chart container and inspect failed network responses.
Only the visible area is saved fullPage was omitted Set fullPage: true or capture the target element.
Sticky header repeats or clips content Fixed positioning during full-page stitching Hide it with temporary CSS or capture the content element.
State file does not restore login Session storage or a passkey-only flow Use interactive setup in the same context or the site’s supported automation account.
Browser hangs Unclosed contexts, resource-heavy pages, or an endless request Close browsers in a finally block, set navigation timeouts, and block unnecessary resources.
Screenshot contains secrets Authenticated page includes private data Use a least-privilege account, mask fields, restrict files, and delete outputs when no longer needed.

Performance, reliability, and cost

Performance

  • Reuse a browser process for a batch, but create isolated contexts per account or permission set.
  • Capture an element when a full document is unnecessary.
  • Wait for the smallest reliable ready signal instead of a long fixed delay.
  • Block analytics, ads, and video only when doing so does not change the page you need to document.
  • Use JPEG or WebP when smaller files matter; use PNG for lossless UI details.

Reliability

  • Record the URL, capture time, viewport, browser version, and account or environment used.
  • Retry transient navigation failures with a bounded retry count and a fresh context.
  • Check the output dimensions and file size so blank or truncated captures fail the job.
  • Keep authentication setup separate from capture so an expired session is easy to diagnose.

Cost

Self-hosted Playwright costs the compute and storage required by your runners. A managed API charges according to its plan and billing rules. ScreenshotNeo bills only clean shots; bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and the response includes X-Page-Verdict and X-Billed headers. Its plans are Free (1,000/month), Starter ($5 for 3,000), Growth ($15 for 15,000), Pro ($39 for 60,000), Scale ($99 for 250,000), and Business ($249 for 1,000,000); yearly billing gives two months free.

Capture checklist

  • Use an authorized account and complete MFA or consent.
  • Confirm the final protected URL and required domain cookies.
  • Choose viewport, full-page, or element scope.
  • Wait for content, images, charts, and lazy sections.
  • Check for login redirects, blank regions, clipping, and sensitive data.
  • Store authentication state and screenshots with restricted access.
  • Record capture metadata if the image is evidence or part of a report.

FAQ

Can I take a screenshot without logging in every time?

Yes. Playwright can save reusable authenticated state with storageState. It will not restore session-storage-only authentication, and cookies or other credentials can expire.

Does a full-page screenshot include content below the fold?

Yes, when using Playwright’s fullPage: true. Wait for lazy-loaded content first so below-the-fold regions are populated.

Can I capture just a dashboard card?

Yes. Locate the card and call its locator’s screenshot method.

Can cURL log in and render a modern web app?

Usually no. cURL can fetch an authorized endpoint, but browser automation is needed for JavaScript rendering, interactions, and screenshot stitching.

What should I do if authentication expires during a batch?

Detect the login URL or a missing ready selector, rerun the setup login, save fresh state, and retry the capture with a bounded limit.