Skip to content
SDETRole
SDETRole

Move setup into a custom fixture

advanced Fixtures Challenge 13 of 22

Two cart tests share a module-level cartPage variable that is set in beforeEach and cleaned up in afterEach. Refactor them to use a custom fixture named cartPage.

CartPage is provided in pages/cart-page.ts (shown below the requirements) with goto(), add(name), applyCoupon(code), clear() and the locators items and total. Your tests run against the demo cart, so the coupon SAVE10 really takes 10% off.

Requirements

  • Extend Playwright’s test with a cartPage fixture using test.extend.
  • In the fixture, create the CartPage, call goto(), pass it to the test with await use(...), and call clear() afterwards.
  • Remove beforeEach, afterEach and the module-level variable.
  • Each test receives the fixture as a parameter: async ({ cartPage }) => { ... }.

Expected output

`test` is extended with a `cartPage` fixture that creates and opens the page object, hands it to the test with `await use(cartPage)`, and clears the cart afterwards. Tests receive it by destructuring `{ cartPage }`, with no hooks or module-level variables.

Scenarios (2)

Your tests run once against each version of the demo app. They must pass on the working app and fail on every version with a planted bug.

  • ○Working app · must pass
  • ○Bug: coupon ignored · must catch

Provided file · pages/cart-page.ts

import { expect, type Locator, type Page } from '@playwright/test';

export class CartPage {
  readonly items: Locator;
  readonly total: Locator;

  constructor(private readonly page: Page) {
    this.items = page.getByTestId('cart-item');
    this.total = page.getByTestId('cart-total');
  }

  async goto() {
    await this.page.goto('/cart');
  }

  async add(product: string) {
    await this.page.getByLabel('Product').selectOption({ label: product });
    await this.page.getByRole('button', { name: 'Add', exact: true }).click();
    await expect(this.items.filter({ hasText: product })).toBeVisible();
  }

  async applyCoupon(code: string) {
    await this.page.getByLabel('Coupon code').fill(code);
    await this.page.getByRole('button', { name: 'Apply' }).click();
    await expect(this.page.getByText('Coupon applied')).toBeVisible();
  }

  async clear() {
    await this.page.getByRole('button', { name: 'Clear cart' }).click();
    await expect(this.items).toHaveCount(0);
  }
}

Checks (5)

  • ○Defines a fixture with test.extend
  • ○Hands the value to the test with await use(...)
  • ○Runs teardown after use()
  • ○No beforeEach/afterEach hooks
  • ○Tests receive cartPage as a fixture parameter
example.spec.tsRuns against a live demo app

Ctrl/⌘ + Enter to run tests · your code is saved in this browser

Results

Run the tests to see results here.

Demo app

The working app. You can click around it; your tests start from a fresh copy.

Type to search across SDETRole.

Full-text search by Pagefind · runs in your browser Open search page →