Taiko Smart Selectors: Find Elements the Way Users Do
The most common reason browser tests break is selector fragility. A CSS path like form > div.field-group:nth-child(3) > input stops working the moment a developer adds a wrapper div or changes a class name. Taiko's smart selectors take a different approach: find elements the way a user would describe them — by the label next to them, by the text they contain, or by their position relative to other visible content.
Why Traditional Selectors Break
Traditional selectors describe DOM structure:
// Brittle — breaks if any ancestor changes
driver.findElement(By.cssSelector('#content > form > div:nth-child(2) > input'));
// Somewhat better but still tied to DOM attribute names
driver.findElement(By.id('user-email-field'));Structure changes constantly during development. Labels and visible text change less often. Taiko's smart selectors use visible content to locate elements.
Text Selectors
text()
Finds elements by their visible text content:
const { text, click } = require('taiko');
// Check if text exists on page
await text('Welcome back, Alice').exists();
await text('Error: Invalid credentials').exists();
// Click element containing specific text
await click(text('Add to Cart'));text() returns a selector object. Use .exists() to check presence, or pass it to click(), hover(), etc.
Partial Text Match
// Exact match
await text('Sign In').exists();
// Contains match (default behavior)
await text('Welcome').exists(); // matches "Welcome back!" toobutton(), link(), image()
Typed text selectors for semantic elements:
const { button, link, image } = require('taiko');
// Find button by its text
await click(button('Submit'));
await click(button('Delete Account'));
// Find link by its text
await click(link('Forgot password?'));
await link('Terms of Service').exists();
// Find image by alt text
await image('Company logo').exists();These are more specific than text() — button('Submit') only matches <button> elements, not a div that says "Submit".
Proximity Selectors
Proximity selectors locate elements by their spatial relationship to other elements on the page. These are Taiko's most powerful feature.
toRightOf() and toLeftOf()
const { inputField, toRightOf, toLeftOf } = require('taiko');
// Find input to the right of a label
await write('alice@example.com', into(inputField(toRightOf('Email'))));
await write('password123', into(inputField(toRightOf('Password'))));
// Find element to the left of something
await checkBox(toLeftOf('Receive newsletters')).check();This matches how HTML forms are structured: label on the left, input on the right.
below() and above()
const { below, above } = require('taiko');
// Find input below a heading
await write('New York', into(inputField(below('Shipping Address'))));
// In a grid layout, find element above footer
await click(button(above('Footer')));near()
Finds the closest matching element to a reference:
// Find the delete button near a specific item
await click(button('Delete', near('Invoice #1042')));
// Find checkbox near a label that doesn't use for/id
await checkBox(near('Enable notifications')).check();near() is the most flexible proximity selector — it finds the geometrically nearest matching element.
within()
Restricts search to elements inside a container:
const { within, $ } = require('taiko');
// Click "Edit" only inside the first card
await click(button('Edit', within($('.card:first-child'))));
// Find input only inside the shipping section
await write('123 Main St', into(inputField(within($('#shipping-form')))));Combining Selectors
Selectors compose. You can combine proximity and type selectors:
// Input field, to the right of "Email", inside the login form
await write('user@example.com', into(
inputField(toRightOf('Email'), within($('#login-form')))
));
// Button with text "Edit", near "Alice Johnson", inside the users table
await click(
button('Edit', near(text('Alice Johnson')), within($('table.users')))
);Form-Specific Selectors
inputField()
const { inputField } = require('taiko');
// By placeholder text
await inputField('Enter your email').exists();
await write('test@example.com', into(inputField('Enter your email')));
// By proximity to label
await write('Doe', into(inputField(toRightOf('Last Name'))));
// By CSS
await write('test', into(inputField($('input[name=query]'))));checkBox() and radioButton()
const { checkBox, radioButton } = require('taiko');
await checkBox('Remember me').check();
await checkBox('Remember me').uncheck();
await checkBox('Remember me').isChecked();
await radioButton('Monthly').select();
await radioButton(toRightOf('Billing cycle')).select();dropDown()
const { dropDown } = require('taiko');
await dropDown('Country').select('United States');
await dropDown(toRightOf('Currency')).select('USD');
await dropDown('Role').select({ index: 2 });
await dropDown('Status').value(); // get selected valueBuilding Resilient Selector Strategies
Prefer Visible Text Over DOM Structure
// Fragile — breaks on DOM restructuring
await click($('div.actions > span.btn-group > button:first-child'));
// Resilient — survives DOM changes
await click(button('Save Draft'));Use Proximity for Forms Without Accessible Attributes
Some legacy forms lack id, name, or for attributes. Proximity selectors handle these:
// Works even when input has no id/name
await write('John', into(inputField(toRightOf('First Name'))));Narrow with within() When Text Is Ambiguous
If "Delete" appears multiple times on a page:
// Ambiguous — which Delete?
await click(button('Delete'));
// Specific — Delete in the right row
await click(button('Delete', near(text('Project Alpha'))));
// Or:
await click(button('Delete', within($('tr[data-id="proj-alpha"]'))));Debug Selectors in the REPL
When a selector doesn't work, use the Taiko REPL:
npx taiko> openBrowser({ headless: false })
> goto('https://your-app.com/page')
> highlight(inputField(toRightOf('Email'))) // visually highlights element
> inputField(toRightOf('Email')).exists() // returns true/false
> $('input[name=email]').exists() // try CSS as fallbackhighlight() draws a red border around matched elements, making it easy to verify your selector targets the right element.
When to Fall Back to CSS
Proximity selectors work best for visible, labeled UI. Fall back to CSS with $() when:
- The element has no nearby text (icon-only buttons)
- You need to match a specific DOM attribute or structure
- Performance is critical (CSS selectors are faster than proximity analysis)
// Icon button with no visible text — use aria-label
await click($('[aria-label="Close modal"]'));
// Specific structural case
await click($('thead th:nth-child(3)'));Summary
Taiko's smart selectors close the gap between how tests describe elements (DOM paths) and how users find them (by visible labels and spatial position). The practical benefit: tests survive UI refactors that change HTML structure but preserve visible labels. inputField(toRightOf('Email')) still works whether the input is inside one div wrapper or three.
For teams writing tests that real users could read as plain English, Taiko's selector model is a significant improvement over CSS-first approaches.