← Back

Playwright Advanced Locator Techniques

Advanced locator composition, filtering, relationships, strictness, frames, shadow DOM, dynamic lists, and real-time Python Playwright examples.

1 What Makes a Locator Advanced?

Advanced locators narrow repeated UI components to one exact target using composition, filtering, relationships, and stable attributes.

Page
→
Component
→
Filter
→
Exact Control
→
Assertion
filter(has=...) Find a component containing a specific child.
filter(has_text=...) Find a component containing business text.
and_() / or_() Combine or express alternative locator conditions.
Real-time: Start with the product card, then locate its Add to cart button.
2 Advanced Locator Strategy Map
Role
→
Scope
→
filter()
→
has / has_text
→
and_ / or_
→
nth / first / last
→
Frame / Shadow DOM
TechniquePython APIUse
Scopelocator().locator()Descendants
Filterfilter()Repeated cards/rows
Containshas=Child-based matching
Texthas_text=Business text matching
Intersectionand_()Two conditions
Alternativeor_()Either UI state
3 Chaining Locators

Chain from a stable component to a specific child instead of searching the whole page.

Python Playwright
product = page.get_by_role("listitem").filter(has_text="Wireless Mouse")
product.get_by_role("button", name="Add to cart").click()
Example: thirty Add to cart buttons become one target after filtering by product name.
4 Nested locator() for Descendants

Use a parent component as the search scope.

Python Playwright
order = page.locator(".order-card").filter(has_text="ORD-10482")
status = order.locator(".status")
expect(status).to_have_text("Shipped")
Use when: the component boundary is stable but the child is not globally unique.
5 filter() with has_text

Filter repeated rows, cards, menu items, and notifications by visible business text.

Python Playwright
row = page.get_by_role("row").filter(has_text="INV-2026-0142")
row.get_by_role("button", name="Download").click()
Real-time: select an invoice row by invoice number, then click its Download button.
6 filter() with has=

Use has= when the component is defined by a descendant.

Python Playwright
card = page.locator(".product-card").filter(
    has=page.get_by_role("heading", name="Noise Cancelling Headphones")
)
card.get_by_role("button", name="Buy now").click()
Why: the component is identified by what it contains, not its position.
7 has_text vs get_by_text

has_text filters a component; get_by_text targets the text element.

Python Playwright
card = page.locator(".product-card").filter(has_text="₹2,499")
page.get_by_text("₹2,499", exact=True).click()
NeedPrefer
Component contains textfilter(has_text=...)
Text is the targetget_by_text(...)
8 and_() — Locator Intersection

Combine two locator conditions when both are meaningful.

Python Playwright
button = page.get_by_role("button").and_(
    page.locator("[data-state='ready']")
)
button.click()
Practical: combine accessible intent with a stable technical attribute.
9 or_() — Alternative UI States

Represent two valid UI states without duplicating the test flow.

Python Playwright
account = page.get_by_role("button", name="Account")
login = page.get_by_role("button", name="Login")
visible = account.or_(login).first()
expect(visible).to_be_visible()
Example: accept either Account for a signed-in user or Login for a signed-out user.
10 Exact Text and Regex

Exact matching avoids broad matches; regex handles dynamic values.

Python Playwright
import re
page.get_by_text("Order confirmed", exact=True).click()
page.get_by_text(re.compile(r"Order #\d+")).click()
Use regex for: generated order numbers, timestamps, or usernames.
11 Advanced get_by_role Options

Role locators can be narrowed by exact names and control state.

Python Playwright
page.get_by_role("button", name="Submit", exact=True).click()
page.get_by_role("checkbox", name="Terms", checked=False).check()
page.get_by_role("option", name="India", exact=True).click()
Priority: prefer role + accessible name before CSS/XPath.
12 nth(), first() and last()

Indexing is useful, but should follow semantic filtering when possible.

Python Playwright
items = page.get_by_role("listitem")
items.first.click()
items.last.click()
items.nth(2).click()
Caution: nth(2) is the third match. UI reordering can change the target.
13 Repeated Cards and Components

Locate the collection, then filter to the business entity.

