Fixtures with AI
Let your agent extract repeated setup into a custom Playwright fixture.
POMs solved the locator duplication problem in the previous lesson, but they introduced a new one. Every test now has to initialize its POMs first.
test.describe("Snowboard purchase flow", () => {
test("searches for a snowboard, opens the first result, and adds it to the cart", async ({
page,
}) => {
const home = new HomePage(page);
const search = new SearchPage(page);
const product = new ProductPage(page);
const cart = new Cart(page);
// ...
});
});
If every test starts with these lines, that's a refactoring waiting to happen.
What is a fixture?
A fixture extends Playwright's test method with named values your specs can access. Every test that uses the page object relies on the concept of Playwright fixtures.
Playwright ships with a handful out of the box:
| Fixture | Type | Description |
|---|---|---|
page | Page | Isolated page for this test run. |
context | BrowserContext | Isolated context for this test run. The page fixture belongs to this context as well. |
browser | Browser | Browsers are shared across tests to optimize resources. |
browserName | string | The name of the browser currently running the test. Either chromium, firefox, or webkit. |
request | APIRequestContext | Isolated APIRequestContext instance for this test run. |
…and you can add your own.
Set up a base file to extend Playwright
To add your own values to the Playwright test runner, it's common to create a new base.ts file.
This file defines the custom fixtures and extends the Playwright test object with them. Here's how it works in code:
// 1. Rename Playwright's original `test` to base
import { test as base } from "@playwright/test";
import { HomePage } from "./poms/home-page";
import { SearchPage } from "./poms/search-page";
import { ProductPage } from "./poms/product-page";
import { Cart } from "./poms/cart";
type Fixtures = {
home: HomePage;
search: SearchPage;
product: ProductPage;
cart: Cart;
};
// 2. Call `extend` on the Playwright method
export const test = base.extend<Fixtures>({
// 3. Specify the values/objects you want to make accessible
home: async ({ page }, use) => {
// 4. Call `use` to hand objects to the tests that want to use `home`
use(new HomePage(page));
},
search: async ({ page }, use) => use(new SearchPage(page)),
product: async ({ page }, use) => use(new ProductPage(page)),
cart: async ({ page }, use) => use(new Cart(page)),
});
With this structure in place, every spec can load test from the base file and access already initialized page object models:
import { test } from "./base";test("buys a snowboard", async ({ home, search, product, cart }) => { // ...});
It's the same four POMs but without any init lines. The fixture sets them up before the test runs and cleans up afterwards if needed.
Setup, use, teardown
Each fixture body has three phases. Anything that runs before await use(...) is setup (like beforeEach), the value handed to use is what the test receives, and anything after use returns is teardown (like afterEach).
export const test = base.extend<Fixtures>({
loggedInPage: async ({ page }, use) => {
// setup
await page.goto("/login");
await page.getByLabel("Your name").fill("stefan");
await page.getByLabel("Your password").fill("12345678");
await page.getByRole("button", { name: "Sign in" }).click();
await use(page);
// teardown
await page.context().clearCookies();
},
});
The POM fixtures above are the simple case because use(new HomePage(page)) has nothing to tear down. But if you want to log in users, seed data, or write temporary files, this approach is beautiful.
Let the agent do the extraction
Reading the duplicated setup, moving it into tests/base.ts, and rewriting each spec to import from there is a lot of repetitive work. Ask your agent to read the duplicates and propose the refactoring.
Hand the fixture refactor to the agent
Extract a fixture from repeated setup
Find the specs in your workshop project that share setup (login, cart seed, navigation to a feature page) or the same Page Object Models. Draft a prompt that refactors your setup and sets up custom fixtures.
Show an example prompt
Find the page object model setup steps repeated across all tests/*.spec.ts. Extract initialization
into a Playwright fixture setup in tests/base.ts (extending Playwright's base test).
Refactor the spec.ts files to import from ./base instead. Run tests to see if things still work!