Test Hooks in Playwright

Pallavi Sharama

Pallavi Sharama

Mar 22, 2026Testing Tools
Test Hooks in Playwright

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

  1. Overusing beforeAll: As mentioned, this leads to flaky tests that are hard to debug.
  2. Heavy Logic in Hooks: Don't put business logic inside hooks. Keep them strictly for setup and teardown.
  3. 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.