ScreenshotNeo

BlogHow-to

Capture Screenshots of Authenticated Web Pages with Java Playwright Storage State

Save Playwright Java authentication state, restore it in an isolated browser context, verify the signed-in page, and capture a reliable screenshot.

By the ScreenshotNeo team4 October 202610 min read

To capture an authenticated page without repeating the interactive login for every run, log in through the site’s supported flow once, save the Playwright Java BrowserContext storage state, and pass that state when creating a new context. Navigate to the protected URL, verify a site-specific signed-in indicator, then call Page.screenshot. Restoring state reuses an existing session; it does not bypass the site’s access controls.

This guide covers a complete Java workflow, full-page and element screenshots, state formats and caveats, reliability, security, troubleshooting, and an API alternative. See the official Playwright Java BrowserContext API, Page API, authentication guide, and screenshots guide.

1. Set up Playwright Java

Add Playwright Java to a Java project using the current installation instructions in the official Java getting started guide, and install the browser binaries for the browser you intend to run. The example uses Chromium and Java’s try-with-resources pattern to close Playwright-managed resources.

Create a local auth directory and exclude it from version control. For example, add this to .gitignore:

playwright/.auth/

The saved state can contain live session cookies and other credentials. Do not commit it or put it in a publicly accessible artifact store.

2. Save state after a successful login

Complete the login flow the same way an authorized user normally does: use the site’s supported identity provider, MFA, or test-environment flow. Do not assume that reaching the login URL means login succeeded. Replace the placeholder readiness check below with an element or URL that reliably appears only after the account is authenticated.

import com.microsoft.playwright.*;
import java.nio.file.Files;
import java.nio.file.Path;

public class SaveAuthState {
  private static final String LOGIN_URL = "https://example.com/login";
  private static final Path STATE_FILE = Path.of("playwright", ".auth", "user.json");

  public static void main(String[] args) throws Exception {
    Files.createDirectories(STATE_FILE.getParent());

    try (Playwright playwright = Playwright.create()) {
      Browser browser = playwright.chromium().launch();
      try {
        BrowserContext context = browser.newContext();
        try {
          Page page = context.newPage();
          page.navigate(LOGIN_URL);

          // Complete the site's supported login flow here.
          // For an automated test account, fill the site's actual fields and submit.
          // If login requires MFA or an identity provider, follow that supported flow.

          // Replace this with a stable, account-only indicator after login.
          page.locator("[data-testid='account-menu']")
              .waitFor(new Locator.WaitForOptions().setTimeout(30_000));

          context.storageState(
              new BrowserContext.StorageStateOptions().setPath(STATE_FILE));
        } finally {
          context.close();
        }
      } finally {
        browser.close();
      }
    }
  }
}

The selector is deliberately a placeholder. Use an indicator that cannot also appear on a logged-out or partially loaded page. If your login flow opens a popup, handle that popup as part of the same context; its pages share the parent context. If a supported login step requires manual interaction, perform it before calling storageState.

3. Restore state and capture the protected page

Create a fresh context with setStorageStatePath, navigate to the protected URL, and check both the resulting location and an authenticated page element. A successful navigation call alone is not proof that the app kept the session: sites commonly redirect unauthenticated users back to login.

import com.microsoft.playwright.*;
import java.nio.file.Path;

public class CaptureAuthenticatedPage {
  private static final Path STATE_FILE = Path.of("playwright", ".auth", "user.json");
  private static final String TARGET_URL = "https://example.com/account";

  public static void main(String[] args) {
    try (Playwright playwright = Playwright.create()) {
      Browser browser = playwright.chromium().launch();
      try {
        BrowserContext context = browser.newContext(
            new Browser.NewContextOptions().setStorageStatePath(STATE_FILE));
        try {
          Page page = context.newPage();
          page.navigate(TARGET_URL, new Page.NavigateOptions()
              .setWaitUntil(WaitUntilState.DOMCONTENTLOADED)
              .setTimeout(30_000));

          // This selector must identify a page rendered for a signed-in account.
          page.locator("[data-testid='account-heading']")
              .waitFor(new Locator.WaitForOptions().setTimeout(30_000));

          if (!page.url().startsWith("https://example.com/account")) {
            throw new IllegalStateException(
                "Expected account page, but ended at: " + page.url());
          }

          page.screenshot(new Page.ScreenshotOptions()
              .setPath(Path.of("authenticated-page.png")));
        } finally {
          context.close();
        }
      } finally {
        browser.close();
      }
    }
  }
}

