← Back

Playwright Locator Strategies

Complete guide to Playwright locators with real-time web application examples, best practices, XPath/CSS strategies, filtering, chaining, auto-waiting, and Selenium comparisons.

1 What Is a Locator?

A locator is the mechanism Playwright uses to identify an element on a web page before performing an action or assertion.

Web Page
→
Locator Strategy
→
Target Element
→
Action / Assertion

User-Facing

Locate elements through roles, labels, visible text, placeholders and other user-facing properties.

DOM-Based

CSS selectors, XPath and attributes can be used when structural or technical identification is appropriate.

Composable

Locators can be chained, filtered and narrowed to a particular component.

2 Complete Locator Strategy Map
Role
→
Label
→
Text
→
Placeholder
→
Test ID
CSS
→
XPath
→
Filter
→
Chain
Strategy Playwright API Typical Use
Role get_by_role() Buttons, links, headings, checkboxes, radio buttons, etc.
Label get_by_label() Form fields associated with labels.
Text get_by_text() Visible user-facing text.
Placeholder get_by_placeholder() Inputs with meaningful placeholders.
Alt Text get_by_alt_text() Images and elements identified through alt text.
Title get_by_title() Elements with title attributes.
Test ID get_by_test_id() Explicit automation identifiers.
CSS locator() Stable DOM selectors.
XPath locator() Complex structural relationships when appropriate.
3 get_by_role() — Most Important Locator

Role locators identify elements using their accessible role and accessible name.

Python Playwright
page.get_by_role(
    "button",
    name="Login"
).click()
get_by_role("button", name="Login") Finds a button named Login.
get_by_role("link", name="Orders") Finds a navigation link named Orders.
get_by_role("heading", name="Dashboard") Finds a heading named Dashboard.
get_by_role("checkbox", name="Remember me") Finds the Remember me checkbox.
get_by_role("radio", name="Credit Card") Finds the Credit Card radio option.
Real-world idea: If a customer says "Click the Login button", a role locator expresses that interaction directly.
4 get_by_label() — Form Fields

Label locators are particularly useful for forms.

HTML
<label for="email">
    Email Address
</label>

<input
    id="email"
    type="email"
/>
Playwright
page.get_by_label(
    "Email Address"
).fill(
    "john@example.com"
)
Why useful? The test doesn't need to know whether the implementation uses a particular ID or CSS class. It identifies the form field through its label.
5 get_by_text() — Visible Text

Use this locator when the visible text itself is the meaningful way to identify an element.

Example
page.get_by_text(
    "Order Confirmed"
).is_visible()

Exact Text

Exact matching
page.get_by_text(
    "Save",
    exact=True
).click()
Watch out: If several elements contain similar text, narrow the locator or use a more specific strategy.
6 get_by_placeholder()

Useful when the placeholder is meaningful and stable.

HTML
<input
    placeholder="Enter username"
/>
Playwright
page.get_by_placeholder(
    "Enter username"
).fill(
    "john"
)
Placeholder text can change with UI redesigns or localization. Prefer a stable label when one exists.
7 get_by_alt_text() and get_by_title()

Alt Text

<img
    src="logo.png"
    alt="Company Logo"
/>

page.get_by_alt_text(
    "Company Logo"
).click()

Title

<button
    title="Delete User"
>
    🗑
</button>

page.get_by_title(
    "Delete User"
).click()
8 get_by_test_id() — Explicit Automation Contract

A test ID is useful when user-facing semantics are not suitable or are not stable enough.

HTML
<button
    data-testid="submit-order"
>
    Submit
</button>
Playwright
page.get_by_test_id(
    "submit-order"
).click()
Best practice: Treat the test ID as an explicit contract between the application and automation suite. Avoid generating unstable IDs randomly.
9 locator() — CSS Selectors

Playwright supports CSS selectors through the generic locator() API.

CSS examples
# ID
page.locator("#username")

# Class
page.locator(".login-button")

# Attribute
page.locator(
    'input[name="email"]'
)

# Multiple classes
page.locator(
    ".btn.btn-primary"
)

