前提

  • 已经熟练掌握了Cypress的基本知识,请参考自动化测试框架[Cypress概述]和自动化测试框架[各自动化测试框架比较]
  • 已经熟练掌握Cypress环境配置,请参考自动化测试框架[Cypress环境搭建与配置详解]
    和自动化测试框架[Cypress测试实例凸显其优势]
  • 已经熟练掌握Cypress框架结构,请参考自动化测试框架[Cypress框架拆解]
    和自动化测试框架[Cypress重试机制]
  • 已经熟练掌握Cypress内置测试报告,请参考自动化测试框架[Cypress内置测试报告详解]
  • 已经熟练掌握Cypress内置测试报告,请参考自动化测试框架[Cypress自定义测试报告详解]

Mocha简介

Cypress底层依赖于很多优秀的开源测试库,其中比较重要的就是Mocha,它是一个适用于Node.js和浏览器的测试框架,它使得异步测试变得简单灵活;而JavaScript是单线程异步执行的,这就产生了一种复杂的场景,因为异步往往无法直接判断函数的返回值是否符合预期,要验证异步函数的正确与否,就需要测试框架支持回调,利用Promise或者其他方式来验证异步函数的正确性,而Mocha就提供了出色的异步支持包括Promise

Cypress继承并扩展了Mocha对异步的支持,而Mocha提供了多种接口来定义测试套件,Hooks和Individual tests,即BDD、TDD、Exports、Qunit和Require

Cypress采用了Mocha的BDD语法,在Mocha中一个BDD风格的测试用例如下代码所示

describe('列表单元测试', function(){
before(function(){
// 实际测试前的准备工作
});
describe('#indexOf()', function(){
context('当元素找不到时', function(){
it('不应该抛出异常', function(){
(function(){
[1,2,3].indexOf(4);
}.should.not.throw());
});
it('应该返回-1', function(){
[1,2,3].indexOf(4).should.equal(-1);
});
});
context('当元素找不到时', function(){
it('应该返回该元素第一次在列表中出现的位置', function(){
[1,2,3].indexOf(3).should.equal(2);
});
});
});
beafter(function(){
// 测试结束后的收尾工作
});
});

Cypress将Mocha硬编码在框架中,因此在Cypress中编写的所有测试用例都基于Mocha提供的如下基本模块

  • describe(): 测试套件,在其中可以设定context(),可以包含多个测试用例it(),也可以嵌套测试套件
  • it():用于描述测试用例,一个测试套件可以不包括任何钩子函数Hook,但必须包含至少一个测试用例it()
  • context():是decribe()的别名,其行为方式与describe()相同,使用context()只是提供一种使测试更易于阅读和组织的方式
  • before()
  • beforeEach()
  • afterEach()
  • after()
  • .only()
  • .skip()

对于一条可执行的测试而言,describe()和it()是两个必要的组成部分,除了这两个功能模块外,其他功能模块对于一条可执行的测试来说都是可选的

Hook函数

Hook约定俗成的被称为钩子函数,Mocha提供了4中Hook函数

  • before():所有测试用例的统一前置动作,before()在一个describe()内只会执行一次,执行顺序是在所有的测试用例it()之前
  • after():所有测试用例的统一后置动作,after()在一个describe()内只会执行一次,执行顺序是在所有的测试用例it()之后
  • beforeEach():每个测试用例的前置动作,在每个测试用例执行前执行,一个describe()内,有多少个it()就会执行多少次该函数
  • afterEach():每个测试用例的后置动作,在每个测试用例执行后执行,一个describe()内,有多少个it()就会执行多少次该函数

如果熟悉Junit和Unittest或者Pytest,那么从函数名字就能看出这几个Hook函数的作用,利用Hook函数可以在测试开始时设置测试的前提条件,测试结束后做一些清理工作,实例代码如下

describe('钩子函数', function(){
before(function(){
// 当前测试中,所有测试用例执行之前运行
});
after(function(){
// 当前测试中,所有测试用例执行之后运行
});
beforeEach(function(){
// 当前测试中,每个测试用例执行前运行
});
afterEach(function(){
// 当前测试中,每个测试用例执行后运行
});
// 测试用例
});

测试用例的排除和包含

.skip()

排除测试套件/测试用例可以使用功能模块.skip(),在使用该功能特性的时候只需要在不想执行的describe()、context()、it()后跟.skip()即可,如下代码所示

describe.skip()
context.skip()
it.skip()

.only()

包含测试套件/测试用例可以使用功能模块.only(),当该特性出现时候,只有被他装饰的测试套件或用例会被执行,未被他装饰的不会被执行即便没有.skip(),如下代码所示

describe.only()
context.only()
it.only()

注意在使用.skip()和.only()装饰的时候,describe()优先级高于it()

