Playwright Python: Persistent context session cookie dropping on browser close

playwright.chromium.launch_persistent_context() drops session cookies on browser closure when cookies lack an explicit expires or max-age attribute, causing the browser engine to classify them as in-memory session cookies that purge upon exit. This also occurs when scripts terminate abruptly via browser.close() or sys.exit() before Playwright flushes the user data directory’s SQLite cookie database to disk. Additionally, navigating across mismatched domain origins or failing to call context.close() prevents session state serialization.

Quick Fix

Explicitly call context.close()—not browser.close()—and manually extract/re-inject session cookies while setting an explicit future expiration timestamp:

# Force-convert session cookies to persistent cookies before context exit
cookies = context.cookies()
for cookie in cookies:
    if "expires" not in cookie or cookie["expires"] == -1:
        cookie["expires"] = 1800000000  # Set explicit future epoch timestamp
context.add_cookies(cookies)
context.close()

Step-by-Step Resolution

  1. Initialize launch_persistent_context with an explicit absolute path to a non-volatile user data directory:
from pathlib import Path
from playwright.sync_api import sync_playwright

USER_DATA_DIR = Path("./user_data_dir").resolve()
  1. Implement explicit context lifecycle management to guarantee that memory-mapped cookie stores (Network Persistent State and Cookies SQLite databases) write to disk before script termination:
from pathlib import Path
from playwright.sync_api import sync_playwright

USER_DATA_DIR = Path("./user_data_dir").resolve()

with sync_playwright() as p:
    context = p.chromium.launch_persistent_context(
        user_data_dir=str(USER_DATA_DIR),
        headless=True,
        accept_downloads=True
    )
    
    page = context.new_page()
    page.goto("https://example.com/login")
    
    # Perform authentication actions...
    
    # Extract and update session cookies that lack explicit expiration
    cookies = context.cookies()
    updated_cookies = []
    for cookie in cookies:
        if cookie.get("expires") == -1 or "expires" not in cookie:
            # Assign a 1-year expiration to prevent session-purge on exit
            cookie["expires"] = 1800000000
        updated_cookies.append(cookie)
    
    context.add_cookies(updated_cookies)
    
    # CRITICAL: Close context explicitly to flush SQLite WAL buffers to disk
    context.close()
  1. Verify directory persistence structure in your filesystem to confirm user_data_dir/Default/Cookies exists and is actively modified on context closure.
See also  Playwright async wait_for_selector timing out on dynamic Shadow DOM elements

Why This Fails for Some Users

  • Closing browser Instead of context: Calling browser.close() on a persistent context object bypasses Playwright’s graceful shutdown sequence for SQLite Write-Ahead Logging (WAL), leaving uncommitted transaction logs in temporary memory.
  • Strict SameSite/Host-Only Cookie Flags: Cookies set with SameSite=Strict or host-only attributes without explicit domain scoping (.example.com vs example.com) fail to rebind to subdomains across new execution runs.
  • Docker Mount and Permission Lockouts: Running inside containers without mounting user_data_dir to a persistent volume, or running as root inside an image where permissions restrict write access to /tmp or mapped paths, discards browser state upon container disposal.
  • Incognito/Ephemeral Context Overrides: Passing no_viewport=True or combining launch_persistent_context with explicit context.new_page() creation in parallel workers can spawn temporary isolated contexts that do not inherit the parent user_data_dir state.

Leave a Comment