# Descendant
page.locator(
    ".checkout-form button"
)
CSS is appropriate when the selector represents a stable DOM contract rather than a generated implementation detail.
10 XPath Locator

Playwright supports XPath through locator().

XPath examples
page.locator(
    '//button[normalize-space()="Login"]'
).click()


page.locator(
    '//input[@name="email"]'
).fill(
    "john@example.com"
)


page.locator(
    '//div[@class="product-card"]'
)
Important: XPath is not removed from Playwright. However, don't automatically use complex XPath when a simpler role, label, text or test-ID locator provides a stable solution.
11 Locator Chaining

Playwright lets you locate a component first and then locate an element inside it.

HTML
<div id="login-form">

    <input
        placeholder="Username"
    >

    <input
        placeholder="Password"
    >

    <button>
        Login
    </button>

</div>
Playwright
login_form = page.locator(
    "#login-form"
)

login_form.get_by_placeholder(
    "Username"
).fill("john")

login_form.get_by_placeholder(
    "Password"
).fill("Password123")

login_form.get_by_role(
    "button",
    name="Login"
).click()
12 filter() — Find the Correct Component

Filtering is especially useful when several components contain identical buttons.

Real-world product cards
<div class="product-card">
    <h3>Laptop Pro</h3>
    <button>Add to Cart</button>
</div>

<div class="product-card">
    <h3>Phone Pro</h3>
    <button>Add to Cart</button>
</div>
Playwright
product = page.locator(
    ".product-card"
).filter(
    has_text="Laptop Pro"
)

product.get_by_role(
    "button",
    name="Add to Cart"
).click()
All Product Cards
→
Filter Laptop Pro
→
Find Add to Cart
13 filter() with has

You can filter one locator based on another locator.

Component filtering
product = page.locator(
    ".product-card"
).filter(
    has=page.get_by_role(
        "heading",
        name="Laptop Pro"
    )
)

product.get_by_role(
    "button",
    name="Add to Cart"
).click()
Useful pattern: Find the correct component first, then perform the action within that component.
14 nth(), first() and last()

When multiple matching elements exist, Playwright provides methods for selecting a particular match.

Examples
# First matching element
page.get_by_role(
    "button",
    name="Edit"
).first.click()

# Last matching element
page.get_by_role(
    "button",
    name="Edit"
).last.click()

# Specific zero-based index
page.get_by_role(
    "button",
    name="Edit"
).nth(2).click()
Avoid relying on indexes when a unique semantic locator can be created. DOM order changes can make index-based tests fragile.
15 Real-Time Login Example

A typical enterprise login page might contain Email, Password, Remember Me and Sign In.

Python Playwright
from playwright.sync_api import expect


def test_login(page):

    page.goto(
        "https://example.test/login"
    )

    page.get_by_label(
        "Email"
    ).fill(
        "john@example.com"
    )

    page.get_by_label(
        "Password"
    ).fill(
        "Password123"
    )

    page.get_by_role(
        "checkbox",
        name="Remember me"
    ).check()

    page.get_by_role(
        "button",
        name="Sign In"
    ).click()

    expect(page).to_have_url(
        r".*/dashboard"
    )
16 Real-Time E-Commerce Example
Products
→
Add to Cart
→
Cart
→
Checkout
→
Order Confirmed
Complete Playwright flow
def test_customer_order(page):

    page.goto(
        "https://shop.example.test"
    )

    expect(
        page.get_by_role(
            "heading",
            name="Products"
        )
    ).to_be_visible()

    product = page.locator(
        ".product-card"
    ).filter(
        has_text="Laptop Pro"
    )

    product.get_by_role(
        "button",
        name="Add to Cart"
    ).click()

    page.get_by_role(
        "link",
        name="Cart"
    ).click()

    page.get_by_role(
        "button",
        name="Checkout"
    ).click()

    page.get_by_label(
        "Email"
    ).fill(
        "john@example.com"
    )

    page.get_by_role(
        "button",
        name="Place Order"
    ).click()

    expect(
        page.get_by_role(
            "heading",
            name="Order Confirmed"
        )
    ).to_be_visible()
