Cypress + Storybook. Penyimpanan case uji, render data dan komponen di satu tempat

Pada awalnya, Cypress dianggap sebagai alat pengujian e2e. Sangat menarik untuk mengamati minat insinyur front-end yang berkembang dalam topik di mana Selenium memerintah sepanjang hidupnya. Pada saat itu, video atau artikel khas yang menunjukkan kemampuan Cypress terbatas pada pengembaraan di sekitar situs yang dipilih secara acak dan ulasan yang layak tentang API untuk entri data.


Banyak dari kita sudah menebak untuk menggunakan Cypress untuk menguji komponen dalam isolasi yang disediakan oleh lingkungan seperti Storybook / Styleguidist / Docz. Contoh yang baik adalah artikel Stefano Magni "Menguji komponen Daftar Virtual dengan Cypress dan Storybook" . Ini mengusulkan untuk membuat Storybook Story, menempatkan komponen di dalamnya dan memasukkan data variabel global yang akan berguna untuk ujian. Pendekatan ini bagus, tetapi di dalamnya tes terbelah antara Storybook dan Cypress. Jika kita memiliki banyak komponen, tes semacam itu akan sulit dibaca dan dipelihara.


Pada artikel ini saya akan mencoba menunjukkan bagaimana melangkah lebih jauh dan mengambil yang maksimal dari kemampuan untuk mengeksekusi JavaScript di Cypress. Untuk melihat cara kerjanya, unduh kode sumber di alamat dan jalankan perintah uji coba npm i dan npm .


tl; dr:


  • Anda dapat menempatkan tautan di jendela ke komponen dari Storybook Story untuk mengujinya secara keseluruhan oleh Cypress (tanpa memecah logika uji menjadi beberapa bagian).
  • Cypress tampak sangat kuat bagi tim kami sehingga kami sepenuhnya meninggalkan alat yang menggunakan js-dom di bawah tenda untuk menguji komponen UI.

Perumusan masalah


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


Tetapi sekarang alat ini memiliki sejumlah masalah yang tidak memungkinkan menggunakannya sebagai lingkungan penuh untuk menjalankan tes.


Saya harap Gleb Bahmutov dan tim Cypress akan mengatasi kesulitan ini.


PS: Pendapat saya dan pendapat rekan saya setuju bahwa pendekatan yang diusulkan memungkinkan kami untuk meninjau monopoli alat menggunakan js-dom. Apa pendapatmu tentang itu?


All Articles