Some theories about front-end testing and E2E testing practice based on Cypress.

Some chatter about front end automation testing

One of the pain points of daily business project development is the front-end regression test, which is unavoidable for various manual points. Whenever a common component or function is changed, it is necessary to point the main page of the project to see if there is any problem. If the project uses GraphQL, the Schema will not be updated in time, and a page will hang if you don’t notice it, and then you will be waiting to open an issue or report an online Bug 😐

Manual dot-dotting is not only tiring, it is not reliable, and there is no guarantee that every time the function that requires regression testing is tested. To solve this pain point, automated testing has to be advanced. Through the command line running test, integrated CI automatic testing is not pleasant.

However, domestic factories for front-end automation testing has not formed a good practice, speaking of automated testing, we think of the back-end testing. Several technical conferences (JSConf, GMTC, etc.) have few topics about front-end testing, if any, they are shared by foreign front-end engineers, or QA about building a test platform.

In my experience, front-end testing is “awkward” for business projects. If you are developing a tool library, you can use unit testing to ensure quality. If you are developing a UI component library, you can use Storybook for visual and snapshot testing. As a result, it was always hard to figure out how to integrate automated tests into a business project (and a few times scared off by the fact that there was more test code than business code).

However, there are not many tool functions in the business project that require unit testing (most of them solve complex logic processing through LoDash or other third-party libraries), and UI components are basically directly adopted by the industry’s mature ant-Mobile, Ant-Design and other schemes. There are not many UI components that need to be developed in business projects, most of which are re-wrapped with business logic on top of third-party UI components (in fact, Ant-Design also places its own components separately at github.com/react-compo… Maintained in).

Business projects need automated test scenarios is mainly want to cover major use the user’s path, such as login, registration and bought the shopping cart, check operation order, modify personal information, etc., are all related to UI rendering logic strong, need to test these page form submission, automatic jump, whether there are abnormal data rendering.

Therefore, our requirements can be summarized as follows:

  1. You can simulate the user’s click-to-input action, event-driven, to verify that the page is rendered as expected
  2. Tests can be run using the command line and can be integrated into CI
  3. Lightweight and efficient, the environment is easy to build, and the test code is easy to write (after all, it is a supplement to agile development and continuous integration, not a test of QA, so it should not be neglected).

It is easy to find that E2E testing is most needed in front-end business projects, but as shown in the test pyramid in the picture above, E2E testing is at the top of the pyramid, and it is costly and slow to execute E2E testing. Therefore, Cypress came into being. Cypress provides a complete solution, from testing E2E at the top of the pyramid to integration testing and unit testing.

Cypress

Cypress is a set of out-of-the-box E2E testing framework developed on the basis of Mocha API. It does not rely on the front-end framework, nor does it need other test tool libraries. It is simple to configure and provides powerful GUI graphics tools, which can automatically capture pictures and record screens, achieve time travel and Debug in the test process, etc.

To sum up, the advantages of Cypress are:

  1. Simple configuration and quick integration into existing projects
  2. Support for all levels of testing (i.e., E2E testing, integration testing, unit testing, and so on mentioned earlier)
  3. Snapshots can be generated for each step of the test, making it easy to Debug
  4. You can obtain and manipulate all DOM nodes in a Web page
  5. Automatic retry, Cypress will retry the current node several times before deciding that the test failed
  6. Easy integration into CI systems

Compared to similar testing tools such as Selenium, Puppeteer, and Nightwatch, Cypress’s test code syntax is simpler and more front-end engineer friendly while keeping the framework lightweight and efficient.

A brief introduction to the use of the method (specific guidance can refer to the official website) :

Installation:

yarn add cypress --dev
Copy the code

Add to the project’s NPM script:

{
  "scripts": {
    "cypress:open": "cypress open"}}Copy the code

Cypress. json in the root directory:

