Ai Driven TestingAdvanced

Advanced Browser Context Management

Isolate cookies, storage, and permissions with Playwright browser contexts, and run authenticated flows as scheduled monitors.

ObserveOne Team
5 min read

A browser context is an isolated browser session with its own cookies, local storage, and permissions. One browser can hold several contexts at once, and nothing leaks between them, so contexts are how you test more than one user at a time and how you keep a logged-in session separate from an anonymous one. This guide covers how contexts work in Playwright, how to persist an authenticated session with storageState, and how to turn a logged-in journey into a scheduled monitor.

Understanding Browser Contexts#

A context carries the state that makes a session a session: cookies, local and session storage, granted permissions, geolocation, and the user agent. Pages opened in the same context share that state; pages in different contexts do not. That isolation is what makes contexts useful for testing:

  • Run an admin session and a standard-user session in the same browser
  • Keep each test on a clean slate instead of leaking cookies between runs
  • Reuse a saved login so you do not sign in on every test

Creating and Managing Contexts#

Create a context from a launched browser and configure it at creation time:

import { chromium } from "playwright";
const browser = await chromium.launch();
// Two independent sessions in one browser
const adminContext = await browser.newContext({
storageState: "admin-storage.json",
});
const userContext = await browser.newContext({
permissions: ["notifications"],
});
const adminPage = await adminContext.newPage();
const userPage = await userContext.newPage();
// Contexts are cheap to create and should be closed when done
await adminContext.close();
await userContext.close();
await browser.close();

Contexts accept the emulation options you would otherwise set per test: viewport, timezone, color scheme, geolocation, and permissions.

const context = await browser.newContext({
viewport: { width: 1920, height: 1080 },
timezoneId: "America/Chicago",
colorScheme: "dark",
permissions: ["geolocation"],
});

Cookies and Storage State#

The most valuable thing a context holds is an authenticated session. Rather than log in at the start of every test, log in once, capture the resulting state, and restore it:

// Capture the session after a successful login
await context.storageState({ path: "user-storage.json" });
// Restore it in a later run, already logged in
const context = await browser.newContext({
storageState: "user-storage.json",
});

You can also set cookies directly when you need a specific value:

await context.addCookies([
{
name: "session_token",
value: "test_token_123",
domain: "example.com",
path: "/",
expires: Date.now() / 1000 + 3600,
},
]);

A saved storageState file contains live session cookies. Treat it as a secret: keep it out of version control and rotate it when it expires.

Running Authenticated Flows as Monitors#

Saving a storageState file works on your own machine, but it does not fit continuous monitoring. A stored session expires, and a monitor that logs in fresh on each run is what tells you the login itself still works. That means the check needs credentials at run time, not a stale session file.

ObserveOne's Autopilot handles the credential side. You add login values as named variables, for example LOGIN_USER and LOGIN_PASSWORD. The values are stored as write-only secrets and injected into the run as environment variables, so the generated test references them by name and never holds a literal password:

await page.goto("https://app.example.com/login");
await page.getByLabel("Email").fill(process.env.LOGIN_USER!);
await page.getByLabel("Password").fill(process.env.LOGIN_PASSWORD!);
await page.getByRole("button", { name: "Sign in" }).click();
await expect(page.getByRole("heading", { name: "Dashboard" })).toBeVisible();

Autopilot generates the suite from your URL, fills and submits the auth form with the supplied credentials, asserts the post-login result, and runs the whole flow on a schedule. When the login markup shifts, the selector healer repoints the steps so the monitor survives the redesign instead of failing on it.

The trade-offs are worth stating plainly. This is credential-based login: the suite fills and submits the real auth form, it does not import an existing storageState file. Secret values are write-only, so you can update a credential but not read it back. And the run gives you logs and terminal output, not a video replay.

Permissions and Security Contexts#

Grant only the permissions a test needs, and keep download and CSP behavior explicit:

const context = await browser.newContext({
permissions: ["geolocation"],
acceptDownloads: false,
bypassCSP: false,
});

Follow least privilege in test contexts. Broad permission grants hide bugs that real users, with narrower grants, would hit.

Troubleshooting Browser Contexts#

Problem
Context does not keep state between tests
Solution
Contexts are isolated by design. Persist the session with storageState() after login and restore it when you create the next context.
Problem
Slowdowns with many contexts open
Solution
Close each context as soon as its work is done and cap how many run at once. Contexts are cheap to create but hold memory while open.
Problem
Cookies behave inconsistently
Solution
Set domain, path, and expiration explicitly on every cookie, and prefer storageState() over hand-set cookies for full session restore.

Best Practices#

  • Close contexts as soon as their work finishes
  • Persist login once with storageState and reuse it across local tests
  • Treat saved session files and credentials as secrets
  • Grant the narrowest permission set each test needs
  • For anything that must stay green over time, run the authenticated flow on a schedule so a broken login surfaces as an alert, not a support ticket

Related comparisons

Weighing the tools you just read about? Here's how they stack up side by side.

See alternatives to your stack

Looking to swap one of these out? Here's where to start.

Ready for AI-Powered Testing?

ObserveOne monitors your selectors 24/7 and automatically heals them when websites change. Never deal with broken tests again.

Start Free Trial