How to keep a browser automation logged in across runs

TL;DR A browser running on your laptop keeps you logged in because it writes cookies and local storage to a profile on disk. Automation throws that profile away every run, so your script logs in again each time, which is slow and trips more anti-bot checks. To stay logged in, you persist the profile: save the auth state after the first login and load it before the next run. Browserbase Contexts do this for you, storing the full profile server-side and re-attaching it to any later session by ID.

You logged into a site in your automation, it worked, you shipped it. Then the next run opened a fresh browser and landed back on the login page. Nothing broke. The browser just did what a brand-new browser always does: it started with no memory of you.

This is the single most common wall people hit when they move an automation off their own machine. On your laptop the browser stays logged in for weeks. In a script, or on a server, or in a serverless function, every run starts clean. Here is why that happens and how to make login survive across runs.

Why does my automation lose its login every run?

Because a browser's memory of you lives in a user profile on disk, and automation usually starts without one.

When you log into a site, the server sends back a session cookie. Your browser stores that cookie, plus any local storage or IndexedDB the site writes, in a user data directory on disk. Next time you open the browser it reads that directory back, finds the cookie, and the site treats you as already signed in. That directory is the only reason your everyday browser remembers you.

Automation frameworks default to a throwaway profile. A fresh cloud session, a CI container, or a serverless invocation each start with an empty user data directory, so there is no cookie to read back and the site sends you to the login page. The login itself worked fine. The state it produced had nowhere durable to live.

What does it mean to persist a session?

Persisting a session means saving the login state produced by one run and loading it into the next, so the second run starts already authenticated.

There are two things worth persisting, and they are not the same. Cookies are the narrow answer: export the session cookie after login and re-add it before the next run. That works for simple cookie-based auth and is quick to wire up. The broader answer is the whole profile: cookies plus local storage, IndexedDB, service workers, and site preferences. Sites that keep auth state outside cookies need the whole profile, not just the cookie.

Both approaches share one shape. Save state at the end of a run, restore it at the start of the next, and check whether the restored state still logs you in before assuming it does.

How do you reuse just the cookies?

Save the site's cookies to durable storage after you log in, then add them back to the browser before the next run and skip the login flow if they still work.

This Playwright example logs in once, stores the cookies to a file, and on later runs restores them and checks a protected page before deciding whether to log in again.

# The first time this file is run, the authentication cookies are stored
# to a file. Subsequent runs load those cookies from the file.
import json
import os
from browserbase import Browserbase
from playwright.sync_api import sync_playwright, Page

SITE_URL = "https://practice.expandtesting.com"
SITE_LOGIN_URL = f"{SITE_URL}/login"
SITE_PROTECTED_URL = f"{SITE_URL}/secure"

COOKIE_FILE = "test-cookies.json"


def store_cookies(browser_tab: Page):
    all_cookies = browser_tab.context.cookies(SITE_URL)
    with open(COOKIE_FILE, "w") as cookie_file:
        json.dump(all_cookies, cookie_file, indent=4)


def restore_cookies(browser_tab: Page):
    try:
        with open(COOKIE_FILE) as cookie_file:
            cookies = json.load(cookie_file)
    except FileNotFoundError:
        return
    browser_tab.context.add_cookies(cookies)


def run(browser_tab: Page):
    restore_cookies(browser_tab)
    browser_tab.goto(SITE_PROTECTED_URL)

    if browser_tab.url != SITE_PROTECTED_URL:
        # Redirected, so the restored cookies did not log us in. Log in again.
        browser_tab.goto(SITE_LOGIN_URL)
        # ... fill username/password, submit ...
        store_cookies(browser_tab)
        browser_tab.goto(SITE_PROTECTED_URL)


with sync_playwright() as playwright:
    bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
    session = bb.sessions.create(proxies=True)
    browser = playwright.chromium.connect_over_cdp(session.connectUrl)
    context = browser.contexts[0]
    browser_tab = context.pages[0]
    run(browser_tab)
    browser.close()

This is fine when auth is a single cookie you control. It gets fragile fast when a site spreads state across local storage and IndexedDB, or rotates cookies on every request. At that point you want the whole profile, not one cookie.

How do you persist the whole profile with Contexts?

A Browserbase Context is a stored browser profile that lives server-side. You attach it to a session by ID, and it carries cookies, local storage, IndexedDB, service workers, and site preferences from one session to the next.

