ScreenshotNeo

BlogHow-to

How to Capture Website Screenshots After Signing In with Playwright

Reuse Playwright authentication state to capture protected pages, with runnable setup, screenshot code, session storage guidance, and troubleshooting.

By the ScreenshotNeo team4 October 20269 min read

To capture a page after signing in with Playwright, save the authenticated browser context as a storage-state file, create a new context with that state, navigate to the protected page, wait for a site-specific sign-in-ready signal, then call page.screenshot(). Add fullPage: true to capture the full scrollable document instead of only the visible viewport. Treat the state file like a password: it can contain cookies and headers that impersonate the account.

1. Install Playwright and prepare a safe auth directory

The examples below use Playwright with Node.js. Install the package and browser, then create a directory for local authentication state and keep it out of version control:

npm install --save-dev playwright
npx playwright install chromium
mkdir -p playwright/.auth

Add this entry to .gitignore:

playwright/.auth/

Use a dedicated test account where possible. Do not commit the state file, print its contents in logs, or upload it as a build artifact unless that storage is secured and access-controlled.

2. Sign in once and save browser state

Create save-auth.mjs. Replace the example URL, selectors, and readiness condition with signals from your application. This version assumes a login form with email and password fields; adapt it if your site uses SSO, MFA, or a different login flow.

import { chromium } from 'playwright';

const email = process.env.TEST_EMAIL;
const password = process.env.TEST_PASSWORD;
if (!email || !password) {
  throw new Error('Set TEST_EMAIL and TEST_PASSWORD before running this script.');
}

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

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

  // Prefer an application-specific signal over a generic load event.
  await page.getByTestId('account-menu').waitFor({ state: 'visible' });
  await page.waitForURL('**/dashboard');

  await context.storageState({ path: 'playwright/.auth/user.json' });
} finally {
  await browser.close();
}

Run it with credentials provided through the environment, not hard-coded into the script:

TEST_EMAIL='you@example.com' TEST_PASSWORD='your-secret' node save-auth.mjs

For real use, provide secrets through your shell’s secret manager or CI secret store. The values above are placeholders; avoid putting actual credentials in shell history or shared logs.

3. Restore state, wait for the protected page, and capture it

Create capture.mjs. The saved state is loaded into the browser context that owns the page. Change the route and readiness selector for the target application.

import { chromium } from 'playwright';

const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({
  storageState: 'playwright/.auth/user.json',
  viewport: { width: 1440, height: 1000 },
  deviceScaleFactor: 1
});
const page = await context.newPage();

try {
  await page.goto('https://example.com/dashboard', { waitUntil: 'domcontentloaded' });
  await page.getByTestId('account-menu').waitFor({ state: 'visible' });
  await page.getByRole('heading', { name: 'Dashboard' }).waitFor({ state: 'visible' });

  await page.screenshot({ path: 'signed-in.png' });
  await page.screenshot({ path: 'signed-in-full.png', fullPage: true });
} finally {
  await browser.close();
}

Run the capture with node capture.mjs. The first screenshot contains the current viewport. The second captures the full scrollable page. For a very long or infinite-scroll page, full-page capture may be large or may not include content that only loads after scrolling; use a bounded viewport or explicitly scroll and wait for the content you need.

4. Choose a readiness condition that proves the page is ready

A navigation event only proves that a navigation reached a particular browser state. It does not prove that the application accepted the saved credentials or finished rendering protected data. Wait for an application-owned signal such as:

  • A stable account menu, sign-out link, or user avatar.
  • A heading or element unique to the authenticated route.
  • A URL that indicates the final protected route after redirects.
  • A specific data panel to appear, if that panel is necessary for the screenshot.

Use one or more signals that fit the page. If an authenticated element is visible but the data is still loading, wait for the data-specific element too. A fixed sleep can help with a known delayed animation, but it is less reliable than waiting for the actual condition.

5. Configure screenshot output

page.screenshot() accepts options for the output format and capture area. These common choices cover most signed-in screenshots:

Need Option Example
Viewport image Default behavior page.screenshot({ path: 'page.png' })
Entire scrollable document fullPage: true page.screenshot({ path: 'page.png', fullPage: true })
JPEG or WebP output type; JPEG quality can be set page.screenshot({ path: 'page.jpg', type: 'jpeg', quality: 80 })
Exact region clip rectangle with x, y, width, height page.screenshot({ path: 'region.png', clip: { x: 20, y: 80, width: 800, height: 500 } })
Hide volatile areas mask locator list and optional mask color page.screenshot({ path: 'page.png', mask: [page.locator('.timestamp')] })
Control animation effects animations page.screenshot({ path: 'page.png', animations: 'disabled' })
CSS pixel or device scale scale page.screenshot({ path: 'page.png', scale: 'css' })

For regression checks, Playwright Test provides toHaveScreenshot(). It waits for consecutive screenshots to match before comparing with an expectation. Keep the browser version, operating system, viewport, settings, and rendering environment consistent where possible; visual output can vary across machines and browser conditions. Review and update expected snapshots deliberately when a change is intended.

6. Use Playwright Test configuration for repeated captures

When screenshots are part of an automated test suite, save state during a setup project and reference it in the test project. A compact configuration looks like this:

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