17 Dynamic Text

Dynamic values such as order IDs can be handled with regular expressions.

Example message
Order ORD-1001 has been successfully created.
Playwright
import re

expect(
    page.get_by_text(
        re.compile(
            r"Order ORD-\d+ has been successfully created"
        )
    )
).to_be_visible()
18 CSS vs Playwright User-Facing Locator

Potentially Fragile

<button
    class="css-92871"
>
    Place Order
</button>

page.locator(
    ".css-92871"
).click()

Generated classes may change when the application is rebuilt.

User-Facing

page.get_by_role(
    "button",
    name="Place Order"
).click()

The locator describes the intended user interaction.

19 Web-First Assertions

Locators become especially powerful when combined with Playwright's web-first assertions.

Immediate Check

assert page.get_by_text(
    "Success"
).is_visible()

Web-First Assertion

expect(
    page.get_by_text(
        "Success"
    )
).to_be_visible()
Key concept: Web-first assertions wait for the expected UI state instead of relying on an immediate boolean check.
20 Locator Auto-Waiting

Playwright actions perform relevant actionability checks before interacting with elements.

Playwright
page.get_by_role(
    "button",
    name="Submit"
).click()

You normally should not solve synchronization problems by inserting arbitrary delays.

Avoid

import time

page.get_by_role(
    "button",
    name="Submit"
).click()

time.sleep(5)

Prefer

page.get_by_role(
    "button",
    name="Submit"
).click()

expect(
    page.get_by_text("Success")
).to_be_visible()
21 Selenium — ID, CSS and XPath

Selenium can use several locator mechanisms to identify elements.

Selenium Python
from selenium.webdriver.common.by import By

# ID
driver.find_element(
    By.ID,
    "username"
)

# CSS
driver.find_element(
    By.CSS_SELECTOR,
    ".login-button"
)

# XPath
driver.find_element(
    By.XPATH,
    "//button[normalize-space()='Login']"
)

# Link text
driver.find_element(
    By.LINK_TEXT,
    "My Orders"
)
Selenium and Playwright can both locate and interact with the same web elements. The APIs and locator model are different.
22 Playwright vs Selenium — Locator Comparison
Requirement Playwright Selenium
By ID page.locator("#username") driver.find_element(By.ID, "username")
By CSS page.locator(".login-button") driver.find_element(By.CSS_SELECTOR, ".login-button")
By XPath page.locator("//button[text()='Login']") driver.find_element(By.XPATH, "//button[text()='Login']")
By visible text page.get_by_text("Login") Commonly XPath or link-text APIs.
By role page.get_by_role("button", name="Login") Usually represented using CSS/XPath or other locator mechanisms.
By label page.get_by_label("Email") Usually handled using CSS/XPath and HTML relationships.
By placeholder page.get_by_placeholder("Username") Usually CSS/XPath.
Test ID page.get_by_test_id("login") CSS/XPath can target the data attribute.
Filtering locator.filter() Usually requires additional selector or lookup logic.
23 Same Login Test — Playwright vs Selenium

Playwright

Playwright Python
page.goto(
    "https://example.test/login"
)

page.get_by_label(
    "Email"
).fill(
    "john@example.com"
)

page.get_by_label(
    "Password"
).fill(
    "Password123"
)

page.get_by_role(
    "button",
    name="Sign In"
).click()

Selenium

Selenium Python
driver.get(
    "https://example.test/login"
)

driver.find_element(
    By.ID,
    "email"
).send_keys(
    "john@example.com"
)

driver.find_element(
    By.ID,
    "password"
).send_keys(
    "Password123"
)

driver.find_element(
    By.XPATH,
    "//button[normalize-space()='Sign In']"
).click()
Important distinction: Selenium can perform the same business workflow. Playwright provides dedicated locator APIs such as role, label, text, placeholder and test-ID locators as part of its locator model.
24 Selenium Explicit Wait vs Playwright Locator Model

Selenium

Explicit wait
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

