跳至主要內容

使用 Cypress 进行端对端测试

Node.jsTesting

使用 Cypress 进行端对端测试

为什么写端对端测试

写端对端测试代码的最大好处就是,把相关的用例变成可执行的代码,成为项目的资产;结合CI系统,可在后续研发维护过程中,将一部分测试过程自动化,减少重复的手工劳动,既保障质量,又提高效率。

谁来写呢?本文的目标读者是前端研发人员,因而相关测试代码是由前端同学去编写的。

为什么用 Cypress

文档齐全,生态好,对 JavaScript 友好,可简单上手。更多详见:why-cypressopen in new window

缺点:全英文档

快速开始

安装

yarn add cypress -D

下载完依赖后,cypress 还会再从网络下载二进制执行包。安装完成后会在本地全局缓存一份二进制执行包,那么这台机器上所有项目都可以使用这份缓存。文档参考open in new window

一般而言,国内用户都会在上述过程中卡住,最好在命令行设置网络代理后再下载(懂的自然懂)。

export https_proxy=http://127.0.0.1:7890 http_proxy=http://127.0.0.1:7890 all_proxy=socks5://127.0.0.1:7890

如果是在 CI 环境,记得缓存 cypress binary。

安装完后,修改 package.json

  "scripts": {
    "e2e": "cypress open"
  }

加速下载

因为安装时,需要科学上网,如果不想设置代理,也能加速下载安装。可以自己先下载官方提供的二进制 cypress.zipopen in new window,再上传至自己的 OSS。

则安装 Cypress 时,设置 CYPRESS_INSTALL_BINARY 指向对应的地址即可。如

CYPRESS_INSTALL_BINARY=https://your-oss.com/6.1.0-linux-x64-cypress.zip yarn

或使用淘宝镜像,缺点是可能包不是最新的。

CYPRESS_INSTALL_BINARY=https://npm.taobao.org/mirrors/cypress/6.2.0/linux/cypress.zip

或这样写

CYPRESS_DOWNLOAD_MIRROR=https://npm.taobao.org yarn add  cypress -D

目录结构

image.png
推荐结构如上图所示的目录结构:

  • cypress 相关的内容放到 test/e2e 文件夹下。与单元测试的 unit 文件夹区分开来
  • config 存放不同环境下的变量open in new window,如 dev/uat 环境的 baseUrl 是不同的,可分别在 config 里
  • fixtures 存放测试 mock 数据
  • integration 存放的就是 cypress 的测试用例了,命名规范同 jest:${name}.spec.js
  • plugins 存放的是相关插件
  • support 存放自定义的 cypress 命令

可以根据要求,修改文件夹目录结构,只要在 cypress.json 里配置好即可:

{
  "fixturesFolder": "test/e2e/fixtures",
  "integrationFolder": "test/e2e/integration",
  "pluginsFile": "test/e2e/plugins/index.js",
  "screenshotsFolder": "test/e2e/screenshots",
  "supportFile": "test/e2e/support/index.js",
  "videosFolder": "test/e2e/videos",
  "viewportWidth": 1280,
  "viewportHeight": 800
}

注意,如果不显示声明这些配置,每次执行 cypress 命令都会自动生成相应的示例文件

cypress.json 是放在项目根目录下的默认配置文件,全部配置项可查看文档open in new window

通过 FEMessage/create-nuxt-appopen in new window 生成的项目默认是使用上面的配置

与 Jest 协同工作

当项目也在使用 jest 进行单元测试时,有两个注意点。

ESLint 配置

推荐项目中存在三份 eslint 配置文件:

// 项目根目录下的 .eslintrc.js
module.exports = {
  root: true,
  extends: [
    'plugin:prettier/recommended',
  ],
}

// test/unit/.eslintrc.js
module.exports = {
  extends: [
    'plugin:jest/recommended',
  ],
}

// test/e2e/.eslintrc.js
module.exports = {
  extends: [
    'plugin:cypress/recommended',
  ],
}

当然,还要安装相应的依赖:

yarn add eslint-plugin-jest eslint-plugin-cypress --dev

测试目录

两个工具都需要明确指定各自的测试目录。

cypress 的测试目录可通过上文所说的 cypress.json 指定。

jest 测试目录则可通过在 jest.config.js 里指定:

module.exports = {
  // 也可使用 testRegex 属性
  // 详见文档:https://jestjs.io/docs/en/configuration#testmatch-arraystring
  testMatch: ['<rootDir>/test/unit/?(*.)+(spec|test).[jt]s?(x)'],
}