{"baseUrl": "http://localhost:8080", // locally started webpack-dev-server address "viewportHeight": 800, // Test environment page viewport height "viewport ": 1280Copy the code
npm run cypress:open
Copy the code

This opens the test GUI locally and is ready to test.

Use an example from the official documentation to illustrate how to write the test code:

describe('My First Test'.function() {
  it('Gets, types and asserts'.function() {
    cy.visit('https://example.cypress.io')

    cy.contains('type').click()

    // Should be on a new URL which includes '/commands/actions'
    cy.url().should('include'.'/commands/actions')

    // Get an input, type into it and verify that the value has been updated
    cy.get('.action-email')
      .type('[email protected]')
      .should('have.value'.'[email protected]')})})Copy the code

This has actually been tested:

  1. Open the target page, here is example.cypress.io for the example, which is actually a locally launched server page in the project such as http://localhost:8080
  2. Find the “Type” button in the DOM of the page and click it (if the page does not render the button, the test fails).
  3. When the button is clicked, the page should be redirected to the route containing ‘/ Commands/Actions’
  4. In the dom of this page, you can find the input field with the class name ‘.action-email’. After typing ‘[email protected]’, the value of the input field should be ‘[email protected]’.

Test code semantics is better, the code is not much, do not need to write a lot of async logic.

So far here has experienced the installation configuration, local testing, the feeling is also ok, rich functions, relatively simple to use, integration into the project is not trouble.

Test Coverage and Continuous Integration (Gitlab as an example)

Any tests that are not integrated into CI are toys and do not count. So let’s take a look at Cypress.

We want Cypress to be configured to execute different test commands at different stages of development. For example, when launching PR to feature branch, the integration test can be performed in the current branch, and when the master branch needs to calculate the test coverage and report the data to the quality inspection platform such as Sonar (you can also set the test coverage does not meet xx%, the test will fail, etc.).

So let’s first look at how test coverage is calculated. Cypress’s test coverage calculation seems to have been an late addition, and the configuration is a bit more complicated.

You can refer to the documentation for details, but the blog is just a brief introduction:

First install dependencies:

npm install -D @cypress/code-coverage nyc istanbul-lib-coverage
Copy the code

Also configure the configuration in Cypress:

// cypress/support/index.js
import '@cypress/code-coverage/support'

// cypress/plugins/index.js
module.exports = (on, config) = > {
  on('task'.require('@cypress/code-coverage/task'))}Copy the code

The documentation stops there, and it’s not enough if the project uses TypeScript. A quick look at the official Github example reveals a few more steps:

npm i -D babel-plugin-istanbul
Copy the code

Set.babelrc

{
  "plugins": ["istanbul"]}Copy the code

Modify cypress/plugins/index.js again

// cypress/plugins/index.js
module.exports = (on, config) = > {
  on('task'.require('@cypress/code-coverage/task'))
  on('file:preprocessor'.require('@cypress/code-coverage/use-babelrc'))}Copy the code
"cy:run": "cypress run && npm run test:report",
"instrument": "nyc instrument --compact=false client instrumented",
"test:report": "npm run instrument && npx nyc report --reporter=text-summary",
Copy the code

Cypress run allows you to run tests directly from the command line without launching the GUI, which is what you would use in CI.

Look at the results. They’re so happy.

Cypress’s E2E testing coverage can also be combined with unit testing coverage, or with testing coverage through other frameworks such as Jest, which can be found on the website.

Let’s take Gitlab CI Runner as an example to see how Cypress integrates into CI:

// .gitlab-ci.yml
variables:
  npm_config_cache: "$CI_PROJECT_DIR/.npm"
  CYPRESS_CACHE_FOLDER: "$CI_PROJECT_DIR/cache/Cypress"

stages:
  - test
  - sonar

cache:
  paths:
  - .npm
  - node_modules/
  - cache/Cypress

build:
  stage: sonar
  tags:
    - docker
  script:
    - yarn
    - sh ci/sonar.sh
    - yarn build
  artifacts:
    expire_in: 7 day
    paths:
      - codeclimate.json
      - build

cypress-e2e-local:
  image: cypress/base:10
  tags:
    - docker
  stage: test
  script:
    - unset NODE_OPTIONS
    - yarn
    - $(npm bin)/cypress cache path
    # show all installed versions of Cypress binary
    - $(npm bin)/cypress install
    - $(npm bin)/cypress cache list
    - $(npm bin)/cypress verify
    - npm run test
  artifacts:
    expire_in: 1 week
    when: always
    paths:
    - coverage/lcov.info
    - cypress/screenshots
    - cypress/videos
Copy the code

Here we have set up two CI stages, Test and Build (with Sonar scanning, data reporting etc.), using Cypress’s official image Cypress/Base :10 in the test stage. (Other environment variable Settings and dependencies such as Sonar scan, YARN etc are all in our own Docker image)

The CI command NPM run test is:

"test": "start-server-and-test start http://localhost:5000 cy:run"
Copy the code

In order to simplify the command, NPM package start-server-and-test is used to implement the logic of executing the test after the local server is started.

We also set artifacts in.gitlab-ci.yml:

  artifacts:
    expire_in: 1 week
    when: always
    paths:
    - coverage/lcov.info
    - cypress/screenshots
    - cypress/videos
Copy the code

This is the Gitlab Job Artifacts feature, which allows you to upload the contents of a particular folder to the server after a step is complete, so that you can view or download the contents of the files on the Web for a valid period of time. This way, ifa CI test fails, you can view the failed test videos and snapshots in the artifacts section, avoiding blind guess debugging.

There are many more points to dig into in CI setup and test case management. For example, test cases can be divided into smoke tests, full tests, Client tests, Node layer tests, etc.

Cypress also has more test scenarios to apply, such as setting cookies to test users with different permissions, introducing Chance. Js to test different tabs randomly by clicking on them, and Mock interface to return values.

glebbahmutov.com/blog/ is the blog of Cypress’s main maintainer, which also records a lot of manipulation (such as checking if the contrast of the page meets the criteria, etc.), if you are interested, you can continue to dig.

A pit

One pitfall I have found in my practice is Cypress’s lack of support for FETCH requests (github.com/cypress-io/… Mock requests can only be hacked in a slightly dirty way.

The introduction of the whatwg – fetch in the project, then modify the cypress/support/command. Js:

// cypress/support/command.js
Cypress.Commands.add('visitWithDelWinFetch', (path, opts = {}) => {
    cy.visit(
        path,
        Object.assign(opts, {
            onBeforeLoad(win) {
                deletewin.fetch; }})); });Copy the code

This allows us to test the login redirection judgment of our project:

describe('Node server'.function() {

  it('no cookie get 401'.function() {
    cy.server()

    cy.clearCookies()

    cy.route('POST'.'**/graphql').as('login')

    cy.visitWithDelWinFetch('/');

    cy.wait('@login').then((xhr) = > {
      expect(xhr.status).to.eq(401)
    })
  })

  it('with cookie get 200'.function() {
    cy.server()

    cy.route('POST'.'**/graphql').as('loginWithCookie')

    cy.visitWithCookie('/');

    cy.wait('@loginWithCookie').then((xhr) = > {
      expect(xhr.status).to.eq(200)})// login successfully, so display the content
    cy.get('.ant-layout-sider')
    cy.get('.ant-layout-content')})})Copy the code

aha~