ScreenshotNeo

BlogHow-to

How to Take Screenshots of a Logged-In Website with Playwright in Python

Save Playwright’s authenticated browser state, restore it in a fresh Python context, and capture a viewport, full page, or element securely.

By the ScreenshotNeo team4 October 20269 min read

To screenshot a logged-in page with Playwright in Python, authenticate once, save the browser context’s storage state, then load that state into a new context before navigating to the page. Capture the viewport with page.screenshot(), the full document with full_page=True, or a specific element with locator.screenshot().

The saved state may contain cookies and headers that can impersonate the account. Treat it like a credential: keep it out of source control, limit who can read it, and use only accounts and pages you are authorized to automate.

1. Install Playwright and its browser

Install the Python package and Chromium browser. Run these commands in the environment where the script will execute:

python -m pip install playwright
python -m playwright install chromium

Playwright’s browser binaries are installed separately from the Python package. If your project already pins Playwright, install the matching browser version with the command above.

2. Authenticate and save browser state

Login pages and authentication flows differ, so there is no universal login selector or sequence. Complete the legitimate login flow for your site and account, wait until authentication succeeds, then save the context’s storage state.

from pathlib import Path
from playwright.sync_api import sync_playwright

STATE = Path("playwright/.auth/state.json")
LOGIN_URL = "https://example.com/login"

STATE.parent.mkdir(parents=True, exist_ok=True)

with sync_playwright() as p:
    browser = p.chromium.launch()
    context = browser.new_context()
    page = context.new_page()
    page.goto(LOGIN_URL)

    # Complete the site's authorized login flow here.
    # For example, fill the site's actual username/password fields,
    # submit the form, and handle any required MFA step.
    # Wait for a site-specific success signal before saving state.
    # Example only: page.get_by_role("link", name="Account").wait_for()

    context.storage_state(path=str(STATE))
    context.close()
    browser.close()

The commented selector is illustrative only; replace it with a stable element or URL that indicates successful login on your application. If authentication requires MFA, a human approval, or a one-time challenge, complete that step before saving. Do not bypass access controls.

3. Restore state and capture the logged-in page

Create a fresh browser context with the saved file, navigate to the target, verify that the expected authenticated UI is present, and take the screenshot.

from pathlib import Path
from playwright.sync_api import sync_playwright

STATE = Path("playwright/.auth/state.json")
TARGET_URL = "https://example.com/account"

with sync_playwright() as p:
    browser = p.chromium.launch()
    context = browser.new_context(storage_state=str(STATE))
    page = context.new_page()
    page.goto(TARGET_URL, wait_until="domcontentloaded")

    # Replace with a stable, site-specific logged-in marker.
    page.get_by_role("link", name="Sign out").wait_for()

    page.screenshot(path="logged-in-page.png", full_page=True)
    context.close()
    browser.close()

This example assumes the application exposes a “Sign out” link when authenticated. Choose a marker that fits your site, such as an account name, a dashboard heading, or an application-specific URL. If the page redirects to login or the marker never appears, investigate the restored session before capturing.

Playwright’s authentication guide recommends saving and reusing authenticated state. The Python BrowserContext API documents storage state for initializing a context with recorded browser storage.

4. Choose viewport, full-page, or element capture

Capture Python Use it for
Viewport page.screenshot(path="view.png") The currently visible browser area; this is the default.
Full page page.screenshot(path="full.png", full_page=True) The full scrollable document.
Element page.locator("main").screenshot(path="main.png") A specific matched element, such as the main content panel.
In memory image_bytes = page.screenshot() Passing image data to another part of a Python program without writing a file first.

The official Python screenshots guide covers screenshot capture. The Page API documents screenshot options, including output format, clipping, animation control, masking, and styles. PNG is the default; JPEG and WebP are also available. For example:

page.screenshot(path="view.webp", type="webp")
page.screenshot(path="region.png", clip={"x": 0, "y": 0, "width": 800, "height": 600})
page.locator("main").screenshot(path="main.png")

Use a fixed viewport when consistent dimensions matter:

context = browser.new_context(
    storage_state=str(STATE),
    viewport={"width": 1440, "height": 1000},
    device_scale_factor=1,
)

For long pages with lazy-loaded images, a full-page screenshot can include content that has not been loaded yet. Scroll through the page and wait for important images or sections before capture if the page loads them on demand. The right readiness condition depends on the target site.

5. Use asyncio when the application is asynchronous

If the surrounding program already uses asyncio, Playwright provides an async API. The same saved state can initialize the context:

import asyncio
from playwright.async_api import async_playwright

async def main():
    async with async_playwright() as p:
        browser = await p.chromium.launch()
        context = await browser.new_context(
            storage_state="playwright/.auth/state.json",
            viewport={"width": 1440, "height": 1000},
        )
        page = await context.new_page()
        await page.goto("https://example.com/account", wait_until="domcontentloaded")
        await page.get_by_role("link", name="Sign out").wait_for()
        await page.screenshot(path="logged-in-page.png", full_page=True)
        await context.close()
        await browser.close()

asyncio.run(main())

