How Do You Set Hooks Before Or After Each Test Globally in Playwright Test?

This was a recent question that was asked in the Playwright Discord channel.

I want to do some kind of "reset" in a specific table after every spec. I could simply achieve it by setting up an afterAll in each spec but this is equal repeating the same line of code a lot (imagine 100 spec files). So what my team did a while ago was adding hooks in our test.ts file. We really thought it was working like we imagined. "Oh, since we defined these hooks in the test file, it will act like a global hook". Little did we know that this is not true.  

I know pw has global setup and teardown, but AFAIK it will only run once, before everything and after everything. We really need a setup that runs after every spec file instead. Could be something after every describe too but I don't think we can override it. Is this already being discussed / a requested feature ? If not, should I do it or is there a way / hack I can achieve the desired behaviour? Thanks in advance! Really keen to read some suggestions.

The first answer in the thread was a link to Automatic Fixtures from Tally Barak.

Fixtures | Playwright
Playwright Test is based on the concept of test fixtures. Test fixtures are used to establish environment for each test, giving the test everything it needs and nothing else. Test fixtures are isolated between tests. With fixtures, you can group tests based on their meaning, instead of their common…
Automatic Fixtures

The docs say "Automatic fixtures are set up for each test/worker, even when the test does not list them directly. To create an automatic fixture, use the tuple syntax and pass { auto: true }."

This means that as long as the fixture is imported at the top of the file, whatever is inside the fixture will run automatically without having a reference to it within the test() block.

💡
The original request isn't solved with the solution below, as the question wanted something that ran after each describe block or after a test file xxxx.spec.ts file completes. As far as I am aware today, this isn't possible. The solution below will cover the use case running an afterEach after every test run.

I've only implemented a few fixtures so I wanted to take a stab at creating a solution for this problem. An example can be found below. This fixture extends the test class, and allows code to be added before await use() which will be executed before the test code, and code added after await use() will be executed after the test code.

// lib/fixtures/hook.ts

import { test as base } from "@playwright/test";

export const test = base.extend<{ testHook: void }>({
  testHook: [
    async ({}, use) => {
      console.log("BEFORE EACH HOOK FROM FIXTURE");
      // Any code here will be run as a before each hook

      await use();

      console.log("AFTER EACH HOOK FROM FIXTURE");
      // Put any code you want run automatically after each test here
    },
    { auto: true },
  ],
});

export { expect } from "@playwright/test"; //Exporting 'expect' from the base test so you have access in your spec.ts file.

The spec that uses the fixture is below. I am using console.log() here so we can see when each line of code gets run. Notice the one change I made in this spec file was where I am importing the test and expect from the fixture we created above ../../lib/fixtures/hook.

// tests/ui/hooks.spec.ts

import { test, expect } from "../../lib/fixtures/hook";

test.describe("Highest Level", () => {
  let x: number;
  x = 1;

  test.beforeAll(() => {
    console.log("--Hello before all tests--");
  });

  test.afterAll(() => {
    console.log("--Hello after all tests--");
  });

  test.afterEach(() => {
    console.log("<<Hello after each test");
  });

  test.beforeEach(() => {
    console.log(">>Hello before each test");
  });

  test.afterAll(() => {
    console.log("Hello after all tests");
  });

  test("One", async ({ page }) => {
    expect(x).toBe(1);
  });

  test("One v2", async ({ page }) => {
    expect(x).toBe(1);
  });

  test("Two", async ({ page }) => {
    expect(x).toBe(1);
  });

  test("Two v2", async ({ page }) => {
    expect(x).toBe(1);
  });
});

The output from the test results are shown below.

The pull request for these changes can be seen and found here.

adding fixture hook and upgrading playwright by BMayhew · Pull Request #29 · BMayhew/playwright-demo
A Repo to show off some playwright scripts. Contribute to BMayhew/playwright-demo development by creating an account on GitHub.

Wrapping up if you have a use case where you need execute code or capture information before or after each test utilizing a fixture like this can be really useful.

⚠️ One risk to consider, is if you are modifying test data via database seed scripts or reset scripts in a before or after hook and running more than 1 shard or more than 1 worker, it's possible that the data modification could affect other tests that are running.


Thanks for reading! If you found this helpful, reach out and let me know on LinkedIn or consider buying me a cup of coffee. If you want more content delivered to you in your inbox subscribe below, and be sure to leave a ❤️ to show some love.