Run SaveAuthState once after adapting its login and verification steps, then run CaptureAuthenticatedPage for captures. In a real project, keep the save and capture actions in separate commands or classes so routine capture jobs do not perform an unnecessary interactive login.

4. Choose the screenshot output

By default, Page.screenshot captures the visible viewport. Choose an option based on the artifact you need; these options affect the dimensions and content of the image.

Need Java option or API Notes
Viewport image setPath(path) Default capture; viewport dimensions are controlled by the context or browser defaults.
Entire scrollable page setFullPage(true) Can produce a very tall image and consume more memory. Verify sticky or lazy-loaded content is rendered as intended.
Specific element locator(selector).screenshot(...) Captures the locator’s element bounds; wait for it to be visible and stable.
Specific rectangle setClip(new Page.ScreenshotOptions.Clip(x, y, width, height)) Coordinates are CSS pixels relative to the page. Keep the clip inside the rendered content.
Image bytes byte[] bytes = page.screenshot() Useful when passing output to an image pipeline or object store without a temporary file.
JPEG or WebP setType(ScreenshotType.JPEG) or WEBP Use supported formats for your downstream consumer; PNG is the default lossless option.
Retina-sized output setScale(ScreenshotScale.DEVICE) Device scale can increase pixel dimensions and output size compared with CSS scale.

Example full-page PNG:

page.screenshot(new Page.ScreenshotOptions()
    .setPath(Path.of("account-full.png"))
    .setFullPage(true));

Example element image:

page.locator("main [data-testid='invoice']")
    .screenshot(new Locator.ScreenshotOptions()
        .setPath(Path.of("invoice.png")));

Page screenshot options also support animation handling, caret behavior, masks for selected locators, background behavior, and a clip rectangle. Consult the version-specific Page screenshot API before relying on an option. Masking can hide known regions, but check the saved output before sharing it; masking is not a substitute for using an account with safe data.

5. Understand what storage state preserves

Storage state is a snapshot for reusing browser authentication and related origin state. The exact contents an application needs depend on its authentication design and on the Playwright Java version in the project.

Storage or credential Handling Important detail
Cookies Included in standard storage state Cookie domain, path, expiry, Secure, HttpOnly, and SameSite rules still apply when the browser sends them.
Local storage Included per origin State for one origin does not automatically authenticate a different domain or identity-provider origin.
IndexedDB Can be included with the IndexedDB snapshot option Use it if the app stores auth tokens there; check that the installed version supports the option (added in v1.51).
Session storage Not persisted by the standard storage-state API If essential, explicitly serialize only the required values and restore them with an init script for the matching origin.
OPFS or virtual WebAuthn credentials Version-specific options Confirm support in the project’s Playwright version; documented additions include v1.63 for OPFS and v1.61 for virtual credentials.

For IndexedDB, newer Java API versions provide an option to request an IndexedDB snapshot when saving state. The exact builder method is version-specific; check the installed release’s BrowserContext reference. Do not copy an example using a newer option into an older dependency without checking its version annotation.

For session storage, the official authentication guide describes an app-specific approach: capture the relevant session-storage values after login and use BrowserContext.addInitScript to restore them before page scripts run. Limit this to the intended origin and required keys. Avoid injecting an entire browser session indiscriminately; it increases exposure and can make state brittle.

6. Reliability and operational notes

  • Validate every capture. Wait for a page-specific signed-in selector and, where useful, assert the final URL. Navigation completion events only describe document loading; a single-page app may still be loading account data.
  • Use explicit waits. Prefer waiting for the heading, table, or other content that proves the page is ready. A fixed delay is less reliable and slows every run when the page is already ready.
  • Refresh expired state deliberately. A site may expire or revoke sessions at any time according to its own policy. When the account indicator is absent or a login redirect is detected, stop and refresh state using the supported login flow.
  • Isolate concurrent jobs. Use a separate context per job or account. This prevents one capture’s cookies, navigation, and page state from leaking into another job.
  • Keep browser and Playwright versions aligned. Pin and update the project dependency intentionally, install matching browser binaries, and check version annotations for newer storage features.
  • Keep output purposeful. Viewport screenshots are generally smaller and faster than full-page captures. Full-page and device-scale images can increase memory, processing time, and storage needs.

