ScreenshotNeo

BlogHow-to

How to Take Authenticated Website Screenshots with a Session Cookie in Python

Use Playwright for Python to add a session cookie before navigation, verify the logged-in page, and capture it safely.

By the ScreenshotNeo team4 October 20268 min read

To take an authenticated website screenshot in Python, create a Playwright browser context, add the valid session cookie to that context before navigating, open the target URL, confirm the page is authenticated, and then capture it. The cookie must belong to the target site and have a scope that covers the URL. A screenshot can capture a login redirect just as easily as an authenticated page, so verify the result before trusting it.

Use this only with an account and session you are authorized to use. Keep the cookie secret: anyone who obtains a usable session cookie may be able to act as that account.

1. Install Playwright and its browser

Install the Python package and Chromium browser from your project environment:

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

Set the cookie value outside your source code. For example, in a Unix-like shell:

export SESSION_COOKIE='paste-the-authorized-session-value-here'

Use the cookie name and value supplied by the site or established through an authorized login flow. Do not copy a real credential into code, commit it, print it, or include it in logs.

This synchronous example adds the cookie to a browser context before opening the page, waits for network activity to settle, and saves a full-page PNG. Replace the example URL, cookie name, and cookie attributes with the values appropriate for your application.

import os
from playwright.sync_api import sync_playwright

url = "https://example.com/account"
session_cookie = os.environ["SESSION_COOKIE"]

with sync_playwright() as p:
    browser = p.chromium.launch(headless=True)
    context = browser.new_context(viewport={"width": 1440, "height": 1000})
    context.add_cookies([{
        "name": "sessionid",
        "value": session_cookie,
        "url": "https://example.com",
        "httpOnly": True,
        "secure": True,
    }])

    page = context.new_page()
    response = page.goto(url, wait_until="networkidle")
    print("Final URL:", page.url)
    print("HTTP status:", response.status if response else "no main response")

    # Replace this check with an element or URL that identifies your signed-in page.
    page.get_by_role("heading", name="Account").wait_for()
    page.screenshot(path="authenticated-page.png", full_page=True)

    context.close()
    browser.close()

For reproducible automation, replace the illustrative heading check with a locator, URL check, or application-ready signal that is specific to the target site. The example cookie name and flags are placeholders; use the real cookie’s name, value, and scope. Playwright accepts a cookie url, or a domain and path pair. Cookie attributes such as secure and httpOnly describe the cookie; setting them does not make an expired or invalid session valid. See the Playwright BrowserContext reference.

Setting How to choose it Common failure
url Use the site URL covered by the cookie, including the correct scheme and host. A cookie scoped to one host or path is not sent to another.
domain and path Use these together when specifying cookie scope this way. A leading dot on a domain can include subdomains. Domain or path does not cover the destination URL.
secure Match the actual cookie attribute; secure cookies are intended for HTTPS. Testing against HTTP when the cookie is scoped for HTTPS.
httpOnly Match the cookie attribute provided by the site. It is mistaken for an authentication bypass or a way to repair a bad credential.
full_page Set True for a full-page image; omit it for a viewport capture. The output is taller or shorter than the intended capture.
wait_until Choose the navigation readiness condition that fits the site. networkidle waits for network activity to settle. Some applications keep requests open or render important content after navigation.

For dynamic pages, wait for a meaningful page element after navigation instead of assuming that a fixed delay always means the application is ready. Playwright’s screenshot guide documents capture behavior and options.

4. Use async Python when the surrounding program is asynchronous

The asynchronous API uses the same context and cookie flow. This can fit an asyncio service or a program that already uses Playwright’s async interface.

import asyncio
import os
from playwright.async_api import async_playwright

async def main():
    url = "https://example.com/account"
    session_cookie = os.environ["SESSION_COOKIE"]

    async with async_playwright() as p:
        browser = await p.chromium.launch(headless=True)
        context = await browser.new_context(viewport={"width": 1440, "height": 1000})
        await context.add_cookies([{
            "name": "sessionid",
            "value": session_cookie,
            "url": "https://example.com",
            "httpOnly": True,
            "secure": True,
        }])
        page = await context.new_page()
        response = await page.goto(url, wait_until="networkidle")
        print("Final URL:", page.url)
        print("HTTP status:", response.status if response else "no main response")
        await page.get_by_role("heading", name="Account").wait_for()
        await page.screenshot(path="authenticated-page.png", full_page=True)
        await context.close()
        await browser.close()

asyncio.run(main())

Use the sync API for straightforward scripts and the async API when it matches the program’s concurrency model. Avoid mixing sync and async Playwright objects in the same flow.

