FRAMEWORK DESIGNINTERMEDIATE

Stop Repeating Playwright Locators: Build Custom Locator Functions Like an Enterprise SDET

Learn how to design reusable Playwright custom locator wrapper functions and helper classes that keep tests clean, consistent, and maintainable across large enterprise automation frameworks.

iff Solution Academy July 28, 2026 12 min read Updated July 28, 2026
Playwright Custom Locators Locator Helper Wrapper Functions Page Object Model Enterprise Framework TypeScript Best Practices

Introduction

As your Playwright automation framework grows, you'll quickly notice a common problem—locator code starts appearing everywhere. Consider these examples:

typescript
await page.getByRole('button', { name: 'Login' }).click();
await page.getByRole('button', { name: 'Save' }).click();
await page.getByRole('button', { name: 'Delete' }).click();
await page.getByRole('button', { name: 'Submit' }).click();

The problem isn't that these locators are wrong—they're actually good locators. The problem is that you're repeating the same locator-building logic hundreds of times throughout your framework.

Large enterprise automation frameworks solve this by creating custom locator wrapper functions. Instead of writing Playwright locators directly in every Page Object or test, they build reusable helper methods that make tests shorter, cleaner, easier to maintain, and more consistent.

In this tutorial, you'll learn how to design reusable locator wrapper functions, understand why enterprise teams use them, and build your own locator utility library.

Why Wrapper Functions?

Imagine your application has over 500 test cases, 120 Page Objects, and 3,000 locators. If your team decides to change its locator strategy—for example, moving from getByRole() to getByTestId()—you may need to update hundreds of files. With wrapper functions, you change the implementation in one place.

The Traditional Approach

Most beginners write code like this:

typescript
await page.getByRole('button', { name: 'Login' }).click();
await page.getByRole('button', { name: 'Register' }).click();
await page.getByRole('button', { name: 'Logout' }).click();

Nothing is technically wrong with it. However, imagine doing this thousands of times across a large framework.

Creating Your First Wrapper

Create a new file at utils/locatorHelper.ts:

utils/locatorHelper.ts
import { Page, Locator } from '@playwright/test';

export function button(page: Page, name: string): Locator {
  return page.getByRole('button', { name });
}

Now your test becomes:

typescript
await button(page, 'Login').click();
await button(page, 'Save').click();
await button(page, 'Delete').click();

Much cleaner.

Wrapping Textboxes

typescript
export function textbox(page: Page, label: string): Locator {
  return page.getByRole('textbox', { name: label });
}

Usage:

typescript
await textbox(page, 'Username').fill('john');
await textbox(page, 'Password').fill('secret');

Notice how your tests now read almost like English.

typescript
export function link(page: Page, name: string): Locator {
  return page.getByRole('link', { name });
}

Usage:

typescript
await link(page, 'Products').click();

Wrapping Checkboxes

typescript
export function checkbox(page: Page, name: string): Locator {
  return page.getByRole('checkbox', { name });
}

Usage:

typescript
await checkbox(page, 'Remember Me').check();

Wrapping Dropdowns

typescript
export function dropdown(page: Page, label: string): Locator {
  return page.getByLabel(label);
}

Usage:

typescript
await dropdown(page, 'Country').selectOption('USA');

Wrapping Test IDs

Many enterprise applications standardize on data-testid.

typescript
export function testId(page: Page, id: string): Locator {
  return page.getByTestId(id);
}

Usage:

typescript
await testId(page, 'login-button').click();

Creating a Base Locator Class

Instead of exporting dozens of standalone functions, many teams create a reusable class.

utils/LocatorHelper.ts
import { Locator, Page } from '@playwright/test';

export class LocatorHelper {
  constructor(private page: Page) {}

  button(name: string): Locator {
    return this.page.getByRole('button', { name });
  }

  textbox(name: string): Locator {
    return this.page.getByRole('textbox', { name });
  }

  link(name: string): Locator {
    return this.page.getByRole('link', { name });
  }

  checkbox(name: string): Locator {
    return this.page.getByRole('checkbox', { name });
  }
}

Usage:

typescript
const locator = new LocatorHelper(page);

await locator.button('Login').click();
await locator.textbox('Username').fill('john');

This pattern centralizes locator creation and keeps Page Objects concise.

Using Wrapper Functions in a Page Object

pages/LoginPage.ts
export class LoginPage {
  constructor(
    private page: Page,
    private locator = new LocatorHelper(page)
  ) {}

  async login(username: string, password: string) {
    await this.locator.textbox('Username').fill(username);
    await this.locator.textbox('Password').fill(password);
    await this.locator.button('Login').click();
  }
}

Now your Page Object focuses on business actions instead of locator details.

Adding Logging

Wrapper functions are also a great place to add logging.