Use the synchronous API for a straightforward standalone script and the async API when it fits the application’s existing event loop. Avoid calling asyncio.run() from inside an already-running event loop; in that setting, await the coroutine from the host application instead.

6. Know what browser state does and does not preserve

  • Cookies and local storage: Browser context storage state can preserve these for reuse. The site may still expire or revoke the session.
  • IndexedDB: Some applications store authentication data there. The Python API supports including IndexedDB in storage state on versions that provide that option. Check the documentation for the Playwright version installed in your project before relying on it.
  • Session storage: This is separate from the built-in storage-state flow. Playwright’s authentication guide describes saving it through page.evaluate() and restoring it with context.add_init_script(). Session storage is scoped to an origin and browser tab session, so restore it for the correct origin before the application reads it.
  • Server-side session: A copied cookie may stop working when the server expires, revokes, or binds the session to other conditions. A saved file does not extend the server-side lifetime.
  • Multiple origins: Authentication may involve several domains. Make sure the state was saved after the complete flow and that the target is on an origin represented in the saved state.

Refer to the current BrowserContext storage-state API for options supported by your installed release. APIs can change between Playwright versions.

7. Keep authentication state secure

Playwright warns that “The browser state file may contain sensitive cookies and headers that could be used to impersonate you or your test account.” Treat the file as a secret.

  • Store it in a dedicated directory such as playwright/.auth/ and add that path to .gitignore.
  • Restrict file permissions and access to CI artifacts, caches, and logs that might contain it.
  • Use a dedicated authorized test account with only the access the task requires.
  • Regenerate or revoke state when a test account’s access changes or the file may have been exposed.
  • Do not paste state contents into issue trackers, chat, or public examples.

See the security guidance in the official Playwright authentication documentation.

8. Troubleshoot common failures

Symptom Likely cause Fix
The target redirects to the login page. The state was saved before login completed, the session expired, or the required cookie is absent. Repeat the authorized login flow, wait for a reliable success marker before saving, then inspect the target URL and authenticated UI after restore.
A login marker wait times out. The marker is wrong, the page is still loading, or the account is not authenticated. Use a stable site-specific marker and inspect the final URL and page state. Do not treat a screenshot as proof of successful login.
The site appears logged out even though cookies were saved. The application may keep its token in IndexedDB or session storage, or depend on a server-side session that has expired. Check the application’s storage mechanism. Use the installed version’s IndexedDB support where applicable; handle session storage separately as documented by Playwright.
The screenshot is blank, incomplete, or missing images. The page or its assets have not finished loading, content is lazy-loaded, or a selector targets an empty element. Wait for a site-specific ready condition, verify the target element exists and is visible, and scroll to trigger lazy loading before full-page capture when needed.
The saved state file cannot be read. The path is wrong, the setup script did not create the directory, or the runtime cannot access the file. Create the parent directory before saving, pass the correct path, and confirm the execution environment has permission to read it.
Playwright cannot launch Chromium. The browser binary is not installed for the current Playwright package or environment. Run python -m playwright install chromium in the same environment, and ensure deployment has the required browser dependencies.
A screenshot differs between runs. Dynamic data, animations, fonts, or network timing changed. Wait for the relevant content, use a stable viewport, and use the documented screenshot animation, masking, or style options where appropriate.

9. Performance, reliability, and cost

A browser launch and page load are the main work in this flow. For repeated captures in one process, keep the browser running and create or close contexts as needed; contexts isolate sessions while avoiding a new browser launch for every page. Do not share one authenticated context across unrelated users or jobs.

Reliability depends on the site’s session lifetime, login policy, network, and page readiness. A saved state is a snapshot, not a guarantee of future access. Refresh it through the authorized authentication flow when it expires, and assert a site-specific authenticated marker before capture so a login screen is not mistaken for the requested page.

Playwright is open-source software, but running captures still uses compute, memory, browser storage, and network resources in your environment. There is no universal per-screenshot cost: it depends on where and how the browser runs. Keep browser concurrency within the capacity of the host and avoid needlessly recapturing unchanged pages.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. A single request captures a public URL; it does not accept your Playwright storage-state file or authenticate to a private logged-in session. For public pages where you do not need a private browser session, use the API:

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 Bun.write('shot.webp', res);

See the ScreenshotNeo API documentation for request options. Cookie banners, popups, and chat widgets are removed before the shot; bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots per month with no card, and paid plans start at $5 for 3,000.

Sign up for free ScreenshotNeo screenshots.

FAQ

Can I take a screenshot without logging in again for every run?

Yes. Save storage state after an authorized login and load it into a new context on later runs. The session can still expire or be revoked.

Does full_page=True include content below the fold?

It captures the full scrollable document, but content that has not loaded yet may still be absent. Trigger lazy loading and wait for important content when the page requires it.

Can I use ScreenshotNeo for a private page that requires my Playwright login?

The API example captures a URL and does not use Playwright’s saved authenticated state. Use the Playwright workflow for private pages that require that session.

Should I save the state file in my repository?

No. It may contain credentials capable of impersonating the account. Keep it out of Git and limit access to it.