5. Reuse saved authentication state for repeated captures

A cookie alone is the simplest option when the application uses a known, current session cookie. Some applications also rely on local storage, IndexedDB, or other supported browser state. If an authorized Playwright login flow established the session, save its storage state and initialize later contexts from it:

# After completing the authorized login flow:
await context.storage_state(path="playwright/.auth/state.json")

# In a later run:
context = await browser.new_context(storage_state="playwright/.auth/state.json")

The same storage_state option is available with the synchronous API. Keep authentication state files out of source control; they can contain cookies and headers that enable account impersonation. Add the auth-state directory to .gitignore. Playwright’s authentication guide explains saved state and its security implications.

Session storage is separate: Playwright’s regular storage-state API does not include it. If the application depends on session storage, follow the guide’s initialization-script pattern and restrict initialization to the intended hostname. Do not assume that copying cookies alone reproduces every site’s login state.

6. Verify authentication before saving or using the screenshot

  1. Inspect page.url after navigation. A redirect to a login route is a strong sign the session was not accepted.
  2. Wait for an element unique to the authenticated page, such as an account heading or signed-in navigation item.
  3. Check the main navigation response status when available, while remembering that a successful HTTP status does not prove the correct account page rendered.
  4. Capture only after the application-specific check succeeds. Treat a screenshot of a login, error, or access-denied page as a failed capture.

A robust automation should fail clearly when its authenticated-page check does not appear, rather than silently saving an image that looks plausible but contains the wrong page.

7. Troubleshooting

Symptom Likely cause Fix
Screenshot shows the login page The cookie is expired, invalid, scoped to another host/path, or the app needs additional state. Obtain a fresh authorized session; verify cookie name, value, domain/path or URL, and check whether the app also uses storage state.
Cookie is present in the context but not sent Its URL, domain, path, or secure setting does not match the destination. Match the actual cookie scope and use the correct HTTPS host and target path.
Works on one subdomain but not another The cookie is host-only or its domain scope omits the second subdomain. Use the site’s actual supported domain scope; do not broaden scope without authorization or need.
Navigation times out waiting for networkidle The site may keep polling, streaming, or analytics requests active. Choose a more suitable navigation condition, then wait for the specific authenticated content you need.
Screenshot is blank or incomplete The app renders after navigation, content is lazy-loaded, or the readiness signal is too early. Wait for the relevant content or application-ready locator before capture; use full-page capture only when desired.
Cookie works manually but not in automation The login depends on local storage, IndexedDB, session storage, or other browser state in addition to cookies. Prefer Playwright storage state from an authorized login flow; handle session storage separately per the authentication guide.
Python cannot find Chromium The Playwright package is installed but its browser binary was not installed in the environment. Run python -m playwright install chromium in that environment.
Cookie value appears in logs or repository history A secret was embedded or printed during debugging. Remove it from logs and source, rotate/revoke the session if exposed, and load future values from a secret store or environment.

8. Performance, reliability, and cost

Browser startup and page loading usually dominate a single capture’s work. For repeated captures in one controlled job, reuse the browser process and create an isolated context for each session; close contexts after use to release resources and flush artifacts. Set timeouts and wait on application-specific readiness, since waiting for every network request to stop can be unreliable on highly dynamic sites.

Keep sessions isolated by context and avoid sharing one account cookie across unrelated jobs. A failed authentication check should be reported as a failure, not treated as a valid screenshot. Browser-based capture has infrastructure and maintenance costs: the browser binary, runtime, and page load all run in your environment. A hosted screenshot API can reduce browser setup, but it cannot make an expired or unauthorized session valid. Do not send a private cookie to a third-party service unless its authentication mechanism and data handling fit your requirements.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. Its one-request API returns an image or PDF, with options for custom headers and cookies. The API response identifies the page verdict and billing status in headers. Read the ScreenshotNeo API documentation before sending any private-page credentials.

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

ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets 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 a month with no card, and paid plans start at $5 for 3,000 screenshots. For private pages, consult the docs and use an authentication setup appropriate to the API; never expose a session cookie in a URL or public code.

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

FAQ

Call context.add_cookies([...]) before navigating, using the real cookie name and value plus a matching URL or domain-and-path scope.

Why does my Playwright screenshot show the login page?

The session may be expired or scoped incorrectly, or the site may require browser state beyond cookies. Check the final URL and an authenticated-page locator before capture.

Can Playwright reuse saved login cookies?

Yes. Save supported authentication state with context.storage_state() and create a later context with that state file. Protect the file as a credential; session storage needs separate handling.