赛普拉斯+故事书。将测试用例,数据和组件渲染存储在一处

最初,赛普拉斯被认为是e2e测试工具。有趣的是,观察到前端工程师对Selenium贯穿其一生的主题越来越感兴趣。当时,展示赛普拉斯功能的典型视频或文章仅限于在随机选择的站点上徘徊,以及有关数据输入API的当之无愧的讨人喜欢的评论。


我们中许多人都猜想使用赛普拉斯来隔离诸如Storybook / Styleguidist / Docz之类的环境所提供的组件。 Stefano Magni的文章“使用Cypress和Storybook测试虚拟列表组件”就是一个很好的例子。它建议创建一个Storybook故事,在其中放置一个组件,并放入一个对测试有用的全局变量数据。这种方法很好,但是其中的测试是在Storybook和Cypress之间进行的。如果我们有很多组件,那么这样的测试将很难阅读和维护。


在本文中,我将尝试展示如何进一步发展并最大程度地利用Cypress中执行JavaScript的能力。为了查看其工作原理,请在该地址下载源代码,然后运行npm inpm run test命令


tl; 医生:


  • 您可以在窗口中放置一个到Storybook Story组件的链接,以由Cypress对其进行完整测试(而无需将测试逻辑分为几部分)。
  • 赛普拉斯对我们的团队似乎是如此强大,以至于我们完全放弃了使用js-dom来测试UI组件的工具。

问题的提法


想象一下,我们正在为将在公司所有站点上使用的现有Datepicker组件编写适配器。为了不意外破坏任何站点,我们希望对其进行测试。


故事书


在Storybook方面,我们需要的是一个空的Story,其中将对被测组件的引用存储在全局变量中。为了避免完全没用,本故事将为我们绘制一个DOM节点。它的作用是为赛普拉斯提供测试目标组件的场所。


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>
    )
};

我们完成了一本故事书。现在将所有注意力转移到赛普拉斯。



我更喜欢通过列出测试用例来开始研究组件。决定测试涂层后,我们得到以下空白:


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


但是现在,此工具有很多 问题,不允许将其用作运行测试的完整环境。


我希望Gleb Bahmutov和Cypress小组能够解决这些困难。


PS:我的观点和同事的观点都同意,该提议的方法使我们可以审查使用js-dom的工具的垄断。您对此有何看法?


All Articles