Cypress 前端测试工具的基本使用和相关命令总结
Cypress是基于JavaScript的前端测试工具,可以对浏览器中运行的任何内容进行一个快速、简单、可靠的测试;Cypress是自集成的,提供了一套完整的端到端测试,无须借助其他外部工具,安装后即可快速地创建、编写、运行测试用例,且对每一步操作都支持回看;不同于其他只能测试UI层的前端测试工具,Cypress允许编写所有类型的测试界面测试,集成测试,单元测试。...
由于在自己的工作和学习过程中,只查看某个大佬的教程或文章无法满足自己的学习需求和解决遇到的问题,所以自己在追赶大佬们步伐的基础上,又自己总结、整理、汇总了一些资料,方便自己理解和后续回顾,同时也希望给大家带来帮助,所以才写下该篇文章。在本文中,所有参考或引用大佬们文章内容的位置,都附上了原文章链接,您可以直接前往查阅观看。在原文章内容的基础上,若无任何补充内容,同时避免直接大段摘抄大佬们的文章,该情况下也只附上了原文章链接供大家学习。本文旨在总结归纳,并希望给大家提供帮助,未用作任何商用用途。文章内容如有错误之处,望各位大佬指出。如果涉及侵权行为,将会第一时间对文章进行删除。
👉 个人博客主页 👈
📝 一个努力学习的程序猿
前端测试工具 Cypress
本文主要是在参考了大佬们的基础知识体系后进行的内容总结,经过总结可了解对 Cypress 的基础使用,主要内容均参考于以下文章。各位若想查看全部更完整的内容,可直接前往以下文章中学习查阅。感谢大佬们的付出:
Cypress 的官方文档:
https://docs.cypress.io/guides/overview/why-cypress#In-a-nutshell
由于官方文档现在不支持中文,所以目前关于 Cypress 的使用可以通过这个博客,进行学习和查看:
https://www.cnblogs.com/poloyy/p/14031640.html
https://www.cnblogs.com/poloyy/tag/Cypress/
Cypress介绍
- Cypress 是基于 JavaScript 的前端测试工具,可以对浏览器中运行的任何内容进行一个快速、简单、可靠的测试;
- Cypress 是自集成的,提供了一套完整的端到端测试,无须借助其他外部工具,安装后即可快速地创建、编写、运行测试用例,且对每一步操作都支持回看;
- 不同于其他只能测试 UI 层的前端测试工具,Cypress 允许编写所有类型的测试:界面测试,集成测试,单元测试。
Cypress原理
Cypress 运行测试的大致流程:
1、运行测试后,Cypress 使用 webpack 将测试代码中的所有模块 bundle 到一个 js 文件中;
2、然后运行浏览器,并且将测试代码注入到一个空白页中,然后它将在浏览器中运行测试代码;
3、每次测试首次加载 Cypress 时,内部 Cypress Web 应用程序先把自己托管在本地的一个随机端口上,如:http://localhost:65874;
4、在识别出测试中发出的第一个 cy.visit() 命令后, Cypress 会更改本地 URL 以匹配远程应用程序的 Origin(满足同源策略),这使得测试代码和应用程序可以在同一个 Run Loop 中运行。
Cypress的优势
而我选择去学习 Cypress 进行一个前端测试的原因是:
1、Cypress 本身拥有着快速、简单、可靠的特点,这使得前端开发人员也可以进行简单快速的测试。开发人员只需要 npm 下载一次,后续无需借助任何其他工具,安装后即可快速创建、编写、运行测试用例。
2、Cypress 运行更快的根本原因:
Cypress 测试代码和应用程序均运行在由 Cypress 全权控制的浏览器中,所以 Cypress 的测试代码可以直接操作 DOM、Window Objects、Local Storages 而无须通过网络访问。
3、Cypress 稳定性、可靠性更高的原因:
① Cypress 可以在网络层进行即时读取和更改网络流量的操作;
② Cypress 背后是 Node.js Process 控制的 Proxy 进行转发,这使得 Cypress 不仅可以修改进出浏览器的所有内容,还可以更改可能影响自动化操作的代码;
③ Cypress 相对于其他测试工具来说,能从根本上控制整个自动化测试的流程。
【图片转自 https://www.cnblogs.com/poloyy/p/12966125.html】
Cypress的特性
1、拥有历史记录
Cypress 在测试代码运行时会自动保存每一步操作,等测试运行结束后,用户可在 Cypress 提供的 Test Runner 里,通过悬停在命令上的方式查看运行时每一步都发生了什么。
2、实时重新加载
当测试代码修改保存后,Cypress 会自动加载改动地方,并重新运行测试。
3、运行结果一致性
Cypress 架构不使用 Selenium 或 Webdriver,在运行速度、可靠性测试、测试结果一致性上均有良好保障。
4、可调试性
当测试失败时,可以直接从开发者工具(F12 Chrome DevTools)进行调试。
5、自动等待
使用Cypress,永远无须在测试中添加强制等待、隐性等待、显性等待,Cypress 会自动等待元素至可靠操作状态时才执行命令或断言。
6、网络流量控制
Cypress 可以 Mock 服务器返回的结果,无须依赖后端服务器,即可实现模拟网络请求。
总结来说,使用 Cypress,开箱即用,想要的内容全部帮我们封装好了。
Windows下建立一个测试项目
Cypress 是一个被安装在电脑上的桌面应用,操作系统需要满足如下条件才能正常安装:
Mac OS 10.9+(仅提供64位二进制文件)
Linux Ubuntu 12.04+, Fedora 21, Debian 8的64位二进制文件
Windows 7+
安装 Cypress + 创建一个测试项目很简单:
1、首先,新建一个空文件夹
2、确保系统中已经安装 Node.js
如果未安装可参考我的文章:
https://blog.csdn.net/qq_45613931/article/details/105538861
3、在空文件夹下生成 package.json 文件:npm init,一路回车,最后输入yes
4、安装 Cypress
npm install cypress --save-dev
或者
cnpm install cypress --save-dev
5、安装完成。现在项目结构如下所示:
虽然还没有写任何内容,但是我们可以通过接下来的步骤,打开 Cypress,也让 Cypress 帮我们创建好项目结构。
6、打开 package.json 文件,对 scripts 中的内容进行改变:
"scripts": {
"open": "cypress open"
},
7、打开命令行窗口,在当前项目目录下,运行 Cypress
8、运行效果:
9、运行过首次后,目录结构就发生了改变,Cypress 帮我们建好了一个测试用例,里面就有基本的使用方法。接下来就通过生成的结构和内容进行下文说明。
Cypress的目录结构
查看目录结构:
1、fixtures 测试夹具
该目录下的内容主要用来存储测试用例的外部静态数据。它通常配合 cy.fixture() 使用。这些测试夹具的静态数据通常存储在 .json 文件中,如自动生成的 example.json。静态数据通常是某个网络请求对应的响应部分,包括HTTP状态码和返回值,一般是复制过来更改而不是自己手工填写。
对于 fixtures 的实际应用场景:
如果测试中需要对某些外部接口进行访问并依赖它的返回值,则可以使用测试夹具而无须真正访问这个接口(有点类似 mock)。
使用测试夹具的好处:
(1)消除了对外部功能模块的依赖
(2)已编写的测试用例可以使用测试夹具提供的固定返回值,并且确切知道这个返回值是我们想要的
(3)无须真正地发送网络请求,测试更快
2、integration 测试文件
该目录下的所有内容,且文件格式是以下的文件都将被 Cypress 识别为测试文件:
.js :普通的JavaScript 编写的文件(常用)
.jsx :带有扩展的 JavaScript 文件,其中可以包含处理 XML 的 ECMAScript
.coffee :一套 JavaScript 转译的语言。有更严格的语法
.cjsx :CoffeeScript 中的 jsx 文件
创建好之后,Cypress 的 Test Runner 刷新之后就可以看到对应测试文件了。
3、plugins 插件文件
该目录用来存放一些现成的插件,使我们可以修改或扩展 Cypress 的内部行为(如:动态修改配置信息和环境变量等),也可以自定义自己的插件。为了方便,每个测试文件运行之前,Cypress 都会自动加载插件文件。
对于 Cypress 为什么还需要自己设置插件:
之前提到过,Cypress 的独有优点就是测试代码运行在浏览器之内,使得 Cypress 跟其他的测试框架相比,有显著的架构优势。但是,该优点虽然提供了可靠性测试,但也使得和在浏览器之外进行通信更加困难。由此,通过插件就可以帮助我们将信息直接从测试代码传递到后端,或者修改特定浏览器的启动参数等等。
4、support 支持文件
该目录用来放置可重用配置项,如底层通用函数或全局默认配置。为了方便,每个测试文件运行之前,Cypress 都会自动加载支持文件。对于它的使用,只需要在该目录中的 index.js 文件里添加beforeEach() 函数即可。
beforeEach(function(){
cy.log('测试')
})
5、cypress.json
通常情况下,我们可以不用更改上述的默认目录结构,但实际上我们也可以去自定义它的默认目录结构以及 Cypress 的各项配置。修改这些配置信息,就是在 cypress.json 文件中来实现(文件默认为空)。
配置项可去其他博客和官网查看:
https://www.cnblogs.com/poloyy/p/13024996.html
https://docs.cypress.io/guides/references/configuration#Cypress-config
除了上述各种直接在 cypress.json 文件里更改配置项之外,Cypress 还允许我们通过 Cypress.config() 去获取或覆盖某些配置项,语法如下:
// 获取所有config信息
Cypress.config()
// 获取指定配置项的信息
Cypress.config(name)
// 更改指定配置项的默认值
Cypress.config(name, value)
// 使用对象字面量(object literal)设置多个配置项
Cypress.config(object)
比如,我们在某个测试文件中,就可以查看它,并去修改它:
describe('测试配置项', function () {
it('测试取值和设置值', function () {
// 获取 pageLoadTimeout默认值
cy.log(`${Cypress.config('pageLoadTimeout')}`)
// 设置 pageLoadTimeout 值
Cypress.config("pageLoadTimeout",100000)
// 再次获取 pageLoadTimeout 的值
cy.log(`${Cypress.config('pageLoadTimeout')}`)
})
})
Cypress的使用
现在以目前 Cypress 自动生成的目录结构中的todo.spec.js 为例,对这个测试用例进行测试。
1、首先在 Cypress 界面中,找到对应的测试用例。
2、单击 todo.spec.js 后,Cypress 就会启动 Test Runner 运行测试。运行成功后,我们就可以看到运行结果页面。
3、调试测试用例
测试用例运行时,难免会发生各种情况导致运行失败,如何快速定位发生错误的位置,了解错误信息,一直是自动化测试的痛点。而 Cypress 提供了多种 debug 能力,可以在测试运行错误时直达错误位置,并支持回放错误发生时的上下文信息,可直接看到测试失败的原因。具体来看:
(1)每个命令均有快照且支持回放
如下图中一样,左侧是测试步骤,右侧是测试页面。当我们鼠标 hover 测试步骤,在右侧就可以看到执行该命令时的页面效果。当我们鼠标点击测试步骤时,可以锁定该步骤,然后查看上下文信息。
(2)暂停测试并逐步运行、恢复执行
在调试测试代码时,Cypress 提供了命令来暂停测试运行:cy.pause()
比如,我们增加一个暂停测试:
效果:
右上角有两个按钮,左边的是 Resume:继续执行测试用例并运行到结束。
右边的是 Next:get:测试会变成逐步运行,点一下只执行下一个命令。
Cypress的基本用法
在总结项目结构时,也简单提到了一些基本命令,现在将主要对这些基本命令的功能和用法进行总结。
首先 Cypress 底层依赖于很多优秀的开源测试框架,其中就有 Mocha。Mocha 是一个适用于 Node.js 和浏览器的测试框架,它使得异步测试变得简单。
而 Cypress 就采纳了 Mocha 的 BDD(行为驱动开发)语法。Cypress 将 Mocha 硬编码在自己的框架中,所以编写测试用例都需要基于 Mocha 提供的基本功能模块。
接下来就以目前 Cypress 自动生成的目录结构中的todo.spec.js 为例,具体来看其中的常见功能模块。
1、对于一条可执行的测试用例来说,有以下两个必要的组成部分
(1)describe()
describe() 代表一个测试套件,所有的测试内容都需要写在其中。其中,可以包括多个测试用例 it() ,也可以嵌套使用测试套件。但需要注意,其中至少含有一条测试用例 it() 。
(2)it()
代表一条测试用例
比如:
// 测试套件 describe
describe('example to-do app', () => {
// 一条测试用例 it
it('displays two todo items by default', () => {
})
// 也能嵌套使用
describe('with a checked task', () => {
// 一条测试用例 it
it('can filter for uncompleted tasks', () => {
})
})
})
效果:
除了上述两个功能模块外,其他功能模块对于一条可执行的测试来说,都是可选的。
2、context()
context() 是 describe() 的别名,其行为方式是一致的,我们可以直接用 context() 去替代 describe() 。比如:
// 测试套件 context
context('example to-do app', () => {
// 一条测试用例 it
it('displays two todo items by default', () => {})
})
3、 cy.log:输出日志
4、钩子函数
钩子函数的作用就是在所有测试用例执行前做一些预置操作(如:准备测试数据、测试环境)或者在测试结束后做一些后置操作(如:清理测试数据)。
Mocha 提供的 Hook 函数共有四个:
before() :在当前测试套件的第一个测试用例中仅调用一次
beforeEach() :在当前测试套件中的所有测试用例(包括子测试套件)使用前调用
afterEach() :在当前测试套件中的所有测试用例(包括子测试套件)完成后调用
after() :在当前测试套件的最后一个测试用例(包括子测试套件)中仅调用一次
比如:
describe('测试生命周期 - 顶级测试套件', () => {
before(() => {
cy.log('顶级测试套件【before】')
})
beforeEach(() => {
cy.log('顶级测试套件【beforeEach】')
})
after(() => {
cy.log('顶级测试套件【after】')
})
afterEach(() => {
cy.log('顶级测试套件【afterEach】')
})
it('顶级测试套件 - 打印日志1', () => {
cy.log('顶级测试套件 - 打印日志1')
})
it('顶级测试套件 - 打印日志2', () => {
cy.log('顶级测试套件 - 打印日志2')
})
context('二级测试套件', () => {
before(() => {
cy.log('二级测试套件【before】')
})
after(() => {
cy.log('二级测试套件【after】')
})
it('二级测试套件 - 打印日志3', () => {
cy.log('二级测试套件 - 打印日志3')
})
})
})
实际效果:
总结来说:若包含多级测试套件,那么父级套件、祖父级套件声明的 hook 函数会作用于所有子级套件的测试用例,孙子级套件的测试用例…以此类推。
5、获取元素的方法
现在我们知道了一条测试用例的基本组成 describe、it、context、生命周期钩子函数,随后在测试用例中,首先就需要想办法查找页面元素。比如在这一节中,就以这段代码为例,进行说明:
<button id="main1" class="btn" data-cy="submit">submit</button>
<button id="main2" class="btn" data-test="submit">submit</button>
<button id="main3" class="btn" data-testid="submit">submit</button>
<ul>
<li id="li1">test1</li>
<li data-cy="li2">test2</li>
<li data-test="li3">test3</li>
<li data-testid="li4">test4</li>
</ul>
在查找页面元素的时候,分为两大类方法:基本方法和辅助方法。
(1)基本方法
① cy.get(selector)
该方法用来在 DOM 树中查找 selector 对应的 DOM 元素。比如:
it('测试get', () => {
cy.get('#main1')
cy.get('.btn')
cy.get('li')
cy.get('ul>[data-testid=li4]')
})
如果可以匹配多个元素,那么就会返回多个元素。(在这里建议适当回顾 CSS 选择器)
在这里需要补充说明,我们看到了上述代码中有一个 data-*
的属性,为什么需要它,它又是用来做什么的?
首先,做 UI 自动化测试,很明显的,每个测试用例都会包含对元素的操作。因此健壮、可靠的元素定位策略就可以保障测试成功率的提高。而在开发过程中,作为开发人员,我们很清楚,有一部分元素的 ID 或 class 可能会动态生成,除此以外,在开发中,我们可能也会做出把元素 CSS 样式名改掉甚至去掉等操作。那这种情况下,测试必然失败。如果一个元素没有 class、id,或者说动态生成,作为测试又不能更改这些代码,那该如何获取元素,从而进行测试呢?
Cypress 就为此提供了一个特别的定位策略,让我们在面对这种情况下,无须过多担心因定位失败而导致的测试失败。它提供了 data-* 属性,就如上面的代码一样。data中包含了下面三个定位器:
data-cy
data-test
data-testid
它们都是 Cypress 专有的定位器,仅用来测试。data-* 属性和元素的行为或样式无关,意味着即使 CSS 样式或 JS 行为改变,也不会导致测试失败。
注意:在实际项目中,显然这个属性是需要自己将 data-* 属性加到元素中,意味着你得有权限修改代码。
② cy.find(selector)
该方法用来在 DOM 树中搜索已被定位到的元素的后代,并将匹配到的元素返回为一个新的 jQuery 对象,比如:
it('测试find - 正确写法', () => {
cy.get("ul").find('#li1')
})
it('测试find - 错误写法', () => {
cy.find('#li1')
})
③ cy.contains(content)
该方法用来获取包含指定文本的 DOM 元素,比如:
it('.contains(content)', () => {
cy.contains('submit')
})
it('.contains(selector, content)', () => {
cy.contains('ul>li', 'test1')
})
it('.contains(content) 正则', () => {
cy.contains('/1$/')
})
总结一下:查找页面元素的基本方法
(2)辅助方法
单一的基础定位元素方法并不一定能满足复杂的场景,所以 Cypress 还提供了一些辅助方法,可以提高找到元素的准确性。对于具体的辅助方法就不在这里演示了,只不过需要注意,它们都需要在定位到元素后才能使用,和 cy.get().find()
一样。比如:
cy.get('ul').children('#li1')
cy.get('ul').children()
cy.get('#li1').parents()
cy.get('ul>li').each(($li) => {
cy.log($li.text())
})
cy.get('ul>li').eq(1)
总结一下:查找页面元素的辅助方法
6、断言
当我们获取到了页面元素后,我们就可以使用断言。断言是测试用例的必要组成部分,没有断言就不知道测试用例的有效性。Cypress 的断言基于 Chai 断言库,并且增加了对 Sinon-Chai、Chai-jQuery 断言库的支持,其中就包括 BDD 和 TDD 格式的断言。
BDD 格式的断言:expect 、should
TDD 格式的断言:assert
常见的断言方式:
(1)长度 have.length
// 通过 get 获取到的元素是否有3个
cy.get('li.selected').should('have.length',3)
(2)类 not.have.class
// 通过 get 找到的表单中的 input 输入框是否没有拥有 disabled 的 class
cy.get('form').find('input').should('not.hava.class','disabled')
(3)值 have.value
// 找到的元素中,是否有值为 poloyy 的元素
cy.get('textarea').should('have.value','poloyy')
(4)文本内容 not.contain
// 找到的元素中的文本是否不含有 'click me'
cy.get('a').parent('span.help').should('not.contain','click me')
(5)针对元素是否可见 be.visible
// 是否有能看到的 button 元素
cy.get('button').should('be.visible')
(6)针对元素是否存在 not.exist
// 是否不存在一个 id 为 loading 的元素
cy.get('#loading').should('not.exist')
(7)针对元素状态 be.checked
// 有没有一个单选框,它的状态为被选择
cy.get(':radio').should('be.checked')
(8)针对 CSS hava.css
// 是否有一个包含指定 CSS 样式的元素
cy.get('.completed').should('have.css','text-decoration','line-through')
(9)针对回调函数
如果内建的断言没有满足需求,那么可以自己写断言函数,然后作为一个回调以参数的形式传给 .should()
cy.get('.div').should(($div) => {
expect($div).to.have.length(1)
const className = $div[0].className
expect(className).to.match(/heading-/)
})
补充说明:重试
重试(Retry-ability)是 Cypress 的核心概念之一,有助于我们写出更加健壮的测试。比如,我们用断言cy.get(‘textarea’).should(‘have.value’,‘poloyy’)。此时我们考虑以下问题:
如果断言发生时,应用程序尚未更新DOM怎么办?
如果断言发生时,应用程序正在等待其后端响应,而导致页面暂无结果怎么办?
如果断言发生时,应用程序正在进行密集计算,而导致页面未及时更新怎么办?
上述情况在测试中经常会发生,这就会导致测试失败。
Cypress 解决上述问题就用到了重试:
(1)cy.get() 命令之后的断言通过,则该命令成功执行完成;
(2)cy.get() 命令之后的断言失败,则 cy.get() 命令会自动重新查询 web 应用程序的 DOM 树,然后 Cypress 将再次尝试对 cy.get() 返回的元素进行断言;
(3)如果断言仍然失败, cy.get() 仍然会重新查询 DOM 树…以此类推;
(4)直到断言成功或 cy.get() 命令超时。
除此以外,在日常测试中,有时候需要多重断言,即获取元素后跟多个断言。在多重断言中,Cypress 将按顺序进行断言,即当第一个断言通过后,会进行第二个断言,通过后进行第三个断言…以此类推。
当然,重试也是有条件的,并不是所有命令都会去不断重试。比如:当命令可能改变被测应用程序的状态时,该命令将不会重试(如: click())。Cypress 仅会重试那些查询 DOM 的命令: cy.get() 、 find() 、 contains() 等
补充:重试的超时时间默认是 4秒,对应的配置项是: defaultCommondTimeout ,如果想改重试的超时时间,在 cypress.json 文件改对应的字段值即可
7、点击事件
点击事件用法比较简单,依然是通过 get 获取到元素后,用 .click 的方式去触发。但其中有很多更深的内容,可参考文章:
https://www.cnblogs.com/poloyy/p/13066005.html
8、操作页面元素的命令
操作页面元素的命令依然是在获取到元素后,再进行操作。比如:
cy.get('input').type('输入')
cy.get('input').first().focus()
cy.get('input').eq(0).type('234').clear()
cy.get('form').submit()
cy.get('[type="radio"]').first().check()
cy.get('select').select('user')
cy.get('footer').scrollIntoView()
cy.get('a').trigger('mousedown')
具体总结如下:
9、其余命令参考书
通过以上内容,已经可以实现一个基本的测试用例:查找页面元素、操作页面元素、断言、触发事件。比如,现在可以去查看 Cypress 自动生成的简例中,查看简单的测试用例。如下面的第十部分内容。由于 Cypress 的相关命令众多,它还可以获取页面全局对象、操作浏览器、操作文件、发送HTTP请求、操作Cookie等等,所以为了避免大篇幅引用大佬们的文章内容,剩下的命令就需要各位去博客中按需查找用法:
https://www.cnblogs.com/poloyy/p/14031640.html
10、官方测试用例
通过对上述命令的简单了解和学习,针对目前 Cypress 自动生成的目录结构中的todo.spec.js 为例,对其中的基本用法再做总结:
// 测试套件 describe
describe('example to-do app', () => {
// 钩子函数:所有测试用例都会使用
beforeEach(() => {
// Cypress 在每次测试中都会是从一张空白页面开始
// 因此,我们必须告诉所有的测试,先用 cy.visit 去访问 Cypress 例子的网站
// 由于我们希望在所有测试开始时,访问相同的 URL
// 所以,我们将它包含在 beforeEach 函数中,以便在每个测试之前运行
cy.visit('https://example.cypress.io/todo')
})
// 一条测试用例:展示 todolist 的两个默认项
it('displays two todo items by default', () => {
// 使用 cy.get 获取与 css 选择器匹配的所有元素
// 随后使用 should 断言,去判断此处是否有两个默认项
cy.get('.todo-list li').should('have.length', 2)
// 更进一步的,我们可以使用 first 和 last 去单独获取第一个和最后一个匹配的元素
// 随后使用 should 断言,去匹配里面是否有对应的文本
cy.get('.todo-list li').first().should('have.text', 'Pay electric bill')
cy.get('.todo-list li').last().should('have.text', 'Walk the dog')
})
// 一条测试用例:是否可以添加一个新的 todoitems 项
it('can add new todo items', () => {
// 创建一个变量,以便后续对它重用
const newItem = 'Feed the cat'
// 寻找到输入框的元素,通过 type 去将输入的内容写入
// 再写入后,我们需要敲击 enter 才会提交这个输入
cy.get('[data-test=new-todo]').type(`${newItem}{enter}`)
// 现在新的内容已经生成,我们先用断言去判断此处是否有三个默认项
// 因为这是新增的内容,这个内容会作为列表的最后一个元素存在
// 注意:断言必须生成在一个元素上,
// 而在这里用 last 获取到了最后的元素,所以这里可以嵌套断言
cy.get('.todo-list li')
.should('have.length', 3)
.last()
.should('have.text', newItem)
})
// 一条测试用例:是否可以检测每一项的 checkbox 状态
it('can check off an item as completed', () => {
// 除了使用 get 利用选择器获取元素外,contains 也可以获取元素
// 这种方式就是寻找页面中存在的文本
// 寻找到这个文本后,我们需要去找到它的父类 input 标签,从而调整 checkbox 状态
// 所以在这里先用 parent 拿到当前文本的父类 input 标签
// 随后在父类中,找到 checkbox 选择器,再使用 check 命令对其进行选中
cy.contains('Pay electric bill')
.parent()
.find('input[type=checkbox]')
.check()
// 现在我们依然使用这种方式,寻找这个文本标签
// 随后使用 parents 一直向上遍历,寻找 li 标签
// 最后找到这个元素后,将它的类改为已完成状态
cy.contains('Pay electric bill')
.parents('li')
.should('have.class', 'completed')
})
// context 是 describe 的别名,我们可以创建嵌套测试套件
// 一条测试用例:与 checked 相关的测试
context('with a checked task', () => {
// 钩子函数
beforeEach(() => {
// 每个测试用例运行前,先把这个文本中的 checkbox 选中,用于后面的测试
cy.contains('Pay electric bill')
.parent()
.find('input[type=checkbox]')
.check()
})
// 测试用例:点击 Active 按钮,去除未完成任务
it('can filter for uncompleted tasks', () => {
// 寻找到拥有 Active 文本的标签,触发点击事件
cy.contains('Active').click()
// 为了检测是否成功,我们去判断 todolist 里是否只有一个 todoitems
// 且它的值为 walk the dog
cy.get('.todo-list li')
.should('have.length', 1)
.first()
.should('have.text', 'Walk the dog')
// 为了更好的测试,我们也可以检测一下,是否页面中不存在拥有这个文本的标签
cy.contains('Pay electric bill').should('not.exist')
})
// 测试用例:点击 Completed 按钮,去除已完成任务
it('can filter for completed tasks', () => {
// 寻找到拥有 Completed 文本的标签,触发点击事件
cy.contains('Completed').click()
// 为了检测是否成功,我们去判断 todolist 里是否只有一个 todoitems
// 且它的值为 Pay electric bill
cy.get('.todo-list li')
.should('have.length', 1)
.first()
.should('have.text', 'Pay electric bill')
// 为了更好的测试,我们也可以检测一下,是否页面中不存在拥有这个文本的标签
cy.contains('Walk the dog').should('not.exist')
// 我们也可以使用这种方式
cy.get('.todo-list li').should('not.have.text', 'Walk the dog')
})
})
})
Cypress 和项目相关的准备工作
1、Cypress 访问项目路径
在进入测试前,必不可少的就是先让 Cypress 能够链接上我们的项目,也就是首先先能够访问项目。而想要实现这一步,需要去 cypress.json 中进行设置。配置方法有以下三种:(不要忘记先运行自己的项目)
(1)修改环境变量
在之前的笔记中,有提到过 cypress.json 其中的配置项。其中,有一个配置项 env,我们可以根据它来设置访问路径:
{
"env": {
"url": "http://localhost:18082"
}
}
此时我们想使用时:
describe('测试', () => {
beforeEach(() => {
cy.visit(Cypress.env('url'))
})
it('进入?', () => {
cy.log('进入')
})
})
效果:
但是这种方式并不是常规的解决方案,因为 Cypress 中本身有直接配置路径的配置项,我们将路径配在这里有点得不偿失。而且我们想要获取路径时,还需要通过 Cypress.env 写出来,很麻烦。对此,可以使用第二种方法。
(2)修改配置项 baseUrl
在 cypress.json 中,添加如下配置项:
{
"baseUrl": "http://localhost:18082",
}
此时我们想使用时:
describe('测试', () => {
beforeEach(() => {
cy.visit('')
})
it('进入?', () => {
cy.log('进入')
})
})
需要注意的是:当我们配置了 baseUrl ,测试套件中的 cy.visit() 、 cy.request() 都会自动以 baseUrl 的值作为前缀。但是这并不代表我们就可以不用写 cy.visit(),或者里面可以为空。调用时,至少也要传个空字符串,如上例。并且,当我们需要访问某些网址或者发起接口请求时,在代码中就可以不用再指定请求的 host 或者 url 了。
效果:
但这个方法也有坏处:虽然我们现在 cy.visit() 中已经什么都不用写了,但是如果我们想要在不同的环境中,访问不同的路径该怎么办?手动去改 baseUrl 还是很麻烦。对此,可以使用第三种方法。
(3)启动项目时进行配置
这次我们不在 cypress.json 中进行配置,而是在 package.json 中进行配置。之前我们在 package.json 中的 scripts 中,添加过运行的命令。现在相关的配置项,依然设置在这个 scripts 中。我们要做的就是在 cypress open 的时候,直接为其配置不同开发环境下的 baseUrl 即可。那么不同开发环境的不同路径就可以在 package.json 中统一管理。具体来看:
"scripts": {
"test:gui": "cypress open --config baseUrl=http://localhost:18082 --env mode=dev",
"dev:gui": "cypress open --config baseUrl=http://172.18.193.237:9800/ --env mode=dev",
"prod:gui": "",
"dev:cli": "cypress run --config baseUrl=http://localhost:18082 --env mode=dev",
"prod:cli": "",
"open": "cypress open"
},
(test测试。dev开发。prod生产)
随后,我们使用 npm run test:gui 运行。
测试是否可行:(不要忘记去掉 cypress.json 中的 baseUrl 配置项)
这种方法和配置 baseUrl 的用法是一样的,只不过是在 npm 运行时,将这个配置项进行了一次配置,从而区分出了不同环境下的不同路径。在这段代码中,还有一些其他用法。
其中的 cypress run 它的作用就是用 cmd 命令行运行 cypress / integration 目录下的所有测试用例,并在运行完成后在控制台输出一个总结的报告。那么如果我们想看到所有测试的结果,比如:成功、失败的测试用例数量、运行时间等,那么就可以直接使用这种方式。
Cypress run 相关的更多内容可参考:
https://www.cnblogs.com/yoyoketang/p/12974805.html
而除了 --config 外,后面还有一个 --env,这就涉及到环境变量了。在这里的含义就是,将 cypress.json 中 env 中的 mode 设置为 dev。这些不同的环境变量,也有它自己的作用。
2、环境变量
环境变量就是根据环境的变化,变量会有不同的值。比如最常见的:开发环境、测试环境、生产环境的 URL 肯定不一样,我们可以根据不同的环境选择不同的环境变量。设置环境变量有以下几种方式。
(1)在 cypress.json 文件中设置
这是一种最常见的使用方式。写在 cypress.json 中的 env 配置中,在测试运行时,就使用 Cypress.env() 访问这些值。就比如在第一节使用 url 一样:
cy.visit(Cypress.env('url'))
当不同环境运行时,如果需要访问不同的数据值,我们只需要改环境变量即可了,而不用动到代码。改变的方式就可以采用第一节中使用的,在不同的运行环境下,通过 --env 去配置不同的mode,那么根据不同的 mode 就可以获取不同的访问值。比如:
it('进入?', () => {
cy.log(Cypress.env('mode'))
})
又或者我们直接在 --env 后,添加我们想要增加的环境变量也可以,但是这样一来在这里添加的内容可能会比较多,麻烦。
这种用法会有一些问题:目前是通过 npm 时传递参数的方式,从而确保在 env 中区分出不同的环境变量,但是如果是团队开发,那么这些文件属于项目共有项,很容易出现冲突。也就是说,这种方法适用于在所有计算机上有相同的值。而为了解决这个问题,有了接下来的方法。
(2)创建一个 cypress.env.json 文件
在项目中是没有这个文件的,我们需要自己去创建 cypress.env.json 文件,在项目的根目录下(cypress.json 同级目录下)。它的作用是,每次运行时,Cypress 将会自动检查它,并且里面的值会覆盖 cypress.json 中重名的环境变量。这样一来第一种方案下的问题就解决了。此时,在团队开发时,如果将cypress.env.json 添加到.gitgnore文件中,那么文件中的值对于每个开发人员的计算机都是不同的,不会互相影响。
这种方法也会有问题:我们需要在项目中单独多处理一个新文件,而且也有可能会出现覆盖掉关键的同名环境变量的情况,在纠错时就很难发现了。 但没关系,只要注意到这些问题,我们依然可以采用这种方案。
(3)导出为 CYPRESS_*
针对第二个问题中,项目需要单独多处理一个新文件,Cypress 也提供了一种新解决方案:在计算机中,任何以 CYPRESS_ 或 cypress_ 开头的环境变量都会自动被 Cypress 识别出来,随后直接覆盖 cypress.json 和 cypress.env.json 文件中重名的环境变量。这样一来,相关文件完全由计算机自己持有。
(4)可以通过 test configuration 设置环境变量
当然,也还有一种最基本的方法,就是给测试用例或测试用例集单独设置环境变量,但是这样很没必要,因为它会覆盖其他方式设置的环境变量。
以上就是对 Cypress 基本使用的说明,希望能给大家带来帮助。
由于在自己的工作和学习过程中,只查看某个大佬的教程或文章无法满足自己的学习需求和解决遇到的问题,所以自己在追赶大佬们步伐的基础上,又自己总结、整理、汇总了一些资料,方便自己理解和后续回顾,同时也希望给大家带来帮助,所以才写下该篇文章。在本文中,所有参考或引用大佬们文章内容的位置,都附上了原文章链接,您可以直接前往查阅观看。在原文章内容的基础上,若无任何补充内容,同时避免直接大段摘抄大佬们的文章,该情况下也只附上了原文章链接供大家学习。本文旨在总结归纳,并希望给大家提供帮助,未用作任何商用用途。文章内容如有错误之处,望各位大佬指出。如果涉及侵权行为,将会第一时间对文章进行删除。
👉 个人博客主页 👈
📝 一个努力学习的程序猿
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)