Python Playwright
cards = page.locator(".product-card")
card = cards.filter(has_text="Laptop Stand")
expect(card).to_have_count(1)
card.get_by_role("button", name="Add to cart").click()
Example: the locator remains stable if the product moves in the grid.
14 Table Row Locators

Rows are ideal for component scoping because action buttons repeat.

Python Playwright
row = page.get_by_role("row").filter(has_text="EMP-2048")
row.get_by_role("button", name="Edit").click()
expect(row.get_by_role("cell", name="Active")).to_be_visible()
Best pattern: identify the row by a unique business value, then act within it.
15 Real-Time E-Commerce Product

Model the product as a component instead of targeting a page-wide button.

Python Playwright
product = page.get_by_role("article").filter(
    has=page.get_by_role("heading", name="USB-C Hub")
)
product.get_by_role("button", name="Add to cart").click()
Product name
→
Product card
→
Child control
→
Action
Why advanced: the locator survives changes to card order.
16 Real-Time Banking Transfer

Use a beneficiary row as the scope for a repeated Transfer action.

Python Playwright
beneficiary = page.get_by_role("row").filter(has_text="Amit Sharma")
beneficiary.get_by_role("button", name="Transfer").click()
page.get_by_label("Amount").fill("25000")
Test safely: use controlled test accounts; this is a locator pattern, not a live transaction.
17 Dynamic Status and Toasts

Assert dynamic feedback rather than sleeping for a fixed time.

Python Playwright
page.get_by_role("button", name="Save changes").click()
expect(page.get_by_role("status")).to_contain_text("saved", ignore_case=True)
Web-first: let Playwright wait for the expected state.
18 Strictness and Ambiguous Matches

Single-target actions should resolve to the intended element. Verify uniqueness when debugging.

Python Playwright
save = page.get_by_role("button", name="Save")
expect(save).to_have_count(1)
save.click()
If count > 1: scope to the component, refine the accessible name, or add a stable condition.
19 Parent-to-Child Navigation

Use a dialog, panel, or region as the root locator.

Python Playwright
dialog = page.get_by_role("dialog", name="Edit profile")
dialog.get_by_label("Display name").fill("Nikita")
dialog.get_by_role("button", name="Save").click()
Example: only the Save button inside Edit profile is targeted.
20 Structural Relationships

When semantic hooks are unavailable, use a stable component boundary and a simple child selector.

Python Playwright
email = page.locator(".form-row").filter(has_text="Email")
email.locator("input").fill("qa@example.com")
Avoid: deeply nested selectors such as div:nth-child(4) > span > input.
21 CSS Attribute Locators

Stable data attributes can be excellent technical anchors.

Python Playwright
page.locator("[data-testid='checkout-submit']").click()
page.locator("[data-state='open']").get_by_role("menuitem", name="Settings").click()
Best practice: use dedicated test IDs intentionally provided for automation.
22 XPath for Advanced Relationships

XPath is a fallback for relationships that are clearer in XPath than in other locator forms.

Python Playwright
price = page.locator("//div[@class='product-card'][.//h2[normalize-space()='Keyboard']]//span[@class='price']")
expect(price).to_have_text("₹2,999")
Do not default to XPath: long expressions are harder to read and more sensitive to DOM changes.
23 Iframes with frame_locator()

Third-party widgets often live inside an iframe. Create a frame-scoped locator.

Python Playwright
payment = page.frame_locator("iframe[title='Secure payment']")
payment.get_by_label("Card number").fill("4111111111111111")
payment.get_by_label("Expiry").fill("12/30")
Real-time: payment gateways and embedded forms commonly use iframes.
24 Shadow DOM Locators

Open Shadow DOM can be reached through normal Playwright locator composition.

Python Playwright
picker = page.locator("my-date-picker")
picker.get_by_role("button", name="Next month").click()
picker.get_by_role("gridcell", name="15").click()
Important: closed shadow roots need an application-level test hook or boundary.
25 Combining Semantic and Technical Selectors

Combine conditions only when the combination describes the intended component.

Python Playwright
checkout = page.get_by_role("region", name="Checkout").and_(
    page.locator("[data-testid='checkout-panel']")
)
checkout.get_by_role("button", name="Place order").click()
Rule: selectors should explain why the target is correct.
26 Reusable Locator Properties in Page Objects

