beforeEach runs globally and creates implicit shared state. In Playwright:
1. On-Demand: A fixture only runs if a test actually declares it in its argument list.
2. Encapsulated Cleanup: The teardown logic lives right after the use() call.
3. Composable: Fixtures can depend on other fixtures.---
// fixtures/test-base.ts
import { test as baseTest } from "@playwright/test";
import { LoginPage } from "../pages/LoginPage";
import { DashboardPage } from "../pages/DashboardPage";// 1. Define the type of custom fixtures
type CustomFixtures = {
loginPage: LoginPage;
dashboardPage: DashboardPage;
};
// 2. Extend base test
export const test = baseTest.extend<CustomFixtures>({
loginPage: async ({ page }, use) => {
// Setup
const loginPage = new LoginPage(page);
// Provide fixture to test
await use(loginPage);
// Automatic teardown happens here when test finishes!
},
dashboardPage: async ({ page }, use) => {
const dashboardPage = new DashboardPage(page);
await use(dashboardPage);
},
});
export { expect } from "@playwright/test";
---
import { test, expect } from "../fixtures/test-base";test("successful login flow", async ({ loginPage, dashboardPage }) => {
await loginPage.navigateTo("/login");
await loginPage.login("admin@targetqa.com", "SecretPass123");
await dashboardPage.expectWelcomeMessage("Admin");
});