Introduction
Maintainable test code is the foundation of a successful automation project. As your test suite grows, you'll find yourself repeating the same setup and teardown logic—such as logging in, navigating to a dashboard, or clearing a database. This is where Test Hooks come in.
Playwright provides a robust set of hooks that allow you to execute code at specific points in the test lifecycle. Understanding the difference between beforeEach and beforeAll is not just a matter of syntax; it's a matter of test isolation and performance.
The Four Pillars of Playwright Hooks
Playwright offers four primary hooks:
1. beforeEach
Runs before every single test case in a file or describe block. Use case: Navigating to the homepage or logging in with fresh credentials for every test.
test.beforeEach(async ({ page }) => {
await page.goto('https://myapp.com/login');
await page.fill('#username', 'testuser');
await page.fill('#password', 'password123');
await page.click('#submit');
});
2. afterEach
Runs after every single test case. Use case: Cleaning up cookies, taking a final screenshot, or logging the final state of an element.
test.afterEach(async ({ page }) => {
console.log(`Finished test on URL: ${page.url()}`);
});
3. beforeAll
Runs once before all tests in the file or describe block start. Use case: Setting up a shared database connection or environment seeding.
test.beforeAll(async () => {
// Global setup logic
await initDatabase();
});
4. afterAll
Runs once after all tests in the file or describe block have finished. Use case: Closing open connections or generating custom reports.
test.afterAll(async () => {
await closeDatabase();
});
Scoping with test.describe
Hooks are scoped to the block they are defined in. If you use a test.describe block, any hooks inside it will only apply to the tests within that specific block.
test.describe('Admin Dashboard', () => {
test.beforeEach(async ({ page }) => {
// Only runs for 'Dashboard' tests
});
test('Check Dashboard Stats', async ({ page }) => { ... });
});
Test Isolation vs. Performance
This is the most critical decision an automation engineer makes when using hooks.
beforeEach(Isolation): Every test starts with a clean slate. If one test fails and leaves the app in a weird state, it won't affect the next test. This is the Gold Standard for reliable automation.beforeAll(Performance): Setting things up once is much faster. However, if Test A changes the user's password and Test B expects the old password, Test B will fail. This is called "test pollution."
Best Practice in 2026: Use beforeEach whenever possible to ensure your tests are atomic. Only use beforeAll for "heavy" operations that don't affect the state of the UI, like starting a backend server.
Handling Asynchronous Hooks
Playwright hooks are async by nature. You must use await inside them to ensure they complete before the test itself begins.
test.beforeEach(async ({ page }) => {
const loginTask = await page.goto('/login');
await loginTask; // Ensure page is loaded
});
Common Pitfalls to Avoid
- Overusing
beforeAll: As mentioned, this leads to flaky tests that are hard to debug. - Heavy Logic in Hooks: Don't put business logic inside hooks. Keep them strictly for setup and teardown.
- Implicit Dependencies: Avoid creating a hook that relies on the side effects of another hook unless they are in the same block.
Conclusion
Test hooks are the glue that holds a professional automation framework together. By mastering the lifecycle of a Playwright test, you can write cleaner, more maintainable, and ultimately more reliable test suites. Start small with a simple beforeEach and as your project scales, use scoping and global hooks to optimize your workflow.
Frequently Asked Questions
Yes, Playwright will execute them in the order they are defined.




