|

Building a SOLID Playwright Framework: The Architecture Guide for Maintainable Test Suites

One class, one job. That is the principle that separates a Playwright framework that scales from one that becomes a maintenance nightmare. Here is how to apply SOLID design principles to test automation.

Contents

The Problem: Monolithic Page Objects

// BAD: One class doing everything
class DashboardPage {
  // 500 lines of methods covering every dashboard interaction
  // Navbar methods mixed with chart methods mixed with sidebar methods
  // Impossible to reuse, impossible to maintain
}

The Solution: Component-Based Architecture

// GOOD: Each component has one responsibility
class Navbar {
  constructor(private page: Page) {}
  async navigateTo(section: string) { /* ... */ }
  async search(query: string) { /* ... */ }
}

class DataChart {
  constructor(private page: Page) {}
  async selectDateRange(start: string, end: string) { /* ... */ }
  async exportToCsv() { /* ... */ }
}

class Sidebar {
  constructor(private page: Page) {}
  async selectFilter(filter: string) { /* ... */ }
  async collapse() { /* ... */ }
}

// Dashboard composes components
class DashboardPage {
  readonly navbar: Navbar;
  readonly chart: DataChart;
  readonly sidebar: Sidebar;

  constructor(private page: Page) {
    this.navbar = new Navbar(page);
    this.chart = new DataChart(page);
    this.sidebar = new Sidebar(page);
  }
}

Folder Structure

tests/
+-- pages/           # Full page classes (compose components)
|   +-- DashboardPage.ts
|   +-- LoginPage.ts
+-- components/      # Reusable UI components
|   +-- Navbar.ts
|   +-- DataTable.ts
|   +-- Modal.ts
+-- fixtures/        # Custom test fixtures
|   +-- auth.fixture.ts
|   +-- data.fixture.ts
+-- utils/           # Helpers (API client, data generators)
+-- data/            # Test data files
+-- specs/           # Test files
+-- playwright.config.ts

Custom Fixtures for DRY Setup

// fixtures/base.fixture.ts
import { test as base } from '@playwright/test';
import { DashboardPage } from '../pages/DashboardPage';
import { ApiClient } from '../utils/ApiClient';

export const test = base.extend<{
  dashboard: DashboardPage;
  api: ApiClient;
}>({
  dashboard: async ({ page }, use) => {
    const dashboard = new DashboardPage(page);
    await use(dashboard);
  },
  api: async ({ request }, use) => {
    const api = new ApiClient(request);
    await use(api);
  },
});

// Usage in tests
test('chart shows correct data', async ({ dashboard, api }) => {
  await api.seedData({ users: 100 });
  await dashboard.chart.selectDateRange('2026-01-01', '2026-03-31');
  // assertions...
});

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.