Element Selectors
Article Summary
Element Selectors are similar in purpose to CSS Selectors. Selectors filter the DOM and return page elements that match your criteria. The following Selector returns an element with the big-red-button
ID:
Selector('#big-red-button');
Test actions and assertions accept Selector queries as arguments:
await t.click(Selector('#big-red-button'));
You can use the Selector Generator to interactively generate and debug Selector queries.
Execute standalone Selector queries to examine the page. The following query returns the textContent
of the #header
element:
const headerText = await Selector('#header').textContent;
TestCafe includes three selector types.
- Keyword-based Selectors look for elements that match the CSS Selector argument.
- Function-based Selectors filter the DOM with a client-side function.
- Selector-based Selectors extend other Selector queries.
Selector methods narrow down your element selection. Unlike CSS Keyword queries, Selector methods can freely traverse the DOM tree.
Users of popular front-end frameworks, such as React and Angular, can create Selectors that reference components by name.
Table of Contents
- Introduction
- Element Selector Basics
- How Selectors Work
- Selector Types
- Keyword-Based Selectors
- Function-based Selectors
- Selector-Based Selectors
- Selector Options
- Selector Methods
- Framework-Specific Selectors
- Reuse Element Selectors
- Selector Limitations
- Access the Shadow DOM
- Debug Selectors
- Further Reading
Introduction
Actions and assertions use Selector query arguments to locate their targets:
t.click('#big-red-button');
The t.click
action targets the result of the #big-red-button
Selector query. This query matches elements with the big-red-button
ID.
Element Selector Basics
Selector queries begin with an invocation of the Selector()
constructor. Constructor arguments determine the initial results of the Selector query.
Selector(/*arguments go here*/);
There are three ways to initialize a Selector. The most common way is to use a CSS Keyword:
Selector('button');
The Selector above returns all the button
elements on the page.
After you initialize a Selector, you can perform additional actions with the results of the query. Append Selector methods to the constructor:
Selector('button').withText('click me');
The withText
method helped you narrow down the results. The query above only matches button
elements with the “click me” label.
Use Element Selectors with Actions and Assertions
Actions and assertions use Selector queries to identify their targets.
Note
TestCafe actions can’t target pseudo-elements and require additional code to target the Shadow DOM.
You can pass any valid Selector query to an action or an assertion:
t.click(Selector('#big-red-button'));
If your Selector query does not include methods, you can omit the Selector
keyword:
t.click('#big-red-button');
Simple Selectors and Compound Selectors
Simple Selectors filter the DOM once:
Selector('#big-red-button');
Compound Selectors filter the DOM multiple times:
Selector('nav .button');
// or
Selector('nav').find('.button')
The example above matches button
class elements that reside inside nav
blocks.
How Selectors Work
Selectors are asynchronous.
TestCafe executes Selector queries when it encounters them in actions and assertions.
Element selectors can return multiple items. The selector.count property indicates the number of page elements that match the query.
If a page action / assertion Selector matches multiple DOM elements, TestCafe performs the action / assertion with the first matching element.
If a Selector query yields zero matches, its return value is null. If a test action uses this Selector, the test fails. If you pass such a Selector to an assertion that expects a non-null return value, the test fails, too:
await t.expect(Selector('.holyGrail').count).eql(1)
.expect(Selector('.holyGrail').exists).ok()
If you save a Selector query to a variable, TestCafe only executes the query when you call or await that variable.
The example below contains three identical actions. Yet, since every action changes the DOM, each call of the buttons
variable yields a different result.
test('Click a button', async t => {
const buttons = Selector('button').withText("A button number");
await t
.click(buttons.nth(0))
.click(buttons.nth(0))
.click(buttons.nth(0))
});
<html>
<body>
<div>
<button onclick= "this.textContent= 'Pressed';">A button number 1</button>
<button onclick= "this.textContent= 'Pressed';">A button number 2</button>
<button onclick= "this.textContent= 'Pressed';">A button number 3</button>
</div>
</body>
</html>
Selector Types
- Keyword-Based Selectors filter the page in search of elements that match a CSS Selector.
- Function-Based Selectors execute a client-side function that traverses the DOM.
- Selector-Based Selectors execute, or filter the results of, another Selector query.
Additional libraries enable the use of Framework-Specific Selectors.
Keyword-Based Selectors
Keyword-Based Selectors filter the page in search of elements that match CSS keywords.
Selector('#some-element');
A few examples:
body
matches thebody
element..login
matches elements with thelogin
class name.#input
matches the first element with theinput
ID.
Selector Keywords
TestCafe Selector keywords are syntactically identical to CSS Selectors.
Top Tip: Use Custom HTML Attributes
Keywords that reference custom HTML attributes are more reliable, because they don’t reference mutable code.
- Selectors break when you change the page code that they reference.
- Bad Selectors reference class names or DOM relationships – things that change fairly often during development.
- Reliable Selectors reference properties that are independent of page design and layout.
Mark your actions’ targets with custom HTML attributes, and write better, more reliable Selectors.
- Add a custom HTML attribute (for example:
data-test-id
) to all the page elements that your test interacts with. - Reference your attribute when you write Selector queries:
t.click('button').withAttribute('data-test-id','left-button') // or
t.click('[data-test-id="left-button"]')
Function-based Selectors
Function-based Selectors execute a client-side function that traverses the DOM and returns one of the following objects:
- A DOM node
- An array of DOM nodes
- A
NodeList
object - An
HTMLCollection
object - A
null
object - An
undefined
object
The function-based Selector below retrieves a string from localStorage
and finds a DOM element with the identical ID:
const element = Selector(() => {
const storedElementId = window.localStorage.storedElementId;
return document.getElementById(storedElementId);
});
Function-based Selectors can have additional parameters:
const elementWithId = Selector(id => {
return document.getElementById(id);
});
await t.click(elementWithId('buy'));
Selector-Based Selectors
You can create Selectors based on other Selectors. This is an easy way to extend existing Selector queries or filter their results.
Copy and extend another Selector’s query
You can create a new Selector based on another Selector’s query.
The example code below saves a simple Selector query to the parent
variable. It then declares a child
variable that adds the find
method to the original Selector.
const parent = Selector('div'); // filters the DOM
const child = parent.find('button'); // inherits the "parent" query, modifies it, and filters the DOM again
Note that the child
Selector does not inherit the return value of the parent
Selector.
You can also nest Selectors. If you pass Selector A to Selector B as an argument, Selector B inherits Selector A’s query. This is an easy way to create a Selector copy with modified options.
The example below saves a simple Selector query to the ctaButton
variable and passes it to another Selector. As a result, the second Selector inherits the first Selector’s query. However, since the second Selector enables the visibilityCheck
option, it only matches visible elements.
const ctaButton = Selector('.cta-button'); //
Selector(ctaButton, { visibilityCheck: true });
You can even pass arguments to Selectors within Selectors:
const elementWithIdOrClassName = Selector(value => {
return document.getElementById(value) || document.getElementsByClassName(value);
});
const submitButton = Selector(elementWithIdOrClassName('main-element'));
Copy and filter another Selector’s results
You can create a new Selector based on another Selector’s return value.
If you want to copy the query’s return value, you need to asynchronously execute the query. Asynchronous Selector calls return a DOM Node State object that contains the query’s results. If you pass this object to another Selector, you can filter the original Selector’s results.
In the example below, the visibleTopMenu
Selector filters the return value of the topMenuSnapshot
Selector. Since it contains an additional visibilityCheck
option, it only returns visible elements.
const topMenuSnapshot = await Selector('#top-menu')(); // TestCafe executes this query asynchronously, filters the DOM and returns a DOMNodeState object.
const visibleTopMenu = Selector(topMenuSnapshot, { visibilityCheck: true }); // TestCafe doesn't filter the DOM. It only filters the results of the topMenuSnapshot query
Selector Options
Main article: Selector Constructor.
Use constructor options to customize Selector behavior.
boundTestRun
The boundTestRun
option binds the TestController
object to a Node.js callback. This is necessary to execute Selectors inside the callback function.
dependencies
Use the dependencies
option to share data with a Selector function.
visibilityCheck
If the visibilityCheck
option is true
, TestCafe imposes a visibility requirement on Selector targets.
TestCafe does not interact with invisible elements.
If the element or one of its parents meets the following criteria, TestCafe considers the element to be invisible.
- The value of the element’s
display
property isnone
- The value of the element’s
visibility
property ishidden
orcollapse
- The element has a
width
orheight
of0
.
Elements that do not meet these criteria may still be invisible to the user. The following factors do not influence the element’s visibility status:
- The element’s
z-index
- The element’s
opacity
- The element’s position on the page
timeout
When TestCafe runs tests, it automatically waits for action targets to appear and become visible. The value of the element selector timeout variable defines the maximum waiting time. If a visible DOM element that matches a Selector query does not appear on the page within the element selector timeout, the test fails.
Use the timeout Selector option to specify a custom selector timeout.
Selector timeouts have no effect on Selector.exists and Selector.count properties. TestCafe calculates their values immediately. If you want to evaluate these properties in an assertion, set a custom assertion timeout.
await t.expect(Selector('#elementId', { timeout: 500 }).innerText).eql('text', 'check element text');
See Automatic Waiting for more information.
Override Selector Options
Use the with method to override the options of another Selector.
import { Selector } from 'testcafe';
const elementWithId = Selector(id => document.getElementById(id));
fixture `My fixture`
.page `http://www.example.com/`;
test('My Test', async t => {
const visibleElementWithId = elementWithId.with({
visibilityCheck: true
});
const visibleButton = await visibleElementWithId('submit-button');
});
Selector Methods
Selector methods perform additional actions with the return value of the main Selector query. Selector methods narrow, expand, or otherwise modify the selection of page elements.
Selector('keyword1 keyword2').method1().method2();
Why use Selector Methods
Keyword-based Selectors have limited capabilities.
Complex keyword-based Selectors are long and difficult to maintain:
await t.click('div > .my-class > div:nth-child(2) > span > a[href="https://my-site.com/page"]');
CSS selectors cannot query parent elements:
<html>
<body>
<!-- ... -->
<div>
<!-- you cannot query this div by its child's ID -->
<div>
<div id="query-my-parent"></div>
</div>
</div>
<!-- ... -->
</body>
</html>
Selector methods can overcome these limitations and freely traverse the DOM tree:
const link = Selector('div')
.child('.my-class')
.child('div')
.nth(2)
.child('span')
.child('a')
.withAttribute('href', 'https://my-site.com/page');
const parent = Selector('#query-my-parent').parent();
Selector Method Types
- Filter methods filter the page element selection.
- Related Element Lookup methods look for page elements related to the existing element selection.
Filter Methods
Filter methods filter the page element selection.
Method | Description |
---|---|
nth | Finds the element with the specified index. The index starts at 0. Negative index values indicate the element’s location relative to the final element of the array. |
withText | Finds elements with a textContent value that contains the specified case-sensitive string or matches the regular expression. |
withExactText | Finds elements with a textContent value equal to the specified string (case-sensitive). |
withAttribute | Finds elements with the specified attribute or attribute value. |
filterVisible | Finds visible elements. |
filterHidden | Finds hidden elements. |
filter | Finds elements that match a CSS selector, or meet the conditions of a filter function. |
Related Element Lookup Methods
Related element lookup methods find elements related to the existing element selection.
Method | Description |
---|---|
find | Returns an array of descendant nodes that match a CSS Selector, or meet the conditions of a filter function. |
parent | Returns an array of parent elements. |
child | Returns an array of child elements. |
sibling | Returns an array of sibling elements. |
nextSibling | Returns an array of succeeding sibling elements. |
prevSibling | Returns an array of preceding sibling elements. |
shadowRoot | Returns the shadow root node. |
Framework-Specific Selectors
Users of popular front-end frameworks can simplify their Selectors with the help of special TestCafe plugins.
For instance, users that install the testcafe-react-selectors plugin can reference the React component tree.
Note
Front-end development tools (such as React DevTools or Vue DevTools) can interfere with TestCafe and cause errors. Do not open them when you run or debug TestCafe tests.
React
Install the testcafe-react-selectors npm package.
Import the ReactSelector
method to reference React components by name.
For more information, refer to the official plugin documentation.
import { ReactSelector } from 'testcafe-react-selectors';
const reactComponent = ReactSelector('MyComponent');
const reactComponentState = await reactComponent.getReact();
// >> reactComponentState
//
// {
// props: <component_props>,
// state: <component_state>
// }
Angular
Install the testcafe-angular-selectors npm package.
Import the AngularSelector
method to reference Angular components by name. Call the method without parameters to obtain the project’s root element.
For more information, refer to the official plugin documentation.
import { AngularSelector } from 'testcafe-angular-selectors';
const rootAngular = AngularSelector();
const list = AngularSelector('list');
const listAngular = await list.getAngular();
await t.expect(listAngular.testProp).eql(1);
AngularJS
Install the testcafe-angular-selectors npm package. Import the AngularJSSelector
method to look for DOM elements by their Angular binding.
For more information, refer to the official plugin documentation.
import { AngularJSSelector } from 'testcafe-angular-selectors';
import { Selector } from 'testcafe';
fixture `TestFixture`
.page('http://todomvc.com/examples/angularjs/');
test('add new item', async t => {
await t
.typeText(AngularJSSelector.byModel('newTodo'), 'new item')
.pressKey('enter')
.expect(Selector('#todo-list').visible).ok();
});
Vue
Install the testcafe-vue-selectors npm package.
Import the VueSelector
method to reference Vue components by name.
For more information, refer to the official plugin documentation.
import VueSelector from 'testcafe-vue-selectors';
const rootVue = VueSelector();
const vueComponent = VueSelector('componentTag');
const vueComponentState = await vueComponent.getVue();
// >> vueComponentState
//
// {
// props: <component_props>,
// state: <component_state>,
// computed: <component_computed>
// }
Aurelia
Install the testcafe-aurelia-selectors plugin.
Import the AureliaSelector
method to look for elements by their Aurelia binding.
For more information, refer to the official plugin documentation.
import AureliaSelector from 'testcafe-aurelia-selectors';
fixture `TestFixture`
.page('http://todomvc.com/examples/aurelia/');
test('add new item', async t => {
await t
.typeText(AureliaSelector.byValueBind('newTodoTitle'), 'new item')
.pressKey('enter')
.expect(AureliaSelector.byShowBind('items.length').exists).ok();
});
Reuse Element Selectors
Tests that repeat identical Selector queries are slower and harder to maintain. You can reuse Selectors in one of the two ways:
Assign the value of a Selector query to a constant, and use that constant later in code. Every time you invoke this constant, you execute the Selector query anew.
const myBeautifulSelector = Selector('div').withText('hello world'); // does not execute the query
<…>
await t.click(myBeautifulSelector); // executes the query
Selector queries take time to resolve. Await the Selector to execute the query once and only re-use its return value.
const myBeautifulSelector = await Selector('div').withText('hello world'); // executes the query
<…>
await t.click(myBeautifulSelector); // uses the query result from before
Selector Limitations
- Selector queries cannot access user variables outside of their scope. If a Selector query needs to access a variable, pass that variable through the dependency option.
- Keyword-based Selectors and Selector-based Selectors do not accept custom arguments. Use the dependencies option to pass additional data to a Selector query.
- Function-based Selectors can not contain generators or
async/await
keywords. - Selector dependencies do not support property shorthands.
Access the Shadow DOM
Important
When TestCafe uses native automation to interact with the Shadow DOM, the mouse cursor is not visible.
You cannot access Shadow DOM elements directly. To interact with the Shadow DOM, identify the root node of a shadow tree, and use other Selector methods to traverse it.
Tip
Reuse shadowRoot Selector queries for greater convenience.
Warning
You cannot perform actions with the shadowRoot()
element, or use it in assertions. Use this element as the shadow DOM entry point.
The following example returns the <p>
element from the shadow DOM:
import { Selector } from 'testcafe';
fixture`Selector.shadowRoot`
.page`https://devexpress.github.io/testcafe/example/`;
test('Get text within shadow tree', async t => {
const shadowRoot = Selector('div').withAttribute('id', 'shadow-host').shadowRoot();
const paragraph = shadowRoot.child('p');
await t.expect(paragraph.textContent).eql('This paragraph is in the shadow tree');
try {
await t.click(shadowRoot);
// causes an error
// do not target the shadow root directly or use it in assertions
}
catch (error) {
await t.expect(error.code).eql('E27');
}
});
Debug Selectors
Main article: Debug Tests
If a Selector or a Selector method does not match any DOM elements, the test fails and TestCafe throws an error.
If you chain multiple Selector methods together, TestCafe stops Selector execution after the first unsuccessful Selector method.
Use the Visual Selector Debugger to debug failing Selector queries.
Further Reading
Read the Best Practices guide for more information on Selector best practices.
The Advanced Selector Techniques guide describes the following advanced Selector techniques:
- Extend the Selector class with Custom Properties and Methods
- Execute Selectors inside Node.JS Callbacks
- Select Elements with Dynamic IDs
The Selector Recipes page features additional Selector code examples.