</>

Technology

Playwright

Difficulty

Advanced

Interview Question

How does Playwright handle test isolation with Workers?

Answer

Test Isolation with Workers in Playwright

The Worker Model

CODE
CI Machine
├── Worker Process 1 (Node.js process)
│   ├── Browser 1 (Chromium)
│   │   ├── Context A → test-file-1.spec.ts
│   │   └── Context B → test-file-2.spec.ts
├── Worker Process 2 (Node.js process)
│   └── Browser 2 (Chromium)
│       └── Context C → test-file-3.spec.ts
└── Worker Process 3 (Node.js process)
    └── Browser 3 (Chromium)
        └── Context D → test-file-4.spec.ts

How Isolation Works

  • Each test gets a fresh BrowserContext with:
    • New cookies (empty)
    • Clean localStorage (empty)
    • No auth state
    • No shared state from other tests
  • Tests in the same file run in the same worker (by default) but each test still gets a new context
  • Tests across files can run in different workers simultaneously

Isolation at the Test Level

TypeScript
// Each test gets fresh isolation automatically
test('test A — sets cookie "foo"', async ({ page, context }) => {
  await context.addCookies([{ name: 'foo', value: 'bar', domain: 'localhost', path: '/' }]);
  // This cookie ONLY exists for this test
});

test('test B — cannot see test A cookies', async ({ page, context }) => {
  const cookies = await context.cookies();
  // cookies is EMPTY — test A's cookies are gone
  expect(cookies).toHaveLength(0);
});

Worker-Level Fixtures (Shared Within Worker)

TypeScript
// Fixtures with scope:'worker' are shared across tests in the same worker
export const test = base.extend({
  sharedAuth: [async ({ browser }, use) => {
    const context = await browser.newContext();
    const page = await context.newPage();
    await page.goto('/login');
    await page.fill('#email', 'user@test.com');
    await page.click('#submit');
    await context.storageState({ path: 'worker-auth.json' });
    await page.close();
    await use('worker-auth.json');
    await context.close();
  }, { scope: 'worker' }],
});

Checking Worker Index

TypeScript
import { test } from '@playwright/test';

test('which worker am I?', async ({}, testInfo) => {
  console.log(`Worker index: ${testInfo.workerIndex}`);   // 0, 1, 2, ...
  console.log(`Parallel index: ${testInfo.parallelIndex}`);
});

Avoid Shared State Between Tests

TypeScript
// BAD — global variable shared between tests
let userId = '';

test.beforeAll(async ({ request }) => {
  const res = await request.post('/api/users', { data: { name: 'Test' } });
  userId = (await res.json()).id; // DANGEROUS in parallel
});

// GOOD — create test-specific data per test
test('each test creates its own user', async ({ request }) => {
  const res = await request.post('/api/users', {
    data: { name: `user-${Date.now()}-${Math.random()}` },
  });
  const { id } = await res.json();
  // Use id in THIS test only
});

Worker Count & Resource Impact

TypeScript
// playwright.config.ts
export default defineConfig({
  workers: 4,           // 4 browser instances running simultaneously
  fullyParallel: true,  // All tests across all files run in parallel
});

workers: 4 = 4 browser processes = 4x CPU/memory. Balance with available CI resources.

Follow AutomateQA

Related Topics