Skip to content

Persistent Browser Sessions

Keep a browser alive across multiple script invocations so an agent can act, observe, think, then act again — without losing page state.

The problem

The Playwright CLI and one-off scripts launch a fresh browser each time. Page state — navigation history, scroll position, in-memory JS state, form input — is lost between invocations. For iterative workflows where the agent needs to see results before deciding what to do next, this doesn't work.

The pattern

Launch Chromium once with Chrome DevTools Protocol (CDP) remote debugging enabled. Connect to the running browser from separate scripts. Each script connects, acts, and disconnects — but the browser and its pages stay alive.

Start the browser

This pattern requires Chromium. CDP (Chrome DevTools Protocol) is a Chromium feature — WebKit and Firefox don't support it, and Playwright's own launchServer/connect doesn't preserve pages across client disconnections.

Find Playwright's Chromium and launch it with remote debugging:

sh
CHROMIUM=$(node -e "const { chromium } = require('playwright'); console.log(chromium.executablePath())")
"$CHROMIUM" --remote-debugging-port=9222 --no-first-run --no-default-browser-check about:blank &

The browser is now running and listening for CDP connections on port 9222.

Connect, act, disconnect

Each agent turn connects to the running browser, does its work, and disconnects. The browser stays alive.

javascript
import { chromium } from "playwright";

const browser = await chromium.connectOverCDP("http://localhost:9222");
const context = browser.contexts()[0];
const page = context.pages()[0];

// Do work
await page.goto("https://example.com");
await page.screenshot({ path: "step1.png" });

// Disconnect — browser stays alive
await browser.close();

browser.close() after connectOverCDP() disconnects the CDP client. It does not kill the browser process.

Reconnect on the next turn

The agent sees the screenshot, thinks, then connects again. The page is exactly where it was left:

javascript
import { chromium } from "playwright";

const browser = await chromium.connectOverCDP("http://localhost:9222");
const context = browser.contexts()[0];
const page = context.pages()[0];

// Page state is preserved — still on example.com
console.log(page.url()); // https://example.com/

await page.click("text=More information");
await page.screenshot({ path: "step2.png" });

await browser.close();

Stop the browser

When done, kill the Chromium process:

sh
pkill -f "remote-debugging-port=9222"

Convenience scripts

Wrap the start/stop/connect lifecycle for easy agent use.

Start

sh
#!/bin/bash
# start-browser.sh — launch persistent Chromium
CHROMIUM=$(node -e "const { chromium } = require('playwright'); console.log(chromium.executablePath())")
"$CHROMIUM" \
  --remote-debugging-port=9222 \
  --no-first-run \
  --no-default-browser-check \
  about:blank &
echo "Browser running on CDP port 9222 (PID $!)"

Connect and run

A reusable module for connecting to the running browser:

javascript
// connect-browser.js
import { chromium } from "playwright";

export async function connectBrowser(port = 9222) {
  const browser = await chromium.connectOverCDP(
    `http://localhost:${port}`,
  );
  const context = browser.contexts()[0];
  const page = context.pages()[0];
  return { browser, context, page };
}

Agent scripts become minimal:

javascript
import { connectBrowser } from "./connect-browser.js";

const { browser, page } = await connectBrowser();

await page.goto("https://example.com/settings");
await page.fill('input[name="email"]', "user@example.com");
await page.click("text=Save");
await page.screenshot({ path: "saved.png" });

await browser.close();

When to use what

ApproachState persistsBest for
CLI (playwright screenshot)NoOne-shot screenshots, PDFs
One-off scriptNoScripted multi-step sequences
Persistent browser (CDP)YesIterative agent workflows
--save-storage / --load-storageCookies onlyPreserving login across runs

Use a persistent browser when the agent needs to observe results between steps before deciding what to do next.