Cypress使用

Cypress使用

2021年08月12日 20:33 ·  阅读 3385

起因

在一次埋点需求过程中,为了方便测试删了官网的下载功能,忘了改回来就上线了。因为没有功能性的开发,涉及的页面比较多也只是让产品玩玩埋点上报,没有经过测试所以也没有人发现。

官网的业务比较简单,存在的意义就是介绍产品,提供地址让用户下载产品,介绍产品的目的也是为了让用户去下载使用,小小的下载功能一旦罢工,就直接意味着客户的流失。为了确保这些核心但是简单不引起测试同事注意的功能在迭代过程中不丢失不出问题,我们决定在项目中引入自动化测试。

为什么选择Cypress

Cypress就是前端E2E测试框架,E2E就是end-to-end。我要测试的功能很简单,点击下载客户端和用户提交消息两个功能。站在用户和测试的角度,他们并不关心前端的使用什么框架什么逻辑写的,只想知道浏览器上的交互效果,ui展示效果是不是正确的,功能使用上是不是正确的,按这种思路测试,也叫E2E测试。

除了业务场景适用之外,我选择它最主要还是因为它基于node js,对前端开发者来说上手快。

使用

安装

进入到需要接入Cypress的项目目录下

cd /your/project/path 
复制代码

安装cypress,我这里是使用npm下载依赖,版本号是8.1.0

npm install cypress --save-dev
复制代码

vs code终端执行cypress open,开启cypress

./node_modules/.bin/cypress open
复制代码

cypress文件夹

open命令执行成功后,就会弹出一个小窗口。"1-getting-started"和"2-advanced-examples"目录下的是自带的测试用例。

同时项目中会多出cypress的文件夹:

  • fixtures:存放测试数据的地方,可以理解为放接口mock数据的地方
  • integration: 一般测试用例会写在integration下面,自带官方示例测试用例文件。
  • plugins:存放插件,插件可以是自己编写的,也可以是第三方,插件是在项目加载之前、浏览器启动之前和测试执行期间在Node中执行用的。
  • support:cypress/support/index.js文件在每个规范文件之前运行,比如有些动作是适用于全局的,那么就可以放在这里。比如说,在cypress/support/index.js里增加如下代码:
beforeEach(() => {
  cy.log('在每个测试执行前都会运行')
})

// 运行后,会看到每个测试用例都会有个log输出。
复制代码

以 cypress run 方式运行测试时,当测试发生错误时,Cypress 会自动截图/录屏,并默认保存cypress/screenshots 文件夹下,而录屏会保存在 cypress/video 文件夹下

编写测试案例

思考测试和用户操作点击下载的过程:

  1. 打开下载页
  2. 点击下载按钮
  1. 等待客户端下载完成

剩下的步骤,有bug也不是前端同学的锅了。

// cypress/integration/download.js

const path = require("path");

describe('The Home Page', () => {
  it('successfully visit', () => {
    cy.visit('http://localhost:3001')
  })

  it('successfully download', () => {
    // 执行下载操作
    cy.contains('点我下载')
    .click()
    
    // 执行测试后,cypress文件夹下会多出downloads目录,存放测试过程中下载的文件
    const downloadsFolder = Cypress.config("downloadsFolder");
    const downloadedFilename = path.join(downloadsFolder, "下载文件.exe");
    // 读取文件
    cy.readFile(downloadedFilename).then(data => {
    // 执行断言
    });
  })
})
复制代码

保存测试文件后,cypress的小窗会多出你的测试js文件,点击即可开启测试

如下图所示,访问下载页和下载客户端测试成功,界面左边是测试日志,中间是展示下载页,右边是浏览器控制台:

但是这里有个问题,想通过检测用户点击下载到下载是否成功,可能需要耗时很久。我并不想测试执行那么长的时间,所以采用另一种方法:将页面获取下载链接的接口代理到本地mock,获取a标签的链接和mock数据对比。

describe('The Home Page', () => {
  before(() => {
    // 拦截请求并传入mock数据
    cy.intercept('GET', '/Download.php', { fixture: 'download.json' })
  })

  it('successfully loads', () => {
    cy.visit('http://localhost:3001')
  })

  it('successfully request download api', () => {
    // 获取mock数据
    cy.fixture('downloadv2.json').then(({ data = [] }) => {
      // cypress获取节点 a标签带上data-lbl属性
      cy.get(`[data-lbl=下载]`)
      .should('have.attr', 'href')
      .then(href=>{
        expect(href).to.eq(config.FUrl)
      })
    })
  })
})
复制代码

