Cypress + Storybook. Almacenamiento de casos de prueba, procesamiento de datos y componentes en un solo lugar.

Al principio, Cypress fue percibido como una herramienta de prueba e2e. Fue interesante observar el creciente interés de los ingenieros de front-end en un tema en el que Selenium gobernó toda su vida. En ese momento, un video o artículo típico que demostraba las capacidades de Cypress se limitaba a deambular por un sitio seleccionado al azar y críticas halagadoras bien merecidas sobre la API para la entrada de datos.


Muchos de nosotros hemos adivinado el uso de Cypress para probar componentes de forma aislada, proporcionados por entornos como Storybook / Styleguidist / Docz. Un buen ejemplo es el artículo de Stefano Magni "Probar un componente de Lista virtual con Cypress y Storybook" . Propone crear una historia de libro de cuentos, colocar un componente en ella y colocar datos variables globales que serán útiles para la prueba. Este enfoque es bueno, pero en él la prueba se divide entre Storybook y Cypress. Si tenemos muchos componentes, tales pruebas serán difíciles de leer y mantener.


En este artículo intentaré mostrar cómo ir un poco más allá y aprovechar al máximo la capacidad de ejecutar JavaScript en Cypress. Para ver cómo funciona, descargue el código fuente en la dirección y ejecute los comandos npm i y npm run test .


tl; Dr:


  • Puede colocar un enlace en una ventana a un componente de Storybook Story para probarlo en su totalidad con Cypress (sin romper la lógica de prueba en varias partes).
  • Cypress parecía tan poderoso para nuestro equipo que abandonamos por completo las herramientas que usan js-dom debajo del capó para probar los componentes de la interfaz de usuario.

Formulación del problema


, 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 = () => {
    // Reference to retrieve it in Cypress during the test
    window.Datepicker = Datepicker;

    // Just a mount point
    return (
        <div id="component-test-mount-point"></div>
    )
};

Storybook. Cypress.


Cypress


-. , :


/// <reference types="cypress" />

import React from 'react';
import ReactDOM from 'react-dom';

/**
 * <Datepicker />
 * * renders text field.
 * * renders desired placeholder text.
 * * renders chosen date.
 * * opens calendar after clicking on text field.
 */

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.', () => {
    // arrange
    cy.window().then((win) => {
        // initial state - popup is visible
        ReactDOM.render(
            <PopupTestWrapper
                showed={true}
                win={win}
            />,
            win.document.querySelector(rootToMountSelector)
        );
    });

    // act
    cy.then(() => { setPopupTestWrapperState(false); })

    // assert
    cy
        .get(selectors.popupWindow)
        .should('not.be.visible');
});

: hook setState , class.


, , . , - -.


Cypress . ref . , ref state .


<Popup /> , ( ). :


it('closes via method call.', () => {
    // arrange
    let popup = React.createRef();
    cy.window().then((win) => {
        // initial state - popup is visible
        ReactDOM.render(
            <win.Popup
                showed={true}
                ref={popup}
            />,
            win.document.querySelector(rootToMountSelector)
        );
    });

    // act
    cy.then(() => { popup.current.hide(); })

    // assert
    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, ..


Pero ahora esta herramienta tiene una serie de problemas que no permiten usarla como un entorno completo para ejecutar pruebas.


Espero que Gleb Bahmutov y el equipo de Cypress aborden estas dificultades.


PD: Mi opinión y la opinión de mis colegas coinciden en que el enfoque propuesto nos permite revisar el monopolio de las herramientas que utilizan js-dom. ¿Qué piensas sobre eso?


All Articles