检查依赖及生产安装依赖命令

请确保生产安装依赖命令为

yarn --frozen-lockfile --production

上述命令,只会安装 package.json 里声明的 dependencies 依赖,避免因为下载 Cypress 而超时。

因此,也要确保项目中 package.json 中的 dependencies devDependencies 等声明是正确的。

第一个用例

新建 common.spec.js

describe('', () => {
  it('联系客服按钮', () => {
    cy.visit('/')
    cy.get('.cy-customer-service').click()
    cy.get('[id*=_QIDIAN_WEB_IM_IFRAME_]').should('exist')
  })

  it('合作生态 加入我们 按钮', () => {
    cy.visit('/cooperation/si')
    cy.get('.cy-cooperation-btn').should('have.css', 'width', '98px')
  })
})

上面的示例覆盖了三个 cypress 常用命令:

  • 跳转页面
  • 获取元素
  • 断言

这里说一下 should 命令,它相当于是 expect.to 的简写。
如: expect($input).to.be.disabled 可写成 get($input).should('be.disabled')

更多命令,可查看APIopen in new window
常见断言,可查看文档open in new window

如果想获得代码提示、代码补全,需在开头添加如下语句,(Webstorm不需要此配置)参考文档open in new window

/// <reference types="Cypress" />

执行 yarn e2e ,出现弹窗
image.png
点击文件,即会执行用例。
image.png

更复杂的示例

  beforeEach(() => {
    // 免登录
    cy.setCookie('_yapi_token', 'GXgIZ7rr7gF1o38EugAWgSgHoyC-e2MbZU43uHapqkk')
    cy.setCookie('_yapi_uid', '2426')
  })
  
  it('import swagger', () => {
    let resp = {"code":"1","payload":{"total":-1,"rows":[]},"success":true}

    let apps = resp.payload.rows.filter(item => item.language == 'Java8' && ['job', 'scheduler', 'openresty'].every(name => !item.name.includes(name)))
    
    apps.forEach(item => {
      let url = `https://dev.domain.com/${item.name}/v2/api-docs`;

      let yapi = 'https://yapi.domain.com/group/2120'
      cy.visit(yapi)
      cy.wait(1000)

      // cy.contains(item.name).click()
      cy.contains('添加项目').click()
      cy.get('#name').type(item.name)
      cy.contains('创建项目').click()

      cy.contains('设 置').click()
      cy.contains('Swagger自动同步').click()
      cy.get('.ant-switch-inner').last().click()
      cy.get('.ant-select-selection').last().click()
      cy.contains('智能合并').click() // 选择下拉列表选项
      cy.get('#sync_json_url').type(url)
      cy.contains('保存').click()
      cy.wait(1000)
    })
  })

结合TypeScript

在 e2e 目录添加 tsconfig.json,内容如下:

{
  "compilerOptions": {
    "noEmit": true,
    // be explicit about types included to avoid clashing with Jest types
    // add more types if have custom commands
    "types": ["cypress"] 
  },
  "include": ["../../node_modules/cypress", "./*/*.ts"]
}

在 e2e/support 添加 index.d.ts,如果有自定义命令的话

/* eslint-disable no-unused-vars */
/// <reference types="cypress" />

declare namespace Cypress {
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  interface Chainable<Subject> {
    /**
     * Create login server
     * @example
     * cy.login()
     */
    login(): Chainable<any>
  }
}

将 e2e/integration/xxx.spec.js 重命名为 e2e/integration/xxx.spec.ts,并添加如下2行内容:

+ /// <reference types="cypress"/>
+ /// <reference types="../../support" />

descript('something', function(){
  // some test code here
})

持续集成

持续集成的第一步要选择合适的包含 Cypress 的镜像open in new window

注意自身的 Node 版本选择合适的镜像。

另一方面,一般 CI 环境下执行的是 cypress run 命令。

run 与 open 的不同之处在于,run 默认不会启动浏览器界面,使用的是 headless 模式执行用例。

同时,需要安装 Cypress 时,需要设置环境变量 CYPRESS_INSTALL_BINARY

最后,还是要强调一下,在生产安装依赖环节,使用如下命令安装依赖,则不会安装 Cypress 依赖

yarn --frozen-lockfile --production

直接运行 Cypress

直接运行 Cypress 的场景是,e2e 作为 CI 的最后一个阶段open in new window,当应用完成部署后,再对应用运行线上的测试。