First, create a Context once and keep the ID it returns.

import { Browserbase } from "@browserbasehq/sdk";

const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY! });
const context = await bb.contexts.create({
  name: "my-context",
});

console.log("Context ID:", context.id);

Then start a session with that Context ID and persist: true. The flag tells Browserbase to save any changes made during the session, so the login you do here is written back to the Context when the session closes.

import { Browserbase } from "@browserbasehq/sdk";

const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY! });

// Use the context ID from the previous step
const contextId = "<context-id>";

const session = await bb.sessions.create({
  browserSettings: {
    context: {
      id: contextId,
      persist: true,
    },
  },
});

console.log("Session URL: https://browserbase.com/sessions/" + session.id);

Log in inside that first session, then let it close. Wait a few seconds for the Context to finish syncing, then start every future session with the same Context ID. Those sessions start already signed in. Set persist: false on later runs if you want to read the stored login without writing anything new back to it.

Contexts live indefinitely on Browserbase, so you create one, log in once, and reuse it for weeks. The difference from the cookie approach is scope. A Context restores the entire profile, so it covers sites whose auth state you could never capture from cookies alone.

Cookie reuse vs Contexts: which should you use?

DimensionCookie reuseBrowserbase Contexts
What it storesCookies you export by handFull profile: cookies, local storage, IndexedDB, service workers, preferences
Where state livesA file or store you manageServer-side, encrypted at rest, attached by ID
Setup effortSave and restore code per siteCreate once, pass an ID
Best forSimple single-cookie authSites with auth state outside cookies, or many runs sharing one login
Multi-run sharingYou handle concurrency and storageOne Context per site per login, avoid simultaneous use

Cookie reuse is the quick fix; Contexts persist the whole profile without per-site export code.

What breaks, even with persistence?

Persisted state can still stop working, because the site controls whether a login stays valid. Persistence keeps your side of the login. The other side can still end it.

  • Cookies expire. Sites set auth cookies to lapse after a period, so a saved login can go stale even though the Context is intact.
  • Server-side logout. A password change or a "log out of all devices" action invalidates stored sessions from the server, and no amount of local state gets around it.
  • Location checks. Some sites tie a session to where it was created. Keep a consistent geolocation across runs with a proxy so the restored login is not rejected.
  • Simultaneous use. Running two sessions on the same Context at once can force a logout. Use one Context per site per login.

The practical takeaway is to never assume the restored login worked. Load the state, hit a protected page, and if you get redirected to login, re-authenticate and save the fresh state. Both patterns above already do this check, and it is the one habit that keeps a persistence setup from silently failing in production.

How do you take this to production?

The reason this problem feels sharp is that production is exactly where the throwaway profile bites. A serverless function or a scaled fleet of runs has no laptop-style profile to fall back on, so persistence has to be explicit.

This is the shape Contexts are built for. State lives server-side rather than in a file next to your code, it is encrypted at rest, and you attach it by ID from any session or serverless invocation. You get the reliability of a real browser profile without hosting the browser or the disk it writes to. Browserbase runs cloud browser sessions you connect to over CDP with Playwright, Puppeteer, or Stagehand, and Contexts, proxies, and login persistence are configuration on the session rather than infrastructure you maintain. The same setup also runs deterministically: pin the same Context, proxy, and inputs and you get a repeatable run rather than a fresh login every time.

Frequently Asked Questions

Why does my Playwright script log in again every run?

Playwright defaults to a fresh browser profile, so cookies and local storage from the last run are gone. Persist the auth state (cookies or the whole profile) and load it before the next run to stay logged in.

Do Browserbase Contexts expire?

No. Contexts live indefinitely on Browserbase until you delete them. The login data inside can still go stale if the site expires the cookie or logs the session out server-side.

What is the difference between saving cookies and using a Context?

Saving cookies persists just the cookies you export. A Context persists the entire browser profile, including local storage, IndexedDB, and service workers, which covers sites that keep auth state outside cookies.

Can I share one Context across many parallel runs?

Avoid using the same Context in multiple sessions at the same time, since some sites force a logout when they see that. Use one Context per site per login, and give parallel work separate Contexts.

To wire this up, create a Context, log in once with persist: true, and reuse the ID on every run. The full walkthrough is in the Contexts docs and the authentication guide.