Selectors
Testplane provides multiple ways to locate elements on a web page.
Recommendations for using selectors
We recommend locating elements the same way users interact with the application being tested.
When choosing a selector, consider 3 factors:
- User-centric — the selector reflects what the user sees and interacts with
- Stability — the selector doesn't break during refactoring, CSS changes, or page structure changes
- Uniqueness — the selector unambiguously identifies the required element
| Selector / Selection method | Recommendation | Notes |
|---|---|---|
By visible content — text, role, ... (getByRole, getByText, ...) | ✅ Best | Locate elements the same way users do: by text, role, label — a stable and "honest" way to find elements. |
By data-testid attribute (e.g., [data-testid="submit"]) | ✅ Good | The data-testid attribute is designed specifically for automation: it doesn't affect styles/accessibility, is stable, and clearly indicates the element's purpose, but requires manual markup. |
By ID (e.g., #main) | ⚠️ Rarely | IDs are more stable than classes, but can still depend on JS logic or markup changes. Use only if the ID is guaranteed to be stable. |
By attribute type (e.g., input[type="text"]) | ⚠️ Rarely | Works if the attribute type is semantically meaningful and stable. However, it can be too generic (many elements of the same type). |
By attributes (except data-testid, e.g., [name="email"]) | ⚠️ Rarely | Depends on HTML attribute semantics. Can be unstable (attributes may change or be removed). |
XPath (e.g., (//button)[2]) | 🔴 Poor | Element position can change, and XPath selectors are generally hard to maintain. However, they can be useful as they provide maximum flexibility for locating elements. |
By class (e.g., .sidebar .list-item.state-opened-qU1azF) | 🔴 Poor | Not related to user behavior and doesn't reflect semantics, with a hard dependency on hierarchy. |
WebdriverIO
Testplane supports element location compatible with WebdriverIO syntax: CSS selectors, XPath, element text, and other methods described below.
By text selectors
This type of selector allows you to find elements by the text they contain:
="text"— exact text match*="text"— partial text matcha*=text— text match inside an<a>tagdiv.=text— case-insensitive text match inside a<div>tag
describe("Link Text selector", () => {
it("Finding an element by exact text match", async ({ browser }) => {
await browser.url("https://testplane.io/");
// Exact text match for a link
const docsLink = await browser.$("=Documentation");
const isDocsLinkFound = await docsLink.isExisting();
console.log(`Element with exact text "Documentation" found: ${isDocsLinkFound}`);
// Partial text match for a link
const partialLink = await browser.$("*=Docum");
const isPartialLinkFound = await partialLink.isExisting();
console.log(`Element with partial text "Docum" found: ${isPartialLinkFound}`);
// Partial match with specified <a> tag
const tagPartialLink = await browser.$("a*=Document");
const isTagPartialLinkFound = await tagPartialLink.isExisting();
console.log(`<a> element with partial text "Document" found: ${isTagPartialLinkFound}`);
// Case-insensitive search with div tag
const divCaseInsensitive = await browser.$("div.=testplane");
const isDivCaseInsensitiveFound = await divCaseInsensitive.isExisting();
console.log(
`<div> element with case-insensitive text "testplane" found: ${isDivCaseInsensitiveFound}`,
);
});
});
Use this approach if:
- the element text is stable
- you need the test to closely mimic real user scenarios
CSS selectors
By the data-testid attribute
This approach is suitable for finding elements that are marked with testing attributes.
describe("CSS selector by the data-testid attribute", () => {
it("Finding an element by data-testid", async ({ browser }) => {
// Open the page and wait for it to load
await browser.openAndWait("https://testplane.io/");
// Find element by the data-testid attribute
const element = await browser.$('[data-testid="main-content"]');
// Check if the element exists in the DOM
const isExisting = await element.isExisting();
console.log("Element with data-testid exists:", isExisting);
});
});
Use this approach if:
- you are creating selectors specifically for testing
- you need selector stability regardless of UI/style changes
By class
To find an element by class, use the ".class-name" selector.
describe("CSS selector by class", () => {
it("Finding an element on the main page", async ({ browser }) => {
await browser.openAndWait("https://testplane.io/");
// Find element by class "navbar"
const navbar = await browser.$(".navbar");
// Check if the element is displayed on the page
const isDisplayed = await navbar.isDisplayed();
console.log("Navbar is displayed:", isDisplayed);
});
});
Use this approach if:
- the class is stable and not generated dynamically
- you need a quick and simple way to find an element
- the class semantically describes the element (e.g.,
.error-message,.success-banner) - you are working with component libraries where classes are part of the API
By ID
To find an element by id, use a selector of the form "#id".
describe("CSS selector by ID", () => {
it("Finding an element by ID on the main page", async ({ browser }) => {
await browser.openAndWait("https://testplane.io/");
// Find element by ID "__docusaurus"
const main = await browser.$("#__docusaurus");
// Check if the element is displayed on the page
const isDisplayed = await main.isDisplayed();
console.log("Element is displayed:", isDisplayed);
});
});
Use this approach if:
- you need a quick and simple way to find elements
- the
idis part of the component's public API - you need maximum selector performance (
idis the fastest selector)
By attribute type
To find an element by attribute, use a selector of the form input[type="name"].
describe("CSS selector by attribute type", () => {
it("Finding an element by attribute type", async ({ browser }) => {
await browser.openAndWait("https://testplane.io/");
// Find a button by the attribute type="button"
// Selector format: element[type="value"]
const button = await browser.$('button[type="button"]');
// Check if the element exists in the DOM
const isExisting = await button.isExisting();
console.log("Button exists:", isExisting);
});
});
Use this approach if:
- you need to find all elements of a certain type (all checkboxes, all radio buttons)
- you need to work with semantic HTML5 types (
email,tel,url,date)
XPath selectors
By element text
To find an element by the text it contains, use the selector //element[text()="text"].
// Exact text match
describe("XPath selector by element text", () => {
it("Finding an element by text", async ({ browser }) => {
await browser.openAndWait("https://testplane.io/");
// Find element by the text inside it
const link = await browser.$('//a[text()="Docs"]');
// Check if the element exists in the DOM
const isExisting = await link.isExisting();
console.log("Element with text exists:", isExisting);
});
});
Use this approach if:
- the element text is unique and stable (button labels, headings)
- other element-finding strategies are not applicable
By attributes
To find an element by attribute, use a selector of the form //element[@type="attribute"].
describe("XPath selector by attribute", () => {
it("Finding an element by attribute", async ({ browser }) => {
await browser.openAndWait("https://testplane.io/");
// Find element by the type attribute
const button = await browser.$('//button[@type="button"]');
// Check if the element exists in the DOM
const isExisting = await button.isExisting();
console.log("Element with attribute exists:", isExisting);
});
});
Use this approach if:
- you need complex search conditions (combinations of attributes)
- you are working with dynamic attributes (
dataattributes with variable values) - you need flexibility in searching (partial matches, start/end of string)
- CSS selectors cannot express the required logic
- you need to find an element by the absence of an attribute