Skip to main content
/tayyab/portfolio — zsh
tayyab
TA
// dispatch.read --classified=false --access-level: public

Playwright POMs Are Causing Test Duplication: The Fix Microsoft Just Hinted At (GitHub Issue #37735)

April 14, 2026 EST. READ: 11 MIN #Quality Assurance

If you've used Playwright with Page Object Models for any meaningful amount of time, you know the duplication problem. You write a LoginPage with a login() method. Then you write 30 tests that call login(). Then someone uses an AI agent to generate 50 more tests, and instead of reusing your LoginPage, the agent inlines the login flow into every single test because it doesn't know your POM exists.

This is the issue raised in GitHub Issue #37735: "Playwright v1.56 agents to effectively use existing POMs to avoid masses of duplicate test code." The Microsoft team has been responding with fragments of where this is heading. It's worth paying attention to because it's a hint at how POM design will shift in the agent era.

Table of Contents

The Duplication Problem (With Real Numbers)

I ran an experiment on a fintech client project last month. They had a healthy POM library — 18 page objects, ~120 methods, well-documented. I ran the Playwright 1.59 generator agent against the same app to produce 30 new tests. The agent saw none of the existing POM library.

The result:

  • 30 tests generated
  • Each test inlined login (~12 lines)
  • Each test inlined navigation to the relevant module (~6 lines)
  • Each test redefined element selectors that already existed in POMs
  • Total redundant code across the 30 tests: roughly 1,200 lines

If those tests had used the existing POMs, total code would have been about 350 lines. Three-quarters of the agent's output was waste — and worse, when the login flow changes, the team now has to update 30 tests instead of one POM method.

This is the gap Issue #37735 is asking Microsoft to close.

Why AI Agents Make POMs Worse Before They Make Them Better

An LLM looking at a test-generation task with no other context will solve it linearly: "to test feature X, I need to log in, navigate, then exercise feature X." It writes all three steps inline because that's the simplest correct answer.

For a human writing the same test, the heuristic is different: "login is shared, navigation is shared, only feature X is unique to this test." The human factors out the shared parts because they know the cost of duplication compounds.

For the LLM to make the same choice, it needs to:

  1. Know the POM library exists
  2. Understand which methods exist and what they do
  3. Reach for them by default rather than reinventing

None of those happen by accident. The agent's training is on millions of independent test files, where inlining is the norm. To override that bias, you have to feed the agent your POMs explicitly.

What Microsoft Is Hinting At in the Thread

Reading between the lines of the issue thread, Microsoft is moving toward three things:

1. POM-aware agent prompts

The next agent revisions will likely include a discovery phase: "before writing any test code, the agent reads tests/pages/*.ts and tests/fixtures/*.ts and indexes available methods." The generator's prompt will instruct it to prefer existing methods over reinventing.

2. POM convention markers

For the agent to recognize a POM, it has to know what one looks like. Expect a convention to emerge — maybe a @pom JSDoc tag, or a class that extends PageObject, or a registered page-objects.config.ts. The exact mechanism isn't clear yet.

3. POM method documentation as agent input

Method names alone aren't enough. login() tells the agent what it does; loginAsAdmin() vs loginAsUser() is helpful. JSDoc comments describing intent will likely become first-class agent input.

None of this is shipped yet. But the thread is clear that this is where the team's thinking is heading.

How to Structure POMs Today to Be Agent-Friendly

You can prepare now without waiting for the official spec. Three patterns I'm using on current client projects:

Pattern 1: Predictable file structure

tests/
├── pages/              ← agents read this directory first
│   ├── LoginPage.ts
│   ├── DashboardPage.ts
│   └── CheckoutPage.ts
├── fixtures/           ← shared test fixtures
│   ├── auth.ts
│   └── data.ts
└── specs/              ← actual test files
    └── checkout.spec.ts

The directory naming matters. Convention over configuration. tests/pages is the standard convention for POMs in Playwright; agents trained on the ecosystem will look there first.

Pattern 2: Documented intent

export class CheckoutPage extends BasePage {
  /**
   * Apply a coupon code to the cart.
   * Returns the new total. Throws if the coupon is invalid.
   * @example await checkout.applyCoupon('FRIEND10')
   */
  async applyCoupon(code: string): Promise<number> {
    await this.couponInput.fill(code);
    await this.applyButton.click();
    await this.page.waitForResponse('**/api/coupons/validate');
    const totalText = await this.totalDisplay.textContent();
    return parseFloat(totalText!.replace(/[^0-9.]/g, ''));
  }
}

The JSDoc tells both humans and agents what the method does, what it returns, and how to use it. The example is huge — agents pattern-match against examples better than against descriptions.

Pattern 3: Avoid clever abstractions

If your POM has a method like doTheThing(context) that takes a magic context object and routes to one of seven internal methods based on a string, the agent will not figure that out. It will fall back to inlining.

Prefer many small, single-purpose methods with clear names:

// AGENT-FRIENDLY
async addItemToCart(productId: string)
async removeItemFromCart(productId: string)
async changeItemQuantity(productId: string, quantity: number)

// AGENT-HOSTILE
async mutateCart(action: 'add' | 'remove' | 'changeQty', productId: string, qty?: number)

The first form has obvious mappings: agent sees "increase quantity" in the test plan, finds changeItemQuantity, uses it. The second form requires reasoning about the action union type, which agents do but with errors.

POM Anti-Patterns the Planner Agent Will Multiply

Some POM patterns I see in inherited codebases will get worse, not better, with agents:

Anti-pattern 1: God POMs

One AppPage class with 80 methods covering every feature. Agents struggle to find the right method in a long flat list. Split by feature: LoginPage, SearchPage, CheckoutPage.

Anti-pattern 2: Implementation-leaking method names

// BAD
async clickElementById(id: string) { ... }
async getElementText(selector: string) { ... }

// GOOD
async clickAddToCart() { ... }
async getCartTotal(): Promise<string> { ... }

The bad version is just a wrapper for Playwright's locators. The good version expresses intent and is reusable.

Anti-pattern 3: Methods returning this for chaining

// CLEVER BUT AGENT-HOSTILE
await page.fillEmail('a@b.c').fillPassword('xxx').submit();

// SIMPLE AND AGENT-FRIENDLY
await loginPage.login('a@b.c', 'xxx');

Method chaining is hard for agents because they don't always understand fluent APIs in test code. Single-method calls map cleanly to test plans.

Making Your POMs Discoverable to Agents Today

Until Microsoft ships the official discovery mechanism, you can manually feed POMs to your agent:

npx playwright test --agent=generator \
  --plan=plan.md \
  --context=tests/pages/*.ts,tests/fixtures/*.ts \
  --output=tests/specs/

The --context flag (introduced in 1.59) tells the agent to read those files as input context. The agent's prompt now includes your POMs and is much more likely to use them.

Result on the same fintech experiment after adding --context: agent-generated tests dropped from 1,200 redundant lines to 380. Still not perfect, but a big improvement.

FAQs

Should I switch back to inlining if agents will inline anyway?

No. Agents won't always be the test author. Humans will still maintain tests, and humans benefit from POMs. Plus, agent quality on POM use is improving — design for the future, not the present.

What about screen-play pattern instead of POM?

The Screenplay pattern is more complex and less standard than POM. Agents will recognize POM faster because there's more training data. If your team isn't already on Screenplay, don't switch for agent compatibility reasons.

Should every page have a POM?

No — only pages with non-trivial interaction. A page with a single button doesn't need a class. POM should reduce duplication; for one-off pages, inline is fine.

How do I know which methods to put in a POM?

If you'll call it from more than one test, it belongs in a POM. If it's a one-off, leave it in the test.

What about utility functions vs POM methods?

POM methods are tied to a page; utilities are global. checkout.applyCoupon() is a method. formatCurrency() is a utility. Don't mix.

Will Microsoft ship a POM convention spec?

Probably. The thread suggests they're aware of the need but haven't committed to a timeline. Watch the issue.

Do React/Vue component testing libraries solve this?

Different category. Component testing is unit-level; POMs are integration/E2E level. Both are useful for different layers.

What about TypeScript types in POMs?

Important — they help both humans and agents. async login(email: string, password: string): Promise<void> is more agent-friendly than untyped JS.

Should I document edge cases in POM JSDoc?

Yes. "Throws if email is empty." "Returns null if no item is selected." These help agents reason about error cases.

How does this interact with the healer agent?

Healer fixes individual failing tests. If a test inlines login and login breaks, healer fixes the inlined login in 30 places independently — even more divergence over time. Using POMs centralizes the fix.

Wrap-Up

The future of POMs in the agent era isn't "replace POMs with agent magic." It's "build POMs that agents can read and use as effectively as humans do." The patterns are mostly the patterns good engineers already follow — small focused methods, clear names, documented intent — just with the addition that agents are now part of the audience.

Issue #37735 is worth following. Microsoft is publicly working through this problem and the design that emerges will shape how every Playwright codebase is structured for the next few years.

If your team is using POMs that will hold up in the agent era — or rebuilding ones that won't — that's part of what I cover in framework engagements. Or book a free call.

Related reading:

Tayyab Akmal
// author

Tayyab Akmal

AI & QA Automation Engineer

6 years of catching critical bugs in fintech, e-commerce, and SaaS — then building the Playwright and Selenium automation that prevents them from shipping again.

// feedback_channel

FOUND THIS USEFUL?

Share your thoughts or let's discuss automation testing strategies.

→ Start Conversation
Available for hire