Playwright Locator Strict Mode Violation Fix: Complete Troubleshooting Guide

Introduction: What Does Playwright Strict Mode Violation Mean?

A Playwright strict mode violation occurs when a locator matches more than one element, but the operation requires a single element.

For example:

await page.getByRole(‘button’, {

 name: ‘Delete’

}).click();

If the page contains three Delete buttons, Playwright does not randomly choose one. It reports a strict-mode violation.

A typical error looks like:

strict mode violation:

locator(‘button’) resolved to 3 elements

This behavior is intentional. It prevents an automation test from silently interacting with the wrong element.

The best playwright locator strict mode violation fix is therefore usually to make the locator more specific, rather than immediately using .nth().


What Is Playwright Strict Mode?

Playwright locators are designed to identify elements precisely.

When an action such as:

await locator.click();

requires a single target, Playwright expects the locator to resolve to exactly one element.

For example:

const loginButton = page.getByRole(‘button’, {

 name: ‘Login’

});

await loginButton.click();

If only one Login button exists, the locator is unambiguous.

But this:

const buttons = page.getByRole(‘button’);

await buttons.click();

may fail if the page contains multiple buttons.

Strictness helps detect incorrect assumptions in test code rather than allowing an unpredictable element selection.


Why Does a Strict Mode Violation Occur?

Common causes include:

  • Duplicate buttons.
  • Repeated product cards.
  • Multiple links with the same text.
  • Duplicate form fields.
  • Nested elements matching the same locator.
  • Mobile and desktop versions rendered simultaneously.
  • Dynamic content.
  • Multiple authenticated user sections.
  • Generic CSS selectors.

For example:

<button>Delete</button>

<button>Delete</button>

<button>Delete</button>

This locator:

page.getByRole(‘button’, {

 name: ‘Delete’

});

matches three elements.

That creates the strict-mode problem.


Common Strict Mode Violation Error Messages

You may see:

strict mode violation: locator resolved to 2 elements

or:

strict mode violation: locator(‘button’)

resolved to 3 elements

You might also encounter errors such as:

strict mode violation: getByText(“Login”)

resolved to 2 elements

The important information is the number of matching elements.


Identifying Locators That Match Multiple Elements

Before changing the locator, inspect how many elements it matches.

const deleteButtons = page.getByRole(‘button’, {

 name: ‘Delete’

});

console.log(await deleteButtons.count());

If the output is:

3

you know exactly why the action fails.

You can also inspect individual elements:

console.log(

 await deleteButtons.allTextContents()

);

This is an excellent first step when troubleshooting a Playwright Locator Error.


Fixing Strict Mode Violations with Better Locators

Problem → Why It Happens → Incorrect Locator → Correct Locator → Best Practice

Duplicate Buttons

Problem:

await page.getByText(‘Delete’).click();

Why: Multiple elements contain the word Delete.

Incorrect locator:

page.getByText(‘Delete’);

Correct locator:

await page.getByRole(‘button’, {

 name: ‘Delete’

}).click();

If there are still multiple Delete buttons, scope the locator to the correct container.

Best practice: Make the locator describe which Delete button you want.


Using getByRole() for Unique Locators

Role-based locators are often a strong first choice.

Instead of:

await page.locator(‘.btn-primary’).click();

use:

await page.getByRole(‘button’, {

 name: ‘Checkout’

}).click();

For links:

await page.getByRole(‘link’, {

 name: ‘Products’

}).click();

For checkboxes:

await page.getByRole(‘checkbox’, {

 name: ‘Subscribe’

}).check();

A meaningful role and accessible name make tests easier to understand and maintain.


Using getByText(), getByLabel(), and getByTestId()

getByText()

Useful when visible text uniquely identifies an element:

await page.getByText(‘Order confirmed’).click();

However, if the same text appears multiple times, it can cause strict mode problems.

getByLabel()

Excellent for form fields:

await page.getByLabel(‘Email’)

 .fill(‘qa@example.com’);

If there are two Email fields, scope them to the appropriate form.

getByTestId()

A stable test ID can make an element unique:

await page.getByTestId(‘checkout-button’).click();

HTML:

