Skip to content
On this page

Structuring a Scalable Playwright Framework

When teams start with Playwright, one of two things usually happens: either tests become a collection of messy scripts, or the team builds a “framework” so complex that even simple test changes feel difficult.

The practical middle ground is simpler.

Playwright already gives you most of what a test framework needs—test runner, fixtures, retries, parallel execution, reporting, screenshots, traces, and browser management. Because of that, the goal is not to build a framework around Playwright. The goal is to organize your automation code in a way that stays maintainable as the test suite grows.

A good framework should help engineers write tests faster—not force them to understand layers of abstraction first.

A clean and scalable structure can look like this:

playwright-framework/

├── tests/
├── pages/
├── components/
├── fixtures/
├── utils/
├── test-data/
├── auth/

├── playwright.config.ts
├── package.json
└── tsconfig.json

This structure is intentionally minimal. Every folder should have a clear purpose.

Folder Responsibilities

tests/

This is where actual test scenarios live.

Organize tests by business functionality rather than execution type. Good:

tests/
  auth/
  checkout/
  orders/

Less maintainable:

tests/
  smoke/
  regression/

Why? Because smoke and regression are execution categories, not product domains. Test tagging handles execution grouping much better.

pages/

This folder contains Page Objects.

The purpose of page objects is simple: keep UI interaction logic out of test files.

Example:

ts
await loginPage.login(user);
await checkoutPage.completePurchase();

A lean page object should contain:

locators meaningful UI actions page-specific assertions (when useful)

Avoid turning page objects into a mini framework with base classes, wrappers, and inheritance chains.

Bad example:

BasePage
ElementManager
UIActionHelper
AuthenticatedBasePage

That complexity rarely adds value.

components/

Some UI sections appear across multiple pages.

Examples:

  • header
  • navigation menu
  • sidebar
  • modal dialogs

Instead of duplicating the same logic in multiple page objects, extract reusable UI components here.

This folder is optional—add it only when duplication becomes real.

fixtures/

Playwright fixtures are one of the most useful features for building reusable test setup.

Good use cases:

  • authenticated user setup
  • shared API client
  • page object initialization
  • common test dependencies

Example:

ts
test('place order', async ({ checkoutPage, authUser }) => {
  await checkoutPage.completePurchase();
});

Fixtures reduce setup duplication and keep tests clean.

utils/

This folder should contain shared non-UI logic.

Examples:

  • environment configuration
  • API helpers
  • random data generators
  • date utilities
  • helper methods

Keep business logic out of utils. Utilities should stay generic.

test-data/

Store static test data here.

Example:

  • users.json
  • products.json
  • addresses.json

This keeps hardcoded values out of tests and improves maintainability.

auth/

Use this folder for authentication state.

Example:

storageState.json

This allows tests to skip repetitive UI login steps, making execution significantly faster.

The most practical combination is:

Lean Page Object Model + Fixtures + Reusable Components

This gives enough structure without unnecessary abstraction.

Use Page Objects when:

  • workflows repeat often
  • selectors need central maintenance
  • tests benefit from readability

Do not create abstraction just because enterprise frameworks do it.

If a direct Playwright call is already readable, wrapping it may not help.

Environment Management

Environment handling should be centralized from the start.

Example:

TEST_ENV=dev

Then manage values through config:

  • base URLs
  • credentials
  • API endpoints
  • feature flags

Example approach:

env.ts
dotenv

One important rule: never hardcode secrets in test code.

What to Avoid Early

Common overengineering mistakes:

  • custom wrapper frameworks
  • generic click/fill helper layers
  • deep inheritance structures
  • giant selector repositories
  • execution orchestration abstractions
  • unnecessary helper classes

If Playwright already solves the problem, avoid rebuilding it.

Growth Strategy

A scalable framework should evolve gradually.

Phase 1

Start simple:

  • tests
  • pages
  • utils

Phase 2

Add when needed:

  • fixtures
  • auth state
  • API helpers

Phase 3

Scale further only if justified:

  • reusable components
  • data factories
  • parallel-safe test data strategies

Build based on real needs, not imagined future complexity.

Final Takeaway

A good Playwright framework is not the one with the most architecture.

It is the one your team can understand, debug, and extend without friction.

If onboarding a new QA engineer takes several days just to understand the framework structure, the design is already too complex.