Get started

Locators

Introduction

Locators in CSTesting represent a way to find one or more elements on the page. You create a locator with browser.locator(selector) or browser.getByAttribute(attribute, value), then perform actions (click, type, check, etc.) or read state (textContent, getAttribute, isVisible) on the matched element(s).

By default, locators are strict: if the selector matches more than one element, actions that target a single element (like click or type) will throw. Use .first(), .last(), or .nth(index) when you expect multiple matches and want to target a specific one. Each time you use a locator for an action, the element is resolved at that moment, so if the DOM changes between calls, the new matching element is used.

CSTesting does not have built-in getByRole, getByText, getByLabel, getByPlaceholder, getByTestId, or getByTitle. You use locator(selector) (CSS or XPath) and getByAttribute(attr, value) instead. Selector shorthand (e.g. #id, name="x", data-testid="submit") makes common cases concise.

You will learn

  • How to create locators (locator, getByAttribute)
  • Selector syntax (CSS, XPath, shorthand)
  • Handling multiple matches (first, last, nth)
  • Locator actions and state
  • Strictness and best practices
  • What is not supported (getByRole, filter, etc.)

Quick guide

By CSS or XPath:

await browser.locator('button[type="submit"]').click();
await browser.locator('#username').type('John');
await browser.locator('[name="password"]').type('secret');

By attribute (convenience for one attribute):

await browser.getByAttribute('data-testid', 'submit').click();
await browser.getByAttribute('aria-label', 'User Name').type('John');

When multiple elements match, pick one:

await browser.locator('button').first().click();
await browser.locator('li').nth(1).click();
await browser.locator('li').last().click();

Creating locators

browser.locator(selector)

Pass a CSS selector or XPath string. CSTesting auto-detects XPath when the string starts with / or (// or ./. Everything else is treated as CSS. Before use, shorthand forms are resolved to CSS (see Selector shorthand).

const submitBtn = browser.locator('button[type="submit"]');
await submitBtn.click();

await browser.locator('#email').type('user@test.com');
await browser.locator('.btn-primary').click();

XPath examples:

await browser.locator('//button[text()="Sign in"]').click();
await browser.locator('(//div[@class="item"])[2]').click();

browser.getByAttribute(attribute, value)

Creates a locator for elements with the given attribute and value: [attribute="value"]. Useful for data-testid, aria-label, name, etc.

await browser.getByAttribute('data-testid', 'directions').click();
await browser.getByAttribute('aria-label', 'User Name').type('John');
await browser.getByAttribute('name', 'password').type('secret');

This is equivalent to browser.locator(`[attribute="value"]`) but avoids quoting and escaping in the selector string.


Selector shorthand

When you use locator(selector) or direct actions like browser.click(selector), the string is normalized: XPath is left unchanged; other forms may be converted to CSS before querying.

ShorthandResolved toExample
#id, .class, button, etc.Passed through as CSS#email, .btn-primary
name="x"[name="x"]name="userName"
id="x"[id="x"]id="submit"
class="x".x (with escaping)class="btn primary"
attr="value"[attr="value"]data-testid="submit", placeholder="Enter name"
XPath (starts with / or (// or ./)Unchanged//button, (//div)[1]

So you can write:

await browser.locator('name="userName"').type('John');
await browser.locator('data-testid="submit"').click();
await browser.locator('#login-form .btn').click();

Locator API (actions and state)

Once you have a locator, you can call:

MethodDescription
click()Click the element
doubleClick(), rightClick()Double-click or right-click
hover()Hover over the element
type(text)Type text into the element (input, textarea, contenteditable)
select(option)Select option(s) in a <select> (e.g. { label: 'Blue' }, { value: '1' })
check(), uncheck()Check or uncheck checkbox/radio
dragTo(targetSelector)Drag this element to the target
pressKey(key)Press a key (e.g. Enter, Tab)
textContent()Get the element's text content
getAttribute(name)Get an attribute value
isVisible(), isDisabled(), isEditable(), isSelected()Element state
first(), last(), nth(n)When multiple match: use first, last, or nth (0-based) element

Example:

await browser.locator('[name="userName"]').type('John');
await browser.locator('[name="password"]').type('secret');
await browser.locator('button[type="submit"]').click();

const text = await browser.locator('h1').textContent();
expect(text).toContain('Welcome');

Multiple elements: first(), last(), nth()

If a selector matches more than one element, a single-element action (click, type, etc.) will throw by default (strictness). Use .first(), .last(), or .nth(index) to target one of them.

// Exactly one button → OK
await browser.locator('button[type="submit"]').click();

// Several buttons → which one? Use first/last/nth
await browser.locator('button').first().click();
await browser.locator('button').last().click();
await browser.locator('li').nth(1).click(); // second item (0-based)

When to use: Prefer a selector that matches a single element (e.g. by id, data-testid, or a unique combination of attributes). Use first() / last() / nth() when the order is stable and you really need "first button" or "second row"; be aware that layout changes can make the wrong element match.


Locators inside frames

To interact with elements inside an iframe, get a frame handle first, then use frame.locator(selector) or frame.getByAttribute(attr, value). The same locator API applies inside the frame.

const frame = browser.frame('iframe#login');
await frame.locator('#username').type('John');
await frame.locator('button[type="submit"]').click();

See Frames for nested frames and the full frame API.


Simulating getByRole, getByLabel, getByText, getByTestId

CSTesting does not provide getByRole, getByLabel, getByText, getByPlaceholder, getByAltText, getByTitle, or getByTestId. You can get similar behavior with locator() and getByAttribute():

Playwright-styleCSTesting equivalent
getByRole('button', { name: 'Sign in' })locator('button:has-text("Sign in")') not available; use locator('//button[text()="Sign in"]') (XPath) or a unique selector like locator('[aria-label="Sign in"]') or getByAttribute('aria-label', 'Sign in')
getByLabel('User Name')Label is often associated with input via for/id or wrapping. Use locator('#user-name') if the input has that id, or locator('[aria-label="User Name"]') / getByAttribute('aria-label', 'User Name')
getByPlaceholder('name@example.com')locator('[placeholder="name@example.com"]') or getByAttribute('placeholder', 'name@example.com')
getByText('Welcome, John')No direct API. Use locator('text=Welcome, John') only if your tool supports it (CSTesting does not); use XPath e.g. locator('//*[contains(text(),"Welcome, John")]') or a more specific selector
getByAltText('logo')locator('img[alt="logo"]') or getByAttribute('alt', 'logo')
getByTitle('Issues count')locator('[title="Issues count"]') or getByAttribute('title', 'Issues count')
getByTestId('submit')locator('[data-testid="submit"]') or getByAttribute('data-testid', 'submit')

Examples:

// By data-testid (test id)
await browser.getByAttribute('data-testid', 'directions').click();

// By placeholder
await browser.locator('[placeholder="name@example.com"]').type('user@test.com');

// By label text (if input has aria-label)
await browser.getByAttribute('aria-label', 'Password').type('secret');

// By button text (XPath)
await browser.locator('//button[text()="Sign in"]').click();

Strictness

Locators are strict: operations that need a single target (click, type, getAttribute, etc.) expect exactly one matching element. If zero or two or more match, CSTesting throws.

  • One match — Action runs on that element.
  • Zero matches — Throws (element not found).
  • Multiple matches — Throws unless you narrow with .first(), .last(), or .nth(index).

So this will throw if there are several buttons:

await browser.locator('button').click(); // strict: must be exactly one

This is OK when you explicitly choose one:

await browser.locator('button').first().click();

Best practice: write selectors that uniquely identify the element (id, data-testid, or a stable combination of attributes) instead of relying on order with nth().


What is not supported (current version)

FeatureSupported?Workaround
locator(selector) (CSS/XPath + shorthand)Yes
getByAttribute(attr, value)Yes
first(), last(), nth(n)Yes
Strictness (one match or throw)Yes
getByRole(role, options)NoUse locator with [role="button"], or getByAttribute('aria-label', name). For "name" use XPath e.g. //button[text()="Sign in"].
getByText(text), getByLabel, getByPlaceholderNoUse locator or getByAttribute: e.g. [placeholder="..."], [aria-label="..."], XPath for text.
getByAltText, getByTitleNoUse locator('img[alt="..."]') or getByAttribute('alt', '...'), same for title.
getByTestIdNoUse getByAttribute('data-testid', 'id') or locator('[data-testid="id"]').
locator.filter({ hasText }), has / hasNotNoNarrow with a more specific selector or XPath (e.g. ancestor + text).
locator.and(), locator.or()NoUse a single selector or XPath that expresses the condition.
locator.count(), locator.all()NoUse evaluate to run document.querySelectorAll(selector).length or iterate in the page.
locator.evaluateAll(fn)NoUse browser.evaluate() with a function that runs in the page and returns an array.
Custom test id attribute (e.g. data-pw)NoUse getByAttribute('data-pw', 'id') or locator('[data-pw="id"]').
Shadow DOMNot documentedQueries run in the main document (or frame document). Shadow roots may require evaluate to pierce.
Chaining locators (e.g. dialog.locator(button))NoUse a combined selector (e.g. #dialog button) or XPath.

Summary

  • Use browser.locator(selector) with CSS or XPath, and browser.getByAttribute(attribute, value) for attribute-based lookup.
  • Selector shorthand is supported: #id, .class, name="x", attr="value", and XPath unchanged.
  • Locators support click, type, select, check, uncheck, hover, dragTo, pressKey, textContent, getAttribute, isVisible, isDisabled, isEditable, isSelected.
  • When multiple elements match, use .first(), .last(), or .nth(n) so actions target a single element.
  • Locators are strict: single-element actions require exactly one match (or use first/last/nth).
  • There are no getByRole, getByText, getByLabel, getByTestId, or filter APIs; use locator and getByAttribute (and XPath for text) instead.