ScreenshotNeo

BlogHow-to

How to Screenshot a Website Page Behind a Login with Playwright

Sign in with Playwright, confirm authentication, and capture a protected page as a viewport, full-page, or element screenshot. Save and protect reusable login state.

By the ScreenshotNeo team4 October 20269 min read

To screenshot a page behind a login with Playwright, create an authorized browser context, sign in through the site’s intended flow, wait for a reliable signal that sign-in succeeded, navigate to the protected page, and capture it. Use fullPage: true for the full scrollable page, omit it for the visible viewport, or take a screenshot of a locator for one element. For repeat runs, save the authenticated context with Playwright’s storageState and keep that file secret.

The example below is TypeScript using Playwright Test. Replace the example URLs, labels, and authenticated UI signal with the target site’s documented login workflow. It is an authorized-access pattern, not a site-specific login recipe. [Playwright authentication guide]

1. Install Playwright

In a new Node.js project, install Playwright Test and its browser binaries:

npm init -y
npm install --save-dev @playwright/test
npx playwright install chromium

Set credentials through environment variables instead of writing passwords into source files. For example, in a local shell set TEST_EMAIL and TEST_PASSWORD before running the script. Use a test account with only the access needed for the task.

2. Sign in, verify, and capture the page

This complete one-off script launches Chromium, enters credentials in the site’s login form, waits for a signed-in control, opens the target page, and writes a full-page PNG:

import { chromium, expect } from '@playwright/test';

const browser = await chromium.launch();
const context = await browser.newContext();
const page = await context.newPage();

try {
  await page.goto('https://example.com/login');
  await page.getByLabel('Email').fill(process.env.TEST_EMAIL ?? '');
  await page.getByLabel('Password').fill(process.env.TEST_PASSWORD ?? '');
  await page.getByRole('button', { name: 'Sign in' }).click();

  // Replace this with a reliable success signal for the target application.
  await expect(page.getByRole('button', { name: 'Account' })).toBeVisible();

  await page.goto('https://example.com/protected/page');
  await expect(page.getByRole('heading', { name: 'Protected page' })).toBeVisible();
  await page.screenshot({ path: 'protected-page.png', fullPage: true });
} finally {
  await context.close();
  await browser.close();
}

The labels, button name, heading, and URLs are illustrative placeholders. A real application may use a link, menu, redirect, or another visible authenticated control. Choose an assertion that confirms the app is signed in and that the target page actually loaded. Playwright locators auto-wait and retry while resolving the current DOM element, which is more robust than selecting an element once and keeping a stale handle. [Playwright locators]

For a viewport screenshot, change the capture line to await page.screenshot({ path: 'protected-page.png' }). For one element, use await page.locator('main article').screenshot({ path: 'article.png' }) with a selector that matches the target element. To process the image in memory instead of saving it directly, call const buffer = await page.screenshot(). [Playwright screenshots]

3. Reuse authenticated state across runs

When you need repeated captures, sign in once and save the browser context’s state after verifying login. The saved state can include cookies and local storage. Playwright recommends storing it under playwright/.auth and excluding that directory from version control because the file may contain credentials that impersonate the account. [Authentication and storage state]

Create playwright/.gitignore or add this to the repository’s .gitignore:

playwright/.auth/

Save state after a successful login:

import { chromium, expect } from '@playwright/test';

const browser = await chromium.launch();
const context = await browser.newContext();
const page = await context.newPage();

