ScreenshotNeo

BlogHow-to

How to Take a Full-Page Screenshot of a Password-Protected Page with Playwright

Log in through the site's normal flow, save and reuse Playwright storage state, then capture the protected page with fullPage: true.

By the ScreenshotNeo team4 October 20268 min read

To capture a password-protected page with Playwright, log in through the site’s normal supported flow, wait for an authenticated success signal, and take the screenshot in that authenticated browser context. Set fullPage: true to capture the page’s full scrollable height. If you need to reuse the login across runs, save the context’s storage state and load it into the capture context.

The examples below use placeholder URLs, selectors, and environment variables. Adapt them to the site’s actual login flow and readiness signals. Never put real credentials or saved authentication state in source control.

1. Install Playwright and prepare credentials

This example uses Playwright Test’s package and TypeScript syntax. Install the package and browser, then provide credentials to the process through your secret manager or environment. The login code reads them from SITE_USERNAME and SITE_PASSWORD.

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

Add the authentication directory to .gitignore before saving any state:

playwright/.auth/

Authentication-state files can contain cookies and other values that may allow someone to impersonate the account. Restrict access to them, do not commit them, and remove or regenerate them when they expire.

2. Log in and save the authenticated state

Use the same browser context for the site’s login flow. A successful button click alone does not prove login finished: wait for an application-specific signal such as the final URL or an account control that only appears when authenticated. Replace the selectors and success condition with ones that match the site.

import { chromium, expect } from '@playwright/test';
import { mkdir } from 'node:fs/promises';

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

const authFile = 'playwright/.auth/user.json';
await mkdir('playwright/.auth', { recursive: true });

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

  await page.goto('https://example.com/login', { waitUntil: 'domcontentloaded' });
  await page.getByLabel('Username').fill(username);
  await page.getByLabel('Password').fill(password);
  await page.getByRole('button', { name: /sign in/i }).click();

  // Choose a success signal specific to the application.
  await expect(page.getByRole('button', { name: /account|profile/i })).toBeVisible();
  await context.storageState({ path: authFile });
  await context.close();
} finally {
  await browser.close();
}

Run it with credentials supplied by your environment. For example, in a shell where the variables are already set:

npx tsx save-auth.ts

If you prefer, keep authentication and capture in one process and one context; you do not need to save and reload state when the page is already authenticated. Saved state is useful when login belongs in a separate setup step or when several capture runs need the same session.

3. Reuse the state and capture the full page

Create a context with the saved state, navigate to the protected URL, wait for the page’s meaningful ready condition, and call page.screenshot with fullPage: true. Keep the browser alive until the capture context is closed.

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

const browser = await chromium.launch();
try {
  const context = await browser.newContext({
    storageState: 'playwright/.auth/user.json',
  });
  try {
    const page = await context.newPage();
    await page.goto('https://example.com/protected', {
      waitUntil: 'domcontentloaded',
    });

    // Replace this with a reliable, page-specific ready condition.
    await expect(page.getByRole('main')).toBeVisible();
    await page.screenshot({ path: 'page.png', fullPage: true });
  } finally {
    await context.close();
  }
} finally {
  await browser.close();
}

This writes page.png to the current directory. A full-page capture covers the full scrollable page rather than just the current viewport. The option does not guarantee that content loaded only after scrolling, infinite-scroll content, or deferred widgets have finished rendering; handle those according to the target application’s behavior.

4. Choose how authentication state is managed

Approach Use it when Trade-off
Log in for every run Sessions expire often, or each run needs a fresh session. Repeats the UI login flow and may involve MFA or other site-specific steps.
Save and reuse state A setup flow can create a session that remains valid for later captures. The file is sensitive and can expire or be invalidated.
Share state across tests Tests use an account without conflicting server-side changes. Tests that mutate shared server-side data may interfere; use separate accounts per parallel worker when needed.

Playwright Test also supports an authentication setup project whose output state is configured for dependent test projects. This keeps the login step separate from tests that consume the authenticated session. Follow the same security precautions for the generated state file.

What storage state includes

Playwright’s storage-state mechanism covers cookies and local storage. Its API also provides optional snapshots for IndexedDB, WebAuthn credentials, and OPFS, with support depending on the storage type and browser context. Use those options only if the application needs them. The API notes that OPFS is not supported in ephemeral WebKit contexts.