<button data-testid=”checkout-button”>

 Checkout

</button>

Use test IDs when semantic locators aren’t sufficient.


Using .filter() to Fix Strict Mode Violations

Filtering is one of the most useful Playwright Locator Fix techniques.

Imagine an e-commerce page:

<div class=”product”>

 <h2>Laptop</h2>

 <button>Add to cart</button>

</div>

<div class=”product”>

 <h2>Phone</h2>

 <button>Add to cart</button>

</div>

This is ambiguous:

await page.getByRole(‘button’, {

 name: ‘Add to cart’

}).click();

There are two matching buttons.

Instead:

const laptop = page.locator(‘.product’).filter({

 hasText: ‘Laptop’

});

await laptop.getByRole(‘button’, {

 name: ‘Add to cart’

}).click();

Now Playwright first identifies the Laptop product and then finds its Add to cart button.

Best practice

Scope first, act second.

This is usually better than blindly selecting the first matching element.


Using .first(), .last(), and .nth()

Playwright provides positional methods.

.first()

await page.getByRole(‘button’, {

 name: ‘Delete’

}).first().click();

Use it when the first matching element is intentionally the correct one.

.last()

await page.getByRole(‘button’, {

 name: ‘Delete’

}).last().click();

Use it when the last element has a meaningful relationship to the test.

.nth()

await page.getByRole(‘button’, {

 name: ‘Delete’

}).nth(1).click();

nth() uses zero-based indexing, so nth(1) means the second matching element.

Important warning

Don’t use:

.nth(4)

simply to silence a strict-mode violation.

If the page order changes, the test can interact with the wrong element.

Better:

const product = page.getByTestId(‘product-card’)

 .filter({

   hasText: ‘Laptop’

 });

await product.getByRole(‘button’, {

 name: ‘Delete’

}).click();

Use .first(), .last(), or .nth() when the positional relationship is intentional and stable.


Handling Repeated Product Cards

A common real-world e-commerce scenario looks like this:

const products = page.getByTestId(‘product-card’);

console.log(await products.count());

Suppose there are 10 products.

This is ambiguous:

await products.getByRole(‘button’, {

 name: ‘Add to cart’

}).click();

Instead:

const laptop = products.filter({

 hasText: ‘Laptop’

});

await laptop.getByRole(‘button’, {

 name: ‘Add to cart’

}).click();

This is more maintainable because the test describes the business requirement.


Handling Duplicate Links and Buttons

Suppose the page has two links:

Products

Products

One belongs to the navigation bar and one belongs to the footer.

Instead of:

await page.getByRole(‘link’, {

 name: ‘Products’

}).click();

scope it:

const navigation = page.getByRole(‘navigation’);

await navigation.getByRole(‘link’, {

 name: ‘Products’

}).click();

This removes ambiguity without depending on DOM position.


Handling Multiple Input Fields

Suppose a checkout page contains:

Billing Email

Shipping Email

This may be ambiguous:

await page.getByLabel(‘Email’).fill(‘test@example.com’);

Use a specific label:

await page.getByLabel(‘Billing Email’)

 .fill(‘billing@example.com’);

await page.getByLabel(‘Shipping Email’)

 .fill(‘shipping@example.com’);

If labels cannot be made unique, scope the locator to the correct form.


Nested Elements and Dynamic Content

Dynamic applications frequently contain repeated components.

Instead of:

await page.locator(‘.button’).nth(3).click();

prefer:

const order = page.getByTestId(‘order’)

 .filter({

   hasText: ‘Order #1001’

 });

await order.getByRole(‘button’, {

 name: ‘View’

}).click();

This remains stable even if another order is inserted before Order #1001.


Responsive and Mobile Applications

Strict-mode problems can appear when both desktop and mobile navigation elements are present in the DOM.

For example:

await page.getByRole(‘link’, {

 name: ‘Menu’

}).click();

If multiple versions exist, scope to the visible navigation:

const mobileMenu = page.getByTestId(‘mobile-menu’);

await mobileMenu.getByRole(‘button’, {

 name: ‘Menu’

}).click();

Or design the application with unique accessible names/test IDs where practical.


