# Fixtures

Course: [Playwright](https://www.devtutorials.ai/learn/playwright) · Section: Test Structure and Configuration
Source: https://www.devtutorials.ai/learn/playwright/test-structure/fixtures

> What fixtures are and why they beat hooks, the built-in fixtures (page, context, browser, browserName, request), the testInfo object, custom fixtures with test.extend and use(), fixtures that depend on others, overriding built-ins, test and worker scope, automatic fixtures, timeouts, option fixtures, and sharing fixtures with mergeTests and types.

## Fixtures

- You have used `{ page }` in every test. `page` is a **fixture**.
- A fixture is something a test needs, prepared by Playwright before the test and cleaned up after it.
- You can write your own fixtures, such as "a logged-in page" or "a cart with one item".
- In this lesson, you will see how fixtures work and build several of your own.

**Simple lesson:** a fixture is ready-made setup that a test asks for by name.

### A Real-World Example

- Many of Priya's tests need a logged-in user.
- Instead of copying the login steps into every test, she writes a `loggedInPage` fixture once.
- Every test that asks for `loggedInPage` gets a page that is already logged in.

- Open `tests/fixtures.ts` in the explorer to see the fixture. The test file imports `test` from it, not from `@playwright/test`.

### What You Will Learn

1. What fixtures are, and why they are better than hooks.
2. The built-in fixtures.
3. The `testInfo` object.
4. How to write custom fixtures with setup and teardown.
5. Test scope, worker scope, automatic fixtures and timeouts.
6. Option fixtures you can set from config or `test.use()`.
7. How to share fixtures between files.

## Fixture Fundamentals

### What Are Fixtures?

- A test asks for fixtures by name in its first argument: `async ({ page, browserName }) => { ... }`.
- Playwright creates only the fixtures the test asks for, in the right order, and cleans them up afterwards.

<figure className="plw-fig">
<svg viewBox="0 0 640 210" role="img" aria-labelledby="fig-plw-fixture" style={{ width: "100%", maxWidth: 640, height: "auto" }}>
  <title id="fig-plw-fixture">Animation: a fixture runs its setup, then use hands the value to the test, the test runs, and after the test the fixture runs its teardown</title>
  <rect x="20" y="40" width="250" height="140" rx="12" style={{ fill: "var(--plw-purple-soft)", stroke: "var(--plw-purple)" }} />
  <text x="145" y="64" textAnchor="middle" style={{ fill: "var(--plw-purple)", fontFamily: "var(--sans)", fontSize: 13, fontWeight: 700 }}>fixture: loggedInPage</text>
  <g style={{ fontFamily: "var(--mono)", fontSize: 12 }}>
    <text x="40" y="96" style={{ fill: "var(--ink)" }}>setup: log in</text>
    <text x="40" y="126" style={{ fill: "var(--plw-orange)", fontWeight: 700 }}>await use(page)</text>
    <text x="40" y="156" style={{ fill: "var(--ink)" }}>teardown: log out</text>
  </g>
  <rect x="380" y="70" width="240" height="80" rx="12" style={{ fill: "var(--plw-green-soft)", stroke: "var(--plw-green)" }} />
  <text x="500" y="100" textAnchor="middle" style={{ fill: "var(--plw-green)", fontFamily: "var(--sans)", fontSize: 13, fontWeight: 700 }}>test body</text>
  <text x="500" y="124" textAnchor="middle" style={{ fill: "var(--plw-green)", fontFamily: "var(--mono)", fontSize: 11 }}>uses loggedInPage</text>
  <rect x="30" width="230" height="24" rx="4" style={{ fill: "none", stroke: "var(--plw-orange)", strokeWidth: 2 }}>
    <animate attributeName="y" values="80;80;110;110;110;140;140" keyTimes="0;0.2;0.25;0.45;0.75;0.8;1" dur="8s" repeatCount="indefinite" calcMode="discrete" />
  </rect>
  <path d="M270 120 H376" style={{ stroke: "var(--plw-orange)", strokeWidth: 2.5 }} opacity="0"><animate attributeName="opacity" values="0;0;1;1;0;0" keyTimes="0;0.25;0.3;0.7;0.75;1" dur="8s" repeatCount="indefinite" /></path>
  <text x="323" y="112" textAnchor="middle" opacity="0" style={{ fill: "var(--plw-orange)", fontFamily: "var(--sans)", fontSize: 11 }}>page<animate attributeName="opacity" values="0;0;1;1;0;0" keyTimes="0;0.25;0.3;0.7;0.75;1" dur="8s" repeatCount="indefinite" /></text>
  <rect x="380" y="70" width="240" height="80" rx="12" style={{ fill: "none", stroke: "var(--plw-orange)", strokeWidth: 3 }} opacity="0"><animate attributeName="opacity" values="0;0;1;1;0;0" keyTimes="0;0.3;0.32;0.72;0.74;1" dur="8s" repeatCount="indefinite" /></rect>
  <text x="320" y="200" textAnchor="middle" style={{ fill: "var(--muted)", fontFamily: "var(--sans)", fontSize: 12 }}>Code before use() is setup. Code after use() is teardown. use() waits while the test runs.</text>
</svg>
<figcaption>Setup, hand over with use(), wait for the test, then tear down.</figcaption>
</figure>

### Fixtures vs Hooks

| | Hooks (`beforeEach`) | Fixtures |
|---|---|---|
| Where | In each test file | In one shared file |
| Runs for | Every test in the group | Only tests that ask for it |
| Setup and teardown | Two separate hooks | One function, around `use()` |
| Reuse | Copy and paste | Import and ask by name |

### Why Fixtures Are Preferred

- Setup and teardown live together, so you never forget the clean-up.
- A test lists what it needs right in its arguments. Reading the test tells you its setup.
- Playwright builds fixtures only when needed, so unused setup costs nothing.

**Simple lesson:** fixtures keep setup and teardown together, and run only for tests that need them.

## Built-in Fixtures

| Fixture | Gives you | Scope |
|---|---|---|
| `page` | A new page (tab) for this test | test |
| `context` | The browser context that page lives in | test |
| `browser` | The shared browser | worker |
| `browserName` | `'chromium'`, `'firefox'` or `'webkit'` | worker |
| `request` | An API client for HTTP requests | test |

- `context` is used in depth in **Browser Contexts, Pages, and Emulation**. `request` is used in **API Testing**.

**Simple lesson:** `page`, `context`, `browser`, `browserName` and `request` come built in.

## The testInfo Object

### test.info()

- `testInfo` describes the running test. Get it as the second argument of a test, or with `test.info()`.

### Test Title, Project and Retry Count

- `testInfo.title`, `testInfo.project.name` and `testInfo.retry` tell you which test, which project and which try this is.

### testInfo.outputPath()

- `testInfo.outputPath('cart.json')` gives a file path inside this test's own folder in `test-results/`.
- `testInfo.attach()` adds a file or text to the report.

- Open the **Steps** tab. The attachment is listed under the test.

**Simple lesson:** `testInfo` tells a test about itself, and lets it attach files to the report.

## Custom Fixtures

### test.extend()

- `base.extend({ ... })` makes a new `test` with extra fixtures. Each key is a fixture name.
- Export the new `test` and import it in your test files instead of the original.

### Setup and Teardown with use()

- A fixture is an `async` function with three arguments: the fixtures it needs, `use`, and `testInfo`.
- Everything before `await use(value)` is setup. Everything after it is teardown.

### Fixtures That Depend on Other Fixtures

- A fixture asks for other fixtures in its first argument, like a test does.
- Playwright builds them in the right order: dependencies first.

### Overriding Built-in Fixtures

- You can redefine `page`. Ask for the original `page` and wrap it.
- Below, every test's `page` starts on the products page, and a `cart` fixture depends on it.

**Simple lesson:** `test.extend()` adds fixtures. Setup goes before `use()`, teardown after it.

## Fixture Scope and Behavior

### Test-Scoped and Worker-Scoped Fixtures

- **Test scope** (the default): a fresh value for every test.
- **Worker scope**: one value shared by all tests in the same **worker** process. Use it for slow setup, such as creating a test account.
- Write the scope in an options object: `[async ({}, use) => { ... }, { scope: 'worker' }]`.

<figure className="plw-fig">
<svg viewBox="0 0 640 200" role="img" aria-labelledby="fig-plw-scope" style={{ width: "100%", maxWidth: 640, height: "auto" }}>
  <title id="fig-plw-scope">Animation: one worker runs three tests. A worker-scoped account is created once at the start and shared by all three tests. A test-scoped page is created fresh for each test and thrown away after it</title>
  <rect x="15" y="15" width="610" height="170" rx="14" style={{ fill: "var(--plw-blue-soft)", stroke: "var(--plw-blue)" }} />
  <text x="30" y="38" style={{ fill: "var(--plw-blue)", fontFamily: "var(--sans)", fontSize: 13, fontWeight: 700 }}>Worker 1</text>
  <rect x="30" y="50" width="580" height="30" rx="8" style={{ fill: "var(--plw-purple-soft)", stroke: "var(--plw-purple)" }} />
  <text x="320" y="70" textAnchor="middle" style={{ fill: "var(--plw-purple)", fontFamily: "var(--sans)", fontSize: 12, fontWeight: 700 }}>account (scope: 'worker'): created once, shared by every test</text>
  {[0, 1, 2].map((i) => (
    <g key={i}>
      <rect x={30 + i * 195} y="95" width="180" height="75" rx="8" style={{ fill: "#fff", stroke: "var(--line)" }} opacity="0.35">
        <animate attributeName="opacity" values="0.35;0.35;1;1;0.35;0.35" keyTimes={`0;${0.1 + i * 0.28};${0.13 + i * 0.28};${0.33 + i * 0.28};${0.36 + i * 0.28};1`} dur="9s" repeatCount="indefinite" />
      </rect>
      <text x={120 + i * 195} y="118" textAnchor="middle" style={{ fill: "var(--ink)", fontFamily: "var(--sans)", fontSize: 12, fontWeight: 700 }}>Test {i + 1}</text>
      <rect x={50 + i * 195} y="128" width="140" height="28" rx="6" style={{ fill: "var(--plw-green-soft)", stroke: "var(--plw-green)" }} opacity="0">
        <animate attributeName="opacity" values="0;0;1;1;0;0" keyTimes={`0;${0.1 + i * 0.28};${0.13 + i * 0.28};${0.33 + i * 0.28};${0.36 + i * 0.28};1`} dur="9s" repeatCount="indefinite" />
      </rect>
      <text x={120 + i * 195} y="147" textAnchor="middle" opacity="0" style={{ fill: "var(--plw-green)", fontFamily: "var(--sans)", fontSize: 11, fontWeight: 700 }}>new page (scope: 'test')
        <animate attributeName="opacity" values="0;0;1;1;0;0" keyTimes={`0;${0.1 + i * 0.28};${0.13 + i * 0.28};${0.33 + i * 0.28};${0.36 + i * 0.28};1`} dur="9s" repeatCount="indefinite" /></text>
    </g>
  ))}
</svg>
<figcaption>A worker fixture lives as long as the worker. A test fixture is made and thrown away for every test.</figcaption>
</figure>

### Automatic Fixtures

- `{ auto: true }` makes a fixture run for every test, even if no test asks for it.
- Good for things that must always happen, such as logging or checking for console errors.

### Fixture Timeouts

- A slow fixture can have its own `timeout`: `{ timeout: 60_000 }`.

- The Terminal shows "create the account once" a single time, for two tests.

A worker-scoped fixture is shared. If one test changes it, the next test sees the change. Keep shared values read-only.

**Simple lesson:** test scope gives each test its own value. Worker scope shares one. `auto` runs it always.

## Fixture Options

### Option Fixtures

- An **option** is a fixture with a default value that tests and config can change.
- Declare it with `{ option: true }`: `currency: ['INR', { option: true }]`.

### Setting Option Values

- Change it for a file or a group with `test.use({ currency: 'USD' })`.
- Change it for a whole project in the config, under `use`.
- Built-in settings such as `baseURL`, `viewport` and `locale` are option fixtures too.

**Simple lesson:** option fixtures have defaults you can change with `test.use()` or the config.

## Sharing Fixtures

### Putting Fixtures in a Shared File

- Keep fixtures in files such as `tests/fixtures.ts`. Export `test` and `expect` from it.
- Every test file imports from that file.

### mergeTests()

- Big projects split fixtures by topic: `auth-fixtures.ts`, `cart-fixtures.ts`.
- `mergeTests(authTest, cartTest)` combines them into one `test` with all their fixtures.

```ts
import { mergeTests } from '@playwright/test';
import { test as authTest } from './auth-fixtures';
import { test as cartTest } from './cart-fixtures';

export const test = mergeTests(authTest, cartTest);
```

### Typed Fixtures (TypeScript)

- `base.extend<MyFixtures>({ ... })` tells TypeScript each fixture's type.
- Then the editor completes `loggedInPage.` with page methods, and marks typos.
- The second type argument lists worker fixtures: `base.extend<TestFixtures, WorkerFixtures>()`.

The first argument of a test or fixture must be an object pattern such as `{ page }`. Playwright reads the names in it to know which fixtures to build. `async (fixtures) => ...` is an error.

Fixtures are where **Page Object Model** classes usually come from. A `productsPage` fixture can hand each test a ready-made page object.

**Simple lesson:** keep fixtures in a shared file, and combine several with `mergeTests()`.

## Try It in Your Project

- These steps take the lesson from the lab to your own app.
- Replace one `beforeEach` that creates data with a fixture that also cleans it up.
- Put your fixtures in `tests/fixtures.ts` and import `test` from there in every spec.
- Make one slow setup worker-scoped and measure the time saved.

**Simple lesson:** use one idea from this lesson on your own app this week.

## TL;DR

- A fixture is setup a test asks for by name. Playwright builds and cleans it up.
- Built-ins: `page`, `context`, `browser`, `browserName`, `request`.
- `testInfo` describes the running test and can attach files.
- `base.extend({ name: async (deps, use) => { setup; await use(value); teardown } })`.
- Scope is test (default) or worker. `auto` runs a fixture always. `option` makes it configurable.
- Share with a fixtures file and `mergeTests()`.

## Common mistakes

| Mistake | What happens | Fix |
|---|---|---|
| `async (fixtures) => ...` | "First argument must use the object destructuring pattern" | Write `async ({ page }) => ...` |
| Forgetting `await use(...)` | The test never gets the value | Call `await use(value)` exactly once |
| Importing `test` from `@playwright/test` in a spec that needs custom fixtures | "Test has unknown parameter" | Import `test` from your fixtures file |
| Teardown code before `use()` | It runs before the test, not after | Put clean-up after `await use(...)` |
| Changing a worker-scoped value in a test | Other tests see the change | Treat worker fixtures as read-only |

## Check Yourself

## Exercises

## What to Remember

- Fixtures are named setup that tests ask for.
- Setup before `use()`, teardown after it.
- Test scope by default. Worker scope for slow, shared, read-only setup.
- `auto` fixtures always run. `option` fixtures are set with `test.use()` or the config.
- Share fixtures through one file, and `mergeTests()` for several.

Next: **Playwright Configuration**
