How to Use Persistent Browser Sessions with `persistent_local_browser` in Webwright
The persistent_local_browser tool in Webwright launches a detached Chromium process with a private user-data directory and reusable CDP endpoint, storing session metadata in a JSON file that enables reconnecting across multiple scripts or CI steps.
Webwright, Microsoft's open-source automation framework, provides a command-line tool called persistent_local_browser that solves the problem of maintaining browser state across disconnected execution steps. Unlike standard ephemeral browser instances that terminate when your script ends, this tool creates a long-living Chromium process that survives the parent Python interpreter and can be reattached via Chrome DevTools Protocol (CDP).
Understanding the Architecture
The tool is implemented in src/webwright/tools/persistent_local_browser.py and manages browser persistence through three core mechanisms: process detachment, session serialization, and lifecycle commands.
Process Detachment and Survival
When you invoke the create command, Webwright launches Chromium using start_new_session=True on POSIX systems (📄[line 41‑44]), placing the browser in a new process group. This architectural decision ensures the Chromium process survives even if the spawning shell terminates or the parent Python process crashes. The browser launches with --remote-debugging-port=0, causing Chromium to select an available port and print a line matching the _DEVTOOLS_RE pattern (📄[line 43]).
Session State Storage
The tool captures the DevTools WebSocket URL and writes an atomic JSON record (via out_path.write_text(), 📄[line 163‑164]) containing:
- session ID: Unique identifier for the session
- connectUrl: The CDP WebSocket endpoint (e.g.,
ws://127.0.0.1:XXXXX/devtools/browser/<id>) - pid: Process ID for liveness checks
- userDataDir: Path to the isolated user-data directory
- executable: Path to the Playwright-bundled Chromium binary
By default, this file is named .lb_session.json and resides in your specified workspace directory.
Three-Command Lifecycle
The tool exposes three sub-commands implemented as distinct functions:
create: Launches Chromium via_chromium_executable()(📄[line 54‑66]), waits for the DevTools URL using_wait_for_devtools_url()(📄[line 81‑104]), and writes the session fileinfo: Validates session health by checking_pid_alive()(📄[line 69‑78]) and displays enriched session metadatarelease: Terminates the browser via_terminate_pid()(📄[line 83‑100]) using SIGTERM followed by SIGKILL if necessary, with optional cleanup of user data
Creating a Persistent Browser Session
To create a session, Webwright resolves paths relative to your workspace using _resolve_path() (📄[line 46‑51]) and constructs launch arguments (📄[line 118‑130]) that include --remote-debugging-port=0 and --user-data-dir.
python -m webwright.tools.persistent_local_browser create \
--workspace-dir /path/to/workspace \
--out .lb_session.json \
--headless \
--no-sandbox \
--startup-timeout 30
The --workspace-dir parameter ensures all file paths (session file, user-data directory) resolve relative to a consistent base, critical for reproducible CI pipelines. The --startup-timeout defaults to 30 seconds but can be adjusted for slower environments.
Upon successful creation, the tool prints environment-variable-style helpers to stdout:
LB_SESSION_ID=abc-123
LB_CONNECT_URL=ws://127.0.0.1:9222/devtools/browser/...
LB_USER_DATA_DIR=/path/to/workspace/.user_data
Connecting to an Existing Session
Reuse the persistent session by reading the JSON file and connecting via Playwright's CDP transport. Do not call browser.close()—this would terminate the shared process. Instead, use browser.disconnect().
import json
from pathlib import Path
from playwright.sync_api import sync_playwright
session_path = Path("/path/to/workspace/.lb_session.json")
session = json.loads(session_path.read_text())
with sync_playwright() as p:
# Connect to the existing CDP endpoint
browser = p.chromium.connect_over_cdp(session["connectUrl"])
page = browser.new_page()
page.goto("https://example.com")
# ... perform actions ...
browser.disconnect() # Detaches without killing Chromium
This pattern allows multiple independent scripts to share authentication state, cookies, and local storage because they all attach to the same underlying Chromium instance using the --user-data-dir specified in the session record.
Managing and Releasing Sessions
Monitor session health before attempting reconnection to avoid errors from stale PIDs.
Checking Session Status
The info command verifies process liveness and displays current metadata:
python -m webwright.tools.persistent_local_browser info \
--workspace-dir /path/to/workspace \
--session-file .lb_session.json
This executes _pid_alive() to check if the stored PID remains active in the process table, helping you identify zombie session files.
Terminating and Cleaning Up
When finished, release the browser deterministically:
python -m webwright.tools.persistent_local_browser release \
--workspace-dir /path/to/workspace \
--session-file .lb_session.json \
--delete-user-data \
--delete-file
The release command first sends SIGTERM, waits gracefully, then escalates to SIGKILL via _terminate_pid() if the process refuses to exit. The --delete-user-data flag removes the temporary profile directory (DEFAULT_USER_DATA_SUBDIR as defined in src/webwright/config/persistent_browser.yaml), while --delete-file removes the session JSON itself.
Configuration and Path Resolution
Webwright respects defaults from persistent_browser.yaml located in src/webwright/config/, including the default user-data subdirectory path referenced as DEFAULT_USER_DATA_SUBDIR (📄[line 42]).
The _resolve_path() utility ensures all file arguments are absolute paths relative to --workspace-dir, preventing path resolution errors when commands execute from different working directories. This is essential when moving between CI steps where $PWD may change between the create and connect phases.
Summary
- Process isolation: The
persistent_local_browsertool launches Chromium in a new session group, ensuring survival after parent process termination - CDP connection: Stores WebSocket endpoint in
.lb_session.jsonfor reconnection viaconnect_over_cdp() - Three-command workflow: Use
createto start,infoto check health (_pid_alive), andreleaseto terminate (_terminate_pid) and clean up - Safe reconnection: Always use
browser.disconnect()instead ofclose()when reusing persistent sessions - CI-friendly: Path resolution relative to
--workspace-dirand configurable startup timeouts accommodate containerized environments
Frequently Asked Questions
How does the browser survive after my script exits?
The tool uses start_new_session=True when spawning the Chromium process on POSIX systems, creating a new process group that is not a session leader. This detaches the browser from the parent shell's session, allowing it to persist even when the original Python process or Bash step terminates.
Can I customize the Chromium executable used for persistent sessions?
Yes. The _chromium_executable() function (📄[line 54‑66]) locates the Playwright-bundled Chromium by default, but you can override the executable path via the --executable CLI argument when running the create command, or by configuring defaults in persistent_browser.yaml.
What happens if I try to connect to a dead session?
The info command checks process liveness using _pid_alive() (📄[line 69‑78]), which verifies the stored PID exists in the process table. If the browser has crashed or been killed externally, the command reports the session as inactive, allowing you to avoid connection errors by validating the session before calling connect_over_cdp().
How do I ensure the user data directory is cleaned up automatically?
Pass both --delete-user-data and --delete-file flags to the release command. The --delete-user-data flag triggers removal of the temporary profile directory created under your workspace, while --delete-file removes the .lb_session.json record, ensuring no stale session files or disk space remain after teardown.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →