IKivan-kozenko -aqa
Try yourself as a QA tester
All topics·Intermediate·18 / 27

Isolation

Every test in Playwright starts with a clean slate: its own cookies, localStorage and session — completely separate from every other test. That's browser contexts at work. The practical consequence: I never need to clean up state between tests, and I can run any test in any order without worrying about leftover state from another test.

What is a browser context

Each test gets its own context — isolated like a separate incognito window

A browser context is like a fresh incognito window — isolated from everything else. It has its own cookies, localStorage, sessionStorage and cache. Playwright creates one per test automatically. When the test finishes, the context is thrown away.

The key thing about isolation: tests can't affect each other. If test A logs in and stores a session cookie, test B starts from zero — no cookie, no session, no state carryover.

Why isolation matters

Without isolation, tests share state. One failed test can corrupt state for ten others. That's the worst kind of bug — a test fails, but the problem isn't in that test. Isolation solves this:

  1. A failing test only affects itself — no cascade
  2. You can run any single test in any order without setup
  3. Parallel execution just works — no race conditions on shared state

The alternative — cleaning up state between tests — sounds fine in theory but breaks in practice. You forget to clean something, or some things are impossible to clean (like visited link styles). Start fresh every time instead.

Context in Playwright Test

When you use @playwright/test, you get page and context as fixtures — both already set up and isolated for your test. You don't create them manually. Two tests running at the same time each get their own completely separate context.

ts
test('order page loads', async ({ page, context }) => {
  // context — це ізольований BrowserContext саме цього тесту
  // page — це Page всередині цього context
  await page.goto('/orders')
  await expect(page.getByRole('heading', { name: 'Замовлення' })).toBeVisible()
})

test('another test', async ({ page, context }) => {
  // context і page тут — повністю відокремлені від попереднього тесту
  // жодних спільних cookies чи localStorage
  await page.goto('/dashboard')
})

Multiple contexts in one test

Sometimes you need two users at once — for example, testing a chat or checking that admin actions affect regular users. You can create multiple contexts manually within a single test, each acting as a different user.

ts
test('admin bans user — user sees ban message', async ({ browser }) => {
  const adminCtx = await browser.newContext()
  const userCtx = await browser.newContext()

  const adminPage = await adminCtx.newPage()
  const userPage = await userCtx.newPage()

  // Адмін логіниться і банить
  await adminPage.goto('/admin/users')
  await adminPage.getByRole('button', { name: 'Ban user123' }).click()

  // Юзер бачить повідомлення про бан
  await userPage.goto('/dashboard')
  await expect(userPage.getByText('Your account has been suspended')).toBeVisible()

  await adminCtx.close()
  await userCtx.close()
})