Introduction
Test hooks allow you to set up and tear down test environments. Playwright provides four types of hooks that run at different stages of the test lifecycle.
Hook Types
From src/common/test.ts:50 and src/common/testType.ts:52-55:
type Hook = {
type : 'beforeEach' | 'afterEach' | 'beforeAll' | 'afterAll' ;
fn : Function ;
title : string ;
location : Location ;
};
beforeEach
Runs before each test in the describe block:
import { test , expect } from '@playwright/test' ;
test . describe ( 'Todo tests' , () => {
test . beforeEach ( async ({ page }) => {
await page . goto ( 'https://demo.playwright.dev/todomvc' );
await page . locator ( '.new-todo' ). fill ( 'Buy milk' );
await page . locator ( '.new-todo' ). press ( 'Enter' );
});
test ( 'should show todo' , async ({ page }) => {
await expect ( page . locator ( '.todo-list li' )). toHaveCount ( 1 );
});
test ( 'should complete todo' , async ({ page }) => {
await page . locator ( '.toggle' ). check ();
await expect ( page . locator ( '.completed' )). toHaveCount ( 1 );
});
});
afterEach
Runs after each test in the describe block:
test . describe ( 'Database tests' , () => {
test . afterEach ( async ({ page }) => {
// Clean up after each test
await page . evaluate (() => localStorage . clear ());
});
test ( 'test 1' , async ({ page }) => {
// Test implementation
});
test ( 'test 2' , async ({ page }) => {
// Test implementation
});
});
beforeAll
Runs once before all tests in the describe block:
test . describe ( 'API tests' , () => {
let apiToken : string ;
test . beforeAll ( async ({ request }) => {
// Setup runs once
const response = await request . post ( '/api/login' , {
data: { username: 'admin' , password: 'admin' },
});
const data = await response . json ();
apiToken = data . token ;
});
test ( 'should list users' , async ({ request }) => {
const response = await request . get ( '/api/users' , {
headers: { 'Authorization' : `Bearer ${ apiToken } ` },
});
expect ( response . ok ()). toBeTruthy ();
});
test ( 'should create user' , async ({ request }) => {
const response = await request . post ( '/api/users' , {
headers: { 'Authorization' : `Bearer ${ apiToken } ` },
data: { name: 'John' },
});
expect ( response . ok ()). toBeTruthy ();
});
});
beforeAll and afterAll hooks have limited fixture support. They cannot use test-scoped fixtures like page and context.
afterAll
Runs once after all tests in the describe block:
test . describe ( 'Resource cleanup' , () => {
test . afterAll ( async ({ request }) => {
// Cleanup runs once after all tests
await request . delete ( '/api/test-data' );
});
test ( 'test 1' , async ({ page }) => {});
test ( 'test 2' , async ({ page }) => {});
});
Hook Execution Order
From src/common/testType.ts:174-184:
test . describe ( 'Outer suite' , () => {
test . beforeAll (() => console . log ( 'Outer beforeAll' ));
test . beforeEach (() => console . log ( 'Outer beforeEach' ));
test . afterEach (() => console . log ( 'Outer afterEach' ));
test . afterAll (() => console . log ( 'Outer afterAll' ));
test ( 'outer test' , () => console . log ( 'Outer test' ));
test . describe ( 'Inner suite' , () => {
test . beforeAll (() => console . log ( 'Inner beforeAll' ));
test . beforeEach (() => console . log ( 'Inner beforeEach' ));
test . afterEach (() => console . log ( 'Inner afterEach' ));
test . afterAll (() => console . log ( 'Inner afterAll' ));
test ( 'inner test' , () => console . log ( 'Inner test' ));
});
});
// Execution order:
// Outer beforeAll
// Outer beforeEach
// Outer test
// Outer afterEach
// Inner beforeAll
// Outer beforeEach
// Inner beforeEach
// Inner test
// Inner afterEach
// Outer afterEach
// Inner afterAll
// Outer afterAll
Hook Titles
From src/common/testType.ts:174-184:
You can provide descriptive titles for hooks:
test . beforeEach ( 'setup test data' , async ({ page }) => {
await page . goto ( '/setup' );
});
test . afterEach ( 'cleanup test data' , async ({ page }) => {
await page . evaluate (() => sessionStorage . clear ());
});
Without a title, the default is "${hookType} hook":
test . beforeEach ( async ({ page }) => {
// Title: "beforeEach hook"
});
Hooks with Fixtures
Hooks can use fixtures just like tests:
type MyFixtures = {
todoPage : TodoPage ;
};
const test = base . extend < MyFixtures >({
todoPage : async ({ page }, use ) => {
const todoPage = new TodoPage ( page );
await use ( todoPage );
},
});
test . describe ( 'Todo tests' , () => {
test . beforeEach ( async ({ todoPage }) => {
await todoPage . goto ();
await todoPage . addDefaultTodos ();
});
test ( 'should display todos' , async ({ todoPage }) => {
await expect ( todoPage . items ). toHaveCount ( 3 );
});
});
Fixture Limitations in beforeAll/afterAll
From src/index.ts:357-364:
beforeAll and afterAll cannot use test-scoped fixtures:
test . describe ( 'Invalid usage' , () => {
test . beforeAll ( async ({ page }) => {
// ERROR: Cannot use 'page' in beforeAll
});
});
Valid fixtures for beforeAll/afterAll:
Worker-scoped fixtures: browser, browserName, request
Custom worker-scoped fixtures
test . describe ( 'Valid usage' , () => {
test . beforeAll ( async ({ browser }) => {
// OK: browser is worker-scoped
const context = await browser . newContext ();
const page = await context . newPage ();
await page . goto ( '/setup' );
await context . close ();
});
});
Hook Error Handling
If a hook fails, subsequent hooks and tests are affected:
beforeEach Failure
test . beforeEach ( async ({ page }) => {
await page . goto ( '/' ); // If this fails...
});
test ( 'test 1' , async ({ page }) => {
// This test is marked as failed and skipped
});
test . afterEach ( async ({ page }) => {
// This still runs for cleanup
});
beforeAll Failure
test . beforeAll ( async ({ request }) => {
await request . post ( '/api/setup' ); // If this fails...
});
test ( 'test 1' , async ({ page }) => {
// All tests in this suite are skipped
});
test ( 'test 2' , async ({ page }) => {
// Skipped
});
test . afterAll ( async ({ request }) => {
// This still runs
});
Shared State Between Hooks
Use describe block scope to share state:
test . describe ( 'Shared state' , () => {
let userId : string ;
test . beforeAll ( async ({ request }) => {
const response = await request . post ( '/api/users' , {
data: { name: 'Test User' },
});
const data = await response . json ();
userId = data . id ;
});
test ( 'use shared data' , async ({ request }) => {
const response = await request . get ( `/api/users/ ${ userId } ` );
expect ( response . ok ()). toBeTruthy ();
});
test . afterAll ( async ({ request }) => {
await request . delete ( `/api/users/ ${ userId } ` );
});
});
Hook Scope and Nesting
Hooks are scoped to their describe block and nested blocks:
test . describe ( 'Parent' , () => {
test . beforeEach ( async ({ page }) => {
console . log ( 'Parent beforeEach' );
});
test ( 'parent test' , async ({ page }) => {});
// Runs: Parent beforeEach -> parent test
test . describe ( 'Child' , () => {
test . beforeEach ( async ({ page }) => {
console . log ( 'Child beforeEach' );
});
test ( 'child test' , async ({ page }) => {});
// Runs: Parent beforeEach -> Child beforeEach -> child test
});
});
Conditional Hooks
You can conditionally run hooks:
test . describe ( 'Conditional setup' , () => {
test . beforeEach ( async ({ page , browserName }) => {
if ( browserName === 'chromium' ) {
await page . addInitScript (() => {
// Chromium-specific setup
});
}
});
});
Hooks with TestInfo
Access test metadata in hooks:
test . afterEach ( async ({ page }, testInfo ) => {
if ( testInfo . status !== 'passed' ) {
// Take screenshot on failure
const screenshot = await page . screenshot ();
await testInfo . attach ( 'failure-screenshot' , {
body: screenshot ,
contentType: 'image/png' ,
});
}
});
Hooks vs Fixtures
Choose between hooks and fixtures based on your needs:
Use Hooks When:
Setup affects multiple tests
Need to run code once per suite (beforeAll/afterAll)
Want explicit setup/teardown in test file
Sharing state between tests
Use Fixtures When:
Setup is reusable across files
Need automatic cleanup
Want dependency injection
Creating page objects or utilities
Hook Example
test . describe ( 'Login flow' , () => {
test . beforeEach ( async ({ page }) => {
await page . goto ( '/login' );
await page . fill ( '[name=username]' , 'user' );
await page . fill ( '[name=password]' , 'pass' );
await page . click ( 'button[type=submit]' );
});
});
Fixture Example
const test = base . extend ({
authenticatedPage : async ({ page }, use ) => {
await page . goto ( '/login' );
await page . fill ( '[name=username]' , 'user' );
await page . fill ( '[name=password]' , 'pass' );
await page . click ( 'button[type=submit]' );
await use ( page );
},
});
test ( 'dashboard' , async ({ authenticatedPage }) => {
// Already logged in
});
Best Practices
Keep hooks focused : Each hook should have a single responsibility
Use beforeAll sparingly : Can lead to test interdependence
Always clean up : Use afterEach/afterAll for cleanup
Handle errors gracefully : Add try-catch for cleanup code
Consider fixtures : For reusable setup across files
Use descriptive titles : Make hooks easier to understand in reports
Avoid shared mutable state : Can cause flaky tests
Common Patterns
Authentication Setup
test . describe ( 'Authenticated tests' , () => {
test . beforeEach ( async ({ page }) => {
await page . goto ( '/login' );
await page . fill ( '[name=email]' , '[email protected] ' );
await page . fill ( '[name=password]' , 'password' );
await page . click ( 'button[type=submit]' );
await page . waitForURL ( '/dashboard' );
});
});
Database Seeding
test . describe ( 'User management' , () => {
test . beforeAll ( async ({ request }) => {
await request . post ( '/api/seed' , {
data: { users: 10 , posts: 50 },
});
});
test . afterAll ( async ({ request }) => {
await request . post ( '/api/reset' );
});
});
Browser Context Configuration
test . describe ( 'Geolocation tests' , () => {
test . beforeEach ( async ({ context }) => {
await context . setGeolocation ({
latitude: 37.7749 ,
longitude: - 122.4194 ,
});
await context . grantPermissions ([ 'geolocation' ]);
});
});
Test Isolation
test . describe ( 'Storage tests' , () => {
test . afterEach ( async ({ page }) => {
// Clear storage after each test
await page . evaluate (() => {
localStorage . clear ();
sessionStorage . clear ();
});
});
});
Next Steps
Test Fixtures Learn about the fixture system
Parallelization Run tests in parallel