Retractor exposes the internals of a React application for end-to-end testing purposes. This allows you to select DOM nodes based on the name of the React Component that rendered the node as well as its state or properties.
A retractor is a surgical instrument with which a surgeon can either actively separate the edges of a surgical incision or wound, or can hold back underlying organs and tissues, so that body parts under the incision may be accessed. – Wikipedia
npm install --save retractor
Retractor uses the React Dev-Tools hooks to expose your app's internals. In order for this to work, Retractor must be loaded before React gets initialized.
In a webpack based setup this can be achieved by adding retractor/client
to the beginning of the entry
array:
module.exports = {
entry: [
'retractor/client',
'./index' //your application entry
],
output: {},
plugins: [],
module: {
loaders: []
}
}
You can verify that Retractor is installed by typing __retractor
in your Browser's console.
Once your app exposes the Retractor API you can interact with it via Selenium:
import React from 'react';
import byJSX from 'retractor';
import webdriver from 'selenium-webdriver';
import TodoItem from '../components/TodoItem';
const driver = new webdriver.Builder().forBrowser('phantomjs').build();
driver.get('http://localhost:3000/');
// Find all TodoItems
driver.findElements(byJSX(<TodoItem />));
// Find one TodoItem with a given text
driver.findElement(byJSX(
<TodoItem todo={{ text: 'Use retractor' }} />
));
The Retractor JSX bindings allow to query the DOM nodes of your components in an intuitive way. You just have to write plain JSX expressions to search and filter the components you would like to interact with.
To query components you have to use the byJSX
locator.
By default it will return all the DOM nodes of the mounted components matching that expression.
import byJSX from 'retractor';
import webdriver from 'selenium-webdriver';
const driver = new webdriver.Builder()
driver.findElements(byJSX(<TodoItem />));
It's also possible to filter components based on their internal data structure (props). Just define the filter criteria as props in the JSX expression. For example, given two TodoItems, calling byJSX
with props {complete: false}
will return the one but not the other.
driver.findElements(byJSX(<TodoItem todo={{ title: 'retractor' }} />));
driver.findElements(byJSX(<TodoItem todo={{ title: 'retractor', completed: false }} />));
driver.findElements(byJSX(<TodoItem editing />));
Retractor uses deep-match internally to support sophisticated filtering. This also allows you to use RegEx patterns for your prop filters.
driver.findElements(byJSX(<TodoItem todo={{ title: /retractor/ }} />))
//TODO verify and document
driver.findElements(
byJSX(<TodoApp model={{ key: 'todolist-2' }} />)).then(
byJSX(<TodoItem todo={{ title: /Retractor/ }} />)
);
Note: While executing your tests you might encounter React warnings about not-fulfilled propTypes
of components that you're trying to lookup. It's safe to ignore those because retractor never actually executes or mounts a component. It simply offers JSX syntax as syntactic sugar, which gets translated by retractor internally to perform the query.
A workaround to disable the warnings is running your tests in a environment other than development
"scripts":
"test-e2e": "NODE_ENV=production mocha ..."
}
//TODO
__retractor.findDOMNodes()
Working with the plain webdriver API like in the example above is often quite verbose. Retractor provides a plugin for Selene, a 100% backwards compatible wrapper around the official Selenium driver. With Selene the very same example can be written as:
import React from 'react';
import selene from 'selene';
import retractor from 'retractor/selene';
import TodoItem from '../components/TodoItem';
const se = selene({browser: 'phantomjs'}).use(retractor);
se.get('http://localhost:3000/');
// Find all TodoItems
se.findAll(<TodoItem />);
// Find one TodoItem with a given text
se.find(<TodoItem todo={{ text: 'Use retractor' }} />);