lecture 14 of 180 completed

Browser contexts and why your tests do not leak into each other

The idea behind Playwright's isolation. It explains why tests start logged out, and it is a common interview question.

Here is a question worth being able to answer, because interviewers ask it: why do Playwright tests not interfere with each other, even running in parallel?

The answer is the browser context, and understanding it explains several behaviours that otherwise look arbitrary.

Three layers, from big to small.

The browser is the running browser process. Starting one is slow, so Playwright starts few of them.

A context is an isolated session inside that browser. Its own cookies, its own localStorage, its own cache. Think of it as a fresh incognito window. Creating one is fast, a few milliseconds.

A page is a tab inside a context.

Now the important part. Every test gets its own context. That is why:

  • Every test starts logged out. The cookie from the previous test lives in a context that is gone.
  • Tests can run in parallel safely. Two contexts cannot see each other's storage.
  • Your login in beforeAll does not carry into the tests. Different context.

This is the design decision behind Playwright's speed. Full isolation without restarting the browser, because the expensive thing (the browser) is reused and the cheap thing (the context) is thrown away.

You normally never create a context yourself. The page your test receives already lives in one. But you can, and there is one case where you should: testing two users at the same time.

test('a message reaches the other user', async ({ browser }) => {
  const aliceContext = await browser.newContext();
  const bobContext = await browser.newContext();

  const alice = await aliceContext.newPage();
  const bob = await bobContext.newPage();

  await alice.goto('/chat');
  await bob.goto('/chat');

  await alice.getByTestId('message').fill('Hello Bob');
  await alice.getByRole('button', { name: 'Send' }).click();

  await expect(bob.getByTestId('messages')).toContainText('Hello Bob');

  await aliceContext.close();
  await bobContext.close();
});

Read what makes this work. Two contexts means two separate logins in one test. With one context you could not be Alice and Bob at once, because they would share cookies and the second login would replace the first.

Chat, live dashboards, shared documents, admin-approves-user-request flows: this pattern is how you test all of them.

Two practical notes.

Close what you open. A context you create yourself is not cleaned up automatically the way the built-in one is.

browser is a different fixture from page. Asking for { browser } gives you the browser so you can make contexts. Asking for { page } gives you a page that already has one.

Remember this: one context per test is what makes tests independent, and two contexts in one test is how you test two users.

Reference: Browser contexts.