Halmurat T.
Halmurat T.

Senior SDET

Home Blog Books ask About

The Dispatch

Weekly QA notes from the trenches.

Welcome aboard!

You're on the list. Expect real-world QA insights — no fluff, no spam.

© 2026 Halmurat T.

Automation 24
  • Selenium
  • Playwright
  • Appium
  • Cypress
AI Testing 5
CI/CD 6
  • GitHub Actions
  • Slack Reporting
QA Strategy 4
Case Studies 5
Blog/Automation
AutomationHalmurat T./December 10, 2024/5 min

From Selenium Wrappers to Playwright Locators

Filed underselenium/playwright/test-automation/locators
From Selenium Wrappers to Playwright Locators

Table of Contents
  • 🔧 The Wrapper Class Era
  • 🎭 Enter Playwright — Proof We Were Right
  • Side-by-Side Comparison
  • 🖱️ Click a Button
  • ✏️ Fill an Input
  • 📋 Select a Dropdown
  • ☑️ Check a Checkbox
  • ⚠️ When Wrappers Still Make Sense
  • ✨ What We Learned from the Migration

On this page

  • 🔧 The Wrapper Class Era
  • 🎭 Enter Playwright — Proof We Were Right
  • Side-by-Side Comparison
  • 🖱️ Click a Button
  • ✏️ Fill an Input
  • 📋 Select a Dropdown
  • ☑️ Check a Checkbox
  • ⚠️ When Wrappers Still Make Sense
  • ✨ What We Learned from the Migration

🔧 The Wrapper Class Era

With Selenium, we used to create wrapper classes for every UI element. Buttons, inputs, dropdowns, checkboxes — each got its own class with custom methods, error handling, and retry logic.

Button button = new Button("Submit");
button.click();
Input email = new Input("Email");
email.type("test@example.com");

Every element got its own class — and it was genuinely useful.

The idea was simple: instead of wasting time inspecting the DOM for IDs, XPaths, or CSS selectors, we could locate elements the same way a user does — by reading the text on the screen. Button("Submit") mirrors how a human interacts with the UI. No DevTools needed. It is the same principle behind using text to locate elements instead of chasing brittle implementation details.

To be clear — these wrappers solve a specific problem: how we locate elements. Instead of inspecting the DOM for IDs or XPaths, we find elements the way a user sees the page:

// Instead of this
driver.findElement(By.id("btn-submit-form")).click()
// We write this
Button("Submit").click()

It eliminates time spent in DevTools and lets anyone write tests by just looking at the UI. No hunting through HTML source for the right selector. You see “Submit” on the screen, you write Button("Submit"). Done.

These wrappers made tests readable, faster to write, and more stable — visible text changes far less often than generated CSS classes or dynamic IDs.

🎭 Enter Playwright — Proof We Were Right

When Playwright launched its locator API, something clicked: they built exactly what we had been building by hand.

page.getByRole("button", { name: "Submit" }).click();
page.getByLabel("Email").fill("test@example.com");

Look familiar? getByRole, getByLabel, getByText — these are the same patterns we implemented in our Selenium wrapper classes. The philosophy is identical: locate elements the way a user sees them, not the way the DOM structures them.

The fact that Playwright made this their entire locator strategy validates what we were doing with Selenium all along. We weren’t over-engineering — we were ahead of the curve. The framework just hadn’t caught up yet.

The difference now is that Playwright ships these patterns built-in, maintained by a dedicated team, with auto-waiting and retry logic baked in. What took us weeks to build and maintain comes free out of the box. If you’ve ever debugged XPath text matching failures caused by text() vs dot notation, the shift toward user-facing locators feels obvious in hindsight.

Side-by-Side Comparison

Here’s what the migration looks like in practice:

🖱️ Click a Button

// Selenium + wrapper
Button("Submit").click()
// Playwright
getByRole("button", { name: "Submit" }).click()

✏️ Fill an Input

