Stop Putting Parameterized Locators in Constructors

If you’re using Playwright with Page Objects, you’ve probably seen something like this:

this.delayedTaskByArticleNo = (articleNo: string) =>
  this.page.locator('div.grid-cols', { hasText: articleNo });

It works.

But it’s a bad pattern.

This article explains what this pattern is, why it causes problems, and what to do instead.


What Is This Pattern Called?

That code is a parameterized locator factory defined in the constructor.

Key traits:

  • It’s dynamic (takes parameters)
  • It’s defined as an arrow function
  • It lives inside the constructor
  • It returns a Locator

It’s often confused with “dynamic locators”, but that’s not the real issue.

The problem is where it lives and how it’s structured.


Why This Is a Problem

1. Constructors Should Be Dumb

A constructor should:

  • Assign this.page
  • Define static locators

That’s it.

When you put logic, parameters, or behavior into the constructor, you turn setup code into business logic.

That makes the class harder to reason about.


2. It Hides Locator Intent

When everything is defined in the constructor:

  • You can’t quickly see what locators exist
  • IDE navigation is worse
  • Code reviews become slower

Reading a page object should feel like reading a map.

Constructor factories turn it into a maze.


3. It Blurs Responsibilities

A locator factory is behavior, not structure.

Putting it in the constructor mixes:

  • Object initialization
  • Query logic
  • Runtime decisions

This violates separation of concerns.


4. It Scales Poorly

As the page grows, constructors turn into long blocks of:

  • Arrow functions
  • Parameters
  • Inline logic

At that point, nobody wants to touch it.

That’s how tech debt starts.


The Better Pattern: Locator Accessor Methods

Instead of defining parameterized locators in the constructor, move them into explicit methods.

Bad

constructor(page: Page) {
  this.page = page;

  this.segmentByIndex = (index: number) =>
    this.page.locator('dl.mb-3').nth(index);
}

Good

getSegmentWithForbiddenTermAtIndex(index: number): Locator {
  return this.page.locator('dl.mb-3').nth(index);
}

Why This Is Better

1. Clear Separation

  • Constructor = setup
  • Methods = behavior

Simple mental model.


2. Better Readability

Methods:

  • Have names
  • Have return types
  • Can be documented
  • Are easy to search and review

You immediately understand intent.


3. Easier Refactoring

When locators are methods:

  • You can change implementation without touching construction
  • You can add logging, guards, or assertions later
  • You can deprecate patterns safely

4. Better Team Consistency

Once enforced, everyone writes page objects the same way.

Less debate.

Less “personal style”.

More predictable code.


Rule of Thumb

Use this simple rule:

If a locator needs parameters, it must be a method, not a constructor property.

Static locator?

  • Constructor is fine.

Dynamic locator?

  • Method only.

No exceptions.


Final Take

Parameterized locator factories in constructors are not “wrong”, but they are lazy shortcuts that hurt long-term maintainability.

Locator accessor methods:

  • Scale better
  • Read better
  • Age better

Your future self (and your teammates) will thank you.