Chainable and reusable locators for Testing Library
A lightweight library that provides Testing Library-style query methods (like getByRole, getByText, getByLabelText) with chainable locator patterns, inspired by Vitest Browser Mode Locators and Playwright's locator API.
- Fully compatible with Testing Library - Use familiar queries like
getByRole,getByLabelText,getByText - Chainable API - Compose complex reusable selectors like
getByRole("list").getByRole("listitem").nth(2) - Extendable - Create custom locators to fit your project's needs
- Lightweight - Small bundle size with minimal overhead
- TypeScript support - Fully typed API with excellent IntelliSense
npm add -D testing-library-locatorsyarn add -D testing-library-locatorspnpm add -D testing-library-locatorsPlease note that @testing-library/dom and @testing-library/user-event are peer dependencies, meaning you should ensure they are installed before installing testing-library-locators.
import { page } from "testing-library-locators";
// Find elements using accessible queries
const submitButton = page.getByRole("button", { name: "Submit" });
const emailInput = page.getByLabelText("Email address");
const welcomeText = page.getByText("Welcome back!");
// Chain locators to narrow down selection
const deleteButton = page.getByRole("article").getByRole("button", { name: "Delete" });
// Interact with elements
await submitButton.click();
await emailInput.type("user@example.com");
// Assert element presence
expect(submitButton.element()).toBeInTheDocument();
expect(welcomeText.query()).not.toBeInTheDocument();A locator is a representation of an element or a number of elements. Locators are lazy - they don't find elements immediately but wait until an action is performed.
// This doesn't find the element yet
const button = page.getByRole("button", { name: "Click" });
// Element is located when you perform an action
await button.click(); // ✅ Finds element and clicksCreates a way to locate an element by its ARIA role, ARIA attributes and accessible name.
<h3>Sign up</h3>
<label>
Login
<input type="text" />
</label>
<label>
Password
<input type="password" />
</label>
<br />
<button>Submit</button>expect(page.getByRole("heading", { name: "Sign up" }).element()).toBeInTheDocument();
await page.getByRole("textbox", { name: "Login" }).type("admin");
await page.getByRole("textbox", { name: "Password" }).type("admin");
await page.getByRole("button", { name: /submit/i }).click();Creates a locator capable of finding an element that has an associated label.
// for/htmlFor relationship between label and form element id
<label for="username-input">Username</label>
<input id="username-input" />
// The aria-labelledby attribute with form elements
<label id="username-label">Username</label>
<input aria-labelledby="username-label" />
// Wrapper labels
<label>Username <input /></label>
// Wrapper labels where the label text is in another child element
<label>
<span>Username</span>
<input />
</label>
// aria-label attributes // Take care because this is not a label that users can see on the page, // so the purpose of
your input must be obvious to visual users.
<input aria-label="Username" />page.getByLabelText("Username");Creates a locator capable of finding an element that has the specified placeholder attribute.
<input placeholder="Username" />page.getByPlaceholderText("Username");Creates a locator capable of finding an element that contains the specified text.
<a href="/about">About ℹ️</a>page.getByText(/about/i);Creates a locator capable of finding the input, textarea, or select element that has the matching display value.
<input type="text" id="lastName" value="Norris" />page.getByDisplayValue("Norris");Creates a locator capable of finding a element (normally an ) that has the given alt text.
<img alt="Incredibles 2 Poster" src="/incredibles-2.png" />page.getByAltText(/incredibles.*? poster/i);Creates a locator capable of finding an element that has the specified title attribute.
<span title="Delete" id="2"></span>page.getByTitle("Delete");Creates a locator capable of finding an element that matches the specified test id attribute.
<div data-testid="custom-element" />page.getByTestId("custom-element");This method returns a new locator that matches only a specific index within a multi-element query result. It's zero based, nth(0) selects the first element.
<div aria-label="one"><input /><input /><input /></div>
<div aria-label="two"><input /></div>page.getByRole("textbox").nth(0); // ✅
page.getByRole("textbox").nth(4); // ❌This method returns a new locator that matches only the first index of a multi-element query result. It is sugar for nth(0).
<input /> <input /> <input />page.getByRole("textbox").first();This method returns a new locator that matches only the last index of a multi-element query result. It is sugar for nth(-1).
<input /> <input /> <input />page.getByRole("textbox").last();This options narrows down the selector to match elements that contain other elements matching provided locator.
<article>
<div>First</div>
</article>
<article>
<div>Second</div>
</article>page.getByRole("article").has(page.getByText("First")); // ✅This option narrows down the selector to match elements that do not contain other elements matching provided locator.
<article>
<div>First</div>
</article>
<article>
<div>Second</div>
</article>page.getByRole("article").not.has(page.getByText("First")); // ✅This method returns a single element matching the locator's selector. If no element matches the selector, an error is thrown.
<div>Hello World</div>page.getByText("Hello World").element(); // ✅ HTMLDivElement
page.getByText("Hello Everyone").element(); // ❌This method returns a single element matching the locator's selector or null if no element is found. If multiple elements match the selector, this method will throw an error.
<div>Hello World</div>page.getByText("Hello World").query(); // ✅ HTMLDivElement
page.getByText("Hello Everyone").query(); // ✅ nullThis method returns an array of elements matching the locator's selector.
This function never throws an error. If there are no elements matching the selector, this method will return an empty array.
<div>Hello <span>World</span></div>
<div>Hello</div>page.getByText("Hello World").elements(); // ✅ [HTMLDivElement]
page.getByText("Hello Everyone").elements(); // ✅ []This method returns a promise that resolves to the first element matching the locator's selector.
<input />await page.getByRole("textbox").find(); // ✅ HTMLInputElementThis method returns a promise that resolves to all elements matching the locator's selector.
<input /> <input /> <input />await page.getByRole("textbox").findAll(); // ✅ [HTMLInputElement, HTMLInputElement, HTMLInputElement]This method prints a signal element matching the locator's selector.
<input />page.getByRole("textbox").debug(); // <input />This method allows you to configure an instance of userEvent.
await page.setup({ delay: 200 }).getByRole("button").click();Click on an element.
await page.getByRole("button").click();Triggers a double click event on an element.
await page.getByRole("button").dblClick();Triggers a triple click event on an element.
await page.getByRole("button").tripleClick();Hover an element.
await page.getByRole("button").hover();Unhover an element.
await page.getByRole("button").unhover();Clears the input element content.
await page.getByRole("textbox").clear();Sets the value of the current input, textarea or contenteditable element.
await page.getByRole("textbox").type("Mr. Bean");Select the given options in an HTMLSelectElement or listbox.
<select multiple>
<option value="1">A</option>
<option value="2">B</option>
<option value="3">C</option>
</select>await page.getByRole("listbox").selectOptions(["1", "C"]);Deselect the given options in an HTMLSelectElement or listbox.
<select multiple>
<option value="1">A</option>
<option value="2" selected>B</option>
<option value="3">C</option>
</select>await page.getByRole("listbox").deselectOptions("2");<div>
<label htmlFor="file-uploader">Upload file:</label>
<input id="file-uploader" type="file" />
</div>const file = new File(["hello"], "hello.png", { type: "image/png" });
await page.getByLabelText(/upload file/i).upload(file);const button = page.getByRole("button", { name: "Click me" });
await expect(button.find()).resolves.toBeInTheDocument();
expect(button.element()).toBeInTheDocument();
expect(button.query()).not.toBeInTheDocument();You can extend built-in locators API by defining an object of locator factories. These methods will exist as methods on the page object and any created locator.
These locators can be useful if built-in locators are not enough. For example, when you use a custom framework for your UI.
import { buildQueries } from "@testing-library/dom";
import { locators, createQuerySelector, type Locator } from "testing-library-locators";
// Use the navite buildQueryies of the DOM Testing Library to create custom queries
const queryAllByDataCy = (container: HTMLElement, id: Matcher, options?: MatcherOptions | undefined) =>
queryHelpers.queryAllByAttribute("data-cy", container, id, options);
const getMultipleError = (_c: Element | null, dataCyValue: string) =>
`Found multiple elements with the data-cy attribute of: ${dataCyValue}`;
const getMissingError = (_c: Element | null, dataCyValue: string) =>
`Unable to find an element with the data-cy attribute of: ${dataCyValue}`;
const queries = [queryAllByDataCy, ...buildQueries(queryAllByDataCy, getMultipleError, getMissingError)];
// Creates a new query selector
const DataCyQuerySelector = createQuerySelector(queries);
// Extends the locators
locators.extend({
getByDataCy(dataCyValue: string) {
return new DataCyQuerySelector(this, dataCyValue);
},
});
// For types
declare module "testing-library-locators" {
interface LocatorSelectors {
getByDataCy<T extends HTMLElement = HTMLElement>(dataCyValue: string): Locator<T>;
}
}<button data-cy="submit">Submit</button>page.getByDataCy("submit");Build reusable, complex selectors:
// Reusable component locator
function getListboxOption(name: string) {
return page.getByRole('listbox').getByRole('option', { name }))
}
// Use in tests
await getListboxOption("First").click();MIT © radqut
- Based on Testing Library principles
- Inspired by Vitest Browser Mode Locators
- Influenced by Playwright's locator API