button = WebDriverWait(
    driver,
    10
).until(
    EC.element_to_be_clickable(
        (By.ID, "login")
    )
)

button.click()

Playwright

Locator action
page.get_by_role(
    "button",
    name="Login"
).click()
Both frameworks provide synchronization mechanisms. Playwright's locator actions incorporate actionability checks, while Selenium commonly uses implicit/explicit wait mechanisms.
25 Checkboxes, Radio Buttons and Comboboxes
Checkbox
page.get_by_role(
    "checkbox",
    name="Accept Terms"
).check()
Radio
page.get_by_role(
    "radio",
    name="Credit Card"
).check()
Select
page.get_by_role(
    "combobox",
    name="Country"
).select_option("IN")
26 Assertions on Locator State
Button state
submit = page.get_by_role(
    "button",
    name="Submit Order"
)

expect(
    submit
).to_be_enabled()

submit.click()
Checkbox state
remember = page.get_by_role(
    "checkbox",
    name="Remember me"
)

remember.check()

expect(
    remember
).to_be_checked()
27 Real-Time Banking Example

Consider a transfer workflow in an online banking application.

Transfer Money
→
Enter Amount
→
Review
→
Confirm
Playwright
def test_transfer(page):

    page.goto(
        "https://bank.example.test/transfer"
    )

    expect(
        page.get_by_role(
            "heading",
            name="Transfer Money"
        )
    ).to_be_visible()

    page.get_by_label(
        "Amount"
    ).fill(
        "500"
    )

    page.get_by_role(
        "button",
        name="Review Transfer"
    ).click()

    expect(
        page.get_by_text(
            "Review your transfer"
        )
    ).to_be_visible()
28 Real-Time Admin Example

Suppose an admin application displays several customer records.

HTML concept
<div class="customer-card">

    <h2>John Smith</h2>

    <p>Premium Customer</p>

    <button>
        View Profile
    </button>

</div>
Playwright
customer = page.locator(
    ".customer-card"
).filter(
    has_text="John Smith"
)

customer.get_by_role(
    "button",
    name="View Profile"
).click()
29 Locator Strategy — What Should I Choose?
Question Recommended Locator Example
Is it a button? Role get_by_role("button", name="Save")
Is it a link? Role get_by_role("link", name="Orders")
Is it a labeled input? Label get_by_label("Email")
Is visible text the identity? Text get_by_text("Order Confirmed")
Is placeholder stable? Placeholder get_by_placeholder("Search")
Do you have a stable automation ID? Test ID get_by_test_id("checkout")
Is there a stable DOM selector? CSS locator(".product-card")
Do you need a complex structural relationship? XPath locator("//div[@data-id='123']")
30 Recommended Locator Priority
Role
→
Label
→
Text
→
Test ID
→
Stable CSS
→
XPath
This is guidance, not a rigid rule.

A stable test ID can be preferable to a frequently changing accessible name. Likewise, a stable CSS selector can be appropriate for a technical element that has no meaningful user-facing identity.
31 Common Locator Mistakes
32 Best Practices
33 Interview Question — Is XPath Bad in Playwright?
Answer: No. Playwright supports XPath.

The important point is to choose the locator based on stability, readability and the application's UI contract.

Long Structural XPath

//div[2]/div[1]/form/div[3]
/button[1]

Highly dependent on DOM structure.

User-Facing Locator

page.get_by_role(
    "button",
    name="Submit"
).click()

Expresses the intended interaction directly.

34 Interview Question — Can Selenium Do the Same Thing?

Yes. Selenium can automate the same business workflows and can locate elements using ID, CSS, XPath, link text and other mechanisms.

The difference is primarily in the APIs, locator model, synchronization model and overall automation architecture.

Concept Playwright Selenium
Semantic role API get_by_role() No equivalent locator API with the same abstraction.
Text API get_by_text() Commonly XPath or link-text mechanisms.
Label API get_by_label() Usually CSS/XPath and HTML relationships.
Test ID API get_by_test_id() CSS/XPath can target the data attribute.
Locator chaining Built into locator model. Can be achieved through WebElement lookups.
Synchronization Actions/assertions integrate with Playwright's waiting model. Explicit and implicit waits are commonly used.
35 Complete Professional Example