Debugging Strict Mode with Playwright Inspector

Run:

npx playwright test –debug

Or pause execution:

import { test } from ‘@playwright/test’;

test(‘debug duplicate locator’, async ({ page }) => {

 await page.goto(‘/products’);

 await page.pause();

 const buttons = page.getByRole(‘button’, {

   name: ‘Add to cart’

 });

 console.log(await buttons.count());

});

The Inspector allows you to examine the current page and test locators interactively.


Debugging with Trace Viewer

Configure:

import { defineConfig } from ‘@playwright/test’;

export default defineConfig({

 use: {

   trace: ‘retain-on-failure’,

   screenshot: ‘only-on-failure’

 }

});

After a failure:

npx playwright show-report

Trace Viewer can help determine whether:

  • Two elements were rendered.
  • A modal appeared.
  • The wrong page loaded.
  • A responsive layout changed.
  • Authentication redirected the test.
  • Dynamic content created duplicate components.

Strict Mode Issues in CI/CD

A test may pass locally but fail in CI because the rendered application differs.

Common causes:

  • Different viewport.
  • Different browser.
  • Different test data.
  • Different authentication state.
  • Different application version.
  • Slower API responses.
  • Responsive layout differences.

Recommended troubleshooting

Run:

npx playwright test –workers=1

Then inspect:

HTML Report

Screenshot

Trace

Browser project

Viewport

Environment variables

Do not solve CI strict-mode failures by adding .first() without understanding why the second element appeared.


Common Mistakes and Solutions

MistakeBetter solution
Use .nth() immediatelyCreate a unique locator
Generic getByText()Scope to a container
Generic CSS selectorPrefer role/test ID
Ignore duplicate elementsCheck count()
Use force: trueFix locator/state
Depend on DOM orderUse business meaning
Ignore responsive duplicatesScope to correct layout
Use dynamic classesUse stable attributes
Ignore CI differencesInspect traces/screenshots

Playwright Locator Best Practices

Follow this hierarchy:

  1. Use getByRole() for accessible UI elements.
  2. Use getByLabel() for forms.
  3. Use getByTestId() for stable test contracts.
  4. Use getByText() when text is unique.
  5. Scope locators with parent containers.
  6. Use .filter() for repeated components.
  7. Use .first() or .last() only when positional meaning is intentional.
  8. Use .nth() only when the index is genuinely part of the requirement.
  9. Avoid fragile XPath and generated CSS selectors.
  10. Check count() when debugging duplicate matches.

The best playwright locator strict mode violation fix is normally a locator that uniquely identifies the intended element.


Playwright Strict Mode Interview Questions

What is Playwright strict mode?

Strict mode ensures that operations requiring one element don’t accidentally operate on multiple matching elements.

Why does Playwright strict mode violation occur?

Because a locator matches multiple elements when the action expects a single target.

How do you fix strict mode violation?

Make the locator unique using roles, labels, test IDs, filtering, or a specific parent container.

When should you use .first()?

When the first matching element is intentionally the correct target.

Is .nth() a good strict-mode fix?

It can solve the immediate ambiguity, but it is often fragile if the DOM order can change.

How do you debug duplicate locators?

Use:

console.log(await locator.count());

and inspect the page with Playwright Inspector or Trace Viewer.


FAQs: Playwright Locator Strict Mode Violation Fix

What is a Playwright strict mode violation?

It occurs when a locator matches multiple elements while an operation requires a single element.

How do I fix Playwright strict mode violation?

Use a more specific locator, scope it to the correct container, or filter the matching elements based on meaningful content.

Why does Playwright locator match multiple elements?

The locator may be too generic, or the page may intentionally contain repeated buttons, links, inputs, or components.

Should I always use .first()?

No. Use .first() only when the first matching element is intentionally the correct element.

What is better than .nth()?

A unique, semantic locator is generally better because it expresses why the element is the intended target.

How can I check how many elements a locator matches?

Use:

console.log(await locator.count());

Does strict mode make Playwright tests more reliable?

Yes. It prevents ambiguous locators from silently interacting with the wrong element.

Leave a Comment

Your email address will not be published. Required fields are marked *