sessionStorage is a common gotcha: the normal storage-state workflow does not persist it. If the application stores authentication there, Playwright’s authentication guide describes reading it from the page and restoring its keys with context.addInitScript for the matching hostname before the application loads. Scope the script carefully and keep those values out of logs. A site-supported login flow or fresh authentication may be simpler and safer.

5. Handle page readiness and full-page content

Choose a readiness condition that represents the content you need in the image. Possible signals include an authenticated landmark becoming visible, a report heading appearing, or a known loading indicator disappearing. Use a selector tied to the application instead of assuming that navigation alone means the page is ready.

  • Lazy-loaded images: full-page capture does not establish a universal way to force every image to load. If the site loads images on scroll, scroll through the relevant content and wait for the images or application state you need before capturing.
  • Infinite scroll: decide how much content belongs in the artifact and load that content explicitly. The page may continue growing as it is scrolled.
  • Animations and changing data: wait for a stable page-specific state when repeatability matters. A screenshot captures the rendered state at that moment.
  • Large pages: a full-page image can be very tall and consume substantial memory or produce a large file. Capture a specific element or viewport if the complete page is not required.

6. Capture in Playwright Test or compare against a baseline

Use page.screenshot when the goal is to write an image artifact. If the goal is visual regression testing with Playwright Test, use its screenshot assertion instead:

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

test('protected page matches its visual baseline', async ({ page }) => {
  await page.goto('https://example.com/protected');
  await expect(page.getByRole('main')).toBeVisible();
  await expect(page).toHaveScreenshot('protected-page.png', {
    fullPage: true,
  });
});

The assertion API is specific to Playwright Test and compares against a baseline; it is not a general replacement for saving a screenshot in a standalone script. Configure the test project’s authentication state if this test needs a protected session.

7. Troubleshoot common failures

Symptom Likely cause What to do
The capture shows the login page Login did not finish, the state was not loaded, or the session expired. Wait for an authenticated UI signal before saving state. Check that the capture context points to the correct state file and regenerate it through the normal login flow.
Login click succeeds but state is unauthenticated The site is still redirecting or setting cookies, or the chosen success signal is too early. Wait for the final URL or a known authenticated element before calling storageState.
Authentication works in one run but not another The site may use sessionStorage, short-lived tokens, device binding, MFA, SSO, or server-side invalidation. Identify the site’s supported session mechanism. Restore sessionStorage before app code runs if required, or create fresh state for each run.
Some images or sections are missing Content is lazy-loaded, depends on scrolling, or has its own readiness condition. Load the required sections using site-specific steps, wait for them, and inspect the output image.
The screenshot is only the visible viewport fullPage was omitted or set to false. Pass fullPage: true to page.screenshot or the screenshot assertion.
Authentication file is missing The directory was not created or the setup step did not complete. Create the parent directory, check the setup process’s exit status, and verify the configured file path.
Tests fail when run in parallel Workers may be changing shared server-side state for the same account. Use accounts isolated per worker when tests mutate shared state, or serialize the conflicting tests.

8. Reliability, performance, and cost

Reusing a valid state avoids repeating the login UI, but it adds state-file handling and session-expiry concerns. Logging in each run creates fresh state but makes each run depend on the site’s login flow and any required MFA or SSO. For either approach, check a page-specific authenticated signal before capture and handle authentication failure as a failed run rather than saving a login page as if it were the target.

Full-page screenshots take more work and can create larger image files than viewport captures, especially for long pages. Limit the capture to the necessary page or element when possible. The research sources do not establish a universal runtime, maximum page size, or cost for a particular site; those depend on the page, browser, and execution environment.

Or skip the browser setup

If you already have an authorized public capture URL, ScreenshotNeo can return an image or PDF from one GET request. See the API documentation for authentication and options. This does not replace the protected-page login flow shown above: do not send private credentials or assume a protected session can be captured without configuring access.

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

ScreenshotNeo removes cookie banners, popups, and chat widgets before the shot. Bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.

FAQ

Can I take the screenshot in the same page where I logged in?

Yes. If that page and its browser context are authenticated and already at the target page, capture directly. Saving state is only needed to transfer the session to another context or run.

Does fullPage: true capture content below the fold?

It captures the full scrollable page. It does not guarantee that content the application has not loaded yet will appear.

Can I use a saved state file indefinitely?

No. The site’s session may expire or be invalidated. Treat the file as sensitive and refresh it through the supported login flow when needed.

Is toHaveScreenshot the same as saving a PNG?

It is a Playwright Test assertion for screenshot comparison with a visual baseline. Use page.screenshot to write a screenshot artifact directly.

Official references