This example combines multiple locator strategies in a realistic customer order journey.

Playwright Python
import re

from playwright.sync_api import expect


def test_customer_completes_order(page):

    # Open application
    page.goto(
        "https://shop.example.test"
    )

    # Heading
    expect(
        page.get_by_role(
            "heading",
            name="Products"
        )
    ).to_be_visible()

    # Find product component
    product = page.locator(
        ".product-card"
    ).filter(
        has_text="Laptop Pro"
    )

    # Add product
    product.get_by_role(
        "button",
        name="Add to Cart"
    ).click()

    # Open cart
    page.get_by_role(
        "link",
        name="Cart"
    ).click()

    # Checkout
    page.get_by_role(
        "button",
        name="Checkout"
    ).click()

    # Customer information
    page.get_by_label(
        "Email"
    ).fill(
        "john@example.com"
    )

    page.get_by_label(
        "Shipping Address"
    ).fill(
        "100 Main Street"
    )

    # Payment
    page.get_by_role(
        "radio",
        name="Credit Card"
    ).check()

    # Terms
    page.get_by_role(
        "checkbox",
        name="Accept Terms"
    ).check()

    # Place order
    page.get_by_role(
        "button",
        name="Place Order"
    ).click()

    # Verify result
    expect(
        page.get_by_role(
            "heading",
            name="Order Confirmed"
        )
    ).to_be_visible()

    expect(
        page.get_by_text(
            re.compile(
                r"Order .* has been confirmed"
            )
        )
    ).to_be_visible()
Role
+
Label
+
Filter
+
Text
+
Assertion
36 Quick Locator Cheat Sheet
Element / Requirement Playwright
Button page.get_by_role("button", name="Login")
Link page.get_by_role("link", name="Orders")
Heading page.get_by_role("heading", name="Dashboard")
Input by label page.get_by_label("Email")
Visible text page.get_by_text("Order Confirmed")
Placeholder page.get_by_placeholder("Search")
Alt text page.get_by_alt_text("Company Logo")
Title page.get_by_title("Delete User")
Test ID page.get_by_test_id("submit-order")
CSS page.locator(".product-card")
XPath page.locator("//button[text()='Login']")
Filter page.locator(".card").filter(has_text="Laptop")
First match locator.first
Last match locator.last
Specific match locator.nth(2)
37 Interview Questions

Q1. What is a locator in Playwright?

A locator identifies an element and provides the mechanism for Playwright to perform actions and assertions against it.

Q2. What is get_by_role()?

It identifies an element through its accessible role and optionally its accessible name.

Q3. Does Playwright support XPath?

Yes. XPath can be used through locator().

Q4. What is get_by_label() used for?

It is useful for locating form controls through their associated labels.

Q5. What is get_by_test_id()?

It locates an element through a configured test identifier such as data-testid.

Q6. Can Selenium do the same UI automation?

Yes. Selenium can automate the same browser workflows. The frameworks differ in APIs, locator abstractions, synchronization behavior and other architecture-level features.

Q7. Which locator should I use?

Choose a stable, understandable locator that matches the application's UI contract. User-facing semantics are often useful, but CSS, test IDs and XPath also have valid use cases.

38 Final Mental Model
Understand Element
→
Choose Stable Locator
→
Scope / Filter
→
Perform Action
→
Assert State
Think like this:

User interaction → Role / Label / Text

Automation contract → Test ID

Stable DOM structure → CSS

Complex structural relationship → XPath

Multiple similar components → Filter + Chain

Expected UI state → Web-first assertion
Key takeaway: Playwright is not different from Selenium simply because it can find elements by text or roles. Selenium can automate the same workflows.

The practical difference is that Playwright provides a cohesive locator model with semantic locators, locator composition, filtering, actionability checks and web-first assertions.

The goal is not to find the most complicated selector. The goal is to find the most stable and maintainable locator.