Keep Playwright Tests Isolated Without Relaunching the Browser
Playwright Test gives each test a fresh browser context by default. Learn how test.use configures that context without the cost of a new browser process.
03 Aug 2026, 11:57 UTC

Your Playwright suite passes when you run one file, but fails intermittently when you run the whole project. One test logs in, the next test inherits that session, and a form submission succeeds because someone else left data in localStorage.
The useful takeaway: Playwright Test already gives every test its own isolated browser context by default. You do not need to launch a new browser for each test. Use test.use to set shared options such as baseURL or storageState, and keep the browser process running across the suite.
Why a fresh context matters
A browser process is the actual Chromium, Firefox, or WebKit instance. A browser context is an isolated session inside that process, similar to an incognito window. It has its own cookies, localStorage, and session storage. By default, Playwright Test starts a new context for every test while reusing the same browser process.
Sharing one context across tests couples them through shared state. If test A saves a draft to localStorage and test B reads that same storage, test B can pass for the wrong reason. Those failures are order-dependent and hard to reproduce because they disappear when you run tests in isolation.
Configure the context once with test.use
Use test.use at the top of a test file to configure every context in that file without repeating options in each test. This keeps the browser process alive while giving each test a clean slate.
import { test, expect } from '@playwright/test';
test.use({
baseURL: 'https://app.example.test',
storageState: 'playwright/.auth/user.json',
extraHTTPHeaders: {
'X-Test-Environment': 'staging',
},
});
Run this from your project root with npx playwright test. You need read access to the project files and Node.js installed; no administrative permissions are required. Replace https://app.example.test with your actual origin. The expected check is that relative URLs such as page.goto('/settings') resolve against baseURL.
A risk with storageState is that it restores a saved login without exercising the real authentication flow. It is a useful shortcut for tests that assume an authenticated session, but it should not replace end-to-end login tests.
Worked example: proving localStorage isolation
The following example writes a different draft message to localStorage in each test and asserts that no test sees another test's draft.
import { test, expect } from '@playwright/test';
test.use({ baseURL: 'https://app.example.test' });
test('saves a draft in localStorage', async ({ page }) => {
await page.goto('/compose');
await page.evaluate(() => localStorage.setItem('draft', 'test-a-message'));
await expect(page.locator('#draft-preview')).toHaveText('test-a-message');
});
test('does not see the previous test draft', async ({ page }) => {
await page.goto('/compose');
await expect(page.locator('#draft-preview')).not.toHaveText('test-a-message');
});
Run the pair with npx playwright test --grep "draft". Both tests should pass whether you run them together or individually. If the second test sees test-a-message, something is sharing state between contexts—check for a shared custom fixture or an accidental storageState that restores the same data.
To inspect what Playwright recorded, open the HTML report after the run:
npx playwright show-report
Each test entry should show its own trace and snapshot, which makes it easier to attribute a failure to a single test rather than a suite-wide ordering problem.
Where context isolation stops
Context isolation resets client-side storage, but it does not reset server-side databases, third-party APIs, or file uploads from previous tests. If test A creates a user record and test B expects an empty list, test B can fail even with isolated contexts.
A practical check is to run your suite in reverse order or with sharding enabled. If failures appear only in certain orders, you likely have backend state leakage that context isolation cannot fix. Add an API cleanup fixture or a database reset step between tests instead of relying on the browser alone.
Launching a new browser with browserType.launch inside beforeEach gives stronger isolation but is much slower. For large suites, the default per-test context is usually the better balance.
What to do next
Keep the default per-test context. Use test.use for shared configuration such as baseURL and headers. Reserve storageState for tests that genuinely need an authenticated session, and add backend cleanup for anything the browser context cannot reset. If you add storageState and start seeing false passes, remove it and restore the full login flow.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.