Advanced locators narrow repeated UI components to one exact target using composition, filtering, relationships, and stable attributes.
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.| Technique | Python API | Use |
|---|---|---|
| Scope | locator().locator() | Descendants |
| Filter | filter() | Repeated cards/rows |
| Contains | has= | Child-based matching |
| Text | has_text= | Business text matching |
| Intersection | and_() | Two conditions |
| Alternative | or_() | Either UI state |
Chain from a stable component to a specific child instead of searching the whole page.
product = page.get_by_role("listitem").filter(has_text="Wireless Mouse")
product.get_by_role("button", name="Add to cart").click()Use a parent component as the search scope.
order = page.locator(".order-card").filter(has_text="ORD-10482")
status = order.locator(".status")
expect(status).to_have_text("Shipped")Filter repeated rows, cards, menu items, and notifications by visible business text.
row = page.get_by_role("row").filter(has_text="INV-2026-0142")
row.get_by_role("button", name="Download").click()Use has= when the component is defined by a descendant.
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()has_text filters a component; get_by_text targets the text element.
card = page.locator(".product-card").filter(has_text="₹2,499")
page.get_by_text("₹2,499", exact=True).click()| Need | Prefer |
|---|---|
| Component contains text | filter(has_text=...) |
| Text is the target | get_by_text(...) |
Combine two locator conditions when both are meaningful.
button = page.get_by_role("button").and_(
page.locator("[data-state='ready']")
)
button.click()Represent two valid UI states without duplicating the test flow.
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()Exact matching avoids broad matches; regex handles dynamic values.
import re
page.get_by_text("Order confirmed", exact=True).click()
page.get_by_text(re.compile(r"Order #\d+")).click()Role locators can be narrowed by exact names and control state.
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()Indexing is useful, but should follow semantic filtering when possible.
items = page.get_by_role("listitem")
items.first.click()
items.last.click()
items.nth(2).click()Locate the collection, then filter to the business entity.
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()Rows are ideal for component scoping because action buttons repeat.
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()Model the product as a component instead of targeting a page-wide button.
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()Use a beneficiary row as the scope for a repeated Transfer action.
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")Assert dynamic feedback rather than sleeping for a fixed time.
page.get_by_role("button", name="Save changes").click()
expect(page.get_by_role("status")).to_contain_text("saved", ignore_case=True)Single-target actions should resolve to the intended element. Verify uniqueness when debugging.
save = page.get_by_role("button", name="Save")
expect(save).to_have_count(1)
save.click()Use a dialog, panel, or region as the root locator.
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()When semantic hooks are unavailable, use a stable component boundary and a simple child selector.
email = page.locator(".form-row").filter(has_text="Email")
email.locator("input").fill("qa@example.com")Stable data attributes can be excellent technical anchors.
page.locator("[data-testid='checkout-submit']").click()
page.locator("[data-state='open']").get_by_role("menuitem", name="Settings").click()XPath is a fallback for relationships that are clearer in XPath than in other locator forms.
price = page.locator("//div[@class='product-card'][.//h2[normalize-space()='Keyboard']]//span[@class='price']")
expect(price).to_have_text("₹2,999")Third-party widgets often live inside an iframe. Create a frame-scoped locator.
payment = page.frame_locator("iframe[title='Secure payment']")
payment.get_by_label("Card number").fill("4111111111111111")
payment.get_by_label("Expiry").fill("12/30")Open Shadow DOM can be reached through normal Playwright locator composition.
picker = page.locator("my-date-picker")
picker.get_by_role("button", name="Next month").click()
picker.get_by_role("gridcell", name="15").click()Combine conditions only when the combination describes the intended component.
checkout = page.get_by_role("region", name="Checkout").and_(
page.locator("[data-testid='checkout-panel']")
)
checkout.get_by_role("button", name="Place order").click()Keep complex locators inside page objects so tests stay focused on business actions.
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)A helper can return a reusable narrowed locator.
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()Combine product identity and availability before choosing the action.
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()Scope repeated navigation links to the correct navigation landmark.
nav = page.get_by_role("navigation", name="Main navigation")
nav.get_by_role("link", name="Reports", exact=True).click()Dialogs are strong component boundaries.
dialog = page.get_by_role("dialog", name="Delete customer")
expect(dialog).to_be_visible()
dialog.get_by_role("button", name="Delete", exact=True).click()Virtualized lists may render only visible items. Find by content and scroll when needed.
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()Scope calendar controls to the date-picker dialog.
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()Type into the combobox, then select a suggestion from the option list.
box = page.get_by_role("combobox", name="City")
box.fill("Beng")
page.get_by_role("option", name="Bengaluru").click()Locate the file input directly and assert the UI confirmation.
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()Validate the locator itself and the state of the control it finds.
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()Before changing a selector, inspect how many elements it matches and what text they contain.
matches = page.get_by_role("button", name="Save")
print("count:", matches.count())
print("texts:", matches.all_text_contents())Prefer selectors that survive layout and styling refactors.
| Fragile | Stronger |
|---|---|
| div:nth-child(3) button | get_by_role("button", name="Save") |
| .css-8f3ab2 | get_by_test_id("save-profile") |
| Absolute XPath | Component + filter + child |
Advanced does not mean complicated. The best locator is short, unique, stable, and readable.