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.
| Goal | CSTesting |
|---|---|
| By role (button, heading, etc.) | CSS or XPath: "button", "button[type=submit]", "//h3[.='Sign up']", "input[type=checkbox]" |
| By text | XPath: "//*[contains(.,'Welcome, John!')]" or "//button[contains(.,'Sign in')]" |
| By label | Use 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:
| Format | Example | Description |
|---|---|---|
| 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 aname(e.g."name=password"). - Use
id=and ensure the label hasfor="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 havefor/idassociation.
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
| Topic | CSTesting |
|---|---|
| Create locator | browser.locator(selector) |
| Selector types | CSS (default), XPath (//... or xpath=...), id=, name=, any attr=value |
| Multiple matches | Use .first(), .last(), or .nth(index) before actions |
| By role | CSS/XPath: "button", "input[type=checkbox]", "//h3[.='Sign up']" |
| By text | XPath: "//*[contains(.,'text')]", "//button[contains(.,'Sign in')]" |
| By label | name=, id=, or XPath //label[contains(.,'Label')]//input |
| By placeholder / alt / title / test id | [attr="value"] or attr=value |
| In frame | browser.frame(iframeSelector) then frame.locator(...) |
| Count | assertThat(locator).hasCount(n) or locatorCount(locator) |
| Strictness | Action on multiple matches throws; use .first() / .last() / .nth() or a unique selector |