Blog
Automation & Tools

Why getByRole() Is Better Than getByText() in Playwright

how this one change can make your tests more stable, readable, and professional

Denis De O
Denis De OOct 04, 2026
Why getByRole() Is Better Than getByText() in Playwright

When you start writing Playwright tests, one of the first things you learn is:

await page.getByText(’Login’).click();

It feels simple.

It works.

And in the beginning… it’s enough.

But then something happens.


The moment things start breaking

You run your tests again.

And suddenly, you see this:

strict mode violation: getByText(’Login’) resolved to 2 elements

Or worse…

Your test clicks the wrong thing.

Or fails randomly.

Or passes locally but fails in CI.

Nothing changed.

But everything broke.


The real problem isn’t Playwright

It’s how we are selecting elements.

getByText() feels natural because it mimics how we read pages:

“Find something that says Login and click it.”

But the browser doesn’t think like a human.

It sees structure, roles, and accessibility, not just visible text.

And that’s where getByRole() changes everything.


What getByRole() really does

Instead of searching for any element with text, getByRole() searches for elements based on what they are meant to do.

Example:

await page.getByRole(’button’, { name: ‘Login’ }).click();

You’re no longer saying:

“Find text ‘Login’ somewhere.”

You’re saying:

“Find the button that the user sees as ‘Login’.”

That’s a completely different level of precision.


Why this matters more than you think

Modern web apps are full of repeated text:

  • “Save”

  • “Submit”

  • “Edit”

  • “Charts”

And getByText() doesn’t know which one you mean.

It might match:

  • hidden elements

  • labels

  • tooltips

  • partial matches (like “Echarts” vs “Charts”)

That’s how flaky tests are born.


A real example

You write:

await page.getByText(’Charts’).click();

But your app also has:

  • “Charts”

  • “Echarts”

Now Playwright finds two elements.

And you get:

strict mode violation

The fix

await page.getByRole(’link’, { name: ‘Charts’, exact: true }).click();

Now you’re saying:

  • it must be a link

  • it must be named exactly “Charts”

No ambiguity.

No guessing.

No flakiness.


The hidden superpower: accessibility

getByRole() doesn’t just make tests better.

It aligns your tests with how real users interact with your app, including:

  • screen readers

  • keyboard navigation

  • accessibility tools

So when you use:

getByRole(’button’, { name: ‘Submit’ })

You’re testing the user experience, not just the DOM.

And that’s a big shift.


Cleaner tests, clearer intent

Compare these two:

await page.locator(’.btn.primary’).nth(2).click();

vs

await page.getByRole(’button’, { name: ‘Submit’ }).click();

The first one says:

“Click the third button with this CSS class.”

The second one says:

“Click the Submit button.”

One is implementation.

The other is intent.

And tests should always reflect intent.


The senior QA mindset

At some point, you stop asking:

“How do I click this element?”

And start asking:

“How would a user find this?”

That’s when your tests become:

  • more stable

  • easier to read

  • easier to maintain

  • easier to trust


When should you use getByText()?

It’s not useless.

You can still use it for:

  • simple static content

  • unique text (like headings)

  • quick experiments

But for interactive elements, prefer:

  • getByRole()

  • getByTestId()


A simple rule to remember

If the element is clickable, typeable, or interactive…

👉 Use getByRole()

If the element is just content…

👉 getByText() is fine


One small change, big impact

You don’t need a new framework.

You don’t need more tools.

Just change this:

getByText()

To this:

getByRole()

And over time, your tests will feel different.

More stable.

More intentional.

More professional.


A small exercise

Open one of your existing tests.

Find a line like this:

await page.getByText(’Login’).click();

Replace it with:

await page.getByRole(’button’, { name: ‘Login’ }).click();

Run your tests again.

Notice how it feels.


Final thought

Good automation isn’t about making tests pass.

It’s about building tests you can trust.

And trust comes from clarity.

Not just for the machine…

…but for the human reading the code.