At first, Cypress was perceived as an e2e testing tool. It was interesting to observe the growing interest of front-end engineers in a topic in which Selenium ruled all its life. At that time, a typical video or article demonstrating the capabilities of Cypress was limited to wandering around a randomly selected site and well-deserved flattering reviews about the API for data entry.
Many of us have guessed to use Cypress to test components in isolation provided by such environments as Storybook / Styleguidist / Docz. A good example is Stefano Magni's article "Testing a Virtual List component with Cypress and Storybook" . It proposes to create a Storybook Story, place a component in it and put in a global variable data that will be useful for the test. This approach is good, but in it the test is torn between the Storybook and Cypress. If we have many components, such tests will be difficult to read and maintain.
In this article I will try to show how to go a little further and take the maximum from the ability to execute JavaScript in Cypress. In order to see how it works, please download the source code at the address and run the npm i and npm run test commands .
tl; dr:
- You can place a link in a window to a component from the Storybook Story to test it in its entirety by Cypress (without breaking the test logic into several parts).
- Cypress seemed so powerful to our team that we completely abandoned the tools that use js-dom under the hood to test UI components.
Formulation of the problem
, Datepicker . , .
Storybook
Storybook , β Story . , Story DOM-. β , Cypress .
import React from 'react';
import Datepicker from './Datepicker.jsx';
export default {
component: Datepicker,
title: 'Datepicker',
};
export const emptyStory = () => {
window.Datepicker = Datepicker;
return (
<div id="component-test-mount-point"></div>
)
};
Storybook. Cypress.
Cypress
-. , :
import React from 'react';
import ReactDOM from 'react-dom';
context('<Datepicker />', () => {
it('renders text field.', () => { });
it('renders desired placeholder text.', () => { });
it('renders chosen date.', () => { });
it('opens calendar after clicking on text field.', () => { });
})
. . Storybook. Story, "Open canvas in new tab" sidebar. URL Cypress:
const rootToMountSelector = '#component-test-mount-point';
before(() => {
cy.visit('http://localhost:12345/iframe.html?id=datepicker--empty-story');
cy.get(rootToMountSelector);
});
, div id=component-test-mount-point. , . :
afterEach(() => {
cy.document()
.then((doc) => {
ReactDOM.unmountComponentAtNode(doc.querySelector(rootToMountSelector));
});
});
. , :
const selectors = {
innerInput: '.react-datepicker__input-container input',
};
it('renders text field.', () => {
cy.window().then((win) => {
ReactDOM.render(
<win.Datepicker />,
win.document.querySelector(rootToMountSelector)
);
});
cy
.get(selectors.innerInput)
.should('be.visible');
});
? props. . . β Cypress!
,
, props.
<Popup /> c props "showed". "showed" true, <Popup /> . "showed" c true false, <Popup /> .
?
, React - .
state. state boolean, "showed" props.
let setPopupTestWrapperState = null;
const PopupTestWrapper = ({ showed, win }) => {
const [isShown, setState] = React.useState(showed);
setPopupTestWrapperState = setState;
return <win.Popup showed={isShown} />
}
, :
it('becomes hidden after being shown when showed=false passed.', () => {
cy.window().then((win) => {
ReactDOM.render(
<PopupTestWrapper
showed={true}
win={win}
/>,
win.document.querySelector(rootToMountSelector)
);
});
cy.then(() => { setPopupTestWrapperState(false); })
cy
.get(selectors.popupWindow)
.should('not.be.visible');
});
: hook setState , class.
, , . , - -.
Cypress . ref . , ref state .
<Popup /> , ( ). :
it('closes via method call.', () => {
let popup = React.createRef();
cy.window().then((win) => {
ReactDOM.render(
<win.Popup
showed={true}
ref={popup}
/>,
win.document.querySelector(rootToMountSelector)
);
});
cy.then(() => { popup.current.hide(); })
cy
.get(selectors.popupWindow)
.should('not.be.visible');
})
:
Storybook:
- Storybook Stories React .
- .
- Story window ( Cypress).
- Story , ( ).
- .
: Storybook . Stories .
Cypress:
- JavaScript .
- Stories, .
- (, ).
- .
- UI .
, . , , .
js-dom . ?
- Js-dom , . DOM .
- js-dom . .
- -, CSS z-index? Cypress, .
- - . ?
?
β !
β .
"" - react-lifecycle β β¦ . . , ? , ?
cypress-react-unit-test? Storybook?
β . Storybook, Cypress, ..
But now this tool has a number of problems that do not allow using it as a full-fledged environment for running tests.
I hope that Gleb Bahmutov and the Cypress team will deal with these difficulties.
PS: My opinion and the opinion of my colleagues agree that the proposed approach allows us to review the monopoly of tools using js-dom. What do you think about that?