Playwright Fixtures & Test Organization

As an end-to-end suite grows from ten tests to a thousand, structure matters as much as individual tests. Playwright Test organizes suites around fixtures: reusable, composable pieces of setup and teardown (a logged-in page, a seeded database, an API client, a page object) that tests request by name. Fixtures replace sprawling beforeEach hooks with explicit, typed dependencies, and Playwright only sets up what each test actually uses.

Combined with projects (browsers, devices, environments), isolation (a fresh browser context per test), and parallelism, fixtures let large suites stay readable, fast, and reliable.

TL;DR

Quick Example

Core Concepts

Built-In Fixtures

Custom Fixtures

test.extend defines fixtures as async functions receiving other fixtures and a use callback:

Key properties:

Test vs Worker Scope

Playwright runs tests in parallel worker processes. Worker-scoped fixtures ({ scope: 'worker' }) are created once per worker and shared by its tests, which suits expensive, read-only or isolatable resources: a service token, a per-worker database schema, a started mock server. Test-scoped fixtures give each test fresh state.

Page Object Models

A page object wraps a page's locators and actions behind a readable API (cartPage.checkout()), so tests describe intent, and selector changes happen in one place. Keep page objects thin (locators plus actions), keep assertions mostly in tests, and provide them via fixtures so tests don't construct them manually. Component objects for reusable widgets (date pickers, tables) follow the same idea. See design patterns.

Configuration and Projects

Projects run the same tests with different settings: browsers, devices, locales, or logged-in roles. Project dependencies run setup projects first, which is the recommended way to handle authentication. See Playwright authentication. webServer starts the app automatically.

Isolation and Parallelism

Every test gets a new browser context (like a fresh incognito profile), so cookies, localStorage, and sessions don't leak between tests. Tests in different files run in parallel across workers, and fullyParallel: true also parallelizes tests within a file. Use test.describe.configure({ mode: 'serial' }) only when tests genuinely depend on each other. Usually it's better to make them independent.

Hooks, Tags, and Annotations

beforeEach, afterEach, beforeAll, and afterAll still exist, but fixtures are usually cleaner. Tags ({ tag: '@smoke' }) with --grep @smoke run subsets. Annotations (test.skip, test.fixme, test.slow, test.fail) document known conditions.

Best Practices

Seed Data Through APIs, Not the UI

Create test data with API calls or database helpers in fixtures. It's much faster and more reliable than clicking through setup screens. Reserve UI interactions for the behavior under test.

Make Every Test Independent

Each test should create what it needs and not depend on order or leftover state. Independence enables parallelism, retries, and running a single test in isolation.

Keep Fixtures Small and Focused

One fixture per concern (an authenticated user, a seeded order, a page object) composes better than a giant "world" fixture. Name them after what they provide.

Use Unique Data per Test

Include unique suffixes (timestamps, worker index, random IDs) in created entities, so parallel tests don't collide on unique constraints or shared records.

Common Mistakes

Sharing State Between Tests Through Globals

Module-level variables set in one test and read in another break under parallel execution and retries. Pass state through fixtures.

Heavy beforeAll Setup With Test-Level Dependencies

Creating data in beforeAll and mutating it in tests causes order-dependent failures. Use per-test fixtures, or worker-scoped read-only data.

Page Objects Full of Assertions and Logic

Page objects that assert, branch, and retry become a second, untested codebase. Keep them to locators and simple actions, with expectations in tests.

FAQ

What is a fixture in Playwright?

A reusable unit of setup and teardown that tests declare as a parameter. Playwright creates it before the test (only if requested), passes the value in, and runs teardown afterwards. Built-in fixtures include page and request, and custom ones are defined with test.extend.

When should I use worker-scoped fixtures?

For expensive setup that can be shared safely by all tests in a worker process: authentication tokens, database schemas isolated per worker, mock servers, or compiled resources. Avoid them for mutable state that tests could interfere with.

Should I use the Page Object Model with Playwright?

It helps in larger suites, by centralizing locators and actions so tests read like user flows and UI changes are fixed in one place. Keep page objects lean, and inject them via fixtures. For small suites, direct locators in tests may be simpler.

How do I run tests across multiple browsers?

Define projects in playwright.config.ts using devices presets (Desktop Chrome, Firefox, Safari, mobile devices). Every test runs once per project, and --project=chromium runs a single one.

Related Topics

References