Playwright Isolation: Choosing Between Browser Contexts and Pages
Learn how to choose between Browser Contexts and Pages in Playwright to balance test isolation and CI performance, including patterns for authenticated state reuse.
13 Jun 2026, 23:46 UTC

The Isolation Dilemma: Contexts vs. Pages
When scaling a Playwright test suite, the primary engineering trade-off is between test isolation and execution speed. If tests share state, a failure in one can cause a cascade of false negatives in others. If isolation is too heavy, CI pipeline costs rise due to increased memory and CPU usage.
The core decision is whether to use a Browser Context (a fresh, isolated session equivalent to an incognito window) or a Page (a tab within an existing session) as the unit of isolation for each test.
Comparison of Isolation Strategies
| Feature | Context-per-Test | Page-per-Test (Shared Context) |
|---|---|---|
| Isolation Level | Complete (Cookies, Storage, Cache) | Partial (Shared Storage/Cookies) |
| Overhead | ~50–150ms per creation | ~40% faster launch than contexts |
| Memory Usage | Higher (~15–25MB per context) | Lower (~2–5MB per page) |
| Risk | CI Memory exhaustion at high scale | State leakage (e.g., LocalStorage) |
Evaluating the Trade-offs
When to use Context-per-Test
This is the recommended default for most enterprise suites. Because a Browser Context provides a clean slate, you avoid the "leaky test" syndrome where a cookie set in Test A affects the behavior of Test B. This is critical for testing multi-user scenarios or permissions where you need to switch between an Admin and a Guest user rapidly.
When to use Page-per-Test
Shared contexts are viable only when the application state is read-only or when you are constrained by severe CI resource limits. If you choose this path, you must manually clear state between tests. Note that Service Workers are particularly sticky; they persist across pages in the same context and must be explicitly unregistered to ensure a clean environment.
Implementation: Optimized Isolation with Auth Reuse
The biggest performance hit in context-per-test strategies is repeated authentication. To solve this, use storageState to save the authenticated session once and inject it into every new context.
// auth.setup.ts - Run once before the test suite
import { test as setup } from '@playwright/test';
setup('authenticate', async ({ page }) => {
await page.goto('https://app.example.com/login');
await page.fill('#username', 'user');
await page.fill('#password', 'pass');
await page.click('#login-button');
// Save cookies and localStorage to a file
await page.context().storageState({ path: 'auth.json' });
});
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
// Every test context now starts with the authenticated state
storageState: 'auth.json',
baseURL: 'https://app.example.com',
},
});
Diagnostic: Verifying Isolation
To verify that your configuration is actually isolating state, run a "leak test." If the second test finds the value set by the first, your isolation is failing.
import { test, expect } from '@playwright/test';
test('Test A: Set State', async ({ page }) => {
await page.evaluate(() => localStorage.setItem('isolation_check', 'failed'));
});
test('Test B: Verify State', async ({ page }) => {
const value = await page.evaluate(() => localStorage.getItem('isolation_check'));
// This should be null in a context-per-test setup
expect(value).toBeNull();
});
Constraints and Limitations
- Storage Limitations:
storageStatecaptures cookies andlocalStorage, but it does not capturesessionStorage, IndexedDB, or the Cache API. Tests relying on these must use a shared context or perform explicit setup. - OS Limits: High parallelism with context-per-test can hit OS file descriptor limits (
ulimit -n) in Linux CI environments. If you see "too many open files" errors, either reduce concurrency or increase the system limit. - Profile Isolation: In Firefox and WebKit, contexts may share OS-level profile directories. For absolute hardware-level isolation, use
launchPersistentContextwith a uniqueuserDataDirfor each instance.
Rollback and Cleanup
If you transition from a shared context to a context-per-test model and encounter memory crashes in CI, revert the storageState usage or reduce the workers count in your config to balance the memory load.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.