typescript
button(name: string): Locator {
  console.log(`Finding button: ${name}`);
  return this.page.getByRole('button', { name });
}

Every locator lookup is now automatically logged without changing any test code.

Adding Automatic Waiting

You can extend wrapper functions to wait for visibility before returning or interacting with elements. For example:

typescript
async clickButton(name: string) {
  const button = this.page.getByRole('button', { name });
  await button.waitFor();
  await button.click();
}

This keeps synchronization logic consistent across the framework.

Supporting Multiple Locator Strategies

Suppose your team decides to switch to data-testid. Instead of updating hundreds of Page Objects, you only modify the helper:

typescript
button(name: string): Locator {
  return this.page.getByTestId(name);
}

Every test continues to work without changes.

Advantages

Using custom locator wrappers provides:

  • Cleaner code
  • Reusable locator logic
  • Centralized maintenance
  • Consistent naming conventions
  • Easier debugging
  • Better logging
  • Faster framework evolution
  • Simpler onboarding for new team members

These are some of the reasons enterprise automation frameworks remain maintainable as they grow.

Best Practices

  • Keep wrapper methods focused on a single responsibility.
  • Return Locator objects so callers can choose the action (click, fill, check, etc.).
  • Avoid hiding too much Playwright functionality—wrappers should simplify, not limit.
  • Use descriptive method names such as button(), textbox(), and link().
  • Document the preferred locator strategy so the whole team follows the same pattern.
  • Combine wrapper functions with the Page Object Model for maximum maintainability.

Common Mistakes

  • Creating wrappers that duplicate every Playwright API without adding value.
  • Embedding business logic inside locator helpers.
  • Returning ElementHandle instead of Locator.
  • Mixing multiple locator strategies inconsistently across the framework.
  • Making wrapper methods too generic to understand.

Conclusion

Custom locator wrapper functions are a simple idea with a significant impact. They reduce repetition, enforce consistency, and make it much easier to evolve your automation framework over time. Whether you're building a small test suite or an enterprise framework with thousands of tests, centralizing locator creation pays off in maintainability and readability.

The key is to keep your wrappers lightweight and let Playwright's powerful Locator API do the heavy lifting. When combined with the Page Object Model and good framework design, custom locator helpers become an important building block of a professional Playwright automation framework.

Get Playwright tutorials in your inbox

Weekly tips, real-world examples, and framework patterns – no spam, unsubscribe anytime.

Playwright Framework Series

View all →
  1. 1Getting Started with Playwright: Installation, Setup, and Your First Test
  2. 2Playwright Locators: The Complete Guide with Real-World Examples
  3. 3Playwright Actions: Complete Guide to Click, Fill, Hover, Keyboard, Mouse & File Upload
  4. 4Playwright Assertions: The Complete Guide with Real-World Examples
  5. 5Playwright Auto Waiting: The Complete Guide with Real-World Examples
  6. 6Playwright Fixtures: The Complete Guide with Real-World Examples
  7. 7Playwright Browser Context & Multiple Tabs: The Complete Guide with Real-World Examples
  8. 8Playwright Authentication & Session Management: Complete Guide with Enterprise Examples
  9. 9Playwright Network Interception & API Mocking: Complete Guide with Real-World Examples
  10. 10Playwright Page Object Model (POM): The Complete Guide with Enterprise Examples
  11. 11Page Object Model with Playwright: A Practical Guide
  12. 12How to Build a Robust Professional Playwright Framework from Scratch (Step-by-Step)
  13. 13Building an Enterprise Playwright Framework from Scratch

API Testing Series

View all →
  1. 1Playwright API Testing: The Complete Guide with Real-World Examples
  2. 2Part 1 : Playwright API Testing Tutorial: Build Enterprise-Level API Automation Framework
  3. 3Part 2: Playwright API Testing Tutorial: Setting Up Project & Sending Your First API Request
  4. 4Part 3: Mastering CRUD Operations in Playwright API Testing
  5. 5Part 4: Authentication in Playwright API Testing (Bearer Token, JWT, API Key & Reusable Fixtures)
  6. 6Part 5: Building an Enterprise-Level Playwright API Automation Framework
  7. 7Part 6: API Models, Schema Validation & Test Data Management in Playwright
  8. 8Part 7: Custom Fixtures, Hooks, Logging & Parallel Execution in Playwright API Testing
  9. 9Part 8: Building a Production-Ready Playwright API Framework (Environment Management, CI/CD, Reporting & Best Practices)
  10. 10Part 9: Advanced Playwright API Testing Techniques for Enterprise Automation
  11. 11Part 10: Building a Complete Enterprise Playwright API Automation Framework (Final Part)
  12. 12API Testing with Playwright: Request Context, Auth, and Assertions

Recommended Next Articles