// Selenium + wrapper
Input("Email").type("test@example.com")
// Playwright
getByLabel("Email").fill("test@example.com")

📋 Select a Dropdown

// Selenium + wrapper
Dropdown("Country").select("US")
// Playwright
getByLabel("Country").selectOption("US")

☑️ Check a Checkbox

// Selenium + wrapper
Checkbox("Agree").check()
// Playwright
getByRole("checkbox", { name: "Agree" }).check()

Notice the pattern? Every Playwright locator maps directly to what our Selenium wrappers already did. We were writing Button("Submit").click() years before Playwright gave us getByRole("button", { name: "Submit" }).click(). The raw Selenium API — driver.findElement(By.id("...")).click() — tells you nothing about what you’re clicking. Our wrappers added that missing semantics. Playwright just made it official.

⚠️ When Wrappers Still Make Sense

Wrapper classes aren’t dead. Complex components like a DataTable — with thead, tbody, rows, and columns — still deserve their own class. Playwright won’t magically parse a table for you.

class DataTable {
constructor(private page: Page, private selector: string) {}
async getRowCount(): Promise<number> {
return this.page.locator(`${this.selector} tbody tr`).count();
}
async getCellValue(row: number, col: number): Promise<string> {
return this.page
.locator(`${this.selector} tbody tr`)
.nth(row)
.locator("td")
.nth(col)
.innerText();
}
async getHeaderNames(): Promise<string[]> {
return this.page.locator(`${this.selector} thead th`).allInnerTexts();
}
}

The rule of thumb: if you’re wrapping a single native element (button, input, checkbox), Playwright already has you covered. If you’re wrapping a composite component with its own internal structure and query patterns, a wrapper class still earns its place.

✨ What We Learned from the Migration

When we moved to Playwright, we built our new framework from scratch. 90% of the wrapper classes we used to write? Gone — not because they were wrong, but because Playwright absorbed them into its core API. The remaining 10%? Still worth it for complex components.

The biggest takeaway: our Selenium wrapper approach was the right idea all along. We identified that tests should find elements the way users do — by text, by label, by role — and we built tooling to make that possible. Playwright’s team independently reached the same conclusion and made it the default.

The lesson isn’t “don’t build abstractions.” It’s that good abstractions sometimes become features in the next generation of tooling. If your wrapper classes are solving a problem that every team faces, there’s a good chance the framework will eventually ship it built-in.

We weren’t over-engineering. We were just early.

§ Further Reading 03 of 03
01Automation

The Browser Errors Your Test Suite Never Catches

Your UI tests pass green while the console throws errors. Learn to catch JavaScript and page errors in Selenium and Playwright Java — before users do.

Read →
02Automation

Your Test Suite Is Slow for 5 Reasons — Not Just One

Parallelism alone won't save your slow test suite. Here are five layers of optimization — from test design to cloud infrastructure — ranked by real impact.

Read →
03Automation

XPath text() vs Dot — Why Your Text Match Fails

The real difference between XPath text(), dot, contains(), and normalize-space() for test automation — with examples that explain real flaky failures.

Read →

Don't miss a thing

Subscribe to get updates straight to your inbox.

HT

No spam · Unsubscribe anytime

Welcome aboard!

You're on the list. Expect real-world QA insights — no fluff, no spam.

§ Colophon

Halmurat T. — Senior SDET writing about test automation, CI/CD, and QA strategy from 10+ years in the enterprise trenches.

Set in
IBM Plex Sans, Lora, and IBM Plex Mono.
Built with
Astro, MDX, Tailwind CSS & Expressive Code. Served by Vercel.
Privacy
No cookies. No tracking scripts on the main thread — analytics run sandboxed via Partytown.
Source
github.com/Halmurat-Uyghur
Terminal
Try /ask to query Halmurat's notes in a shell prompt.

© 2026 Halmurat T. · Written in plain text, shipped in plain time.

Search
Esc

Search is not available in dev mode.

Run npm run build then npm run preview:local to test search locally.