Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/devexpress/testcafe-react-selectors
TestCafe selector extensions for React apps.
https://github.com/devexpress/testcafe-react-selectors
e2e plugin react reactjs testcafe testing
Last synced: 2 days ago
JSON representation
TestCafe selector extensions for React apps.
- Host: GitHub
- URL: https://github.com/devexpress/testcafe-react-selectors
- Owner: DevExpress
- License: mit
- Created: 2016-08-24T08:26:27.000Z (over 8 years ago)
- Default Branch: master
- Last Pushed: 2024-12-19T05:57:20.000Z (21 days ago)
- Last Synced: 2025-01-06T23:57:01.258Z (2 days ago)
- Topics: e2e, plugin, react, reactjs, testcafe, testing
- Language: JavaScript
- Homepage: https://testcafe.io
- Size: 2.47 MB
- Stars: 203
- Watchers: 21
- Forks: 43
- Open Issues: 5
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# testcafe-react-selectors
This plugin provides selector extensions that make it easier to test ReactJS components with [TestCafe](https://github.com/DevExpress/testcafe). These extensions allow you to select page elements in a way that is native to React.
## Install
`$ npm install testcafe-react-selectors`
## Usage
* [Wait for application to be ready to run tests](#wait-for-application-to-be-ready-to-run-tests)
* [Creating selectors for ReactJS components](#creating-selectors-for-reactjs-components)
* [Selecting elements by the component name](#selecting-elements-by-the-component-name)
* [Selecting nested components](#selecting-nested-components)
* [Selecting components by the component key](#selecting-components-by-the-component-key)
* [Selecting components by display name](#selecting-components-by-display-name)
* [Selecting components by property values](#selecting-components-by-property-values)
* [Properties whose values are objects](#properties-whose-values-are-objects)
* [Searching for nested components](#searching-for-nested-components)
* [Combining with regular TestCafe selectors](#combining-with-regular-testcafe-selectors)
* [Obtaining component's props and state](#obtaining-components-props-and-state)
* [TypeScript Generic Selector](#typescript-generic-selector)
* [Composite Types in Props and State](#composite-types-in-props-and-state)
* [Limitations](#limitations)### Wait for application to be ready to run tests
To wait until the React's component tree is loaded, add the `waitForReact` method to fixture's `beforeEach` hook.
```js
import { waitForReact } from 'testcafe-react-selectors';fixture `App tests`
.page('http://react-app-url')
.beforeEach(async () => {
await waitForReact();
});
```The default timeout for `waitForReact` is `10000` ms. You can specify a custom timeout value:
```js
await waitForReact(5000);
```If you need to call a selector from a Node.js callback, pass the current test controller as the second argument in the `waitForReact` function:
```js
import { waitForReact } from 'testcafe-react-selectors';fixture `App tests`
.page('http://react-app-url')
.beforeEach(async t => {
await waitForReact(5000, t);
});
```The test controller will be assigned to the [boundTestRun](https://devexpress.github.io/testcafe/documentation/test-api/obtaining-data-from-the-client/#optionsboundtestrun) function's option. Otherwise, TestCafe would throw the following error: `ClientFunction cannot implicitly resolve the test run in context of which it should be executed`. See the [TestCafe documentation](https://devexpress.github.io/testcafe/documentation/test-api/obtaining-data-from-the-client/#calling-client-functions-from-nodejs-callbacks) for further details.
### Creating selectors for ReactJS components
`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.
```xml
Item 1
Item 2
Items count: {this.state.itemCount}```
#### Selecting elements by the component name
To get a root DOM element for a component, pass the component name to the `ReactSelector` constructor.
```js
import { ReactSelector } from 'testcafe-react-selectors';const todoInput = ReactSelector('TodoInput');
```#### Selecting nested components
To obtain a nested component or DOM element, you can use a combined selector or add DOM element's tag name.
```js
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')`.
#### Selecting components by the component key
To obtain a component by its key, use the `withKey` method.
```js
import { ReactSelector } from 'testcafe-react-selectors';const item = ReactSelector('TodoItem').withKey('HighPriority');
```#### Selecting components by display name
You can select elements by the component's [displayName](https://reactjs.org/docs/react-component.html#displayname).
For instance, consider the `TodoList` component whose `displayName` class property is specified as follows:
```js
class TodoList extends React.Component {
// ...
}TodoList.displayName = 'TodoList';
```In this instance, you can use `todo-list-display-name` to identify `TodoList`.
```js
import { ReactSelector } from 'testcafe-react-selectors';const list = ReactSelector('todo-list-display-name');
```#### Selecting components by property values
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.
```js
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.
```js
import { ReactSelector } from 'testcafe-react-selectors';const element = ReactSelector('componentName').withProps({
propName: 'value',
anotherPropName: 'differentValue'
});
```##### Properties whose values are objects
React selectors allow you to filter components by properties whose values are objects.
When the `withProps` function filters properties, it determines whether the objects (property values) are strictly or partially equal.
The following example illustrates strict and partial equality.
```js
object1 = {
field1: 1
}
object2 = {
field1: 1
}
object3 = {
field1: 1
field2: 2
}
object4 = {
field1: 3
field2: 2
}
```* `object1` strictly equals `object2`
* `object2` partially equals `object3`
* `object2` does not equal `object4`
* `object3` does not equal `object4`Prior to version 3.0.0, `withProps` checked if objects are strictly equal when comparing them. Since 3.0.0, `withProps` checks for partial equality. To test objects for strict equality, specify the `exactObjectMatch` option.
The following example returns the `componentName` component because the `objProp` property values are strictly equal and `exactObjectMatch` is set to true.
```js
// props = {
// simpleProp: 'value',
// objProp: {
// field1: 'value',
// field2: 'value'
// }
// }const element = ReactSelector('componentName').withProps({
simpleProp: 'value',
objProp: {
field1: 'value',
field2: 'value'
}
}, { exactObjectMatch: true })
```Note that the partial equality check works for objects of any depth.
```js
// props = {
// simpleProp: 'value',
// objProp: {
// field1: 'value',
// field2: 'value',
// nested1: {
// someField: 'someValue',
// nested2: {
// someField: 'someValue',
// nested3: {
// field: 'value',
// someField: 'someValue'
// }
// }
// }
// }
// }const element = ReactSelector('componentName').withProps({
simpleProp: 'value',
objProp: {
field1: 'value',
nested1: {
nested2: {
nested3: {
field: 'value'
}
}
}
}
}, { exactObjectMatch: false })
```#### Searching for nested components
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.
```xml
Item 1
Item 2
```
The following sample demonstrates how to obtain the `TodoItem` subcomponent.
```js
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:
```js
import { ReactSelector } from 'testcafe-react-selectors';const subComponent = ReactSelector('TodoApp').findReact('div').findReact('TodoItem');
```You can also combine `.findReact` with regular selectors and [other](http://devexpress.github.io/testcafe/documentation/test-api/selecting-page-elements/selectors.html#functional-style-selectors)) methods like [.find](http://devexpress.github.io/testcafe/documentation/test-api/selecting-page-elements/selectors.html#find) or [.withText](http://devexpress.github.io/testcafe/documentation/test-api/selecting-page-elements/selectors.html#withtext), for example:
```js
import { ReactSelector } from 'testcafe-react-selectors';const subComponent = ReactSelector('TodoApp').find('div').findReact('TodoItem');
```#### Combining with regular TestCafe selectors
Selectors returned by the `ReactSelector` constructor are recognized as TestCafe selectors. You can combine them with regular selectors and filter with [.withText](http://devexpress.github.io/testcafe/documentation/test-api/selecting-page-elements/selectors.html#withtext), [.nth](http://devexpress.github.io/testcafe/documentation/test-api/selecting-page-elements/selectors.html#nth), [.find](http://devexpress.github.io/testcafe/documentation/test-api/selecting-page-elements/selectors.html#find) and [other](http://devexpress.github.io/testcafe/documentation/test-api/selecting-page-elements/selectors.html#functional-style-selectors) functions. To search for elements within a component, you can use the following combined approach.
```js
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.
```js
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);
});
```### Obtaining component's props and state
As an alternative to [testcafe snapshot properties](http://devexpress.github.io/testcafe/documentation/test-api/selecting-page-elements/dom-node-state.html), you can obtain `state`, `props` or `key` of a ReactJS component.
To obtain component's properties, state and key, use the React selector's `.getReact()` method.
The `.getReact()` method returns a [client function](https://devexpress.github.io/testcafe/documentation/test-api/obtaining-data-from-the-client.html). This function resolves to an object that contains component's properties (excluding properties of its `children`), state and key.
```js
const reactComponent = ReactSelector('MyComponent');
const reactComponentState = await reactComponent.getReact();// >> reactComponentState
//
// {
// props: ,
// state: ,
// key:
// }
```The returned client function can be passed to assertions activating the [Smart Assertion Query mechanism](https://devexpress.github.io/testcafe/documentation/test-api/assertions/#smart-assertion-query-mechanism).
**Example**
```js
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);
await t.expect(component.key).eql('componentID');
});
```As an alternative, the `.getReact()` method can take a function that returns the required property, state or key. This function acts as a filter. Its argument is an object returned by `.getReact()`, i.e. `{ props: ..., state: ..., key: ...}`.
```js
ReactSelector('Component').getReact(({ props, state, key }) => {...})
```**Example**
```js
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)
.expect(el.getReact(({ key }) => key)).eql('componentID');
});
```The `.getReact()` method can be called for the `ReactSelector` or the snapshot this selector returns.
### TypeScript Generic Selector
Use the generic `ReactComponent` type to create scalable selectors in TypeScript.
Pass the `props` object as the type argument to `ReactComponent` to introduce a type for a specific component.
```ts
type TodoItem = ReactComponent<{ id: string }>;
```You can then pass the created `TodoItem` type to the `withProps` and `getReact` generic methods.
```ts
const component = ReactSelector('TodoItem');
type TodoItem = ReactComponent<{ id: string }>;const item1 = component.withProps('id', 'tdi-1');
const itemId = component.getReact(({ props }) => props.id);
```**Example**
``` ts
import { ReactSelector, ReactComponent } from 'testcafe-react-selectors';fixture`typescript support`
.page('http://react-page-example.com')test('ReactComponent', async t => {
const todoList = ReactSelector('TodoList');
type TodoListComponent = ReactComponent<{ id: string }>;const todoListId = todoList.getReact(({ props }) => props.id);
await t.expect(todoListId).eql('ul-item');
});
```#### Composite Types in Props and State
If a component's props and state include other composite types, you can create your own type definitions for them. Then pass these definitions to `ReactComponent` as type arguments.
The following example shows custom `Props` and `State` type definitions. The `State` type uses another composite type - `Option`.
``` ts
import { ReactComponent } from 'testcafe-react-selectors';interface Props {
id: string;
text: string;
}interface Option {
id: number;
title: string;
description: string;
}interface State {
optionsCount: number;
options: Option[];
}export type OptionReactComponent = ReactComponent;
```### Limitations
* `testcafe-react-selectors` support ReactJS starting with version 16. To check if a component can be found, use the [react-dev-tools](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi) extension.
* Search for a component starts from the root React component, so selectors like `ReactSelector('body MyComponent')` will return `null`.
* 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:
```js
{ keepClassName: true, keepFnName: true }
```In UglifyJS, use the following configuration:
```js
{
compress: {
keep_fnames: true
},mangle: {
keep_fnames: true
}
}
```