This plugin provides selector extensions that make it easier to test ReactJS components with TestCafe. These extensions allow you to select page elements in a way that is native to React.
$ npm install testcafe-react-selectors
ReactSelector
allows you to select page elements by the name of the component class or the nested component element.
Suppose you have the following JSX.
<TodoApp className="todo-app">
<TodoInput />
<TodoList>
<TodoItem priority="High">Item 1</TodoItem>
<TodoItem priority="Low">Item 2</TodoItem>
</TodoList>
<div className="items-count">Items count: <span>{this.state.itemCount}</span></div>
</TodoApp>
To get a root DOM element for a component, pass the component name to the ReactSelector
constructor.
import { ReactSelector } from 'testcafe-react-selectors';
const todoInput = ReactSelector('TodoInput');
To obtain a nested component or DOM element, you can use a combined selector or add DOM element's tag name.
import { ReactSelector } from 'testcafe-react-selectors';
const TodoList = ReactSelector('TodoApp TodoList');
const itemsCountStatus = ReactSelector('TodoApp div');
const itemsCount = ReactSelector('TodoApp div span');
Warning: if you specify a DOM element's tag name, React selectors search for the element among the component's children without looking into nested components. For instance, for the JSX above the ReactSelector('TodoApp div')
selector will be equal to Selector('.todo-app > div')
.
React selectors allow you to select elements that have a specific property value. To do this, use the withProps
method. You can pass the property and its value as two parameters or an object.
import { ReactSelector } from 'testcafe-react-selectors';
const item1 = ReactSelector('TodoApp').withProps('priority', 'High');
const item2 = ReactSelector('TodoApp').withProps({ priority: 'Low' });
You can also search for elements by multiple properties.
import { ReactSelector } from 'testcafe-react-selectors';
const element = ReactSelector('componentName').withProps({
propName: 'value',
anotherPropName: 'differentValue'
});
You can search for a desired subcomponent or DOM element among the component's children using the .findReact(element)
method. The method takes the subcomponent name or tag name as a parameter.
Suppose you have the following JSX.
<TodoApp className="todo-app">
<div>
<TodoList>
<TodoItem priority="High">Item 1</TodoItem>
<TodoItem priority="Low">Item 2</TodoItem>
</TodoList>
</div>
</TodoApp>
The following sample demonstrates how to obtain the TodoItem
subcomponent.
import { ReactSelector } from 'testcafe-react-selectors';
const component = ReactSelector('TodoApp');
const div = component.findReact('div');
const subComponent = div.findReact('TodoItem');
You can call the .findReact
method in a chain, for example:
import { ReactSelector } from 'testcafe-react-selectors';
const subComponent = ReactSelector('TodoApp').findReact('div').findReact('TodoItem');
You can also combine .findReact
with regular selectors and other) methods like .find or .withText, for example:
import { ReactSelector } from 'testcafe-react-selectors';
const subComponent = ReactSelector('TodoApp').find('div').findReact('TodoItem');
Selectors returned by the ReactSelector
constructor are recognized as TestCafe selectors. You can combine them with regular selectors and filter with .withText, .nth, .find and other functions. To search for elements within a component, you can use the following combined approach.
import { ReactSelector } from 'testcafe-react-selectors';
var itemsCount = ReactSelector('TodoApp').find('.items-count span');
Example
Let's use the API described above to add a task to a Todo list and check that the number of items changed.
import { ReactSelector } from 'testcafe-react-selectors';
fixture `TODO list test`
.page('http://localhost:1337');
test('Add new task', async t => {
const todoTextInput = ReactSelector('TodoInput');
const todoItem = ReactSelector('TodoList TodoItem');
await t
.typeText(todoTextInput, 'My Item')
.pressKey('enter')
.expect(todoItem.count).eql(3);
});
As an alternative to testcafe snapshot properties, you can obtain state
or props
of a ReactJS component.
To obtain component properties and state, use the React selector's .getReact()
method.
The .getReact()
method returns a client function. This function resolves to an object that contains component's properties (excluding properties of its children
) and state.
const reactComponent = ReactSelector('MyComponent');
const reactComponentState = await reactComponent.getReact();
// >> reactComponentState
//
// {
// props: <component_props>,
// state: <component_state>
// }
The returned client function can be passed to assertions activating the Smart Assertion Query mechanism.
Example
import { ReactSelector } from 'testcafe-react-selectors';
fixture `TODO list test`
.page('http://localhost:1337');
test('Check list item', async t => {
const el = ReactSelector('TodoList');
const component = await el.getReact();
await t.expect(component.props.priority).eql('High');
await t.expect(component.state.isActive).eql(false);
});
As an alternative, the .getReact()
method can take a function that returns the required property or state. This function acts as a filter. Its argument is an object returned by .getReact()
, i.e. { props: ..., state: ...}
.
ReactSelector('Component').getReact(({ props, state }) => {...})
Example
import { ReactSelector } from 'testcafe-react-selectors';
fixture `TODO list test`
.page('http://localhost:1337');
test('Check list item', async t => {
const el = ReactSelector('TodoList');
await t
.expect(el.getReact(({ props }) => props.priority)).eql('High')
.expect(el.getReact(({ state }) => state.isActive)).eql(false);
});
The .getReact()
method can be called for the ReactSelector
or the snapshot this selector returns.
-
testcafe-react-selectors
support ReactJS starting with version 15. To check if a component can be found, use the react-dev-tools extension. -
Search for a component starts from the root React component, so selectors like
ReactSelector('body MyComponent')
will returnnull
. -
ReactSelectors need class names to select components on the page. Code minification usually does not keep the original class names. So you should either use non-minified code or configure the minificator to keep class names.
For
babel-minify
, add the following options to the configuration:{ keepClassName: true, keepFnName: true }
In UglifyJS, use the following configuration:
{ compress: { keep_fnames: true }, mangle: { keep_fnames: true } }