Cypress + Storybook. Armazenamento de casos de teste, renderização de dados e componentes em um único local

A princípio, o Cypress foi percebido como uma ferramenta de teste do e2e. Foi interessante observar o crescente interesse dos engenheiros de front-end em um tópico em que o Selenium governou toda a sua vida. Naquela época, um vídeo ou artigo típico demonstrando os recursos do Cypress limitava-se a vaguear por um site selecionado aleatoriamente e a merecidas críticas lisonjeiras sobre a API para entrada de dados.


Muitos de nós imaginamos usar o Cypress para testar componentes isolados fornecidos por ambientes como Storybook / Styleguidist / Docz. Um bom exemplo é o artigo de Stefano Magni "Testando um componente da lista virtual com Cypress e Storybook" . Ele propõe criar um Storybook Story, colocar um componente nele e inserir dados variáveis ​​globais que serão úteis para o teste. Essa abordagem é boa, mas nela o teste é dividido entre o Storybook e o Cypress. Se tivermos muitos componentes, esses testes serão difíceis de ler e manter.


Neste artigo, tentarei mostrar como ir um pouco mais longe e aproveitar ao máximo a capacidade de executar JavaScript no Cypress. A fim de ver como ele funciona, faça o download do código fonte no endereço e executar o npm i e NPM Test Run comandos .


tl; dr:


  • Você pode colocar um link em uma janela para um componente do Storybook Story para testá-lo completamente pelo Cypress (sem quebrar a lógica do teste em várias partes).
  • O Cypress parecia tão poderoso para nossa equipe que abandonamos completamente as ferramentas que usam js-dom sob o capô para testar os componentes da interface do usuário.

Formulação do 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, ..


Mas agora esta ferramenta tem vários problemas que não permitem usá-la como um ambiente completo para a execução de testes.


Espero que Gleb Bahmutov e a equipe de Cypress lidem com essas dificuldades.


PS: Minha opinião e a de meus colegas concordam que a abordagem proposta nos permite revisar o monopólio de ferramentas usando js-dom. O que você acha disso?


All Articles