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.