export default defineConfig({
  projects: [
    { name: 'setup', testMatch: /auth\.setup\.ts/ },
    {
      name: 'chromium',
      dependencies: ['setup'],
      use: {
        browserName: 'chromium',
        storageState: 'playwright/.auth/user.json'
      }
    }
  ]
});

The setup test should perform the login and save state only after the authenticated-ready condition succeeds. For tests that mutate shared server-side data, a single shared account can cause parallel tests to interfere with each other. Use separate accounts and a separate state file per worker when tests need isolated data. For tests that only read stable data, a shared account may be appropriate if the application permits concurrent sessions.

7. Handle authentication mechanisms beyond cookies

State expiration

Cookies and other saved credentials can expire or be revoked. If the target redirects to login, rerun the setup script and replace the state file. In CI, make authentication setup an explicit prerequisite rather than assuming yesterday’s state remains valid.

Session storage

Playwright’s normal storage-state file does not persist session storage. If the app relies on session storage, capture the relevant values and restore them with an initialization script scoped to the application’s origin before navigating to the protected route. Avoid copying unrelated session data or persisting short-lived tokens longer than needed.

API-based login

If the application offers an authorized login API, it can be simpler to authenticate with an API request context and save its resulting state for the browser context. Follow the application’s supported authentication flow and keep resulting credentials protected just like browser-generated state.

Multiple roles

For a test that needs two users or roles at once, create two browser contexts with different state files. Each page then has an independent cookie jar and storage state; do not attempt to represent both identities with one shared context.

IndexedDB and WebAuthn

Authentication setups can depend on browser storage beyond cookies and local storage, or use WebAuthn. BrowserContext state options and credential APIs are version-sensitive. Check the documentation for the installed Playwright version and confirm that the application’s auth mechanism is covered before relying on such options.

8. Troubleshooting

Symptom Likely cause Fix
Capture shows the login page State expired, was saved before login completed, or belongs to another domain. Regenerate state after the final redirect; confirm the saved context reached the authenticated route and that the target uses the same origin.
Login succeeds but protected content is blank The screenshot ran before client-side data finished loading. Wait for a stable, page-specific content selector or data panel before capture.
Element selector times out Selector is stale, hidden, or differs between account roles. Inspect the current page and use a stable role, label, test ID, or route-specific selector. Confirm the selected account has access.
Works locally but fails in CI Different browser version, missing browser install, environment-specific login/MFA, or timing differences. Install the required Playwright browser in CI, use the same supported auth flow, and wait for app readiness rather than adding arbitrary delays.
Session appears logged out despite saved cookies Authentication depends on session storage or another state mechanism. Identify the storage used by the app; restore session storage with a domain-scoped init script or use the app’s supported API login.
Full-page image is unexpectedly huge The document is very tall, uses endless scrolling, or expands during capture. Capture the viewport or a clip, stabilize dynamic content, and load only the relevant page region before capturing.
Visual snapshot fails intermittently Animations, timestamps, data, fonts, or host rendering differ between runs. Disable animations, mask expected volatile regions, wait for stable content, and keep browser and host conditions consistent.
State file appears in a commit The auth directory was not ignored or was already tracked. Add the directory to ignore rules, remove the file from the repository index, rotate/revoke the session, and create fresh local state.

9. Performance, reliability, and cost

Reusing storage state avoids repeating the UI login for every capture, which reduces setup work and removes one source of timing variability. The browser still needs to load and render the page, and full-page images take more memory and produce larger files than viewport captures. Choose only the output dimensions and page area that the task requires.

Reliability comes primarily from a valid auth state and a precise readiness condition. Make setup fail clearly when login does not reach the expected account state. Refresh expired credentials through the supported login flow, isolate accounts if parallel tests mutate shared data, and keep visual comparisons in a consistent environment.

Playwright itself is open-source browser automation software. The practical cost of this workflow is the machine or CI capacity used to run a browser, plus any account or infrastructure costs imposed by the application. Avoid high concurrency against a production account or site unless permitted.

10. Or skip the browser setup

For public pages, ScreenshotNeo is a website screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP, or PDF. It is not a substitute for your private authenticated browser session; use Playwright when the page requires your signed-in account.

For a page accessible without that private session, this is the one-call API pattern. See the ScreenshotNeo 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.com -o shot.webp
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
    timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com' });
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 accepts cookie and consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each of those steps can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers report the page verdict and billing status. Its MCP server offers take_screenshot, get_page_info, and capture_pdf for AI agents. 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 for 1,000 screenshots a month, with no card.

FAQ

Does storage state save my whole browser profile?

No. It saves the authentication-related browser storage supported by Playwright’s storage-state format, not every aspect of a browser profile. Session storage needs separate handling when an application relies on it.

Should I capture from a setup script or a test?

Use a setup project when many tests need the same authenticated session. Use a focused script for a one-off image or operational capture. In either case, verify the authenticated page before taking the screenshot.

Can a screenshot include content below the fold?

Yes. Set fullPage: true for the full scrollable document. For pages that load content only as the user scrolls, trigger that loading behavior first or capture a specific region.

Can I safely share the resulting screenshot?

Only after checking it for personal, confidential, or account data. A screenshot can expose the same protected information visible in the signed-in page.