Keep complex locators inside page objects so tests stay focused on business actions.

Python Playwright
class CheckoutPage:
    def __init__(self, page):
        self.place_order = page.get_by_role("button", name="Place order")
        self.coupon = page.get_by_label("Coupon code")

    def apply_coupon(self, code):
        self.coupon.fill(code)
Benefit: locator maintenance stays in one place.
27 Component Factory for Repeated Cards

A helper can return a reusable narrowed locator.

Python Playwright
def product_card(page, name):
    return page.locator(".product-card").filter(has_text=name)

product_card(page, "Mechanical Keyboard").get_by_role(
    "button", name="Add to cart"
).click()
Example: reuse the same component locator across search and category pages.
28 Advanced Search Results

Combine product identity and availability before choosing the action.

Python Playwright
result = page.get_by_role("article").filter(
    has=page.get_by_role("heading", name="4K Monitor")
)
result.filter(has_text="In stock").get_by_role(
    "button", name="Add to cart"
).click()
Search
→
Product
→
Availability
→
Action
29 Advanced Navigation Locators

Scope repeated navigation links to the correct navigation landmark.

Python Playwright
nav = page.get_by_role("navigation", name="Main navigation")
nav.get_by_role("link", name="Reports", exact=True).click()
Example: a footer Reports link cannot accidentally be selected.
30 Modal and Dialog Scoping

Dialogs are strong component boundaries.

Python Playwright
dialog = page.get_by_role("dialog", name="Delete customer")
expect(dialog).to_be_visible()
dialog.get_by_role("button", name="Delete", exact=True).click()
Use the dialog as root: this prevents matching a Delete button elsewhere.
31 Dynamic Lists and Virtualized UIs

Virtualized lists may render only visible items. Find by content and scroll when needed.

Python Playwright
item = page.get_by_role("listitem").filter(has_text="Project Atlas")
item.scroll_into_view_if_needed()
item.get_by_role("button", name="Open").click()
Practical: do not assume every virtualized item exists in the DOM at once.
32 Date Picker Example

Scope calendar controls to the date-picker dialog.

Python Playwright
picker = page.get_by_role("dialog", name="Choose date")
picker.get_by_role("button", name="Next month").click()
picker.get_by_role("gridcell", name="15", exact=True).click()
Real-time: the dialog scope prevents another Next month button from matching.
33 Autocomplete / Combobox

Type into the combobox, then select a suggestion from the option list.

Python Playwright
box = page.get_by_role("combobox", name="City")
box.fill("Beng")
page.get_by_role("option", name="Bengaluru").click()
Advanced pattern: the option list may be rendered outside the input container.
34 File Upload and Feedback

Locate the file input directly and assert the UI confirmation.

Python Playwright
upload = page.get_by_label("Resume")
upload.set_input_files("tests/data/resume.pdf")
expect(page.get_by_text("resume.pdf", exact=True)).to_be_visible()
Example: keep the input locator stable and validate the upload feedback.
35 Advanced Locator Assertions

Validate the locator itself and the state of the control it finds.

Python Playwright
expect(card).to_have_count(1)
expect(card).to_be_visible()
expect(card).to_contain_text("In stock")
expect(card.get_by_role("button", name="Buy now")).to_be_enabled()
Think in states: count, visibility, text, enabled, checked, and selected states.
36 Locator Debugging

Before changing a selector, inspect how many elements it matches and what text they contain.

Python Playwright
matches = page.get_by_role("button", name="Save")
print("count:", matches.count())
print("texts:", matches.all_text_contents())
Debug order: scope → count → text/attributes → refine.
37 Avoiding Brittle Selectors

Prefer selectors that survive layout and styling refactors.

FragileStronger
div:nth-child(3) buttonget_by_role("button", name="Save")
.css-8f3ab2get_by_test_id("save-profile")
Absolute XPathComponent + filter + child
Rule: select by stable intent, not accidental DOM shape.
38 Locator Readability and Maintainability

Advanced does not mean complicated. The best locator is short, unique, stable, and readable.

Readable
→
Unique
→
Stable
→
Composable
→
Maintainable
Example: a filtered product-card locator is usually better than a long XPath.