You write five tests for the contacts page. Every one begins with the same two lines. Copying them is tedious, and when the flow changes you edit five places.
A hook is code that runs around your tests automatically.
import { test, expect } from '@playwright/test';
test.describe('contacts', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/contacts');
});
test('shows the contact list', async ({ page }) => {
await expect(page.getByTestId('contact-row')).not.toHaveCount(0);
});
test('can search', async ({ page }) => {
await page.getByTestId('search').fill('Dana');
});
});
beforeEach runs before each test in the block. Note that it receives { page } exactly like a test does.
There are four:
test.beforeEachruns before every test. You will use this almost always.test.afterEachruns after every test, for cleanup.test.beforeAllruns once before the first test in the file.test.afterAllruns once after the last one.
Now the part that matters, because choosing between them decides whether your tests stay independent.
beforeEach keeps tests independent. beforeAll can destroy that.
Suppose beforeAll creates one contact and three tests share it. Test 1 renames it. Test 2 now finds an unexpected name and fails. Worse, test 2 passes when run alone, so the failure looks random.
In Playwright this bites harder than in some tools, for a specific reason: tests run in parallel by default. Shared state does not fail occasionally, it fails immediately and confusingly.
There is a second Playwright-specific trap. beforeAll does not get the same page as your tests. Each test gets a fresh page in a fresh context. If you try to log in during beforeAll and expect the tests to be logged in, they will not be. Playwright has a proper answer for that (storageState, in the intermediate track), and beforeAll is not it.
So the rule: use beforeEach unless you have measured a reason not to. The setup runs more often, costing seconds. Order dependencies cost days.
One more thing. Do not put assertions in hooks. When a hook fails, the failure is reported against a test that never really ran, and the message points at the wrong place. Hooks arrange, tests assert.
Remember this: beforeEach by default, and remember every test gets its own fresh page.
Reference: Test hooks.