Advanced Playwright Design Patterns (Factory, Builder, Strategy)

As Playwright automation frameworks grow, applying design patterns helps improve maintainability, scalability, reusability, and test readability. These patterns are commonly discussed in interviews for Senior SDET, Automation Architect, and QA Lead roles.

Why Use Design Patterns?

Without design patterns, automation frameworks often suffer from:

  • Duplicate code
  • Tight coupling
  • Difficult maintenance
  • Large Page Objects
  • Hardcoded logic
  • Poor scalability

Using design patterns helps create frameworks that are easier to extend and maintain.


1. Factory Pattern

What is Factory Pattern?

The Factory Pattern centralizes object creation instead of allowing tests to instantiate classes directly.

Instead of:

const loginPage = new LoginPage(page);
const dashboardPage = new DashboardPage(page);
const cartPage = new CartPage(page);

Create objects through a factory.


Why Use Factory Pattern?

Benefits

  • Centralized object creation
  • Easier maintenance
  • Supports Dependency Injection
  • Simplifies test code
  • Reduces duplicate initialization

Architecture

               Test

                │

                ▼

          Page Factory

        ┌──────┼──────┐

        ▼      ▼      ▼

   LoginPage Dashboard CartPage

Example Implementation

LoginPage

import { Page } from '@playwright/test';

export class LoginPage {

    constructor(private readonly page: Page) {}

    async login(username: string, password: string) {
        // Login steps
    }

}

DashboardPage

import { Page } from '@playwright/test';

export class DashboardPage {

    constructor(private readonly page: Page) {}

    async verifyDashboard() {
        // Verification logic
    }

}

PageFactory

import { Page } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';
import { DashboardPage } from '../pages/DashboardPage';

export class PageFactory {

    constructor(private readonly page: Page) {}

    get loginPage() {
        return new LoginPage(this.page);
    }

    get dashboardPage() {
        return new DashboardPage(this.page);
    }

}

Usage

const factory = new PageFactory(page);

await factory.loginPage.login(username, password);

await factory.dashboardPage.verifyDashboard();

Advantages

✔ Cleaner tests

✔ Centralized object creation

✔ Easy maintenance

✔ Supports Dependency Injection

✔ Easily extendable


Disadvantages

  • Slightly more abstraction
  • May be unnecessary for very small projects

Factory Pattern Interview Questions

Q1. Why use the Factory Pattern?

Answer

To centralize object creation, reduce duplicate initialization, improve maintainability, and simplify test code.


Q2. When should you avoid the Factory Pattern?

Answer

For very small frameworks with only a few page objects, a factory may add unnecessary complexity.


2. Builder Pattern

What is Builder Pattern?

The Builder Pattern creates complex objects step by step.

Instead of creating long constructors:

const user = {

    username: "admin",

    password: "pass",

    role: "Manager",

    department: "QA",

    active: true

};

Use a builder.


Why Use Builder Pattern?

Benefits

  • Readable object creation
  • Optional fields
  • Fluent API
  • Easier maintenance
  • Less constructor complexity

Example Architecture

             User Builder

                    │

                    ▼

             Build User Object

                    │

                    ▼

              Test Uses Object

Example

User Model

export interface User {

    username: string;

    password: string;

    role: string;

    department: string;

}

Builder

export class UserBuilder {

    private user = {

        username: "",

        password: "",

        role: "",

        department: ""

    };

    withUsername(username: string) {

        this.user.username = username;

        return this;

    }

    withPassword(password: string) {

        this.user.password = password;

        return this;

    }

    withRole(role: string) {

        this.user.role = role;

        return this;

    }

    withDepartment(department: string) {

        this.user.department = department;

        return this;

    }

    build() {

        return this.user;

    }

}

Usage

const user = new UserBuilder()

    .withUsername("admin")

    .withPassword("admin123")

    .withRole("Manager")

    .withDepartment("QA")

    .build();

Benefits

  • Fluent syntax
  • Easy to read
  • Easy to extend
  • Avoids large constructors
  • Great for test data creation

Real Playwright Use Cases

Builder Pattern is useful for creating:

  • Test users
  • Customer objects
  • Orders
  • Payment requests
  • API payloads
  • Product data
  • Registration forms

Builder Pattern Interview Questions

Q1. Why use Builder instead of constructors?

Answer

Builders improve readability, support optional fields, and prevent constructors with many parameters that are difficult to understand and maintain.


Q2. Where is Builder commonly used in automation?

Answer

Creating complex test data, API payloads, registration forms, and domain objects used across tests.


3. Strategy Pattern

