PLAYWRIGHT UIINTERMEDIATE

Playwright Hooks Explained: beforeAll, beforeEach, afterEach & afterAll with Real Examples

Master Playwright hooks — beforeAll, beforeEach, afterEach, and afterAll. Learn execution order, scoping, hook vs fixture, best practices, and real test setup patterns for scalable Playwright frameworks.

iff Solution Academy July 23, 2026 14 min read Updated July 23, 2026
Playwright Playwright Hooks beforeEach afterEach beforeAll afterAll Test Setup Test Teardown Playwright Fixtures

Introduction

Every serious Playwright test suite needs setup and teardown logic — logging in a user, seeding a database, resetting state, or capturing artifacts on failure. Playwright gives you four hooks to control this lifecycle: beforeAll, beforeEach, afterEach, and afterAll.

Used correctly, hooks make your tests faster, cleaner, and more reliable. Used incorrectly, they cause hidden coupling, flaky failures, and slow suites. This guide covers every hook with real examples, execution order, scoping rules, and when to reach for a fixture instead.

What Are Playwright Hooks?

Hooks are special functions that run automatically at defined points around your tests. They come from @playwright/test and let you inject setup or cleanup code without repeating it in every test.

  • beforeAll — runs once before all tests in a file or describe block.
  • beforeEach — runs before every individual test.
  • afterEach — runs after every individual test.
  • afterAll — runs once after all tests in a file or describe block.

The Four Hook Types

tests/hooks-overview.spec.ts
import { test, expect } from '@playwright/test';

test.beforeAll(async () => {
  console.log('Runs ONCE before all tests');
});

test.beforeEach(async ({ page }) => {
  console.log('Runs before EACH test');
  await page.goto('https://example.com');
});

test.afterEach(async ({ page }, testInfo) => {
  console.log(`Runs after EACH test: ${testInfo.title} — ${testInfo.status}`);
});

test.afterAll(async () => {
  console.log('Runs ONCE after all tests');
});

test('first test', async ({ page }) => {
  await expect(page).toHaveTitle(/Example/);
});

test('second test', async ({ page }) => {
  await expect(page.locator('h1')).toBeVisible();
});

Execution Order

Playwright guarantees a strict order. For a file with two tests, the sequence is:

  1. beforeAll
  2. beforeEach → test 1 → afterEach
  3. beforeEach → test 2 → afterEach
  4. afterAll
Each Playwright worker runs its own copy of beforeAll and afterAll. If you shard tests across 4 workers, beforeAll runs 4 times — once per worker — not globally.

beforeAll in Practice

Use beforeAll for expensive, read-only setup that every test in the file shares — seeding data, generating tokens, or warming up an API client.

tests/auth-token.spec.ts
import { test, expect, request, APIRequestContext } from '@playwright/test';

let apiContext: APIRequestContext;
let authToken: string;

test.beforeAll(async () => {
  apiContext = await request.newContext({ baseURL: 'https://api.example.com' });
  const res = await apiContext.post('/login', {
    data: { email: 'qa@example.com', password: 'secret' },
  });
  authToken = (await res.json()).token;
});

test('fetch user profile', async () => {
  const res = await apiContext.get('/me', {
    headers: { Authorization: `Bearer ${authToken}` },
  });
  expect(res.status()).toBe(200);
});

test.afterAll(async () => {
  await apiContext.dispose();
});

beforeEach in Practice

Use beforeEach for setup that must be fresh for each test — navigating to a starting URL, resetting UI state, or logging in through a persisted storage state.

tests/dashboard.spec.ts
import { test, expect } from '@playwright/test';

test.beforeEach(async ({ page }) => {
  await page.goto('/dashboard');
  await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
});

test('shows welcome message', async ({ page }) => {
  await expect(page.getByText('Welcome back')).toBeVisible();
});

test('shows sidebar links', async ({ page }) => {
  await expect(page.getByRole('link', { name: 'Reports' })).toBeVisible();
});

afterEach in Practice

afterEach is perfect for per-test cleanup — clearing cookies, deleting created records, or capturing artifacts when a test fails.

tests/screenshot-on-failure.spec.ts
import { test } from '@playwright/test';

test.afterEach(async ({ page }, testInfo) => {
  if (testInfo.status !== testInfo.expectedStatus) {
    const screenshotPath = testInfo.outputPath(`failure-${testInfo.title}.png`);
    await page.screenshot({ path: screenshotPath, fullPage: true });
    await testInfo.attach('failure-screenshot', {
      path: screenshotPath,
      contentType: 'image/png',
    });
  }
});

afterAll in Practice

Use afterAll to release resources created in beforeAll — disposing API contexts, closing DB connections, or deleting seed data.

tests/db-cleanup.spec.ts
import { test } from '@playwright/test';
import { seedUsers, deleteUsers } from '../utils/db';

let createdIds: string[] = [];

test.beforeAll(async () => {
  createdIds = await seedUsers(5);
});

test.afterAll(async () => {
  await deleteUsers(createdIds);
});

Scoping with describe Blocks

Hooks respect the block they're declared in. A hook inside a describe block only runs for tests within that block — top-level hooks apply to the whole file.

tests/scoped-hooks.spec.ts
import { test } from '@playwright/test';

test.beforeEach(async ({ page }) => {
  await page.goto('/');
});