cypress:
  image:
    name: cypress/base:12.19.0
    entrypoint: [""] # https://github.com/cypress-io/cypress-docker-images/issues/300
  stage: e2e
  when: manual
  tags:
    - docker
  script:
    - CYPRESS_INSTALL_BINARY=your-domain/cypress.zip yarn --frozen-lockfile
    - $(npm bin)/cypress run --env config=dev

不推荐选择 cypress/included 镜像直接执行 Cypress 命令,因为很可能会遇到以下问题。
当然,如果自己的用例写的不好,也很可能会出现下面的问题。
image.png

使用 start-server-and-test

如果需要本地起个 localhost 服务,然后再运行 cypress。那么可以合作官方推荐的 start-server-and-test 模块。它在 CI 上的执行顺序是:

  1. 在系统后台执行拉起本地服务的命令
  2. 使用 wait-on 模块监听并等待该本地服务响应 200
  3. 执行 test 命令,完成并退出
  4. CI 环境此时会自动关闭所有后台进程并退出

下面以 gitlab.comopen in new window 为例,展示执行完 gitlab jobs 后可看到 test 记录和下载测试产物(视频及截图)

  • package.json
{
  "scripts": {
    "e2e": "start-server-and-test \"yarn dev\" http-get://localhost:3000 \"cypress run \""
  },
  "devDependencies": {
    "cypress": "^6.0.1",
    "start-server-and-test": "^1.10.8"
  }
}

  • .gitlab-ci.yml
# 参考 https://gitlab.com/cypress-io/cypress-example-docker-gitlab/-/blob/master/.gitlab-ci.yml
# 参考 https://github.com/cypress-io/cypress-example-kitchensink/blob/master/.gitlab-ci.yml

# 这是 gitlab-ci 默认执行顺序。也可以省略
stages:
  - build
  - test

# to cache both npm modules and Cypress binary we use environment variables
# to point at the folders we can list as paths in "cache" job settings
variables:
  YARN_CACHE_FOLDER:  "$CI_PROJECT_DIR/.yarn"
  npm_config_cache: "$CI_PROJECT_DIR/.npm"
  CYPRESS_CACHE_FOLDER: "$CI_PROJECT_DIR/cache/Cypress"

# cache using branch name
# https://gitlab.com/help/ci/caching/index.md
cache:
  key: ${CI_COMMIT_REF_SLUG}
  paths:
    - .npm
    - .yarn
    - cache/Cypress
    - node_modules

# this job installs NPM dependencies and Cypress
install:
  only:
    - merge_requests
  image: cypress/base:12.19.0
  stage: build
  script:
    - yarn --frozen-lockfile
    # check Cypress binary path and cached versions
    # useful to make sure we are not carrying around old versions
    - $(npm bin)/cypress cache path
    - $(npm bin)/cypress cache list
    - $(npm bin)/cypress verify

# all jobs that actually run tests can use the same definition
e2e test:
  only:
    - merge_requests
  image: cypress/base:12.19.0
  stage: test
  script:
    # start the server in the background
    - yarn e2e
  artifacts:
    when: always
    paths:
      - cypress/videos/
      - cypress/screenshots/
    expire_in: 1 week

This job is stuck

image.png如果是自建的 gitlab, 可能会遇到这个问题。
这是任务没有打对标签,导致无法给任务分配对应的 Runner。
image.png
进入上图所示页面,注意找到可使用的 Runner
image.png
如上图所示, docker 标签对应的 Runner 是处于激活状态的,则在 CI 文件里配置即可

cypress:
  tags:
    - docker

Cypress Dashbord

Cypress 官方提供了一个测试记录托管服务。在 CI 命令中,只需要加上 --record --key $key 即可。

script:
  - cypress run --record --key $key

CI 日志如下:
image.png
更多介绍请查阅官方文档open in new window

总结

cypress 比较适合写一个流程测试。一般情况下,只需要把整个正常流程操作使用 cypress 记录下来即可。

一个流程可能长这样:创建->验证->修改->验证->删除->验证。那我们就可以根据该流程,模拟填写合法数据,模拟点击提交按钮,检查页面是否有相应内容即可。

这样,每次开发新功能后,编写测试用例,再跑 Cypress,就能把一部分的回归测试自动化了,保证完成新功能的同时,原有功能最低限度地保持可用。

拓展阅读

上次编辑于:
贡献者: levy