Get started

Locators

Introduction

Locators are how you find element(s) on the page in CSTesting. They work with auto-waiting: before an action (click, type, etc.), CSTesting waits for the element to be actionable. You create a locator with browser.locator(selector) and then call actions or assertions on it.

Each time a locator is used for an action, the element is resolved at that moment. If the DOM changes between calls (e.g. re-render), the next action uses the current matching element.

Quick guide

CSTesting uses selector strings (CSS, XPath, or attribute shortcuts) instead of separate methods like getByRole or getByText. The table below shows the recommended way to achieve the same goals.

GoalCSTesting
By role (button, heading, etc.)CSS or XPath: "button", "button[type=submit]", "//h3[.='Sign up']", "input[type=checkbox]"
By textXPath: "//*[contains(.,'Welcome, John!')]" or "//button[contains(.,'Sign in')]"
By labelUse name= (if input has name) or id= (and ensure <label for="id">). For "label text" use XPath: "//label[contains(.,'User Name')]/input" or "//input[@placeholder='User Name']"
By placeholder"[placeholder='name@example.com']" or "placeholder=name@example.com" (attr shortcut)
By alt text"img[alt='playwright logo']" or "alt=playwright logo" (attr shortcut)
By title"[title='Issues count']" or "title=Issues count"
By test id"data-testid=directions" or "data-testid=directions"[data-testid="directions"]

Example (login-style flow):

browser.locator("name=user").type("John");
browser.locator("name=password").type("secret-password");
browser.locator("//button[contains(.,'Sign in')]").first().click();
browser.assertThat(browser.locator("//*[contains(.,'Welcome, John!')]")).isVisible();

Selector formats

Create a locator with browser.locator(selector). The selector can be:

FormatExampleDescription
CSS"button", "#id", ".class", "button[type=submit]"Default. Use for tags, ids, classes, attributes.
XPath"//button", "xpath=//button"Use for text content, hierarchy, or complex conditions.
id"id=myId"Resolved to #myId (CSS).
name"name=submit"Resolved to [name="submit"] (CSS).
Any attribute"data-testid=foo", "placeholder=Email"Resolved to [attr="value"] (CSS).

When multiple elements match, use .first(), .last(), or .nth(index) before an action; otherwise the action throws.

browser.locator("button").click();                    // OK if exactly one button
browser.locator("button").first().click();            // first match
browser.locator("li").nth(2).click();                 // third item
browser.locator("a").last().click();                  // last link

Locating elements

Locate by role

Use CSS or XPath to target elements by their tag or ARIA role:

  • Button: "button", "input[type=submit]", "[role='button']"
  • Heading: "h1", "h2", "//h3[.='Sign up']"
  • Checkbox: "input[type=checkbox]"
  • Link: "a", "a[href='/login']"

Example – button with text "Sign in":

browser.locator("//button[contains(.,'Sign in')]").first().click();

Example – heading "Sign up", checkbox "Subscribe", button "Submit":

browser.assertThat(browser.locator("//h3[.='Sign up']")).isVisible();
browser.locator("//label[contains(.,'Subscribe')]//input").check();
browser.locator("//button[contains(.,'Submit')]").first().click();

When to use: Prefer role/tag-based selectors (e.g. button, input[type=checkbox]) plus text or attributes so tests align with how users see the page.

Locate by label

Form controls often have a <label>. In CSTesting you can:

  • Use name= if the input has a name (e.g. "name=password").
  • Use id= and ensure the label has for="id" (e.g. "id=username").
  • Use XPath to find the input by its label text: "//label[contains(.,'Password')]//input" or "//input[@id=//label[contains(.,'Password')]/@for]" if you have for/id association.

Example:

browser.locator("name=password").type("secret");
// or by label text
browser.locator("//label[contains(.,'Password')]//input").type("secret");

When to use: Use when locating form fields; prefer name= or id= when possible.

Locate by placeholder

Use the placeholder attribute (CSS or attribute shortcut):

browser.locator("[placeholder='name@example.com']").type("playwright@microsoft.com");
browser.locator("placeholder=name@example.com").type("user@example.com");

When to use: When the field has no (reliable) label but has a placeholder.

Locate by text

Use XPath and contains(., 'text') or exact match .='text':

browser.assertThat(browser.locator("//*[contains(.,'Welcome, John')]")).isVisible();
browser.assertThat(browser.locator("//span[.='Welcome, John']")).isVisible();

For a button or link with text, narrow the tag to avoid matching random divs:

browser.locator("//button[contains(.,'Sign in')]").first().click();
browser.locator("//a[contains(.,'Get Started')]").first().click();