test.describe('admin section', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/admin');
  });

  test('shows admin panel', async ({ page }) => {
    // Runs: top beforeEach -> describe beforeEach -> test
  });
});

test('home page loads', async ({ page }) => {
  // Runs: top beforeEach -> test (describe beforeEach is skipped)
});
When multiple beforeEach hooks apply, they run in declaration order: outer first, then inner. afterEach runs in reverse: inner first, then outer.

Hooks vs Fixtures

Hooks are convenient but global to a file — they share state through module-level variables and can't be reused across files. Fixtures solve the same problem in a composable, type-safe way.

tests/authenticated.spec.ts
// Hook approach - state in module scope
let token: string;
test.beforeAll(async () => { token = await login(); });

// Fixture approach - reusable across files
export const test = base.extend<{ token: string }>({
  token: async ({}, use) => {
    const t = await login();
    await use(t);
  },
});
  • Use hooks for per-file setup that won't be reused elsewhere.
  • Use fixtures when the same setup is needed across multiple spec files.
  • Fixtures give you dependency injection, automatic cleanup, and per-test isolation without shared module state.

Best Practices

  • Keep hooks small — they run for every test, so slow hooks slow the whole suite.
  • Never assert business logic inside a hook. Assertions belong in tests so failures point at the right place.
  • Prefer beforeEach over beforeAll for anything that mutates state — shared mutable state causes flaky, order-dependent tests.
  • Always pair beforeAll with afterAll for resources like API contexts, DB connections, or spawned processes.
  • Use testInfo.status inside afterEach to capture artifacts only on failure — avoids screenshot noise on green runs.
  • Move reusable setup into a fixture as soon as more than one spec file needs it.

Common Mistakes

  • Sharing mutable state in beforeAll and relying on test execution order — Playwright can parallelise tests and reorder them.
  • Doing heavy UI login in beforeEach for every test instead of using storageState — kills suite speed.
  • Forgetting that beforeAll runs per worker, not globally. Use a globalSetup file for truly one-time setup.
  • Throwing inside afterAll — errors there mark the whole file as failed even if every test passed.
  • Mixing hooks and fixtures for the same concern, which duplicates work and hides ownership.

Summary

Hooks are the backbone of Playwright test lifecycle management. beforeAll and afterAll handle expensive one-time setup and cleanup per worker. beforeEach and afterEach keep every test isolated and capture artifacts on failure.

As your framework grows, promote repeated hook logic into fixtures — you get reusability, type safety, and automatic cleanup for free. Combined with globalSetup for one-time initialization, hooks and fixtures let you scale from a handful of tests to enterprise-grade suites without losing reliability.

Rule of thumb: if two spec files copy the same beforeEach, it's time for a fixture. If setup must happen exactly once for the whole run, use globalSetup — not beforeAll.

Get Playwright tutorials in your inbox

Weekly tips, real-world examples, and framework patterns – no spam, unsubscribe anytime.

Playwright Framework Series

View all →
  1. 1Getting Started with Playwright: Installation, Setup, and Your First Test
  2. 2Playwright Locators: The Complete Guide with Real-World Examples
  3. 3Playwright Actions: Complete Guide to Click, Fill, Hover, Keyboard, Mouse & File Upload
  4. 4Playwright Assertions: The Complete Guide with Real-World Examples
  5. 5Playwright Auto Waiting: The Complete Guide with Real-World Examples
  6. 6Playwright Fixtures: The Complete Guide with Real-World Examples
  7. 7Playwright Browser Context & Multiple Tabs: The Complete Guide with Real-World Examples
  8. 8Playwright Authentication & Session Management: Complete Guide with Enterprise Examples
  9. 9Playwright Network Interception & API Mocking: Complete Guide with Real-World Examples
  10. 10Playwright Page Object Model (POM): The Complete Guide with Enterprise Examples
  11. 11Page Object Model with Playwright: A Practical Guide
  12. 12How to Build a Robust Professional Playwright Framework from Scratch (Step-by-Step)
  13. 13Building an Enterprise Playwright Framework from Scratch

API Testing Series

View all →
  1. 1Playwright API Testing: The Complete Guide with Real-World Examples
  2. 2Part 1 : Playwright API Testing Tutorial: Build Enterprise-Level API Automation Framework
  3. 3Part 2: Playwright API Testing Tutorial: Setting Up Project & Sending Your First API Request
  4. 4Part 3: Mastering CRUD Operations in Playwright API Testing
  5. 5Part 4: Authentication in Playwright API Testing (Bearer Token, JWT, API Key & Reusable Fixtures)
  6. 6Part 5: Building an Enterprise-Level Playwright API Automation Framework
  7. 7Part 6: API Models, Schema Validation & Test Data Management in Playwright
  8. 8Part 7: Custom Fixtures, Hooks, Logging & Parallel Execution in Playwright API Testing
  9. 9Part 8: Building a Production-Ready Playwright API Framework (Environment Management, CI/CD, Reporting & Best Practices)
  10. 10Part 9: Advanced Playwright API Testing Techniques for Enterprise Automation
  11. 11Part 10: Building a Complete Enterprise Playwright API Automation Framework (Final Part)
  12. 12API Testing with Playwright: Request Context, Auth, and Assertions

Recommended Next Articles