CTCaliberTek
Course: Playwright with TypeScript: End to End · Lesson 4 of 9Log in to track your progress and earn a certificate when you finish the course.
Software Testing

Playwright Fixtures, Hooks, and Data-Driven Tests

Published August 18, 2026·Updated October 29, 2026·9 min read
Like SaveLog in to like, save, and track progress.

Introduction

Fixtures are Playwright's way of giving each test the setup it needs, such as a logged-in page or a prepared page object, without repeating code. This lesson covers hooks, custom fixtures, and running a test over several data sets.

Prerequisites

  • The Page Object Model lesson
  • Async and await in TypeScript

Step-by-Step Guide

Step 1: Group tests and use hooks

test.describe groups related tests, and hooks such as beforeEach run before every test in the group. test.step labels sections so they show up in the report.

import { test, expect } from '@playwright/test';

test.describe('Todo list', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/todomvc');
  });

  test('adds an item', async ({ page }) => {
    await test.step('Add the item', async () => {
      await page.getByPlaceholder('What needs to be done?').fill('Buy milk');
      await page.keyboard.press('Enter');
    });
    await expect(page.getByTestId('todo-title')).toHaveText(['Buy milk']);
  });
});

Step 2: Create a custom fixture

test.extend adds your own fixture. The code before use() is setup, and anything after it is teardown. Fixtures are created only for the tests that ask for them.

// tests/fixtures.ts
import { test as base } from '@playwright/test';
import { TodoPage } from './pages/todo-page';

export const test = base.extend<{ todoPage: TodoPage }>({
  todoPage: async ({ page }, use) => {
    const todoPage = new TodoPage(page);
    await todoPage.goto();
    await use(todoPage);
  },
});

export { expect } from '@playwright/test';

Step 3: Use the fixture in tests

Import test and expect from your fixtures file instead of from @playwright/test. The page object arrives ready to use.

import { test, expect } from './fixtures';

test('adds a todo', async ({ todoPage }) => {
  await todoPage.addTodo('Write tests');
  await expect(todoPage.titles).toHaveText(['Write tests']);
});

Step 4: Run a test over multiple data sets

Generate tests from an array so each data row appears as its own test in the report. Give each one a unique title.

const items = ['Buy milk', 'Write tests', 'Deploy'];

for (const item of items) {
  test('adds item: ' + item, async ({ todoPage }) => {
    await todoPage.addTodo(item);
    await expect(todoPage.titles).toHaveText([item]);
  });
}

Step 5: Tag and filter tests

Put a tag such as @smoke in the title, then run only those tests with --grep. Use test.skip and test.fixme to disable tests intentionally and visibly.

test('adds a todo @smoke', async ({ todoPage }) => {
  await todoPage.addTodo('Smoke check');
  await expect(todoPage.titles).toHaveCount(1);
});

// npx playwright test --grep @smoke

FAQs

What is the difference between a fixture and a hook?
Hooks run setup and teardown for a group of tests in a fixed order. Fixtures are created on demand, can depend on each other, and are only built for tests that use them, which makes them more flexible and reusable.
Are tests isolated from each other?
Yes. Each test gets its own browser context by default, so cookies and storage do not leak between tests, which keeps them independent and safe to run in parallel.

Summary

Use hooks for simple shared steps, fixtures for reusable setup and teardown, and arrays to drive a test with several data sets. Tags let you run focused subsets such as a smoke suite.

Related tutorials

Comments

Log in or create a free account to join the discussion.

  • No comments yet. Be the first to comment.