What is Strategy Pattern?

The Strategy Pattern allows you to switch algorithms or behaviors at runtime without changing the client code.

Instead of writing:

if(browser === "chrome") {

}

else if(browser === "firefox") {

}

else if(browser === "webkit") {

}

Move each behavior into its own strategy.


Why Use Strategy Pattern?

Benefits

  • Easy to add new behavior
  • No large if-else chains
  • Better maintainability
  • Open for extension
  • Easier unit testing

Architecture

             Test

              │

              ▼

      Browser Strategy

       ┌─────┼─────┐

       ▼     ▼     ▼

 Chrome Firefox WebKit

Example

Interface

export interface BrowserStrategy {

    launch(): Promise<void>;

}

Chrome Strategy

export class ChromiumStrategy implements BrowserStrategy {

    async launch() {

        console.log("Launch Chromium");

    }

}

Firefox Strategy

export class FirefoxStrategy implements BrowserStrategy {

    async launch() {

        console.log("Launch Firefox");

    }

}

Context

export class BrowserLauncher {

    constructor(private strategy: BrowserStrategy) {}

    async start() {

        await this.strategy.launch();

    }

}

Usage

const launcher = new BrowserLauncher(

    new ChromiumStrategy()

);

await launcher.start();

Playwright Use Cases

Strategy Pattern is useful for:

  • Browser selection
  • Authentication methods
  • Environment-specific behavior
  • Payment gateway flows
  • Report generation
  • File upload mechanisms
  • Different login types (UI, API, SSO)

Authentication Example

               Login

                 │

        ┌────────┼────────┐

        ▼        ▼        ▼

     UI Login  API Login  SSO Login

The test selects the appropriate authentication strategy without changing its own logic.


Report Generation Example

Report Strategy

      │

┌─────┼──────┐

▼     ▼      ▼

HTML Allure JUnit

Advantages of Strategy Pattern

✔ Removes large conditional statements

✔ Supports Open/Closed Principle

✔ Easy to extend

✔ Highly testable

✔ Easy maintenance


Factory vs Builder vs Strategy

FeatureFactoryBuilderStrategy
Main PurposeCreate objectsConstruct complex objectsChange behavior dynamically
FocusObject creationObject configurationAlgorithm/behavior selection
ReturnsReady-to-use objectsConfigured objectSelected implementation
Typical Playwright UsePage Objects, API clientsTest data, payloadsBrowser, authentication, reporting
Supports ExtensibilityYesYesExcellent

Combining Patterns in a Playwright Framework

Enterprise frameworks often combine multiple patterns:

                   Test
                     │
                     ▼
               Workflow Layer
                     │
                     ▼
               Page Factory
                     │
        ┌────────────┼────────────┐
        ▼            ▼            ▼
   LoginPage   ProductPage   CartPage
                     │
                     ▼
              Builder Objects
                     │
                     ▼
             Test Data Objects
                     │
                     ▼
             Strategy Selection
        ┌────────────┼────────────┐
        ▼            ▼            ▼
    Browser      Authentication   Reporting

Real-World Example

Imagine an e-commerce application:

  1. Factory Pattern
    • Creates LoginPage, CartPage, CheckoutPage, and OrderPage.
  2. Builder Pattern
    • Creates customer profiles, shipping addresses, payment requests, and order payloads.
  3. Strategy Pattern
    • Chooses:
      • Browser (Chromium, Firefox, WebKit)
      • Login method (UI, API, SSO)
      • Payment method (Credit Card, PayPal, UPI)
      • Report type (HTML, Allure)

Together, these patterns produce a framework that is easier to maintain, easier to extend, and capable of supporting large-scale automation projects with minimal changes to existing code.


Senior SDET Interview Questions

1. Which design patterns have you used in your Playwright framework?

Answer:
I commonly use the Page Object Model, Factory Pattern for page object creation, Builder Pattern for test data and API payloads, Strategy Pattern for browser and authentication selection, Facade/Workflow Pattern for business processes, and Dependency Injection through Playwright fixtures.


2. Which design pattern is most useful in automation frameworks?

Answer:
There isn’t a single best pattern. Page Object Model is foundational, Factory simplifies object creation, Builder improves test data management, Strategy removes conditional logic, and Workflow (Facade) keeps business flows reusable. The best choice depends on the problem being solved.


3. How do these patterns improve maintainability?

Answer:
They separate responsibilities, reduce code duplication, isolate changes, improve readability, and make it easier to extend the framework without modifying existing test code. This aligns with SOLID principles and results in a more scalable automation architecture.

Leave a Comment