Intermediate Chapter 13 · 15 min read

Test Configuration & Fixtures

Master playwright.config options, build custom fixtures for dependency injection, and organise multi-project setups.

The Configuration File

All global settings live in playwright.config.ts (JavaScript) or are passed as command-line arguments / conftest.py fixtures (Python). The most important options are baseURL (allows relative goto('/login') calls), retries (automatic retry on failure in CI), timeout (action timeout), expect.timeout (assertion timeout), and use.trace (when to capture a trace file).

Tip: Set retries: 2 in CI and retries: 0 locally. Retries hide real flakiness during development; you want tests to fail fast on your machine so you fix the root cause immediately.

Custom Fixtures

Fixtures are Playwright's dependency injection system. A custom fixture is a function that sets up a resource before a test and tears it down afterwards — exactly like a Jest beforeEach/afterEach pair but composable and scoped. You can extend the built-in test object with your own fixtures and then import that extended test in every test file to get automatic access to page objects, authenticated contexts, or seeded database state.

Projects

The projects array in playwright.config.ts lets you run the same tests across multiple browsers, device emulations, or environment configurations in a single command. Each project inherits the top-level use options but can override any setting. This is how you achieve true cross-browser coverage without writing any browser-specific test code.

playwright.config.ts
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  timeout: 30_000,
  expect: { timeout: 10_000 },
  fullyParallel: true,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 4 : undefined,
  reporter: 'html',
  use: {
    baseURL: process.env.BASE_URL || 'http://localhost:3000',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
  },
  projects: [
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
    { name: 'firefox',  use: { ...devices['Desktop Firefox'] } },
    { name: 'mobile-chrome', use: { ...devices['Pixel 7'] } },
  ],
});
fixtures/index.ts
import { test as base } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';
import { DashboardPage } from '../pages/DashboardPage';

type Fixtures = {
  loginPage: LoginPage;
  dashboardPage: DashboardPage;
  authenticatedPage: void;
};

export const test = base.extend<Fixtures>({
  loginPage: async ({ page }, use) => {
    await use(new LoginPage(page));
  },
  authenticatedPage: async ({ page }, use) => {
    // Log in once before every test that requests this fixture
    const lp = new LoginPage(page);
    await lp.goto();
    await lp.login('admin@example.com', 'adminpass');
    await use();
  },
});

export { expect } from '@playwright/test';
BaseTest.java
import com.microsoft.playwright.*;
import org.junit.jupiter.api.*;

public abstract class BaseTest {
  static Playwright playwright;
  static Browser browser;
  BrowserContext context;
  Page page;

  @BeforeAll
  static void setup() {
    playwright = Playwright.create();
    browser = playwright.chromium().launch(
        new BrowserType.LaunchOptions().setHeadless(true)
    );
  }

  @BeforeEach
  void createContextAndPage() {
    context = browser.newContext(
        new Browser.NewContextOptions()
            .setBaseURL("http://localhost:3000")
            .setViewportSize(1280, 720)
    );
    page = context.newPage();
  }

  @AfterEach
  void closeContext() { context.close(); }

  @AfterAll
  static void tearDown() {
    browser.close(); playwright.close();
  }
}
conftest.py
import pytest
from playwright.sync_api import Page, BrowserContext
from pages.login_page import LoginPage

# Global browser options
def pytest_configure(config):
    config.addinivalue_line(
        "markers", "smoke: mark test as smoke test"
    )

@pytest.fixture(scope="session")
def browser_context_args(browser_context_args):
    return {
        **browser_context_args,
        "base_url": "http://localhost:3000",
        "viewport": {"width": 1280, "height": 720},
        "record_video_dir": "test-results/videos",
    }

@pytest.fixture
def authenticated_page(page: Page) -> Page:
    # Log in once and yield the ready page
    lp = LoginPage(page)
    lp.navigate()
    lp.login("admin@example.com", "adminpass")
    yield page

@pytest.fixture
def login_page(page: Page) -> LoginPage:
    return LoginPage(page)

Playwright Intermediate Test Configuration & Fixtures

Written by PV

© 2026 All Rights Reserved