动态忽略

///<reference types="cypress"/>
describe('登陆', function(){
const username='davie.yang'
const password='Ms123!@#'
context('HTML Table Login Testing', function(){
it('登陆成功,跳转dashboard', function(){
if(Cypress.env('runFlag')===1){
cy.visit('http://127.0.0.1:7077/login')
cy.get('input[name=username]').type(username)
cy.get('input[name=password]').type(password)
cy.get('form').submit()
cy.get('h1').should('contain', 'davie.yang')
}
else{
this.skip()
cy.log("runFlag为0,用例不执行")
}
})
})
})

然后输入命令执行该用例

yarn cypress:open -env runFlag=0

然后在Test Runner中选择该用例文件执行,则可以看到测试用例未被执行,因为命令中设定了runFlag为0,并且在this.skip()后的语句也不会被执行,this.skip()表示终止这条测试用例,所以后面的log语句也未执行

动态生成测试用例

所谓动态生成测试用例,也就是在Selenium中的数据驱动,测试步骤相同只是测试输入和输出不同,在Cypress中同样支持该特性,新建js文件,例如命名为​​testLogin.data.js​​用于存放测试输入

export const testLoginUser=[
{
summary:"Login pass",
username:"davie.yang",
password:"Ms123!@#"
},
{
summary:"Login fail",
username:"davie.yang1",
password:"Ms123!@#1"
},
]

在同级路径下创建测试文件,写入如下内容

///<reference types="cypress"/>
import{testLoginUser} from '../xxx/testLogin.data'
describe('登陆', function(){
const username='davie.yang',
const password='Ms123!@#',
context('HTML Table Login Testing', functino(){
for(const user of testLoginUser){
it(user.summary, function(){
cy.visit('http://localhost:7077/login')
cy.get('input[name=username]').type(user.username)
cy.get('input[name=password]').type(user.password)
cy.get('form').submit()
cy.get('h1').should('contain', user.username)
})
}
})
})

然后执行测试观察结果即可

断言

Cypress的断言基于Chai断言库,并且增加了对Sinon-Chai、Chai-jQuery断言库的支持,并且Cypress支持多种风格的断言包括BDD风格(expect/should)和TDD风格(assert)格式的断言

针对长度的断言

//重试, 直到找到3个匹配的<li.selected>
cy.get('li.selected').should('hava.length',3)

针对类的断言

//重试,直到input元素没有类被disabled为止或直到超时为止
cy.get('form').find('input').should('not.hava.clacc', 'disabled')

针对值的断言

//重试,直到textarea的值为'davieyang'
cy.get('testarea').should('hava.value', 'davieyang')

针对文本内容的断言

//重试,知道这个span不包含“click me”字样
cy.get('a').parent('span.help').should('not.contain', 'click me')

针对元素是否可见的断言

//重试,直到这个button可见为止
cy.get('button').should('be.visible')

针对元素存在与否的断言

//重试,直到id为loading的空间不存在
cy.get('#loading').should('not.exist')

针对元素状态的断言

//重试,直到这个radio button是选中状态为止
cy.get(':radio').should('be.checked')

针对CSS的断言

//重试,直到completed的这个类有匹配的CSS为止
cy.get('.completed').should('hava.css', 'text-decoration', 'line-through')

针对回调函数的断言

假设源HTML如下

<div class="main-davieyang heading-yangdavie83">introduction</div>

如果判断类名是否一定含有heading字样,则

cy.get('div').should(($div)=>{
expect($div).to.have.length(1)
const className = $div[0].className
//检查类名匹配通配符/heading-/
expect(className).to.match(/heading-/)
})

更详细的可参考​​chai​​​和​​sinon-chai​​​和​​chai-jquery​​​和​​chaijs-assert​

Test Runner

自动化测试框架[Cypress测试用例]_用例排除


Cypress TestRunner是众多测试框架中特殊的存在,它使得测试在一个独特而强大的交互式运行器中运行,在同一个窗口内不仅可以看到测试的实际执行,还能看到被测应用的状态变化,如图所示大致分为两大部分

  • 第一个区域中从左到右分别是
  • 测试运行结果和状态,包括测试用例执行的成功和失败条数以及每个测试用例运行的时间
  • Cypress元素定位辅助器(Selector Playground),就是蓝色圈出的按钮,点击该按钮便可以识别页面元素
  • URL预览区,展示的是测试并令执行时被测应用程序所处的URL,它可以更方便的查看测试路由
  • 视窗大小,可以通过在cypress.json中配置viewportWidth和viewportHeight两个配置项来控制视窗大小
  • 第二个区域分为两个部分,左侧是Command Log,也就是测试用例中各个命令执行的详细情况;右侧伴随着Command的执行应用程序的实时状态预览