How to Isolate Browser Sessions for Reliable Web Automation
Use fresh Playwright browser contexts to prevent cookie and storage leakage. Learn how to reuse authentication safely and isolate parallel tests.
In Playwright, create a fresh BrowserContext for each independent browser session or test, create its pages inside that context, and close it when the work is complete. A context isolates browser-side cookies and web storage. For direct automation, use browser.newContext(); Playwright Test creates an isolated context for each test by default. If a test needs a signed-in user, seed a new context with an intentional storage-state snapshot. Also isolate server-side records, accounts, and files: separate browser contexts do not separate backend data.
Playwright describes its tests as running in “isolated clean-slate environments called browser contexts.” This separation helps tests avoid carrying browser state into later runs and limits cascading failures. See the official Playwright isolation documentation.
1. Choose the right session boundary
A browser context is the practical session boundary. Cookies and browser storage belong to a context, and pages created in that context share its session. Use multiple pages in one context when they should represent the same identity, such as a page and its popup. Use different contexts when they must behave as independent visitors.
| Need | Use | State behavior |
|---|---|---|
| Independent test or automation identity | A fresh non-persistent context | Starts without another context’s cookies or web storage; does not write browsing data to disk |
| Several pages belonging to one identity | Several pages in the same context | Pages share that context’s session |
| Fast setup for an authenticated test | A new context initialized from saved storage state | Explicitly copies selected authentication state into the new context |
| Browser continuity across runs | A persistent context with its own user-data directory | Uses a disk-backed profile; one context is available for that browser instance |
For most test and automation jobs, prefer disposable contexts. Reserve persistent profiles for cases that need disk-backed continuity. Persistent contexts use a user-data directory; do not run concurrent browser instances against the same directory. Playwright also warns that automating Chrome’s default user profile is unsupported, so use a separate automation profile. Details are in the BrowserType API reference.
2. Create and close a fresh context
This runnable Node.js example uses the Playwright library directly. Install Playwright and its browser first with npm install playwright and npx playwright install chromium. Save the code as capture.js and run node capture.js.
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch();
try {
// A new context starts with independent cookies and web storage.
const context = await browser.newContext({
viewport: { width: 1440, height: 900 },
});
try {
const page = await context.newPage();
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.screenshot({ path: 'example.png', fullPage: true });
} finally {
// Close the context to dispose of its pages and session state.
await context.close();
}
} finally {
await browser.close();
}
})();
The important boundary is the call to browser.newContext(). Do not reuse a page or context between independent identities. Closing the context disposes its pages and browser-side state; closing the browser at the end releases the browser process. If a browser process is intentionally shared across tasks, still create and close a separate context for each independent task.
3. Isolate tests with Playwright Test
Playwright Test provides a fresh browser context and page to each test by default. Use the supplied page fixture for ordinary isolated tests rather than creating a shared module-level page.
import { test, expect } from '@playwright/test';
test('visitor sees the home page', async ({ page }) => {
await page.goto('https://example.com');
await expect(page).toHaveTitle(/Example Domain/);
});
When a test needs more than one independent identity, create another context explicitly and close it in a finally block. For example, a two-user workflow can use the fixture page as the first user and a separately created context for the second.
import { test, expect } from '@playwright/test';
test('two users have independent browser sessions', async ({ page, browser }) => {
const secondContext = await browser.newContext();
try {
const secondPage = await secondContext.newPage();
await page.goto('https://example.com');
await secondPage.goto('https://example.com');
// Authenticate each page as a different user here.
// Their cookies and web storage are scoped to separate contexts.
await expect(page).toHaveURL(/example\.com/);
await expect(secondPage).toHaveURL(/example\.com/);
} finally {
await secondContext.close();
}
});
Parallel execution does not make shared backend fixtures independent. Give tests unique records or worker-specific accounts when they create or edit server-side data. Write screenshots and downloads to unique paths per test. Playwright’s guidance on parallelism covers the test-runner side of concurrency.
4. Reuse authentication without reusing a live session
When logging in is expensive, save the required storage state during setup, then initialize a fresh context from that snapshot. This transfers authentication deliberately while keeping each test’s live browser session independent.
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch();
try {
const setupContext = await browser.newContext();
try {
const setupPage = await setupContext.newPage();
await setupPage.goto('https://example.com/login');
// Fill in credentials and submit the login form for your application.
// await setupPage.getByLabel('Email').fill(process.env.TEST_EMAIL);
// await setupPage.getByLabel('Password').fill(process.env.TEST_PASSWORD);
// await setupPage.getByRole('button', { name: 'Sign in' }).click();
await setupPage.context().storageState({ path: 'playwright/.auth/user.json' });
} finally {
await setupContext.close();
}
// Each consumer gets its own context, initialized from the same snapshot.
const testContext = await browser.newContext({
storageState: 'playwright/.auth/user.json',
});
try {
const page = await testContext.newPage();
await page.goto('https://example.com/account');
// Run authenticated work here.
} finally {
await testContext.close();
}
} finally {
await browser.close();
}
})();
The example’s login actions are application-specific, so replace the comments with your site’s real form and credentials. Keep the state file out of source control: it can contain cookies or headers that allow someone to impersonate the account. Playwright recommends placing authentication state under a directory such as playwright/.auth and adding it to .gitignore.
# .gitignore
playwright/.auth/
Storage state can cover cookies and local storage, and Playwright documents options for IndexedDB and WebAuthn credentials. Check what the application actually uses. Playwright does not provide a direct API to persist session storage; if authentication depends on it, save and restore it explicitly with an initialization script or other application-specific setup. Consult the official authentication guide and BrowserContext API for version-specific details.
5. Make parallel runs safe beyond the browser
A new context prevents browser-side state carryover, but it cannot isolate state held by your application or external services. For each parallel worker or test, plan the following:
- Accounts: use worker-scoped accounts when tests modify user settings, carts, or other shared account data.
- Records: create unique IDs for orders, projects, or other mutable backend records.
- Files: give screenshots, downloads, and generated artifacts unique paths; avoid two tests writing the same filename.
- Cleanup: remove test-created records when safe, or use disposable test data with a clear lifetime.
- Process state: avoid mutable module-level variables that tests update and avoid depending on test order.
- External services: isolate test tenants or namespaces when a third-party service stores state used by the workflow.
Think of independence as several boundaries: browser context for cookies and web storage, unique fixtures for server state, and unique paths for files. A context alone only addresses the first boundary.
6. Handle storage and special cases
Popups and multiple tabs
Pages and popups opened from a page belong to its context. That is correct when they represent the same signed-in identity. If two pages need separate cookies or local storage, create two contexts instead of opening another tab.
Session storage
Do not assume a storage-state file contains session storage. If the application stores authentication there, capture the needed values and restore them with an initialization script before application code reads them. Keep this logic specific to the application and treat copied values as credentials.
Persistent profiles
A persistent context is useful when a workflow must preserve a profile on disk. Give each concurrently used browser instance its own user-data directory. Reusing one directory concurrently can cause conflicts, and using Chrome’s default profile for automation is unsupported by Playwright. If persistence is not a requirement, a normal non-persistent context is simpler to dispose of and does not write browsing data to disk.
Visual regression
Session isolation alone does not make screenshots comparable. Keep operating-system and browser versions consistent for visual comparisons, and make the test data and page state deterministic. Use resilient user-facing locators: Playwright locators auto-wait and retry relevant actionability checks. See Playwright best practices.
7. Troubleshoot isolation problems
| Symptom | Likely cause | Fix |
|---|---|---|
| A test appears signed in as a previous user | The same context or page is reused, or state is explicitly seeded from a shared snapshot | Create a fresh context per identity; inspect any configured storageState for intentional shared authentication |
| A new context is unexpectedly signed in | The context was initialized with an authenticated storage-state file, or the application authenticates through a mechanism outside the assumed storage | Check context options and inspect the app’s cookie, local storage, IndexedDB, session storage, or WebAuthn flow |
| Login works in setup but fails in the test | The saved snapshot omitted a required storage mechanism or expired before use | Confirm the app’s actual auth storage, regenerate state, and explicitly restore session storage if required |
| Parallel tests overwrite or corrupt the same data | Contexts are isolated but tests share an account, backend record, or output path | Use unique records, worker-specific accounts, and per-test artifact paths |
| Concurrent persistent launches fail or behave unpredictably | Browser instances share a user-data directory | Assign a distinct profile directory to each instance, or use non-persistent contexts |
| Visual screenshots differ despite clean contexts | Browser or operating-system versions, test data, or rendered timing differs | Keep browser and OS versions consistent, stabilize data, and use locator-based waits instead of arbitrary timing assumptions |
| Context cleanup is skipped after an error | Close calls are not protected against exceptions | Put context.close() in finally; close the browser in an outer finally |
8. Performance, reliability, and cost
Fresh contexts add setup and cleanup work, while reusing a browser process and creating separate contexts can preserve the isolation boundary without launching a browser for every task. The reviewed Playwright documentation does not provide a numerical speed, memory, or failure-rate comparison, so measure your own workload before tuning concurrency.
For reliability, close contexts even when a task fails, bound parallel work to what the environment can support, and make backend fixtures unique. For authenticated jobs, reusing a storage-state snapshot can avoid repeating login while preserving separate live contexts; protect that file as a secret and refresh it when the application’s credentials expire. Cost depends on your browser runtime and infrastructure; the sources here do not establish a general cost figure.
Or skip the browser setup
If your task is to capture a website rather than automate an interactive workflow, ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. It makes a screenshot or PDF from one GET request, so you do not need to launch and manage a browser for that capture. See the API documentation.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
Cookie banners are accepted like a visitor would and removed before the shot, along with known newsletter popups and chat widgets; each step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers say the page verdict and billing status. An MCP server lets AI agents use screenshot, page-info, and PDF tools. The free plan includes 1,000 shots a month with no card; paid plans start at $5 for 3,000 shots. Sign up for 1,000 free screenshots a month, with no card.
FAQ
Does a new page create a new browser session?
No. A page created inside an existing context shares that context’s session. Create a new context for an independent identity.
Can separate contexts use the same test account?
They can, but browser separation does not prevent simultaneous changes to the same server-side account data. Use separate accounts or coordinate changes when tests mutate shared state.
Should every test get a separate browser process?
Not necessarily. The isolation boundary described here is the context; Playwright Test supplies isolated contexts for tests. A browser process can host multiple independent contexts.
Does storage state preserve every kind of browser storage?
No. In particular, Playwright documents no direct API for persisting session storage. Verify the mechanisms your application uses and restore unsupported state explicitly.