When to use: For non-interactive elements (div, span, p) use text locators. For buttons/links, combine tag + text (e.g. //button[contains(.,'...')]).

Locate by alt text

Use CSS or attribute shortcut for images:

browser.locator("img[alt='playwright logo']").click();
browser.locator("alt=playwright logo").click();

When to use: For img or area elements with alt text.

Locate by title

Use the title attribute:

browser.assertThat(browser.locator("[title='Issues count']")).hasText("25 issues");
browser.locator("title=Issues count").getTextContent();

Locate by test id

Use data-testid= (or any attr=value for a custom attribute):

browser.locator("data-testid=directions").click();
browser.locator("[data-testid='directions']").click();

When to use: For a stable testing contract. Less user-facing than role/text; combine with role or text when the visible behavior matters.

Custom test id attribute: If your app uses e.g. data-pw instead of data-testid, use the attribute shortcut: "data-pw=directions"[data-pw="directions"].

Locate by CSS or XPath

Use browser.locator(selector) with a CSS selector or XPath. CSS is default; XPath is used when the string starts with // or xpath=.

browser.locator("button").click();
browser.locator("//button").click();
browser.locator("css=button").click();
browser.locator("xpath=//button").click();

When to use: Prefer user-facing selectors (role/tag + text, label, placeholder, test id). Avoid long CSS/XPath chains tied to layout (e.g. #tsf > div:nth-child(2) > ...); they break when the DOM changes.

Locators inside frames

To interact with elements inside an iframe, get a browser scoped to the frame, then use locator on it:

CSTestingBrowser frame = browser.frame("iframe#my-frame");
frame.locator("button").click();
frame.locator("name=user").type("John");

See Frames for nested frames and more examples.

Multiple matches and strictness

Locators are strict: if an action (click, type, check, etc.) is performed and the selector matches more than one element, CSTesting throws. Use .first(), .last(), or .nth(index) to pick one.

Throws if more than one button:

browser.locator("button").click();  // error if multiple buttons

OK – explicitly pick one:

browser.locator("button").first().click();
browser.locator("button").nth(1).click();
browser.locator("button").last().click();

Count is OK with multiple matches:

int n = browser.locatorCount(browser.locator("button"));
browser.assertThat(browser.locator("button")).hasCount(3);

Use .first() / .last() / .nth() only when necessary; prefer a selector that uniquely identifies the element (e.g. text, test id, or more specific CSS/XPath).

Lists

Count items

Use assertThat(locator).hasCount(n) or browser.locatorCount(locator):

browser.assertThat(browser.locator("li")).hasCount(3);
int count = browser.locatorCount(browser.locator("li"));

Get a specific item by text

Use XPath to find the list item that contains the text, then act on it:

browser.locator("//li[contains(.,'orange')]").click();

Get by index (nth)

When order is the only way to distinguish items:

Locator secondItem = browser.locator("li").nth(1);
secondItem.click();

Use .nth() with care: if the list order changes, the wrong element may be targeted. Prefer text or test id when possible.

Assert text in a list

Assert that a locator's text matches:

browser.assertThat(browser.locator("//li[contains(.,'apple')]")).isVisible();
browser.assertThat(browser.locator("li").first()).containText("apple");

Filtering (workarounds)

CSTesting does not have filter(hasText) or filter(has(locator)). Use instead:

  • By text: XPath //element[contains(.,'text')] or a more specific selector.
  • By child/descendant: XPath e.g. //li[.//h3[.='Product 2']]//button[contains(.,'Add to cart')].
  • By index: .nth(index) when you must rely on position.

Example – "Product 2" "Add to cart" button:

browser.locator("//li[contains(.,'Product 2')]//button[contains(.,'Add to cart')]").click();

Example – row with "Mary" and "Say goodbye" button (chaining conditions in XPath):

browser.locator("//li[contains(.,'Mary') and .//button[contains(.,'Say goodbye')]]").click();

Summary

TopicCSTesting
Create locatorbrowser.locator(selector)
Selector typesCSS (default), XPath (//... or xpath=...), id=, name=, any attr=value
Multiple matchesUse .first(), .last(), or .nth(index) before actions
By roleCSS/XPath: "button", "input[type=checkbox]", "//h3[.='Sign up']"
By textXPath: "//*[contains(.,'text')]", "//button[contains(.,'Sign in')]"
By labelname=, id=, or XPath //label[contains(.,'Label')]//input
By placeholder / alt / title / test id[attr="value"] or attr=value
In framebrowser.frame(iframeSelector) then frame.locator(...)
CountassertThat(locator).hasCount(n) or locatorCount(locator)
StrictnessAction on multiple matches throws; use .first() / .last() / .nth() or a unique selector