7. Security checklist

  • Use an authorized account and the site’s normal supported login process.
  • Use a dedicated test account with only the access needed for the capture.
  • Exclude the auth-state directory from Git and CI logs.
  • Store state in a restricted filesystem or managed secret location; limit which jobs and people can read it.
  • Refresh or delete stale state under the site’s session policy and your team’s retention rules.
  • Review screenshots for personal information, tokens, private records, and other sensitive page content before distributing them.

Playwright’s authentication guide warns that the browser state file may contain sensitive cookies and headers that could be used to impersonate the account. Treat it as a credential, not a harmless test fixture.

8. Troubleshooting

Symptom Likely cause Fix
Capture lands on the login page Session expired, revoked, or state belongs to another account or origin. Check the final URL and authenticated selector. Save fresh state after a supported login; confirm the protected page’s domain matches the stored origin.
Cookies appear present but the app is logged out The app also uses local storage, IndexedDB, session storage, or a server-side session policy. Identify the app’s actual auth storage. Include IndexedDB where supported; handle session storage separately if required. Re-authenticate if the server invalidated the session.
State file is missing or cannot be read Wrong working directory, missing parent directory, or insufficient filesystem permissions. Use an explicit path, create the parent directory before saving, and ensure the runtime user can read the file.
Wait for selector times out Selector is incorrect, page is still loading, or the signed-in view did not render. Inspect the page and choose a stable authenticated indicator. Check for login redirects and app errors before extending the timeout.
Screenshot is blank or incomplete Capture happened before app content rendered, or a lazy-loaded region was never brought into view. Wait for the relevant content and, for lazy content, scroll it into view before capture. Check that the selector is visible and the page did not navigate away.
Full-page image is unexpectedly huge The page is long, uses large device scale, or has an unbounded layout. Use viewport or element capture, CSS scale, or a clip; inspect the page’s document height and output dimensions.
Screenshot includes variable or private content Account data changes between runs, or the capture region includes sensitive information. Use a synthetic test account, narrow the capture, or mask known regions. Review the artifact before sharing.
Storage option does not compile The code uses an option added after the project’s Playwright Java version. Check API version annotations and either update Playwright deliberately or use only options available in the installed version.
Browser launch fails in CI Browser binaries or required environment dependencies are missing. Install the browser build matching the Playwright dependency using the official Java installation instructions, and check the CI runtime’s permissions and libraries.

9. Alternative request formats

The title’s Java Playwright method is appropriate when the target requires a real authorized browser session. These cURL, Python, and Node.js examples apply when the service accepts a direct screenshot request; they do not transfer your Playwright storage-state file or authenticate to a protected page unless the service’s documented authentication mechanism supports that access.

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

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. For pages the service can access, one GET request returns a screenshot; see the ScreenshotNeo API documentation for request options and access details. It does not import Playwright storage-state files, so keep the Java workflow above for pages that need your authenticated browser session.

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://stripe.com \
  -o shot.webp
  • Cookie banners are accepted like a visitor and 60+ known consent platforms, newsletter popups, and chat widgets are removed before capture; each step can be turned off.
  • Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed; responses identify the page verdict and billing status in headers.
  • An MCP server gives AI agents tools for screenshots, page information, and PDF capture.
  • 1,000 screenshots a month are free with no card; paid plans start at $5 for 3,000 screenshots.

Create a free ScreenshotNeo account to get 1,000 screenshots a month with no card.

10. FAQ

Does restoring storage state bypass a login or paywall?

No. It reuses state created by an authorized login. The website still controls whether that session is valid and what it can access.

Can I use one state file for several users?

Use a distinct state file and browser context for each account. Sharing a state file also shares the authenticated identity and its access.

Can I save the screenshot as a byte array instead of a file?

Yes. The no-options Page.screenshot() method returns image bytes; use this when the next step uploads or processes the image directly.

Why is the screenshot not proof that the user is still authorized?

A screenshot records rendered pixels at one moment. Validate authorization through the application-specific state and its own access-control behavior, not from the image alone.