try {
  await page.goto('https://example.com/login');
  await page.getByLabel('Email').fill(process.env.TEST_EMAIL ?? '');
  await page.getByLabel('Password').fill(process.env.TEST_PASSWORD ?? '');
  await page.getByRole('button', { name: 'Sign in' }).click();
  await expect(page.getByRole('button', { name: 'Account' })).toBeVisible();

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

In a later script, create a fresh context from that state, then navigate and capture:

import { chromium, expect } from '@playwright/test';

const browser = await chromium.launch();
const context = await browser.newContext({
  storageState: 'playwright/.auth/user.json',
});
const page = await context.newPage();

try {
  await page.goto('https://example.com/protected/page');
  await expect(page.getByRole('heading', { name: 'Protected page' })).toBeVisible();
  await page.screenshot({ path: 'protected-page.png', fullPage: true });
} finally {
  await context.close();
  await browser.close();
}

In Playwright Test suites, configure a setup project to create the state and have dependent test projects load it. This avoids repeating an interactive login for every test. Refresh the state when the site’s session expires. If tests run in parallel and modify server-side data, use isolated accounts or otherwise prevent tests from interfering with one another. [Playwright setup projects]

4. Choose the right screenshot scope

Need Playwright capture Notes
Visible viewport page.screenshot({ path: 'page.png' }) Captures the current viewport, so scroll first if the relevant content is lower down.
Whole scrollable page page.screenshot({ path: 'page.png', fullPage: true }) Captures beyond the viewport; long pages can produce large images.
One element page.locator('.target').screenshot({ path: 'element.png' }) Wait for the element to be visible and ensure the selector matches the intended content.
Image bytes in memory const buffer = await page.screenshot() Use when another part of the program will store or process the returned buffer.
Visual regression check await expect(page).toHaveScreenshot() A test assertion that compares against a baseline; it is not required for a simple screenshot file.

Playwright’s screenshot assertion waits for consecutive stable screenshots before comparing with its expectation. Keep that separate from ordinary file capture: use it when the goal is detecting visual changes in a test suite. [PageAssertions API]

5. Handle authentication cases and page readiness

Redirects and asynchronous sign-in

Do not assume that clicking the sign-in button means the session is ready. Wait for a final URL, a signed-in control, or another application-specific success signal before opening the target. Avoid fixed sleeps where an observable condition can be checked; redirects and network timing can vary between runs. The sample’s locator assertion provides an explicit readiness check.

Session storage

storageState covers cookies and local storage. Some applications keep authentication data in sessionStorage, which Playwright does not persist directly through that API. If the target application requires it, the authentication guide describes reading session storage and restoring it with context.addInitScript() for the matching hostname. Treat those values as secrets too, and use this workaround only when the app’s behavior requires it. [Authentication guide]

HTTP authentication

HTTP authentication is different from a normal web login form. For a site protected by HTTP Basic or related browser-level credentials, configure the context with httpCredentials, for example await browser.newContext({ httpCredentials: { username: process.env.HTTP_USER ?? '', password: process.env.HTTP_PASSWORD ?? '' } }). Do not use this in place of the site’s normal login flow. [BrowserContext API]

SSO, MFA, CAPTCHA, and access policy

Identity-provider redirects, multi-factor steps, CAPTCHA, and session rules are specific to the target site. Use its authorized workflow and follow its access policy. The general Playwright pattern does not imply that a particular login flow can be automated or that any access control should be bypassed. If a flow requires a human step, arrange an approved test setup with the site or use its supported authentication mechanism.

Lazy-loaded content and long pages

A full-page screenshot captures the scrollable page, but applications may load content only after scrolling or after asynchronous requests. Wait for the relevant content to appear before capture. If content appears only when scrolled into view, scroll through the relevant sections and wait for their visible signals before taking the final screenshot. Avoid capturing while a loading indicator or skeleton is still present.

6. Troubleshooting

Symptom Likely cause Fix
Capture shows the login page Login did not complete, stored state expired, or authentication depends on session storage or a site-specific identity flow. Confirm the post-login URL or signed-in control. Reauthenticate and save fresh state. Check whether the application requires sessionStorage or a separate approved SSO process.
The script captures before the page is ready The script navigated or captured before redirects and app rendering finished. Wait for a reliable locator or final URL that indicates the page is ready rather than using a guessed delay.
Locator times out The accessible label, role, or selector does not match the current page, or the expected content never appeared. Inspect the page’s actual accessible name and structure, update the locator, and verify the login succeeded before asserting the target content.
Works locally but fails in a clean run The script relied on an existing browser profile or manually established state that is absent in the new context. Sign in in the script or explicitly load a current storage-state file into the context.
State file does not restore the session Session expired, the app uses sessionStorage, or the identity flow requires additional site-specific state. Refresh the state after an authorized login, investigate the app’s storage behavior, and use the documented sessionStorage workaround only if needed.
Full-page image is unexpectedly tall or large The page has a long scroll area or repeated/infinite content. Capture a specific element or viewport, or constrain the page to the content needed for the task.
Login fails at an MFA or CAPTCHA step The site’s sign-in policy requires a human or a supported test flow. Follow the site’s authorized testing process. Do not treat a screenshot workflow as permission to bypass access controls.

7. Performance, reliability, and cost

  • Reuse state thoughtfully: saving a state file avoids repeating interactive sign-in, but the session can expire and needs refresh. Keep the file private and avoid sharing it across untrusted jobs.
  • Wait on app signals: a visible authenticated control and target-page content provide clearer readiness than a fixed delay. Locators retry as the page updates, improving resilience to timing variation.
  • Limit capture scope: viewport or element screenshots generally involve less page content than a very long full-page capture. Choose the smallest scope that answers the task.
  • Plan for parallelism: independent browser contexts isolate browser state, but tests can still collide through shared server-side account data. Use dedicated test accounts where concurrent actions affect data.
  • Account for browser resources: browser startup and rendering use compute and memory. Reusing a browser process for multiple isolated contexts can avoid repeated launches in a larger script, while still keeping each task’s context separate.
  • Cost: Playwright is an open-source automation library, but running browsers consumes the resources of the machine or CI environment you provide. CI, storage, and image retention costs depend on that environment and workload.

Or skip the browser setup

If the page is publicly reachable, ScreenshotNeo can return an image or PDF with one request. It is a website screenshot API and MCP server from Yorker Media. The call below captures a public page; it does not sign into private accounts. See the ScreenshotNeo API documentation for request options.

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}`);
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));

ScreenshotNeo accepts a cookie or consent banner like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, with the outcome identified in response headers. Its MCP server lets AI agents 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. Every feature is available on every plan.

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

FAQ

Can Playwright take a screenshot without saving a file?

Yes. Calling page.screenshot() without a path returns a buffer that your script can pass to another storage or image-processing step. [Page API]

Should I use a screenshot or a screenshot assertion?

Use page.screenshot() to produce an image. Use toHaveScreenshot() when a Playwright Test should compare the current rendering with a visual baseline. [PageAssertions API]

Can I use saved state in a different browser context?

Yes. Provide the state file when creating the new context. It restores supported browser storage such as cookies and local storage, but it does not remove the need to verify that the site’s session remains valid. [Playwright authentication guide]