25. Dependency Injection with Custom Playwright Fixtures

Why Fixtures Beat Traditional Hooks

In traditional frameworks, 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.

---

Creating Custom POM 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";

---

Using Custom Fixtures in Tests

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");
});

Task

  • Define the FixtureLifecycle interface:
  • 1. Generic parameter T.
  • 2. setup property returning Promise<T>.
  • 3. teardown property accepting resource of type T and returning Promise<void>.
πŸ’‘Common doubts
Did you like the problem?
Lesson 25 of 36
TypeScript
Loading code editor…
Next