cypress
下载与安装
安装方式一
- 安装node.js
- 因为npm直接下载会也很慢,所以先修改下载源
1. 执行命令 npm config set registry http://registry.npm.taobao.org
2. 查看是否更改成功 npm config get registry - 本地创建一个名为cypresses的目录
- 在该目录下执行 npm install cypress --save-dev
- .bin目录下启动cypress cypress open
- 启动过后会发现缺少文件 npm WARN saveError ENOENT: no such file or directory, open 'D:Cypresspackage.json'
- 在新建的目录下创建package.json,(如果有直接添加如下代码)
- 根目录下执行命令 npm run cypress:open
{
"scripts": {
"cypress:open": "cypress open"
}
}
安装方式二
执行命令 yarn add cypress --dev
基本介绍
简介
- 基于JavaScript的前端测试工具,可以对浏览器中运行的任何内容进行快速、简单、可靠的测试
- Cypress 是自集成的,提供了一套完整的端到端测试,无须借助其他外部工具,安装后即可快速地创建、编写、运行测试用例,且对每一步操作都支持回看
- 不同于其他职能测试 UI 层的前端测试工具,Cypress 允许编写所有类型的测试,覆盖了测试金字塔模型的所有测试类型【界面测试,集成测试,单元测试】
- Cypress 底层协议不采用 WebDriver
原理
- 大多数测试工具是通过外部浏览器运行,并在网络上执行远程命令来运行,主要是因为webdriver底层通信协议基于JSON Wire Protocol,运行需要网络通信.
- 但是cypress和webdriver的方式完全相反,它与应用程序在相同的声明周期里执行.
cypress运行更快的原因
- cypress测试代码和应用程序均运行在由cypress全权控制的浏览器中
- 且它们运行在同一个Domain下的不同iframe中,所以cypress的测试代码可以直接操作DOM、window objects、local storages而无需通过网络访问
cypress稳定性、可靠性更高的原因
- cypress可以在网络层进行及时读取和更改网络流量的操作
- cypress背后是node.js process控制的proxy进行转发,使得cypress不仅可以修改进出浏览器的所有内容,还可以更改可能影响自动化操作的代码.
- cypress相对于其他测试工具来说,能从根本上控制整个自动化测试的流程.
cypress的特性
- cypress在测试代码运行时会自动拍照,也就是等测试结束之后,用户可以在cypress提供的test runner,通过悬停在命令上的方式看运行时的步骤
- 实时重新加载,当测试代码修改保存后,cypress会自动加载改动的地方,重新运行测试
- 可调试性,当测试失败时,可以直接通过开发者工具进行调试
- 自动等待,使用cypress,就无需与selenium一样在测试中添加强制、显示等待以及隐式等待了,cypress会自动等待元素到可靠操作状态时菜执行命令或断言
- 截图和视频,cypress在测试运行失败时会自动截图,在无头运行时,会录制整个测试套件的视频
解析Cypress的默认文件结构
fixture测试夹具
简介
- 测试夹具通常配合cy.fixture()使用
- 主要用来存储测试用例的外部静态数据
- fixtures默认就在cypress/fixtures目录下,单也可以配置到另一个目录
外部静态数据的详解
- 测试夹具的静态数据通常存储在.json文件中,如自动生成的examples.json
- 静态数据通常是某个网络请求对应的响应部分,包括HTTP状态码和返回值,一般是复制过来更改而不是自己手工填写.
- 如果你的测试需要对某些外部接口进行访问并依赖它的返回值,则可以使用测试夹具而无须真正访问这个接口(用过mock的能有体会)
使用测试夹具的好处
- 消除了对外部功能模块的依赖.
- 已编写的测试用例可以使用测试夹具提供的固定返回值,并且你确切知道这个返回值是你想要的.
- 因为无需真正的发送网络请求,所以测试更快.
test file 测试文件
- 测试文件就是测试用例,默认位于cypress/integration,但也可以配置到另一个目录
- 所有在integration文件下,且文件格式是以下的文件都将被Cypress识别为测试文件.
- 创建好后,Cypress的Test Runner刷新就能看到对应测试文件
plugin file 插件文件
前言
- Cypress的优点就是在运行是在浏览器之内,使得Cypress跟其他的测试框架相比,有显著的架构优势
- 虽然提供了可靠性测试,但也使在浏览器之外进行通信更加困难
插件文件的诞生与应用
- 为了解决跟外部通信,可以修改或扩展Cypress的内部行为(如:动态修改配置信息和环境变量等),也可以自定义自己的插件.
- 默认情况,插件位于cypress/plugins/index.js中,单可以配置到另一个目录
- 为了方便,每个测试文件运行之前,Cypress都会自动加载插件文件
- 我们一般通过动态修改来自cypress.json、cypress.env.json,CLI或系统环境变量的已解析配置和环境变量以及修改特定浏览器的启动参数、将消息直接从测试代码传递到后端
support file 支持文件
简介
- 支持文件目录防止可重用配置项,如底层通用函数或全局默认配置
- 支持文件默认位于cypress/support/index.js中,单可以配置到另一个目录
- 为了方便,每个测试文件运行之前,Cypress都会自动加载支持文件
使用
只需要在cypress/support/index.js文件添加beforeEach()函数即可,这将实现每次测试运行前能打印出所有的环境给变量信息.如下
beforeEach(function () {
cy.log('当前环境变量为${JSON.stringify(Cypress.env())}')
});
自定义Cypress
前言
- Cypress不仅支持用户自定义文件结构,还支持用户自定义Cypress的各项配置
- Cypress可以通过cypress.json文件来实现各项配置的自定义(默认文件是空的)
全局配置项
配置项 | 默认值 | 描述 |
---|---|---|
baseUrl | null | url前缀,cy.visit()或cy.request()命令经常会用,它的值通常被设置为系统主域名 |
env | {} | 任何想用做环境变量的变量都可以设置在env中 |
ignoreTestFiles | *.hot-update.js | 忽略某些测试用例:被此项规则匹配的测试用例不会被执行,建议使用http://globtester.com来测试哪些文件匹配 |
numTestsKeptLnMemory | 50 | 保留在内存中的测试用例(主要是:快照和命令数据)的数量,如果在测试运行期间浏览器的内存消耗很高,请减少这个数字 |
port | null | Cypress占用的端口,默认随机生成 |
reporter | spec | Cypress运行期间使用哪个reporter,有Mocha内置的reporter、teamcity、junit等 |
reporterOptions | null | reporter支持的选项配置 |
testFiles | **/. | 要加载的测试文件,可以指定具体文件,也可以模糊匹配 |
watchForFileChanges | true | Cypress在运行中自动检测文件变化,当有变化时,自动重新运行受影响的测试用例(建议打开) |
超时Timeouts相关
- 超时是必须要了解的核心概念
- 几乎所有命令都可以某种方式超时
- 所有断言,无论它们是默认断言还是自己添加的断言都具有相同的超时时间
配置项 | 默认值 | 描述 |
---|---|---|
defaultCommandTimeout | 4000 | 命令默认超时时间,ms为单位 |
execTimeout | 60000 | cy.exec()命令期间,等待系统命令完成执行的超时时间 |
taskTimeout | 60000 | cy.task()命令期间,等待系统命令完成执行的超时时间 |
pageLoadTimeout | 60000 | 等待页面加载或cy.visit()、cy.go()、cy.reload()命令来触发它们的页面加载时间的时间 |
requestTimeout | 5000 | 等待cy.wait()命令中的XHR请求发出的超时时间 |
responseTimeout | 30000 | 如下命令的响应超时时间cy.request()、cy.wait()、cy.fixture()、cy.getCookie()、cy.getCookies()、cy.setCookie()、cy.clearCookie()、cy.clearCookies()、cy.screenshot() |
文件夹/文件相关
相对于默认文件结构来说,Cypress支持用户自定义的文件结构
配置项 | 默认值 | 描述 |
---|---|---|
fileServerFolder | 项目根目录 | fileserver目录 |
fixturesFolder | cypress/fixtures | 测试夹具默认文件夹,可更改默认值为false来禁用它 |
integrationFolder | cypress/integration | 测试用例默认文件夹 |
pluginsFile | cypress/plugins/index.js | 插件文件默认文件夹 |
screenshotsFolder | cypress/screenshots | 由测试失败或cy.screenshot()命令引发的截图,截图默认文件夹 |
supportFile | cypress/support/index.js | 测试加载之前要加载的路径 |
Cypress.config()
除了直接在cypress.json文件里更改配置项之外,Cypress还允许我们通过Cypress.config()去获取或覆盖某些配置项,语法如下:
// 获取所有config信息
Cypress.config()
// 获取指定配置项的信息
Cypress.config(name)
// 更改指定配置项的默认值
Cypress.config(name, value)
// 使用对象字面量(object literal)设置多个配置项
Cypress.config(object)
Cypress的重试机制
问题前言
现在的web应用基本都是异步的,出现一下的情况就要做重试的操作
- 如果断言发生时,应用程序尚未更新DOM怎么办?
- 如果断言发生时,应用程序正在等待其后端响应,而导致页面暂无结果怎么办?
- 如果断言发生时,应用程序正在进行密集计算,而导致页面未及时更新怎么办?
上述情况在测试中经常会发生,一般处理方法是在断言前增加断言时间(或像selenium一样中使用显式或者隐式等待),不过仍然会出现测试再次失败的情况。、
Cypress如何处理问题前言
- cy.get()命令之后的断言通过,则该命令成功执行完成。
- cy.get()命令之后的断言失败,则cy.get()命令会自动重新查询web应用程序的DOM树,然后Cypress将再次尝试对cy.get()返回的元素进行断言。
- 如果断言仍然失败,cy.get()仍然会重新查询DOM树,以此类推
- 直到断言成功或cy.get()命令超时
Cypress的处理机制有点像selenium的显示等待,只不过Cypress是全局的,不用针对元素去单独识别,Cypress这种自动重试机制避免了在测试代码中编写硬编码等待(强制等待),使测试代码更加健壮。
多重断言
- 在日常测试汇总,有时候需要多重断言,即获取元素后跟多个断言。
- 在多重断言中,Cypress将按顺序进行断言,也就是当第一个断言通过后,会进行第二个断言,通过后进行第三个断言,依次类推。
重试机制的条件
- Cypress并不会重试所有的命令,当命令可能改变被测应用程序的状态时,该命令将不会重试(比如点击输入等)
- Cypress仅会重试那些查询DOM的命令:cy.get()、find()、contains()等。
Mocha介绍
前言
- Cypress底层依赖于很多优秀的开源框架,其中就有Mocha
- Mocha是一个适用于Node.js和浏览器的测试框架,它使得异步测试变的简单。
JS语言带来的问题
JS是单线程异步执行的,这使得测试变的复杂,因为无法像测试同步执行的代码那样,直接判断函数的返回值是否符合预期(因为给函数赋值时函数可能并未执行)
如何验证异步函数的正确性
- 需要测试框架爱支持回调Promise或者其他方式来验证异步函数的正确性。
- Mocha提供了出色的异步支持报货Promise,从而使得异步测试变得简单。
Mocha提供了什么
- 多种接口来定义测试套件,Hooks,单个测试
- BDD,行为驱动开发
- TDD,测试驱动开发
- Exports、QUnit、Require
常见Mocha模块
- describe()
- context()
- it()
- before()
- beforEach()
- afterEach()
- after()
- .only()
- .skip()
必要的组成部分
- 对于每一条可执行的测试用例,有两个必要的组成部分,describe()代表测试套件,里面可以设定context(),也可以包括多个测试用例it(),还能嵌套子测试套件
- 一个测试条件可以不包括任何钩子函数,但必须要包含至少一条测试用例it()
- context()是describe()的别名,行为方式是一致的,可以直接使用context()代替describe()
钩子函数Hook
Mocha提供的Hook
- before()
1. 该测试套件下,所有测试用例的统一前置操作
2. 它在一个describe()或context()内只会执行一次,在所有it()之前都会执行 - beforeEach()
1. 该测试套件下,每个测试用力的前置操作
2. 一个describe()或contest()内有多少个测试用例it(),就会执行几次beforeEach() - afterEach()
1. 该测试套件下,每个测试用例的后置操作
2. 一个describe()或context()内有多少个测试用例it(),就会执行几次afterEach() - after()
1. 该测试套件下,所有测试用例的统一后置操作
2. 在一个describe()或context()内只会执行一次,在所有it()之前执行。
hook的作用
利用钩子函数可以在所有测试用例执行前做一些预置操作,或者在测试结束后做一些后置操作
跳过执行与指定执行
- 在做自动化测试中,跳过执行某些测试用例
1. 通过.skip()可以完成跳过执行测试套件或测试用例
2. 通过describe.skip()或者context.skip()来跳过不需要执行的测试套件
3. 通过it.skip()来跳过不需要执行的测试用例 - 同样也可以只运行某些指定的测试用例
1. 通过.only()可以完成指定执行测试套件或测试用例,当存在.only()指定某个测试套件或测试用例时,只有这个测试套件或测试用例会被执行,其他未加.only()的测试套件或测试用例都不会执行
2. 通过describe.only()或者context.only()来指定需要执行的测试套件(不过注意的是即使添加了.only()的子套件,即使父套件没有添加,它也会执行。添加了.only()的套件,该套件下的所有测试用例默认都会执行)
3. 通过it.only()来指定需要执行的测试用例(如果当前测试套件下有it.only(),那么即使存在测试套件添加了.only(),该测试套件也不会执行。如果同个测试套件下有多个it.only()时,都会执行)
断言
Cypress的断言基于Chai断言库,并且增加了对Sinon-Chai、Chai-jQuery断言库的支持,其中就包括BDD和TDD格式的断言。
长度
// 重试,直至找到3个匹配的<li.selected>
cy.get('li.selected').should('have.length',3)
类
// 重试,直至这个input不再有disabled的class
cy.get('form').find('input').should('not.hava.class','disabled')
值
// 重试,直至这个textarea的值为 poloyy
cy.get('textarea').should('have.value','poloyy')
文本内容
// 重试,直至这个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状态是checked
cy.get(':radio').should('be.checked')
针对CSS
// 重试,直至complete这个类有匹配的css为止
cy.get('.completed').should('have.css','text-decoration','line-through')
环境变量
baseUrl
- 通过环境变量设置测试套件访问的URL,这只是其中的一种方式
- Cypress可以通过配置baseUrl取代环境变量的方式,当配置了baseUrl,测试套件中的cy.visit()、cy.request()都会自动以baseUrl的值作为前缀
- 并且,当需要访问某些网址或者发起接口请求时,在代码中就可以不用在指定请求的host或者url
在cypress.json文件进行配置就可以,如下
{
"baseUrl": "http://localhost:7077"
}
如何调用
需要注意的是:在调用visit或request时,不管如何都需要穿个空的字符串
cy.visit("")
通过环境变量覆盖baseUrl
即使配置了baseUrl,也可以通过环境变量来覆盖它
CYPRESS_baseUrl=https://staging.app.com cypress run
设置环境变量的几种方式
最常见的做法就是在测试运行时,使用Cypress.env()访问环境变量的值
cypress.json中设置
在cypress.json的env键下设置的任何key:value都是环境变量
cypress.json代码
{
"baseUrl": "http://localhost:7077",
"env": {
"foor": "bar",
"key": "value"
}
}
测试代码
// 获取所有环境变量
Cypress.env()
// 获取某个环境变量的值
Cypress.env("foor")
创建cypress.env.json文件
- 创建自己的cypress.env.json文件,Cypress将会自动检查它
- 里面的值会覆盖cypress.json中重名的环境变量
- 创建与cypress.json统计目录下
环境变量操作实例
// 测试用例代码
describe('测试百度', function () {
context('搜索功能', function () {
beforeEach(function () {
cy.visit("")
});
it('搜索QQ ', function () {
cy.get(Cypress.env("name")).type("qq").should('have.value', 'qq')
});
it('搜索谷歌', function () {
cy.get('#kw').type('谷歌').should('have.value', '谷歌')
Cypress.config()
});
})
});
// 第一种设置变量的方式 在cypress.json添加name的参数("env"必须要加上)
{
"baseUrl": "https://www.baidu.com",
"env": {
"name": "#kw"
}
}
// 第二种设置变量的方式 根目录下创建cypress.env.json,添加name的参数
{
"name": "#kw"
}
加载文件固定数据(.cypress()命令)
加载位于文件中的一组固定数据
基本语法格式
cy.fixture(filePath)
cy.fixture(filePath, encoding)
cy.fixture(filePath, options)
cy.fixture(filePath, encoding, options)
参数解释
- filepath,文件路径默认会从cypress/fixtures 文件夹下找文件
- encoding
- ascii
- base64
- binary
- hex
- latin1
- utf8
- utf-8
- ucs2
- ucs-2
- utf16le
- utf-16le
基本实例
// 从 users.json 文件中加载数据
cy.fixture('users').as('usersJson')
cy.fixture('logo.png').then((logo) => {
// 加载 logo.png
})
注意使用方式
一般情况下,我们使用命令cy.fixture('admin').as('adminJSON'),admin.json是不指定后缀,直接使用admin,但是cypress有自己的读取规则,读取顺序如下:
- cypress/fixtures/admin.json
- cypress/fixtures/admin.js
- cypress/fixtures/admin.coffee
- cypress/fixtures/admin.html
- cypress/fixtures/admin.txt
- cypress/fixtures/admin.csv、
- cypress/fixtures/admin.png
- cypress/fixtures/admin.jpg
- cypress/fixtures/admin.jpeg
- cypress/fixtures/admin.gif
- cypress/fixtures/admin.tif
- cypress/fixtures/admin.tiff
- cypress/fixtures/admin.zip
读取数据的实例
cypress/fixture/下创建user.json文件,并写入如下内容
[
{
"name": "#kw"
}
]
编写测试用例
describe('测试百度', function () {
context('测试百度搜索', function () {
beforeEach(function () {
cy.visit('https://www.baidu.com')
});
it('测试qq', function () {
cy.fixture('user').then(function(user){
// 怎么获取json,就怎么写
cy.get(user[0].name).type('qq')
cy.get("#su").click()
})
});
});
});
元素定位选择器
对于难以使用普通方式定位的元素,提供了以下两种选择器
Cypress.$('#main2')
// 等价于
cy.get('#main2')
常规CSS选择器
选择器 | 例子 | 例子描述 | CSS |
---|---|---|---|
.class | .intro | 选择 class="intro" 的所有元素。 | 1 |
#id | #firstname | 选择 id="firstname" 的所有元素。 | 1 |
* | * | 选择所有元素。 | 2 |
element | p | 选择所有 元素。 |
1 |
element,element | div,p | 选择所有 元素和所有 元素。 |
1 |
element element | div p | 选择 元素内部的所有 元素。 |
1 |
element>element | div>p | 选择父元素为 元素的所有 元素。 |
2 |
element+element | div+p | 选择紧接在 元素之后的所有 元素。 |
2 |
[attribute] | [target] | 选择带有 target 属性所有元素。 | 2 |
[attribute=value] | [target=_blank] | 选择 target="_blank" 的所有元素。 | 2 |
[attribute~=value] | [title~=flower] | 选择 title 属性包含单词 "flower" 的所有元素。 | 2 |
[attribute|=value] | [lang|=en] | 选择 lang 属性值以 "en" 开头的所有元素。 | 2 |
:link | a:link | 选择所有未被访问的链接。 | 1 |
:visited | a:visited | 选择所有已被访问的链接。 | 1 |
:active | a:active | 选择活动链接。 | 1 |
:hover | a:hover | 选择鼠标指针位于其上的链接。 | 1 |
:focus | input:focus | 选择获得焦点的 input 元素。 | 2 |
:first-letter | p:first-letter | 选择每个 元素的首字母。 |
1 |
:first-line | p:first-line | 选择每个 元素的首行。 |
1 |
:first-child | p:first-child | 选择属于父元素的第一个子元素的每个 元素。 |
2 |
:before | p:before | 在每个 元素的内容之前插入内容。 |
2 |
:after | p:after | 在每个 元素的内容之后插入内容。 |
2 |
:lang(language) | p:lang(it) | 选择带有以 "it" 开头的 lang 属性值的每个 元素。 |
2 |
element1~element2 | p~ul | 选择前面有 元素的每个
|
3 |
[attribute^=value] | a[src^="https"] | 选择其 src 属性值以 "https" 开头的每个 元素。 | 3 |
[attribute$=value] | a[src$=".pdf"] | 选择其 src 属性以 ".pdf" 结尾的所有 元素。 | 3 |
[attribute**=value*] | a[src*="abc"] | 选择其 src 属性中包含 "abc" 子串的每个 元素。 | 3 |
:first-of-type | p:first-of-type | 选择属于其父元素的首个 元素的每个 元素。 |
3 |
:last-of-type | p:last-of-type | 选择属于其父元素的最后 元素的每个 元素。 |
3 |
:only-of-type | p:only-of-type | 选择属于其父元素唯一的 元素的每个 元素。 |
3 |
:only-child | p:only-child | 选择属于其父元素的唯一子元素的每个 元素。 |
3 |
:nth-child(n) | p:nth-child(2) | 选择属于其父元素的第二个子元素的每个 元素。 |
3 |
:nth-last-child(n) | p:nth-last-child(2) | 同上,从最后一个子元素开始计数。 | 3 |
:nth-of-type(n) | p:nth-of-type(2) | 选择属于其父元素第二个 元素的每个 元素。 |
3 |
:nth-last-of-type(n) | p:nth-last-of-type(2) | 同上,但是从最后一个子元素开始计数。 | 3 |
:last-child | p:last-child | 选择属于其父元素最后一个子元素每个 元素。 |
3 |
:root | :root | 选择文档的根元素。 | 3 |
:empty | p:empty | 选择没有子元素的每个 元素(包括文本节点)。 |
3 |
:target | #news:target | 选择当前活动的 #news 元素。 | 3 |
:enabled | input:enabled | 选择每个启用的 input 元素。 | 3 |
:disabled | input:disabled | 选择每个禁用的 input 元素 | 3 |
:checked | input:checked | 选择每个被选中的 input 元素。 | 3 |
:not(selector) | :not(p) | 选择非 元素的每个元素。 |
3 |
::selection | ::selection | 选择被用户选取的元素部分。 | 3 |
jQuery选择器
元素 元素选择器 | 实例 | 选取 |
---|---|---|
* | $("*") | 所有元素 |
#id | $("#lastname") | id="lastname" 的元素 |
.class | $(".intro") | 所有 class="intro" 的元素 |
element | $("p") | 所有 元素 |
.class.class | $(".intro.demo") | 所有 class="intro" 且 class="demo" 的元素 |
:first | $("p:first") | 第一个 元素 |
:last | $("p:last") | 最后一个 元素 |
:even | $("tr:even") | 所有偶数 |
:odd | $("tr:odd") | 所有奇数 |
:eq(index) | $("ul li:eq(3)") | 列表中的第四个元素(index 从 0 开始) |
:gt(no) | $("ul li:gt(3)") | 列出 index 大于 3 的元素 |
:lt(no) | $("ul li:lt(3)") | 列出 index 小于 3 的元素 |
:not(selector) | $("input:not(:empty)") | 所有不为空的 input 元素 |
:header | $(":header") | 所有标题元素 - |
:animated | 所有动画元素 | |
:contains(text) | $(":contains('W3School')") | 包含指定字符串的所有元素 |
:empty | $(":empty") | 无子(元素)节点的所有元素 |
:hidden | $("p:hidden") | 所有隐藏的 元素 |
:visible | $("table:visible") | 所有可见的表格 |
s1,s2,s3 | $("th,td,.intro") | 所有带有匹配选择的元素 |
[attribute] | $("[href]") | 所有带有 href 属性的元素 |
[attribute=value] | $("[href='#']") | 所有 href 属性的值等于 "#" 的元素 |
[attribute!=value] | $("[href!='#']") | 所有 href 属性的值不等于 "#" 的元素 |
[attribute$=value] | (("[href)='.jpg']") | 所有 href 属性的值包含以 ".jpg" 结尾的元素 |
:input | $(":input") | 所有 input 元素 |
:text | $(":text") | 所有 type="text" 的 input 元素 |
:password | $(":password") | 所有 type="password" 的 input 元素 |
:radio | $(":radio") | 所有 type="radio" 的 input 元素 |
:checkbox | $(":checkbox") | 所有 type="checkbox" 的 input 元素 |
:submit | $(":submit") | 所有 type="submit" 的 input 元素 |
:reset | $(":reset") | 所有 type="reset" 的 input 元素 |
:button | $(":button") | 所有 type="button" 的 input 元素 |
:image | $(":image") | 所有 type="image" 的 input 元素 |
:file | $(":file") | 所有 type="file" 的 input 元素 |
:enabled | $(":enabled") | 所有激活的 input 元素 |
:disabled | $(":disabled") | 所有禁用的 input 元素 |
:selected | $(":selected") | 所有被选取的 input 元素 |
:checked | $(":checked") | 所有被选中的 input 元素 |
查找页面元素的基本方法
.get()
在DOM中查找selector对应的DOM元素,如果匹配多个元素,则返回多个元素.
// 以选择器定位
cy.get(selector)
// 以别名定位,后续会讲到
cy.get(alias)
.find()
在DOM中搜索已经被定位到的元素的后代,并将匹配到的元素返回一个新的jQuery对象(不是元素对象)
//先获取id为ai的元素,在后代找到class为name元素
cy.get("#ai").find(".name")
.contains()
用来获取包含指定文本的DOM元素,只会返回匹配的第一个元素
cy.contains(content)
cy.contains(selector, content)
查找页面元素的辅助方法
辅助定位元素方法 | 描述 |
---|---|
.children() | 用来获取DOM元素的子元素 |
.parent() | 用来获取DOM元素的第一层父元素 |
.parents() | 用来获取DOM元素的所有父元素,包括爷爷级别、祖父级别 |
.siblings() | 用来获取DOM元素的所有同级元素 |
.first() | 匹配给定的DOM元素列表中的第一个元素,如果是单个DOM元素调用此方法,则返回自己 |
.last() | 匹配给定的DOM元素列表中的最后一个元素,如果是单个DOM元素调用此方法,则返回自己 |
.next() | 获取给定的DOM元素后面紧跟的下一个同级元素 |
.nextAll() | 获取给定的DOM元素后面紧跟的所有同级元素 |
.nextUntil(selector) | 获取给定的DOM元素后面紧跟的所有同级元素,知道遇到Until里定义的元素为止,表示碰到了3,实际上获取的是2 |
.prev() | 获取给定的DOM元素前面紧跟的上一个同级元素 |
.prevAll() | 获取给定的DOM元素前年紧跟的所有同级元素 |
.prevUntil() | 获取给定的DOM元素前面紧跟的所有同级元素,知道遇到Until里定义的元素位置,表示碰到了3,实际上获取的是4 |
.each(callbackFn) | 用来遍历数据或者及其类似的结构(对象有length即可) |
.eq() | 在元素或者数组中的特定所引出获取DOM元素,作用跟:nth-child()选择器一样,只不过下标从0开始 |
cypress操作页面元素
命令 | 作用 |
---|---|
.type() | 输入框输入文本元素 |
.focus() | 聚焦DOM元素 |
.blur() | DOM元素失去焦点 |
.clear() | 清空DOM元素 |
.submit() | 提交表单 |
.click() | 点击DOM元素 |
.dbclick() | 双击 |
.rightclick() | 右键点击 |
.check() | 选中单选框、复选框 |
.uncheck() | 取消选中复选框 |
.select() | select options选项框 |
.scrollIntoView() | 将DOM元素滑动到可视区域 |
.trigger() | DOM元素上触发事件 |
cy.scrollTo() | 滑动滚动条 |
.type()
基本语法格式
// 输入文本
.type(text)
// 带参数输入文本
.type(text, options)
正常文本实例
it('正常实例', function () {
cy.get('input').type("正常实例")
});
特殊字符实例
it('输入特殊字符', function () {
cy.get('input').type("{del}")
});
所支持特殊字符表
特殊字符 | 描述 |
---|---|
{{} | 输入{ |
{backspace} | 键盘的backspace键 |
{del} | 键盘的del键 |
{downarrow} | 键盘的↓键 |
{leftarrow} | 键盘的←键 |
{rightarrow} | 键盘的→键 |
{uparrow} | 键盘的↑键 |
{end} | 键盘的end键,将光标移动到行尾 |
{enter} | 回车键 |
{esc} | 退出键 |
{home} | 键盘的home键,将光标移动到行首 |
{insert} | 的键盘insert键 |
{pagedown} | 键盘的pagedown,向下滚动 |
{pageup} | 键盘pageup,向上滚动 |
{selectall} | 通过创建选择范围来选择所有文本 |
.focus()、.blur()
聚焦与失焦DOM元素
- 必须是DOM元素才能调用.focus()、.blur()方法,不一定是输入框
- 确保DOM元素是可聚焦、失焦的
基本语法格式
.focus()
.focus(options)
.blur()
.blur(options)
基本实例
//input进行聚焦
cy.get('input').first().focus()
// 输入内容后,再让输入框失焦
cy.get('[type="email"]').type('me@email.com').blur()
// 先聚焦再失焦
cy.get('[tabindex="1"]').focus().blur()
所支持的参数传递
Options | Default | Description |
---|---|---|
log | true | 在命令日志中显示命令 |
timeout | defaultCommandTimeout | 执行.type()之前的等待超时时间 |
force | false | 强制执行操作,禁用等待执行操作 |
.clear()
- 表示清空输入框的所有内容
- .clear()等价于.type("{selectall}{backspace}")
基本语法格式
.clear()
.clear(options)
基本实例
cy.get('.action-clear').type('Clear this text')
.should('have.value', 'Clear this text')
.clear()
.should('have.value', '')
// 下面.clear()等价于.type('{selectAll}{backspace}')
it('clear()', function () {
cy.get('input').type('qq').clear()
});
it('.type({},{})', function () {
cy.get('input').type('{selectAll}{backspace}')
});
所支持的参数传递
Option | Default | Description |
---|---|---|
log | true | 在命令日志中显示命令 |
force | false | 强制还行操作,禁用等待执行操作 |
timeout | defaultCommandTimeout | 等待执行.clear()之前的超时时间 |
.submit()
必须是form元素才能调用.submit()
基本语法格式
.submit()
.submit(options)
基本实例
it('form操作', function () {
cy.get('form').submit()
});
所支持的参数传递
Option | Default | Description |
---|---|---|
log | true | 在命令日志中显示命令 |
timeout | defaultCommandTimeout | 等待执行.submit()之前的超时时间 |
.click()、.dblclick()、.rightclick()
三个点击事件的不同用法大同小异
基本语法格式
// 单击某个元素
.click()
// 带参数的单击
.click(options)
// 在某个位置点击
.click(position)
// 在某个位置点击,且带参数
.click(position, options)
// 根据页面坐标点击
.click(x, y)
// 根据页面坐标点击,且带参数
.click(x, y, options)
基本实例
// 单击DOM元素(常规用法)
cy.get('#action-canvas').click('bottom')
// 双击DOM元素
cy.get('.action-div').dblclick().should('not.be.visible')
// 右击DOM元素
cy.get('.action-div').rightclick().should('not.be.visible')
所支持的位置参数传递
总共有9个参数值
it('.click(position)', function () {
cy.get('#main1').click('top')
cy.get('#main1').click('topLeft')
cy.get('#main1').click('topRight')
cy.get('#main1').click('left')
cy.get('#main1').click('right')
cy.get('#main1').click('bottom')
cy.get('#main1').click('bottomLeft')
cy.get('#main1').click('bottomRight')
cy.get('#main1').click('center')
});
所支持的横纵坐标参数
it('.click(x,y)', function () {
cy.get('#main1').click(15,17)
});
所支持的参数传递
- cypress可以通过Test Runner的快照找到阻止DOM元素交互的情况,但某些情况下可能会阻碍测试的进行
- 场景:有一个嵌套的导航结构,用户必须将鼠标hover在一个非常特定的模式中,才能拿到所需的链接,当测试的时候,我们只是想获取链接而已,过多的繁琐操作会导致测试失败.
- 当设置force:true时,cypress会强制操作命令的发生,避开前面的所有检查,可以传递{force:true}给大多数操作命令
option | 默认值 | 描述 |
---|---|---|
log | true | 在命令日志中显示命令 |
force | false | 强制执行操作,禁用等待执行操作 |
multiple | false | 连续点击多个元素 |
timeout | defaultCommandTimeout | 执行.click()之前的等待超时时间 |
混合实例
it('.click(options)', function () {
cy.get('#btn').click({force:true})
});
it('.click(position,options)', function () {
cy.get('#btm').click('top',{force:true})
});
it('.click(x,y,options)', function () {
cy.get('#btn').click(15,17,{force:true})
});
it('.click({multiple:true})', function () {
cy.get('btn').click({multiple:true})
});
.check()、.uncheck()
- 检查选项框是否被选中,针对input标签的单选框或复选框达到选中的作用
- 和check()作用相反,取消选中复选框,注意的是,只有复选框checkbox可以使用uncheck(),其他的语法格式、写法和check()一样.
基本语法格式
// 所有匹配到的选择框都会被选中一遍
.check()
// 选中指定值的选项
.check(value)
// 选中多个选项(多选框)
.check(values)
// 所有匹配到的选择框都会被选中一遍,且带参数
.check(options)
// 选中指定值的选项,且带参数
.check(value, options)
// 选中多个选项(多选框),且带参数
.check(values, options)
基本实例
it('case1', function () {
// 表示选中所有的复选框
cy.get('[type="checkbox"]').check()
// 表示只选中第一个复选框
cy.get('[type="checkbox"]').first().check()
});
附:只有type类型为checkbox或者radio才可以调用.check()
.check(value)
it('case1', function () {
// 表示选中value值为sz的复选框
cy.get('[type="checkbox"]').check("sz")
// 表示选中value值为monkey的复选框
cy.get('[type="checkbox"]').check("money")
});
.check(values)
it('case1', function () {
// 表示选中value值为sz和money的复选框
cy.get('[type="checkbox"]').check(["sz","money"])
});
可支持的带参数传递
Option | Default | Description |
---|---|---|
log | true | 在命令日志中显示命令 |
timeout | defaultCommandTimeout | 等待执行.submit()之前的超时时间 |
.select()
基本语法格式
// 选中指定值的选项
.select(value)
// 选中指定值的多个选项
.select(values)
// 选中指定值的选项,且带参数
.select(value, options)
// 选中指定值的多个选项,且带参数
.select(values, options)
基本实例
// 选择值为user的option(注意的是get里面的select必须是元素)
cy.get('select').select('user')
.select(value)
it('.select(value)', function () {
cy.get('select').eq(0).select('1').should('contain.text','apples')
cy.get('select').eq(0).select('orange').should('contain.value','2')
});
.select(values)
//选中多选框中的多条数据
it('.select(values)', function () {
cy.get('select').eq(0).select(['1', '2']).invoke('val').should('deep.equal', ['1', '2'])
});
.select(value,options)
//因为默认的第二个元素是隐藏的所以不加force:true会报错,并且在报错的时候会提示加上force:true
it('.select(value,options)', function () {
cy.get('select').eq(1).select('1',{force:true}).should('contain.value', 'apples')
});
注意的是,如果在选中元素的情况下,加了disabled,表示不可选择,那么即使加了force:true依然会报错
.trigger()
在DOM元素上触发指定事件
基本语法格式
// eventName表示触发的事件名称,position表示位置(topLeft、top、topRight、left、center、right、bottomLeft、bottom、bottomRight)
.trigger(eventName)
.trigger(eventName, position)
.trigger(eventName, options)
.trigger(eventName, x, y)
.trigger(eventName, position, options)
.trigger(eventName, x, y, options)
基本实例
//区别mousedown与cleck,cleck是按下抬起触发,mousedown是按下触发
it('mousedown事件', function () {
cy.get('a').trigger('mousedown')
});
//鼠标悬停,hover效果
it('鼠标悬停,hover', function () {
cy.get('a').trigger('mouseover')
});
it('左键长按操作,三秒后抬起', function () {
cy.get('a').trigger('mousedown', {force: true, button: 0}).wait(3000).trigger("mouseleave", {force: true})
});
it('长按操作,鼠标的左键、滚轮、右键点击', function () {
cy.get('a').trigger('mousedown', {force: true, button: 0})
cy.get('a').trigger('mousedown', {force: true, button: 1})
cy.get('a').trigger('mousedown', {force: true, button: 2})
});
可支持的带参数传递
Option | Default | Description |
---|---|---|
log | true | 在命令日志中显示命令 |
force | false | 强制执行操作,禁用等待执行操作 |
timeout | defaultCommandTimeout | 等待执行.trigger()之前的超时时间 |
cancelable | true | 事件是否取消 |
bubbles | true | 事件是否可冒泡传递 |
以上是通用的option,以下还有一些事件特有的options | ||
clientX、clientY | 相当于浏览器左上角的距离 | |
pageX、pageY | 相当于整个页面左上角的距离 | |
screenX、screenY | 相当于电脑屏幕左上角的距离 |
指定事件不应该冒泡
// 默认情况下,指定的事件将在DOM树中冒泡,false可以防止事件冒泡
cy.get('#s-usersetting-top').trigger('mouseover', {bubbles: false})
设置clientX和clientY
覆盖基于元素本身的默认自动定位(x,y),对于mousemove之类的事件很有用,可能需要将元素拖动到元素本身之外的地方,基于浏览器本身的X、Y坐标
cy.get('button').trigger('mousemove', { clientX: 200, clientY: 300 })
.scrollIntoView()
将元素滚动到视图中,需要注意的是,Cypress运行的命令快照不会显示滚动的过程,如果要查看滚动的过程,需要用.pause()遍历每个命令,或者通过观察测试运行的视频.
基本语法
.scrollIntoView()
.scrollIntoView(options)
基本实例
// 将 footer 元素 滚动到视图中
cy.get('footer').scrollIntoView()
可支持的带参数传递
Option | Default | Description |
---|---|---|
log | true | 在命令日志中显示命令 |
timeout | defaultCommandTimeout | 等待执行.submit()之前的超时时间 |
duration | 0 | 滚动持续的时间,ms为单位 |
easing | swing | 滚动的动画效果 |
offset | {top:0,left:0} | 元素滚动后的偏移量 |
.scrollTo()
浏览器自带的滚动条
基本语法格式
// 可以是cy直接调用,也可以是DOM元素来调用position表示位置(topLeft、top、topRight、left、center、right、bottomLeft、bottom、bottomRight)
cy.scrollTo(position)
cy.scrollTo(x, y)
cy.scrollTo(position, options)
cy.scrollTo(x, y, options)
// ---或---
.scrollTo(position)
.scrollTo(x, y)
.scrollTo(position, options)
.scrollTo(x, y, options)
基本实例
必须是DOM元素才能调用,而且可以是针对浏览器窗口,也可以针对有滚动条的元素.
// 整个页面往下滑动 500px
cy.scrollTo(0, 500)
// 滚动 .sidebar 元素到它的底部
cy.get('.sidebar').scrollTo('bottom')
可支持的带参数传递
Option | Default | Description |
---|---|---|
log | true | 在命令日志中显示命令 |
timeout | defaultCommandTimeout | 等待执行.submit()之前的超时时间 |
duration | 0 | 滚动持续的时间,ms为单位 |
easing | swing | 滚动的动画效果 |
获取页面全局对象
命令 | 作用 |
---|---|
.window() | 获取当前页面的窗口对象 |
.title() | 获取当前页面的title |
.url() | 获取当前页面的URL |
.location() | 获取当前页面的全局window.location对象 |
.document() | 获取当前页面的全局window.document对象 |
.hash() | 获取当前页面的URL 哈希值 |
.root() | 获取根DOM元素 |
.window()
获取当前页面的window对象
基本语法格式
cy.window()
cy.window(options)
可支持的带参数传递
Option | Default | Description |
---|---|---|
log | true | 在命令日志中显示命令 |
timeout | defaultCommandTimeout | 等待执行.submit()之前的超时时间 |
.title()
获取页面的标题
基本语法格式
cy.title()
cy.title(options)
可支持的带参数传递
Option | Default | Description |
---|---|---|
log | true | 在命令日志中显示命令 |
timeout | defaultCommandTimeout | 等待执行.submit()之前的超时时间 |
.url()
获取当前页面的url,等价于cy.location('href')
基本语法格式
cy.url()
cy.url(options)
可支持的带参数传递
Option | Default | Description |
---|---|---|
log | true | 在命令日志中显示命令 |
timeout | defaultCommandTimeout | 等待执行.submit()之前的超时时间 |
.location()
获取当前页面的window.location
基本语法格式
cy.location()
cy.location(key)
cy.location(options)
cy.location(key, options)
基本实例
cy.location()
// 获取 host 值
cy.location('host')
// 获取 port 值
cy.location('port')
可支持的带参数传递
Option | Default | Description |
---|---|---|
log | true | 在命令日志中显示命令 |
timeout | defaultCommandTimeout | 等待执行.submit()之前的超时时间 |
location对象的其他属性
属性 | 解释 |
---|---|
hash | 获取URL的锚部分 |
host | 获取当前的主机名称和端口 |
hostname | 获取当前的主机名 |
href | 获取当前的链接 |
origin | 获取当前协议 |
pathname | 获取当前的路径名 |
port | 获取url的端口 |
protocol | 返回当前协议 |
search | 获取当前URL的搜索部分 |
toString | --- |
.document()
获取当前页面的window.document
基本语法格式
cy.document()
cy.document(options)
基本操作实例
describe('测试百度', function () {
context('搜索功能', function () {
beforeEach(function () {
cy.visit("")
});
it('测试.document()', function () {
cy.document()
});
})
});
.hash()
获取页面当前的url的哈希值,等价于cy.location('hash')
基本语法
cy.hash()
cy.hash(options)
可支持的参数传递
Option | Default | Description |
---|---|---|
log | true | 在命令日志中显示命令 |
timeout | defaultCommandTimeout | 等待执行.submit()之前的超时时间 |
.root()
获取当前根元素
基本语法格式
cy.root()
cy.root(options)
基本操作实例
describe('测试百度', function () {
context('搜索功能', function () {
beforeEach(function () {
cy.visit("")
});
it('测试.root()', function () {
cy.root()
cy.get("#kw").within(function(){
cy.root()
})
});
})
});
操作浏览器
命令 | 作用 |
---|---|
.go() | 浏览器前进、后退 |
.reload() | 刷新页面 |
.viewport() | 控制浏览器窗口的大小和方向 |
.visit() | 访问指定的 url |
.wait() | 强制等待 |
.go()
在浏览器历史记录中,访问前一个或后一个URL
基本语法格式
cy.go(direction)
cy.go(direction, options)
基本实例
// 相当于单击浏览器左上角的后退←按钮
cy.go("back")
// 相当于单击浏览器左上角的前进→按钮
cy.go("forward")
//上面两条等价于下面两条
// 相当于单击浏览器左上角的后退←按钮
cy.go(-1)
// 相当于单击浏览器左上角的前进→按钮
cy.go(1)
可支持的带参数传递
Option | Default | Description |
---|---|---|
log | true | 在命令日志中显示命令 |
timeout | defaultCommandTimeout | 等待执行.submit()之前的超时时间 |
.reload()
刷新页面
基本语法格式
cy.reload()
cy.reload(forceReload)
cy.reload(options)
cy.reload(forceReload, options)
forceReload
- 是否在不使用缓存的情况下重新加载当前页面
- true表示强制重新加载不适用缓存,所有资源文件都会重新拉取一遍,好处就是可从取服务器获取最新的资源文件,坏处就是加载时间会变长
基本实例
cy.reload()
cy.reload(true)
可支持的带参数传递
Option | Default | Description |
---|---|---|
log | true | 在命令日志中显示命令 |
timeout | defaultCommandTimeout | 等待执行.submit()之前的超时时间 |
.viewport()
控制浏览器窗口的尺寸和方向,也可以通过在配置项中定义viewportWidth和viewportHeight来全局设置浏览器窗口的宽度和高度.,默认高度1000px*660px
基本语法格式
cy.viewport(width, height)
cy.viewport(preset, orientation)
cy.viewport(width, height, options)
cy.viewport(preset, orientation, options)
参数width、height
- 必须为非负数
- 像素单位px
参数options
Option | Default | Description |
---|---|---|
log | true | 在命令日志中显示命令 |
参数orientation
- 默认是纵向,portrait
- 可改横向,landscape
参数preset
预设值,Cypress提供了一下的预设值
预设值 | 宽度 | 高度 |
---|---|---|
ipad-2 | 768 | 1024 |
ipad-mini | 768 | 1024 |
iphone-3 | 320 | 480 |
iphone-4 | 320 | 480 |
iphone-5 | 320 | 568 |
iphone-6 | 375 | 667 |
iphone-6+ | 414 | 736 |
iphone-x | 375 | 812 |
iphone-xr | 414 | 896 |
macbook-11 | 1366 | 768 |
macbook-13 | 1280 | 800 |
macbook-15 | 1440 | 900 |
samsung-note9 | 414 | 846 |
samsung-s10 | 360 | 760 |
基本实例
需要注意的是,cy.viewport()后面不能在链接其他命令
//550px*750px
cy.viewport(550,750)
//设置iphone6的尺寸
cy.viewport("iphone-6")
自动缩放
- 默认情况下,如果屏幕不够大,无法显示应用程序所有像素,则Cypress会将应用程序缩放并居中,以适应Cypress的Test Runner
- 缩放应用程序不会影响应用程序的任何计算或行为
- 自动缩放的好处:无论屏幕大小如何,测试都始终通过或失败,测试最终在CI中运行,因此无论Cypress在什么计算机上运行,所有viewports都将相同.
Cypress.config()
可以通过设置全局的方式,设定viewport的宽高
Cypress.config('viewportWidth',800)
//默认800
Cypress.config('viewportWidth')
.visit()
访问远程URL
基本语法格式
cy.visit(url)
cy.visit(url, options)
cy.visit(options)
URL
- 两种值,一种是需要直接访问的URL,可以是一个完整的URL,比如:https://www.cnblogs.com
- 第二种是html文件的相对路径,路径是相对于Cypress的安装目录,不需要file://前缀
可执行的带参数传递
参数(options) | 默认 | 作用 |
---|---|---|
method | GET | 请求方法,GET或POST |
body | null | l 与POST请求一起发送的数据体l 如果是字符串,则将其原封不动地传递l 如果是一个对象,它将被URL编码为字符串,并加上Content-Type:application / x-www-urlencoded |
headers | {} | 请求头 |
qs | null | Url的请求参数 |
log | true | 是否打印日志 |
auth | null | 添加基本授权标头 |
failOnStatusCode | true | 是否在2xx和3xx以外的响应代码上标识为失败 |
onBeforeLoad | function | 在页面加载所有资源之前调用指定的方法 |
onLoad | function | 页面触发加载事件后调用 |
retryOnStatusCodeFailure | false | 当状态码是错误码时,Cypress是否自动重试,最多重试4次 |
retryOnNetworkFailure | true | 当网络错误时,Cypress是否自动重试,最多重试4次 |
timeout | pageLoadTimeout | 最长等待 .visit() 完成的时间 |
基本实例
// 在新的窗口打开 URL
cy.visit('http://localhost:3000')
cy.visit('./pages/hello.html')
// 添加timeout
cy.visit('http://localhost:3000',{timeout:3000})
baseUrl
- 建议在使用cy.visit()时,在cypress.json里设置一个baseUrl
- baseUrl相当于一个全局共享的host,在使用visit()和request()等命令时自动将baseUrl传递进去
- 优势:首次启动Cypress测试时,添加baseUrl还可以节省一些时间
- 不添加baseUrl的影响就是一旦遇到cy.visit(),Cypress便将主窗口的URL切换到访问指定的URL,首次开始测试时,可能会导致刷新或重新加载.
baseUrl实例
{
"baseUrl": "http://192.168.102.50:6434/",
"env": {
"foor": "#root main>div>div>div:nth-child(1)>div>div>div>div>div:nth-child(1)>div:nth-child(2)",
"key": "value"
}
}
.wait()
等待数毫秒或等待别名资源解析,然后在继续执行下一个命令.
基本语法格式
cy.wait(time)
cy.wait(alias)
cy.wait(aliases)
cy.wait(time, options)
cy.wait(alias, options)
cy.wait(aliases, options)
参数time
等待的时间(一毫秒为单位)
参数alias、aliases
- 使用.alias()命令定义兵役@字符和别名命名的别名路由
- alias组成的数组
参数alias的基本实例
it.skip('基本实例', function () {
cy.server()
cy.route({
url: '**/login',
status: 503,
response: {
success: false,
data: 'Not success'
},
}).as("login")
cy.get("input[name=username]").type(username)
cy.get("input[name=password]").type(`${password}{enter}`)
// 等待请求的响应
cy.wait('@login').then((res) => {
// 针对响应进行断言
expect(res.status).to.eq(503)
expect(res.responseBody.data).to.eq('Not success')
expect(res.responseHeaders).to.have.property('x-token', 'abc-123-foo-bar')
})
});
参数aliases的基本实例
it.skip('基本实例', function () {
cy.server()
cy.route('users/*').as('getUsers')
cy.route('activities/*').as('getActivities')
cy.route('comments/*').as('getComments')
cy.visit('/dashboard')
cy.wait(['@getUsers', '@getActivities', '@getComments']).then((xhrs) => {
// xhrs现在将是匹配的XHR的数组
// xhrs[0] <-- getUsers 的响应
// xhrs[1] <-- getActivities 的响应
// xhrs[2] <-- getComments 的响应
})
});
参数options
参数 | 默认 | 作用 |
---|---|---|
log | True | 是否打印日志 |
timeout | requestTimeout,responseTimeout | 等待.wait()完成的时间 |
requestTimeout | 5000 | 请求超时时间 |
responseTimeout | 30000 | 响应超时时间 |
基本实例
cy.wait(500)
cy.wait('@getProfile')
测试报告
内置的测试报告包括Mocha的内置报告和直接嵌入在Cypress中的测试报告,主要有三种:spec格式报告、json格式、junit格式
前置工作
- 确保package.json文件的scripts模块加入了如下键值对"cypress:run":"cypress run"
- cypress run是以无头浏览器模式跑测试用例,用例文件夹下的所有测试用例
- cypress open会打开测试用例集的解密那,需要手动运行
spec格式报告
spec格式是Mocha的内置报告,它的输出是一个嵌套的分级视图
进入Cypress安装的目录,执行命令 npm run cypress:run --reorter=spec
json格式报告
json测试报告格式将输出一个大的json对象
在Cypress中使用json格式的报告非常简单,在命令行运行时加上--reporter=json
进入Cypress安装的目录,执行命令 npm run cypress:run --reporter=json --reporter-options "toConsole=true"
junit格式报告
junit测试报告格式将输出一个xml文件
在Cypress中使用xml格式的报告非常简单,在命令行运行时加上 --reporter junit
进入Cypress安装的目录,执行命令 npm run cypress:run --reporter junit --reporter-options "mochaFile=results/test_output.xml,toConsole=true"
Mochawesome测试报告
除了内置的测试报告,Cypress也支持用户自定义报告格式.
第一步
将Mocha、Mochawesome添加至项目中
npm install --save-dev mocha
npm install --save-dev mochawesome
注意点
- 先查看node_modules目录下是否有mocha文件夹,如果有直接装mochawesome
- 如果安装mocha失败,出现很古怪的错误,比如mkdirp版本不行
- 尝试先update mkdirp库,如果也报错,则uninstall mkdirp库,如果仍然报错,则把Cypress目录下的node_moudules整个文件夹删掉,重新自行npm install,大概率就可以解决问题了
第二步
进入Cypress安装目录,执行命令 npm run cypress:run --reporter mochawesome
命令行运行Cypress
- 在交互模式下打开Cypress测试运行器
- 在测试用例的运行过程中,测试用例的每一条命令,每一个操作都将显式的显示在测试运行器中
前置操作
通过package.json指定scripts,在package.json文件下,添加"cypress:open":"cypress open"
yarn运行
yarn cypress:open
npm运行
npm run cypress:open
cypress open详解
- cypress open运行时支持指定多个参数,指定的参数将自动应用于你通过测试运行器打开的项目
- 这些参数讲应用于每一次测试运行,直到关闭测试运行器为止
- 指定的参数将会覆盖配置文件cypress.json中的相同参数
可选参数列表明细
可选参数 | 含义 | 实例命令 |
---|---|---|
--browser,-b | 指定运行测试的浏览器 | cypress open --browser /usr/bin/chromium |
--config,-c | 指定运行时的配置项 | cypress open --config pageLoadTimeout=100000,watchForFileChanges=false |
--config-file,-C | 指定运行时的配置文件,默认情况配置项都定义在cypress.json中 | cypress open --config-file tests/cypress-config.json |
--detached,-d | 以静默模式打开Cypress | |
--env,-e | 定义环境变量 | 单个环境变量 cypress open --env host=api.dev.local 多个环境变量 cypress open --env host=api.dev.local,port=4222值为 json 字符串 cypress open --env flags='{"feature-a":true,"feature-b":false}' |
--global | 全局模式,允许多个嵌套项目中共享同一个安装好的Cypress版本 | cypress open --global |
--help,-h | 帮助文档 | |
--port,-p | 重载默认端口 | cypress open --port 8080 |
--project,-P | 指定项目路径,用来指定待运行的项目,如果你的项目包含多个子项目,可以用此参数运行指定的子项目 | cypress open --project ./some/nested/folder |
cypress-skip-and-only-ui插件
前言
- 一个测试用例集通常包含多个测试用例
- 当网络不稳定而引起测试失败时,我们希望进重试失败的用例而不是重跑整个测试用例集
- 单测试运行器默认进支持重试整个测试用例集
安装
npm i -D cypress-skip-and-only-ui
配置
在cypress/supprt/index.js文件,任意位置添加配置项如下:
require('cypress-skip-and-only-ui/support')
在cypress/plugins/index.js文件,任意位置添加配置如下:
const task=require('cypress-skip-and-only-ui/task')
module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
on('task',task)
}
现在新版本的Cypress是不支持这个插件的,但是不影响真实使用,毕竟这也是调试作用居多.
操作上一条命令返回结果的命令集
命令 | 作用 |
---|---|
then()] | 将上一条命令返回的结果注入到下一个命令中 |
and()] | 创建一个断言。断言将自动重试,直到它们通过或超时 |
should() | and() 的别名 |
invoke() | 对上一条命令的结果执行调用方法操作 |
its() | 获取属性值 |
as() | 取别名 |
within() | 限定命令作用域 |
each() | 遍历当前元素 |
spread() | 将数组内容作为单独的参数传回到回调函数 |
.then()
作用
- 在Cypress中,保存一个值或者引用的最好方式是使用闭包
- then()就是Cypress对闭包的一个典型应用
- the()返回的是上一个命令的结果,并将其注入到下一个命令中
基本语法格式
// options:只有timeout,4000s
// callbackFn:回调函数,需要将上一个命令的结果作为参数进行传递
.then(callbackFn)
.then(options, callbackFn)
基本实例
it('then()命令', function () {
cy.get("#ui").then(function (t) {
cy.log(t.text())
})
});
通过then和should比较文本是否相等
it('then()比较值', function () {
cy.get("#ui").then(function (t) {
const txt=t.text()
cy.get("#li1").should(function (t2) {
expect(t2.text()).to.be.eq(txt)
})
})
});
.and()、.should()
作用
- 创建一个断言,断言将自动重试,直到它们通过或超时
- 和should()一个用法
基本语法格式
// chainers:断言器
// value:需要断言的值
// method:需要调用到的方法
// callbackFn:回调方法,可以满足自己想要断言的内容,且总是返回前一个cy命令返回的结果,方法内的return是无效的,会一直运行直到里面没有断言
.and(chainers)
.and(chainers, value)
.and(chainers, method, value)
.and(callbackFn)
.and()返回的结果
//在大多数情况下,.and()返回与上一个命令相同的结果
cy
.get('nav') // 返回 <nav>
.should('be.visible') // 返回 <nav>
.and('have.class', 'open') // 返回 <nav>
//但是,某些chainer会改变返回的结果
cy
.get('nav') // 返回 <nav>
.should('be.visible') // 返回 <nav>
.and('have.css', 'font-family') // 返回 'sans-serif'
.and('match', /serif/) // 返回 'sans-serif'
对同一结果的操作实例
cy.get('button').should('have.class', 'active').and('not.be.disabled')
chainer改变返回结果的实例
<!--html代码-->
<li>
<a href="users/123/edit">Edit User</a>
</li>
// cypress代码
cy
.get('a')
.should('contain', 'Edit User') // 返回的是 <a>
.and('have.attr', 'href') // 返回的是 href 的值
.and('match', /users/) // 返回的是 href 的值
.and('not.include', '#') // 返回的是 href 的值
method+value的实例
断言href属性值是否等于/users
cy
.get('a')
.should('have.class', 'active')
.and('have.attr', 'href', '/users')
.invoke()
作用
对前一条命令返回的结果进行调用方法
基本语法格式
// functionName:需要调用的方法名
// options:log和timeout
// args:传递给函数的参数,数量没有限制
.invoke(functionName)
.invoke(options, functionName)
.invoke(functionName, args...)
.invoke(options, functionName, args...)
基本实例
// 调用 animate 方法
cy.wrap({ animate: fn }).invoke('animate')
// 找到.modal 元素并调用 show 方法
cy.get('.modal').invoke('show')
混合实例
// 断言函数返回值
it('断言函数返回值', function () {
const fn=function () {
return "bar"
}
cy.wrap({foo:fn}).invoke('foo').and('eq','bar')
});
// 调用函数并传递参数
it('调用函数并传递参数', function () {
const fn=function (a,b,c) {
return a+b+c
}
cy.wrap({sum:fn}).invoke('sum',1,2,3).should("be.eq",6).and("be.lt",10)
});
// 作为函数的属性被调用
it('作为函数的属性被调用', function () {
cy.get('#content').should('be.hidden').invoke('show').should('be.visible').find('input[name="email]').type('text')
});
.its()
作用
获取上一条命令结果的属性值
基本语法格式
// propertyName:索引、属性名、要获取的嵌套属性名称
// options:log与timeout
.its(propertyName)
.its(propertyName, options)
基本实例
cy.wrap({ '50' }).its('width') // 获取宽度属性
cy.window().its('sessionStorage') // 获取 sessionStorage 属性
混合实例
// 获取字典对象的属性值
cy.wrap({age: 52}).its('age').should('eq', 52) // true
// 数组对象,根据索引取值
cy.wrap(['polo', 'yy']).its(1).should('eq', 'yy')
// 获取元素的属性值
cy.get('ul li').its('length').should('be.gt',4)
// 获取字符串对象的属性值
cy.url().its('length').should('be.gt', 20)
// 属性值是函数
const fn = () => {
return 42
}
cy.wrap({getNum: fn}).its('getNum').should('be.a', 'function')
//获取嵌套属性值
const user = {
contacts: {
work: {
name: 'Kamil'
}
}
}
cy.wrap(user).its('contacts.work.name').should('eq', 'Kamil') // true
.as()
作用
- 起别名以供以后使用
- 可在cy.get()或cy.wait()命令中引用别名
基本语法格式
.as(aliasName)
基本实例
// 给第一个 li 元素起别名
cy.get('.main-nav').find('li').first().as('firstNav')
// 给网络请求的响应起别名
cy.route('PUT', 'users', 'fx:user').as('putUser')
引用别名的方式
cy.get()或cy.wait()命令中使用@前缀引用过的别名名称,如@firstNav、@putUser
基本调用实例
一般使用.wrap()和.as()混合使用
describe('测试百度', function () {
context('搜索功能', function () {
beforeEach(function () {
cy.visit('https://www.baidu.com')
cy.contains("百度首页").then(function (name) {
cy.wrap(name.text()).as('input')
});
});
it('使用as', function () {
cy.get("#kw").type("qq")
cy.get("#su").click()
})
it('调用as ', function () {
cy.get('@input').then(function (name2) {
cy.log(name2)
})
});
})
});
within()
将所有后续cy命令的作用域限定在此元素中,在特定的元素组中工作时很有用.
基本语法格式
.within(callbackFn)
.within(options, callbackFn)
参数解释
- callbackFn 回调函数,第一个参数是上一条命令的返回结果(必须是元素)
- log:是否将命令显示到命令日志中,默认true
基本实例
cy.get('form').within(($form) => {
// 在回调函数里,cy 命令的作用域将限定在 form 中(不能直接通过cy调用).并且返回的是跟cy.get('form')命令一样的结果,在回调函数里面的操作都是在form中进行的
})
.each()
遍历数组数据结构(具有length属性的数组或对象)
基本语法格式
.each(callbackFn)
// callbackFn 回调函数,可以拥有三个参数:value、index、collection
基本实例
返回的结果和上一条命令一样
// 遍历每个 li 元素
cy.get('ul>li').each(() => {...})
// 遍历每个 cookie
cy.getCookies().each(() => {...})
// 遍历每个li元素,并且打印出来
it('.each()', function () {
cy.get('li').each(function (item,index,name) {
cy.log(item,index,name)
})
});
.spread()
将数组拆分为多个参数
基本语法格式
callbackFn:回调函数,将数组拆分后作为函数的多个参数
.spread(callbackFn)
.spread(options, callbackFn)
操作实例
spread命令不会出现在命令日志中,spread命令的回调函数的参数个数无论是多了还是少了都不会报错,少了不会获取后面的值,多了则是一个空格.
describe('测试spread', function () {
context('带li标签', function () {
beforeEach(function () {
cy.visit('https://example.cypress.io/commands/connectors')
});
it('测试li', function () {
const arr=['foo','bar','baz']
cy.wrap(arr).spread(function (foo,bar,baz) {
expect(foo).to.equal('foo')
expect(bar).to.eq('bar')
expect(baz).to.eq('baz')
})
});
});
});
其他命令
.wrap()
作用
- 返回传递给它的对象
- 返回的是一个Promise对象,可以直接接Cypress其他命令
- 如果传递给它的就是一个Promise对象,则返回它的值
基本语法格式
// subject表示需要返回的对象
// options有两个,log:是否将命令系那是到命令日志中,默认true;timeout:命令超时时间
cy.wrap(subject)
cy.wrap(subject, options)
基本实例
// 声明一个整数
cy.wrap(123).should('eq', 123)
// 声明一个字符串
cy.wrap('abc').and('contain', 'a')
最佳实践
设置全局URL
- 为了绕过同源策略,当 Cypress 开始运行测试时,会在 localhost 上打开一个随机端口进行初始化
- 直到遇见第一个 cy.visit() 命令里的 URL 才匹配被测应用程序的 URL
- 当 Cypress 以交互模式启动时,会看到 Cypress 先运行在 localhost 上然后又切换到 URL 重新运行(也就多消耗了时间)
- 在cypress.json中设置baseUrl
- 可以在运行时节省 Cypress 匹配被测应用程序 URl 的时间
- 还可以在编写待访问的 URL 时,忽略 baseUrl,直接写后面的路径
设置全局实例
// 不加 baseUrl 的写法
cy.visit('https://example.cypress.io/commands/actions')
// 加了上面 baseUrl 的写法
cy.visit('/commands/actions')
避免访问多个站点
- 为了绕开同源策略的限制而实现的方案,使得 Cypress 不能支持在一个测试用例文件里访问多个不同域名的 URL
- 如果访问了多个不同域名的站点,Cypress 会直接报错
# 避免访问多个站点基本实例
// 访问相同超域(如果访问的是同一个超域下的不同子域,则 Cypress 允许你正常访问)
it('访问同一超域下的不同子域', function () {
cy.visit('https://example.cypress.io')
cy.visit('https://www.cypress.io/features')
});
// 访问不同超域(如果访问的不是同一个超域下的子域,则 Cypress 不允许你正常访问)
it('访问不同超域,会报错', function () {
cy.visit('https://example.cypress.io')
cy.visit('https://www.cnblogs.com/wp950416/p/13915633.html')
});
删除等待代码
- 在其他的自动化测试框架中,很大概率会用到强制等待及隐式等待
- 在cypress中,无需使用等待,Cypress的许多命令都自带自动重试机制(所以在编写脚本的时候,删除所有的等待代码)
截图与录屏
在测试运行时截图和录屏能够在测试错误时快速定位到问题所在
以cypress run方式运行测试时,当测试发生错误时,Cypress会自动截图,并默认保存在cypress/screenshots文件夹下,而录屏会保存在cypress/video文件夹下
自定义截图基本语法格式
.screenshot()
.screenshot(fileName)
.screenshot(options)
.screenshot(fileName, options)
// ---or---
cy.screenshot()
cy.screenshot(fileName)
cy.screenshot(options)
cy.screenshot(fileName, options)
参数解释
- fileName
- 代保存图片的名称
- 图片默认保存在 cypress/screenshots 文件夹下,可以在 cypress.json 修改默认文件夹路径(配置项 screenshotsFolder )
- options
参数 | 默认 | 作用 |
---|---|---|
log | true | 在命令行中显示 |
blackout | [ ] | 接收一个数组类型的字符选择器,此选择器匹配的元素会被涂黑 这个选项在capture是runner时不起作用 |
capture | fullPage | 决定截图截取测试运行器的哪个部分 此参数仅限在cy.后使用游戏哦啊,针对某个元素截图则不起作用 有三个可选项: 1. viewport:截图大小是被测应用程序当前视窗大小 2. fullpage:整个被测程序界面(整个页面) 3. runner:截图测试运行器的整个部分 |
clip | null | 用于裁剪最终屏幕截图图像的位置和尺寸(以px为单位) 格式:{x:0,y:0,100,height:100} |
disableTimersAndAnimations | true | 截图时禁止JS计时器(setTimeout、setInterval)和CSS动画运行 |
padding | null | 用于更改元素屏幕截图尺寸的填充 仅适用于元素屏幕截图 格式:padding:[ 'background-color':'#fff'; ] |
scale | false | 是否缩放应用程序以适合浏览器窗口 当capture=runner时,强制为TRUE |
timeout | responseTimeout | Timeout时间 |
onBeforeScreenshot | null | 非因测试失败截图前,要执行的回调函数 当此参数应用于元素屏幕截图时,它的参数是被截取的元素 当此参数应用于其他截图时,它的参数是document本身 |
onAfterScreenshot | null | 非因测试失败截图后,要执行的回调函数 当此参数应用于元素屏幕截图时,第一个参数是被截取的元素 当此参数应用于其他截图时,第一个参数是document本身,第二个参数是有关屏幕截图的属性 |
通过onBeforeScreenshot、onAfterScreenshot.可以在截图发生前或发生后应用自定义的行为
基本实例
it('简单的栗子', function () {
// 截图整个页面
cy.screenshot()
});
断言最佳实践
介绍
- Cypress的断言库是基于Chai断言库
- 并且增加了对 Sinon-Chai,Chai-jQuery 断言库的支持,带来了强大的断言功能
- Cypress 支持 BDD(expect/should)和 TDD(assert)格式的断言
BDD、TDD格式断言实例
# BDD断言案例
describe('测试BDD', function () {
context('BDD', function () {
it('BDD', function () {
function add(a,b){
return a+b
}
expect(add(1,2)).to.eq(3)
});
})
});
# TDD断言案例
describe('测试BDD', function () {
context('BDD', function () {
it('BDD', function () {
function add(a,b){
return a+b
}
assert.equal(add(1,3),4,'相加等于4')
});
})
});
BDD形式的常见断言
命令 | 示例 |
---|---|
not | expect(name).to.not.equal('jane') |
deep | expect(obj).to.deep.equal({name:'jane'}) |
nested | expect({a:{b:['x','y']}}).to.have.nested.property('a.b[1]') expect({a:{b:['x','y']}}).to.nested.include({'a.b[1]':'y'}) |
any | expect(arr).to.have.any.keys('age') |
all | expect(arr).to.have.all.keys('name','age') |
a(type) 别名:an |
expect('test').to.be.a('string') |
include(value) 别名:contain,includes,contains |
expect([1,2,3]).to.include(2) |
ok | expect(undefined).to.not.be.ok |
true | expect(true).to.be.true |
false | expect(false).to.be.false |
null | expect(null).to.be.null |
undefined | expect(undefind).to.be.undefind |
exist | expect(myVar).to.exist |
empty | expect([]).to.be.empty |
arguments 别名:Arguments |
expect(arguments).to.be.arguments |
equal(value) 别名:equals,eq |
expect(42).to.equal(42) |
deep.equal(value) | expect({name:'jane'}).to.deep.equal({name:'jane'}) |
eql(value) 别名:eqls |
expect({name:'jane'}).to.eql({name:'jane'}) |
greaterThan(value) 别名:gt,above |
expect(10).to.be.greaterThan(5) |
least(value) 别名:gte |
expect(10).to.be.at.least(10) |
lessThan(value) 别名:lt,below |
expect(5).to.be.lessThan(10) |
most(value) | expect('test').to.have.length.of.at.most(4) |
within(start,finish) | expect(7).to.be.within(5,10) |
instanceOf(constructor) 别名:instanceof |
expect([1,2,3]).to.be.instanceOf(Array) |
property(name,[value]) | expect(obj).to.have.property('name') |
deep.property(name,[value]) | expect(deepObj).to.have.deep.property('tests[1]','e2e') |
ownProperty(name) 别名:haveOwnProperty,own.property |
expect('test').to.have.ownProperty('length') |
ownPropertyDescriptor(name) 别名:haveOwnPropertyDescriptor |
expect({a:1}).to.have.ownPropertyDescriptor('a') |
lengthOf(value) | expect('test').to.have.lengthOf(3) |
match(RegExp) 别名:matches |
expect('testing').to.match(/^test/) |
string(string) | expect('testing').to.have.string('test') |
keys(key1,[key2],[...]) 别名:key |
expect({pass:1,fail:2}).to.have.keys('pass','fail') |
TDD形式的断言
命令 | 示例 |
---|---|
.isOk(object,[message]) | assert.isOk('everything','everything is ok') |
.isNotOk(object,[message]) | assert.isNotOk('false','this will pass') |
.equal(actual,expected,[message]) | assert.equal(3,3,'vals equal') |
.notEqual(actual,expected,[message]) | assert.notEqual(3,4,'vals not equal') |
.deepEqual(actual,expected,[message]) | assert.deepEqual({id:'1'},{id:'2'}) |
.notDeepEqual(actual,expected,[message]) | assert.notDeepEqual({id:'1'},{id:'2'}) |
.isAbove(valueToCheck,valueToBeAbove,[message]) | assert.isAbove(6,1,'6 greater than 1') |
.isAtLeast(valueToCheck,valueToBeAtLeast,[message]) | assert.isAtLeast(5,2,'5 gt or eq to 2') |
.isBelow(valueToCheck,valueToBeBelow,[message]) | assert.isBelow(3,6,'3 strict lt 6') |
.isAtMost(valueToCheck,valueToBeAtMost,[message]) | assert.isAtMost(4,4,'4 lt or eq to 4') |
.isTrue(value,[message]) | assert.isTrue(true,'this val is true') |
.isNotTrue(value,[message]) | assert.isNotTrue('tests are no fun','val not true') |
.isFalse(value,[message]) | assert.isFalse(false,'val is false') |
.isNotFalse(value,[message]) | assert.isNotFalse('tests are fun','val not false') |
.isNull(value,[message]) | assert.isNull(err,'there was no error') |
.isNotNull(value,[message]) | assert.isNotNull('hello','is not null') |
.exists(value,[message]) | assert.exists(5,'5 is not null or undefined') |
.notExists(value,[message]) | assert.notExists(null,'val is null or undefined') |
.isUndefined(value,[message]) | assert.isUndefined(undefined,'val has been defined') |
.isDefined(value,[message]) | assert.isDefined('hello','val has been defined') |
.isFunction(value,[message]) | assert.isFunction(x=>x*x,'val is func') |
.isnNotFunction(value,[message]) | assert.isNotFunction(5,'val not funct) |
.isObject(value,[message]) | assert.isObject({num:5},'val is object') |
.isNotObject(value,[message]) | assert.isNotObject(3,'val not object') |
.isArray(value,[message]) | assert.isArray(['unit','e2e'],'val is array') |
.isNotArray(value,[message]) | assert.isNotArray('e2e','val not array') |
.isString(value,[message]) | assert.isString('e2e','val is string') |
.isNotString(value,[message]) | assert.isNotString(2,'val not string') |
.isNumber(value,[message]) | assert.isNumber(2,'val is number') |
.isNotNumber(value,[message]) | assert.isNotNumber('e2e','val not number') |
.isBoolean(value,[message]) | assert.isBoolean(true,'val is bool') |
.isNotBoolean(value,[message]) | assert.isNotBoolean('true','val not bool') |
.typeOf(value,name,[message]) | assert.typeOf('e2e','string','val is string') |
.notTypeOf(value,name,[message]) | assert.notTypeOf('e2e','number','val not number') |
Cypress命令内置断言实例
Cypress命令通常具有内置的断言,这些断言将导致命令自动重试,以确保命令成功(或者超时后失败)
it('cypress 命令自带断言', function () {
cy.wrap({body: {name: 'sunfree'}})
.its('body')
.should('deep.eq', {name: 'sunfree'})
});
Cypress常见的内置断言操作命令
命令 | 内置断言事件 |
---|---|
cy.visit() | 期望访问的URL返回的status code是200 |
cy.request() | 期望远程server存在并且能连通 |
cy.contains() | 期望包含某些字符的页面元素能在DOM里找到 |
cy.get() | 期望页面元素能在DOM里找到 |
cy.type() | 期望页面元素处于可输入的状态 |
cy.click() | 期望页面元素处于可点击的状态 |
cy.its() | 期望能从当前对象找到一个属性 |
隐性断言
- should()、and()是Cypress推崇的方式
- and()和should()其实是使用方式和效果是完全一样的
cy
.get('form')
.should('be.visible')
.and('have.class', 'open')
显性断言
expect允许传入一个特定的对象并且对它进行断言
expect(true).to.be.true
po模式(不推荐使用)
Custom Commands替代PO
介绍
- Custom Commands 被认为是替代 PageObject 的良好选择
- 使用 Custom Commands 可以创建自定义命令和替换现有命令
- Custom Commands 默认存放在 cypress/support/commands.js 文件中,因为它是通过 supportFile( 定义在 cypress/support/index.js )中的 import 语句导入的,所以会在所有测试用例执行前加载
基本语法格式
Cypress.Commands.add(name, callbackFn)
Cypress.Commands.add(name, options, callbackFn)
Cypress.Commands.overwrite(name, callbackFn)
# 参数说明
name:要添加或覆盖的命令的名称
cakkbackFn:自定义命令的回调函数,回调函数里自定义函数所需完成的操作步骤
options:允许自定义命令的隐性行为
# options可选参数列表
参数:prevSubject
可接受的值类型:Boolean,String or Array
1. false:忽略任何以前命令产生的对象(父命令)默认:false
2. true:接收上一个命令产生的对象(子命令)
3. optional:可以启动链,也可以使用现有链(双命令,除了控制命令的隐式行为,还可以对上一条命令产生的对象类型进行验证,例如:
4. element:要求上一条命令产生的对象是DOM元素
5. document:要求上一条命令产生的对象为文档
6. window:要求上一条命令产生的对象是窗口
描述:如何处理上一条命令产生的对象
注意:仅在Cypress.Commands.add()中支持使用 options,而在Cypress.Commands.overwrite()中不支持使用options
使用Customn Commands的益处
- 定义在 cypress/support/command.js 中的命令可以像 Cypress 内置命令那样直接使用,无须 import 对应的 page(实际上 PageObject 模式在 Cypress 看来无非是数据/操作函数的共享)
- 自定义命令可以比 PageObject 模式运行更快,Cypress 和应用程序运行在同一个浏览器中,意味着 Cypress 可以直接发送请求到应用程序并设置运行测试所需要的用户状态,而这一切通常无须通过页面操作,这使得使用了自定义命令的测试会更加稳定
- 自定义命令允许重写 Cypress 内置命令,意味着可以自定义测试框架并立刻全局应用