cypress的API

下面是我接触到测试案例时遇到的一些常见的API,虽然官网有指导但是,但是刚开始接触不了解基本概念,还是有点一头雾水。Cypress官网API文档传送门

测试套件和用例

对于一条可执行的测试用例来说,describe()和it()是两个必要的组成部分

  • describe: 代表测试套件,里面可以设定 ,一个测试套件可以不包括任何钩子函数(Hook),但必须包含至少一条测试用例 it() ,能嵌套子测试套件
  • it: 代表一条测试用例

context: 是 describe() 的别名,其行为方式是一致的,可以直接用 context() 代替 describe()

钩子函数

  • before() : 运行 cypress via cypress open时,打开项目时将触发该事件。每次cypress run执行时都会触发该事件。如上我的测试案例中,在测试之前将接口代理到mock数据,这样visit()接口打开页面时,节点上渲染的是mock数据。

before会在第一个用例之前运行,afeter会在跑完所有的用例之后运行。beforeEach会在每一个用例前运行,afterEach会在每一个用例结束后运行。

查找,操作dom节点

  • get(): 用来在 DOM 树中查找 DOM 元素,get方法可以像jquery一样通过selector查找到对应的dom。

还有其他查找方法如:children获取一组 DOM 元素中每个 DOM 元素的子元素, parent获取一组 DOM 元素的父 DOM 元素,siblings获取兄弟 DOM 元素等。

  • trigger(): 在 DOM 元素上触发事件。如:
// 触发dom的mouseover事件
dom.trigger('mouseover')

// 语法使用示例
// eventName(string)event 在DOM元素上要触发的的名称。
.trigger(eventName)

// position(string)
// 应该触发事件的位置。该center位置是默认位置。
// 有效的位置topLeft,top,topRight,left,center,right,bottomLeft,bottom,和bottomRight。
.trigger(eventName, position)

// options: 传递选项对象以更改的默认行为
.trigger(eventName, options)

// x(number): 从元素左侧到触发事件的距离(单位px)。
// y(number): 从元素顶部到触发事件的距离(单位px)。
.trigger(eventName, x, y)

.trigger(eventName, position, options)
.trigger(eventName, x, y, options)
复制代码

网络接口

  • intercept: 在网络层管理 HTTP 请求的行为

如上我的测试案例中,就利用这个API拦截请求,代理到我本地的mock数据

cy.intercept(url, staticResponse)
cy.intercept(method, url, staticResponse)
cy.intercept(routeMatcher, staticResponse)
cy.intercept(url, routeMatcher, staticResponse)
复制代码

Actions行为事件

ui自动化操作页面上的元素,常用的方法输入如文本,点击元素,清空文本,点击按钮。还有一些特殊的checkbox,radio,滚动条等。cypress都可以api操作

  • type(): 往输入框输入文本元素。
    focus(): 聚焦DOM元素。
    clear(): 清空DOM元素。
    rightclick(): 右击 DOM 元素
    select(): select 选项框

如何自动化

这里有两个思路,一是把测试提前放到本地,在测试分支合并代码commit阶段触发自动测试,这样可以把问题抛出的步骤提前。第二个是接入到ci/cd上,解放部署,不用等待测试。我先尝试了在commit阶段触发。

添加 npm 脚本

指定了要执行的测试案例,不指定的话,默认所有的测试都会执行。

// package.json
"scripts": {
    "test": "cypress run --spec cypress/integration/download.js",
    "pretest": "在这里执行打包,并运行服务的程序",
  },
复制代码

添加git钩子

我是利用husky创建git commit的钩子

下载husky, 我当前使用的版本是7.0.1:

npm install husky --save-dev
复制代码

在packgae.json中添加prepare脚本,prepare脚本会在npm install(不带参数)之后自动执行。也就是说当我们执行npm install安装完项目依赖后会执行 husky install命令,该命令会创建.husky/目录并指定该目录为git hooks所在的目录:

npm set-script prepare "husky install"
npm run prepare
复制代码

添加commit的钩子:

npx husky add .husky/pre-commit "npm test"
git add .husky/pre-commit
复制代码

这样就实现了,在每次git commit阶段都会执行测试用例。

存在的问题

  1. 在vs code终端提交代码可以顺利进行测试,但是使用vs code的源代码管理工具则会报错。husky类似的issue还是开放的。据说可以通过降版本解决。

  1. 在commit阶段触发会增加开发的等待时间,所以后续会尝试把测试接入到流水线上,和打包部署这些操作一样异步进行。

后续新的尝试有进度我会继续更新,欢迎指正。