cypress

cypress

下载与安装

安装方式一

  1. 安装node.js
  2. 因为npm直接下载会也很慢,所以先修改下载源
    1. 执行命令 npm config set registry http://registry.npm.taobao.org
    2. 查看是否更改成功 npm config get registry
  3. 本地创建一个名为cypresses的目录
  4. 在该目录下执行 npm install cypress --save-dev
  5. .bin目录下启动cypress cypress open
  6. 启动过后会发现缺少文件 npm WARN saveError ENOENT: no such file or directory, open 'D:\Cypress\package.json'
  7. 在新建的目录下创建package.json,(如果有直接添加如下代码)
  8. 根目录下执行命令 npm run cypress:open
{
  "scripts": {
    "cypress:open": "cypress open"
  }
}

安装方式二

执行命令 yarn add cypress --dev

基本介绍

简介

  1. 基于JS,可以对浏览器中运行的任何内容进行快速、简单、可靠的测试
  2. Cypress是自集成的,提供了一套完整的端到端测试,无须借助其他外部工具,安装后即可快速地创建、编写、运行测试用例,支持回放。
  3. Cypress允许编写所有类型的测试,覆盖了测试金字塔模型的所有测试类型【界面测试,集成测试,单元测试】
  4. Cypress 底层协议不采用 WebDriver

原理

  1. 大多数测试工具是通过外部浏览器运行,并在网络上执行远程命令来运行,主要是因为webdriver底层通信协议基于JSON Wire Protocol,运行需要网络通信。
  2. 但是cypress和webdriver的方式完全相反,它与应用程序在相同的声明周期里执行。

cypress运行更快的原因

  1. cypress测试代码和应用程序均运行在由cypress全权控制的浏览器中
  2. 且它们运行在同一个Domain下的不同iframe中,所以cypress的测试代码可以直接操作DOM、window objects、local storages而无需通过网络访问

cypress稳定性、可靠性更高的原因

  1. cypress可以在网络层进行及时读取和更改网络流量的操作
  2. cypress背后是node.js process控制的proxy进行转发,使得cypress不仅可以修改进出浏览器的所有内容,还可以更改可能影响自动化操作的代码。
  3. cypress相对于其他测试工具来说,能从根本上控制整个自动化测试的流程。

cypress的特性

  1. cypress在测试代码运行时会自动拍照,也就是等测试结束之后,用户可以在cypress提供的test runner,通过悬停在命令上的方式看运行时的步骤。
  2. 实时重新加载,当测试代码修改保存后,cypress会自动加载改动的地方,重新运行测试。
  3. 可调试性,当测试失败时,可以直接通过开发者工具进行调试。
  4. 自动等待,使用cypress,就无需与selenium一样在测试中添加强制、显示等待以及隐式等待了,cypress会自动等待元素到可靠操作状态时才执行命令或断言。
  5. 截图和视频,cypress在测试运行失败时会自动截图,在无头运行时,会录制整个测试套件的视频。

解析Cypress的默认文件结构

fixture测试夹具

简介

  1. 测试夹具通常配合cy.fixture()使用
  2. 主要用来存储测试用例的外部静态数据
  3. fixtures默认就在cypress/fixtures目录下,单也可以配置到另一个目录

外部静态数据的详解

  1. 测试夹具的静态数据通常存储在.json文件中,如自动生成的examples.json
  2. 静态数据通常是某个网络请求对应的响应部分,包括HTTP状态码和返回值,一般是复制过来更改而不是自己手工填写.
  3. 如果你的测试需要对某些外部接口进行访问并依赖它的返回值,则可以使用测试夹具而无须真正访问这个接口(用过mock的能有体会)

使用测试夹具的好处

  1. 消除了对外部功能模块的依赖.
  2. 已编写的测试用例可以使用测试夹具提供的固定返回值,并且你确切知道这个返回值是你想要的.
  3. 因为无需真正的发送网络请求,所以测试更快.

test file 测试文件

  1. 测试文件就是测试用例,默认位于cypress/integration,但也可以配置到另一个目录
  2. 所有在integration文件下,且文件格式是以下的文件都将被Cypress识别为测试文件.
  3. 创建好后,Cypress的Test Runner刷新就能看到对应测试文件

plugin file 插件文件

前言

  1. Cypress的优点就是在运行是在浏览器之内,使得Cypress跟其他的测试框架相比,有显著的架构优势
  2. 虽然提供了可靠性测试,但也使在浏览器之外进行通信更加困难

插件文件的诞生与应用

  1. 为了解决跟外部通信,可以修改或扩展Cypress的内部行为(如:动态修改配置信息和环境变量等),也可以自定义自己的插件.
  2. 默认情况,插件位于cypress/plugins/index.js中,单可以配置到另一个目录
  3. 为了方便,每个测试文件运行之前,Cypress都会自动加载插件文件
  4. 我们一般通过动态修改来自cypress.json、cypress.env.json,CLI或系统环境变量的已解析配置和环境变量以及修改特定浏览器的启动参数、将消息直接从测试代码传递到后端

support file 支持文件

简介

  1. 支持文件目录防止可重用配置项,如底层通用函数或全局默认配置
  2. 支持文件默认位于cypress/support/index.js中,单可以配置到另一个目录
  3. 为了方便,每个测试文件运行之前,Cypress都会自动加载支持文件

使用

只需要在cypress/support/index.js文件添加beforeEach()函数即可,这将实现每次测试运行前能打印出所有的环境给变量信息.如下

beforeEach(function () {
    cy.log(`当前环境变量为${JSON.stringify(Cypress.env())}`)
    cy.log(`当前配置项信息为${JSON.stringify(Cypress.config())}`)
})

自定义Cypress

前言

  1. Cypress不仅支持用户自定义文件结构,还支持用户自定义Cypress的各项配置
  2. 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相关

  1. 超时是必须要了解的核心概念
  2. 几乎所有命令都可以某种方式超时
  3. 所有断言,无论它们是默认断言还是自己添加的断言都具有相同的超时时间
配置项 默认值 描述
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的重试机制

重试是Cypress的核心概念之一,有助于我们写出更加健壮的测试。

问题前言

现在的web应用基本都是异步的,出现一下的情况就要做重试的操作

  1. 如果断言发生时,应用程序尚未更新DOM怎么办?
  2. 如果断言发生时,应用程序正在等待其后端响应,而导致页面暂无结果怎么办?
  3. 如果断言发生时,应用程序正在进行密集计算,而导致页面未及时更新怎么办?
    上述情况在测试中经常会发生,一般处理方法是在断言前增加断言时间(或像selenium一样中使用显式或者隐式等待),不过仍然会出现测试再次失败的情况。、

Cypress如何处理问题前言

  1. cy.get()命令之后的断言通过,则该命令成功执行完成。
  2. cy.get()命令之后的断言失败,则cy.get()命令会自动重新查询web应用程序的DOM树,然后Cypress将再次尝试对cy.get()返回的元素进行断言。
  3. 如果断言仍然失败,cy.get()仍然会重新查询DOM树,以此类推
  4. 直到断言成功或cy.get()命令超时
    Cypress的处理机制有点像selenium的显示等待,只不过Cypress是全局的,不用针对元素去单独识别,Cypress这种自动重试机制避免了在测试代码中编写硬编码等待(强制等待),使测试代码更加健壮。

多重断言

  1. 在日常测试汇总,有时候需要多重断言,即获取元素后跟多个断言。
  2. 在多重断言中,Cypress将按顺序进行断言,也就是当第一个断言通过后,会进行第二个断言,通过后进行第三个断言,依次类推。

重试机制的条件

  1. Cypress并不会重试所有的命令,当命令可能改变被测应用程序的状态时,该命令将不会重试(比如点击输入等)
  2. Cypress仅会重试那些查询DOM的命令:cy.get()、find()、contains()等。

断言的相关实例

context('登录测试', function () {

    const username = 'wupeng'
    const password = 'bb961202'

    beforeEach(function () {
        cy.visit('http://192.168.102.210:10001/zentao/user-login.html')

    })

    it('登录成功,跳转到首页', function () {
        cy.get('input[name=account]').type(username)
        cy.get('input[name=password]').type(password)
        // 验证input下的属性type,值是否为password
        cy.get('input[name=password]').should('have.attr','type','password')
        cy.get('#submit').click()
        // 验证url中是否包含/my
        cy.url().should('include','/my')
    });
})

Mocha介绍

前言

  1. Cypress底层依赖于很多优秀的开源框架,其中就有Mocha
  2. Mocha是一个适用于Node.js和浏览器的测试框架,它使得异步测试变的简单。

JS语言带来的问题

JS是单线程异步执行的,这使得测试变的复杂,因为无法像测试同步执行的代码那样,直接判断函数的返回值是否符合预期(因为给函数赋值时函数可能并未执行)

如何验证异步函数的正确性

  1. 需要测试框架爱支持回调Promise或者其他方式来验证异步函数的正确性。
  2. Mocha提供了出色的异步支持报货Promise,从而使得异步测试变得简单。

Mocha提供了什么

  1. 多种接口来定义测试套件,Hooks,单个测试
  2. BDD,行为驱动开发
  3. TDD,测试驱动开发
  4. Exports、QUnit、Require

常见Mocha模块

  1. describe()
  2. context()
  3. it()
  4. before()
  5. beforEach()
  6. afterEach()
  7. after()
  8. .only()
  9. .skip()

必要的组成部分

  1. 对于每一条可执行的测试用例,有两个必要的组成部分,describe()代表测试套件,里面可以设定context(),也可以包括多个测试用例it(),还能嵌套子测试套件
  2. 一个测试条件可以不包括任何钩子函数,但必须要包含至少一条测试用例it()
  3. context()是describe()的别名,行为方式是一致的,可以直接使用context()代替describe()

钩子函数Hook

Mocha提供的Hook

  1. before()
    1. 该测试套件下,所有测试用例的统一前置操作
    2. 它在一个describe()或context()内只会执行一次,在所有it()之前都会执行
  2. beforeEach()
    1. 该测试套件下,每个测试用力的前置操作
    2. 一个describe()或contest()内有多少个测试用例it(),就会执行几次beforeEach()
  3. afterEach()
    1. 该测试套件下,每个测试用例的后置操作
    2. 一个describe()或context()内有多少个测试用例it(),就会执行几次afterEach()
  4. after()
    1. 该测试套件下,所有测试用例的统一后置操作
    2. 在一个describe()或context()内只会执行一次,在所有it()之前执行。

代码实例

describe('hook', function () {
    before(function () {
        cy.log("所有测试用例之前的操作,只会执行一次")
    });
    beforeEach(function () {
         cy.log("所有测试用例之前的操作,每个it之前执行一次")
    });
    after(function () {
         cy.log("所有测试用例之后的操作,只会执行一次")
    });
    afterEach(function () {
         cy.log("所有测试用例之后的操作,每个it之后都会执行一次")
    });
    it('打印数字1', function () {
         cy.log("1")
    });
    it('打印数字2', function () {
         cy.log("2")
    });
});

hook的作用

利用钩子函数可以在所有测试用例执行前做一些预置操作,或者在测试结束后做一些后置操作

跳过执行与指定执行

  1. 在做自动化测试中,跳过执行某些测试用例
    1. 通过.skip()可以完成跳过执行测试套件或测试用例
    2. 通过describe.skip()或者context.skip()来跳过不需要执行的测试套件
    3. 通过it.skip()来跳过不需要执行的测试用例
  2. 同样也可以只运行某些指定的测试用例
    1. 通过.only()可以完成指定执行测试套件或测试用例,当存在.only()指定某个测试套件或测试用例时,只有这个测试套件或测试用例会被执行,其他未加.only()的测试套件或测试用例都不会执行
    2. 通过describe.only()或者context.only()来指定需要执行的测试套件(不过注意的是即使添加了.only()的子套件,即使父套件没有添加,它也会执行。添加了.only()的套件,该套件下的所有测试用例默认都会执行)
    3. 通过it.only()来指定需要执行的测试用例(如果当前测试套件下有it.only(),那么即使存在测试套件添加了.only(),该测试套件也不会执行。如果同个测试套件下有多个it.only()时,都会执行)
  3. 通过Cypress.env('flag')==1来实现动态跳过测试用例,最后重启cypress,启动命令:npm run cypress:open --env flag=1
// 实例代码
describe('测试跳过', function () {
    beforeEach(function () {
        cy.log("前置操作")
    });
    it('测试开始1', function () {
        if (Cypress.env('flag')===1){
            cy.log("执行该用例")
        }else{
            cy.log("跳过执行")
            this.skip()
        }
    });
    it('测试开始2', function () {
       cy.log("测试成功")
    });
});

this.skip(),当测试用例内调用该方法时,方法后面的代码都不会执行,方法前面若是调用Cypress的方法则也不会执行。
只有非Cypress方法才会执行,如console.log("log")。

动态生成测试用例

前言

  1. 自动化测试中,数据驱动是很重要的一个点
  2. 实际项目中,肯定会出现这种情况:多条测试用例的执行步骤,断言步骤完全一致,只有输入和输出数据不一样
  3. 这个时候依靠数据驱动(数据参数化)来解决这个问题可以提升我们的测试效率
  4. 在 Cypress,可以通过数据来动态生成测试用例,以达到数据驱动的效果

实际操作步骤

  1. 新建一个数据文件,存放输入数据。在Cypress安装目录/cypress/integration文件下,创建一个目录data,在该目录下创建一个test.data.js文件(名字随意取)。
  2. 创建测试文件
  3. 执行

代码实例

// 新建数据文件
export const testLoginUser=[
    {
        summary:"登录成功",
        username:"admin",
        password:"123456"
    },
    {
        summary:"登录失败",
        username:"admin",
        password:"1234567"
    }
]

// 新建测试文件
import {testLoginUser} from "./data/test.data";
describe('登录', function () {
    beforeEach(function () {
        cy.visit("http://192.168.102.49:5001/#/")
    });
    // 循环测试数据
    for (const user of testLoginUser){
        it(user.summary, function () {
            cy.get("#username").type(user.username)
            cy.get("#password").type(user.password)
            cy.get("button[type=submit]").click()
            cy.url().should('include','bay/stationManage')
        });
    }
});

断言

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')

针对元素属性值

// 重试,验证元素属性值是否存在

cy.get('#kw').should('hava.attr','username','wupeng')

针对CSS

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

环境变量

baseUrl

  1. 通过环境变量设置测试套件访问的URL,这只是其中的一种方式
  2. Cypress可以通过配置baseUrl取代环境变量的方式,当配置了baseUrl,测试套件中的cy.visit()、cy.request()都会自动以baseUrl的值作为前缀
  3. 并且,当需要访问某些网址或者发起接口请求时,在代码中就可以不用在指定请求的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文件

  1. 创建自己的cypress.env.json文件,Cypress将会自动检查它
  2. 里面的值会覆盖cypress.json中重名的环境变量
  3. 创建与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.$('#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*\]](https://www.w3school.com.cn/cssref/selector_attr_end.asp) | 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)

实例代码

describe('基本查询元素方法', function () {
    beforeEach(function () {
        cy.visit("http://192.168.102.49:5001/#/")
    });
    it('开始测试', function () {
        cy.get("#username").type("admin").should("have.value","admin")
        cy.get("#root").find("#password").type("123456").should("have.value","123456")
        cy.contains("登 录").click()
    });
});

查找页面元素的辅助方法

辅助定位元素方法 描述
.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开始
.closest() 获取匹配的第一个DOM元素(在操作上一条命令返回结果集有详细的实例)

.children()

用来获取DOM元素的子元素

基本语法格式

.children()
.children(selector)

操作实例代码

describe('基本元素辅助方法', function () {
    beforeEach(function () {
        cy.login('admin','123456')
    });
    it('.children()', function () {
        cy.get('ul').children()
    });
    it('.child(selector)', function () {
        cy.get('ul').children('#li1')
    });
});

.parents()、.parent()

带s的表示获取DOM元素的所有父元素,不带s的藐视获取DOM的第一层父元素

基本语法

.parents()
.parent()

操作实例代码

describe('基本元素辅助方法', function () {
    beforeEach(function () {
        cy.login('admin','123456')
    });
    it('.parents()', function () {
        cy.get('#li').parents()
    });
    it('.parent()', function () {
        cy.get('#li').parent()
    });
});

.siblings()

用来获取DOM元素的所有同级元素

基本操作语法

.siblings()

操作实例代码

describe('基本元素辅助方法', function () {
    beforeEach(function () {
        cy.login('admin','123456')
    });
    it('.siblings()', function () {
        cy.get('#li').siblings()
    });
   
});

.first()、.last()

  1. 匹配给定的DOM元素列表中的第一个元素(如果是单个DOM元素调用此方法,则返回自己)
  2. 匹配给定的DOM元素列表中的最后一个元素

基本语法

.first()
.last()

操作实例代码

describe('基本元素辅助方法', function () {
    beforeEach(function () {
        cy.login('admin', '123456')
    });
    it('.first()', function () {
        cy.get('ul>li').first()
    });
    it('.last()', function () {
        cy.get('ul>li').last()
    });
});

.next()、.nextAll()、.nextUntil()

  1. 获取给定的DOM元素后面紧跟的下一个同级元素
  2. 获取给定的DOM元素后面紧跟的所有同级元素
  3. 获取给定的DOM元素后面紧跟的所有同级元素,知道遇到Until里定义的元素为止。

基本语法

.next()
.nextAll()
.nextUntil(selector)

操作实例代码

describe('基本元素辅助方法', function () {
    beforeEach(function () {
        cy.login('admin', '123456')
    });
    it('.next()', function () {
        cy.get('ul>li').next()
    });
    it('.nextAll()', function () {
        cy.get('ul>li').nextAll()
    });
    it('.nextUntil()', function () {
        cy.get('ul>li').nextUntil("#li3")
    });
});

.prev()、.prevAll()、.prevUntil()

  1. 获取给定的DOM元素前面紧跟的上一个同级元素
  2. 获取给定的DOM元素前面紧跟的所有同级元素
  3. 获取给定的DOM元素前面紧跟的所有同级元素,知道遇到Until里定义的元素为止

基本语法

.prev()
.prevAll()
.prevUntil(selector)

操作实例代码

describe('基本元素辅助方法', function () {
    beforeEach(function () {
        cy.login('admin', '123456')
    });
    it('.prev()', function () {
        cy.get('ul>li:nth-child(2)').prev()
    });
    it('.prevAll()', function () {
        cy.get('ul>li:nth-child(2)').prevAll()
    });
    it('.prevUntil()', function () {
        cy.get('ul>li:nth-child(2)').prevUntil("#li3")
    });
});

.each()

用来遍历数据或者及其类似的解耦股(对象有length即可)

基本语法格式

.each(callbackFn)

操作实例代码

describe('基本元素辅助方法', function () {
    beforeEach(function () {
        cy.login('admin', '123456')
    });
    it('.prev()', function () {
        cy.get('ul>li:nth-child(2)').each(function (el) {
            cy.log(el.text())
        })
    });
});

.eq()

  1. 在元素或者数组中的特点索引处获取DOM元素
  2. 作用跟:nth-child()选择器一样,只不过下标从0开始

基本语法

.eq()

操作实例代码

describe('基本元素辅助方法', function () {
    beforeEach(function () {
        cy.login('admin', '123456')
    });
    it('.prev()', function () {
        cy.get('ul>li').eq(0)
    })
})

.closest()

获取匹配到的第一个DOM元素(无论是它本身还是它的祖先之一)

基本语法格式

.closest(selector)
.closest(selector, options)

基本用法

// 正确用法
// 找到离 td 标签元素最近的 .filled 元素
cy.get('td').closest('.filled') 

// 错误用法
// 不能通过 cy 直接调用
cy.closest('.active')

// url() 返回的并不是 DOM 元素
cy.url().closest() 

option基本参数

  1. log 是否将命令显示到命令日志中,默认true
  2. timeout:命令超时时间

实例代码

describe('登录', function () {
    beforeEach(function () {
        cy.visit("http://192.168.102.49:5001/#/")
    });
    it("测试closest", function () {
        cy.get("#username").type("admin")
        cy.get("#password").type("123456")
        cy.get("button[type=submit]").click()
        cy.get("ul[role=menu]").closest("li").click()
    });
});

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键
键盘的del键
键盘的↓键
键盘的←键
键盘的→键
键盘的↑键
键盘的end键,将光标移动到行尾
回车键
退出键
键盘的home键,将光标移动到行首
的键盘insert键
键盘的pagedown,向下滚动
键盘pageup,向上滚动
通过创建选择范围来选择所有文本

.focus()、.blur()

聚焦与失焦DOM元素

  1. 必须是DOM元素才能调用.focus()、.blur()方法,不一定是输入框
  2. 确保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()

  1. 表示清空输入框的所有内容
  2. .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)
});

所支持的参数传递

  1. cypress可以通过Test Runner的快照找到阻止DOM元素交互的情况,但某些情况下可能会阻碍测试的进行
  2. 场景:有一个嵌套的导航结构,用户必须将鼠标hover在一个非常特定的模式中,才能拿到所需的链接,当测试的时候,我们只是想获取链接而已,过多的繁琐操作会导致测试失败.
  3. 当设置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()

  1. 检查选项框是否被选中,针对input标签的单选框或复选框达到选中的作用
  2. 和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 元素滚动后的偏移量

.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

  1. 是否在不使用缓存的情况下重新加载当前页面
  2. 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

  1. 必须为非负数
  2. 像素单位px

参数options

Option Default Description
log true 在命令日志中显示命令

参数orientation

  1. 默认是纵向,portrait
  2. 可改横向,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")

自动缩放

  1. 默认情况下,如果屏幕不够大,无法显示应用程序所有像素,则Cypress会将应用程序缩放并居中,以适应Cypress的Test Runner
  2. 缩放应用程序不会影响应用程序的任何计算或行为
  3. 自动缩放的好处:无论屏幕大小如何,测试都始终通过或失败,测试最终在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

  1. 两种值,一种是需要直接访问的URL,可以是一个完整的URL,比如:https://www.cnblogs.com
  2. 第二种是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

  1. 建议在使用cy.visit()时,在cypress.json里设置一个baseUrl
  2. baseUrl相当于一个全局共享的host,在使用visit()和request()等命令时自动将baseUrl传递进去
  3. 优势:首次启动Cypress测试时,添加baseUrl还可以节省一些时间
  4. 不添加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

  1. 使用.alias()命令定义兵役@字符和别名命名的别名路由
  2. 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格式

前置工作

  1. 确保package.json文件的scripts模块加入了如下键值对"cypress:run":"cypress run"
  2. cypress run是以无头浏览器模式跑测试用例,用例文件夹下的所有测试用例
  3. 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

注意点

  1. 先查看node_modules目录下是否有mocha文件夹,如果有直接装mochawesome
  2. 如果安装mocha失败,出现很古怪的错误,比如mkdirp版本不行
  3. 尝试先update mkdirp库,如果也报错,则uninstall mkdirp库,如果仍然报错,则把Cypress目录下的node_moudules整个文件夹删掉,重新自行npm install,大概率就可以解决问题了

第二步

进入Cypress安装目录,执行命令 npm run cypress:run --reporter mochawesome

用户自定义报告的步骤

第一步:配置reporter选项

  1. cyress.json文件中配置reporter选项,指定reporter文件位置。
  2. 把reporter定义在custom_reporter.js文件中

编写自定义报告文件

  1. 进入 Cypress 安装目录下的cypress目录下
  2. 创建 reporter 文件夹,然后创建一个custom_reporter.js文件
  3. 写以下代码(此自定义报告扩展了内置报告,仅更改了成功的显示样式)
var mocha = require('mocha');
module.exports = MyReporter;

function MyReporter(runner) {
    mocha.reporters.Base.call(this, runner)
    var passes = 0
    var failures = 0

    runner.on('pass', function (test) {
        passes++
        console.log('pass:%s', test.fullTitle())
    })

    runner.on('fail', function (test, err) {
        failures++
        console.log('fail:%s -- error:%s', test.fullTitle(), err.message)
    })

    runner.on('end', function () {
        console.log('用户自定义报告:%d/%d', passes, passes + failures)
    })
}

第三步:运行测试

输入命令:yarn cypress:run --reporter ../cypress/reporters/custom_reporter.js

命令行运行Cypress

  1. 在交互模式下打开Cypress测试运行器
  2. 在测试用例的运行过程中,测试用例的每一条命令,每一个操作都将显式的显示在测试运行器中

前置操作

通过package.json指定scripts,在package.json文件下,添加"cypress:open":"cypress open"

yarn运行

yarn cypress:open

npm运行

npm run cypress:open

cypress open详解

  1. cypress open运行时支持指定多个参数,指定的参数将自动应用于你通过测试运行器打开的项目
  2. 这些参数讲应用于每一次测试运行,直到关闭测试运行器为止
  3. 指定的参数将会覆盖配置文件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插件

前言

  1. 一个测试用例集通常包含多个测试用例
  2. 当网络不稳定而引起测试失败时,我们希望进重试失败的用例而不是重跑整个测试用例集
  3. 单测试运行器默认进支持重试整个测试用例集

安装

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() 将数组内容作为单独的参数传回到回调函数
closest() 上一条命令返回的DOM元素,在获取匹配的第一个DOM元素

.then()

作用

  1. 在Cypress中,保存一个值或者引用的最好方式是使用闭包
  2. then()就是Cypress对闭包的一个典型应用
  3. 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()

作用

  1. 创建一个断言,断言将自动重试,直到它们通过或超时
  2. 和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({ width: '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()

作用

  1. 起别名以供以后使用
  2. 可在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)

参数解释

  1. callbackFn 回调函数,第一个参数是上一条命令的返回结果(必须是元素)
  2. 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')
            })
        });

    });

});

操作文件相关命令

.fixture()

加载位于文件中的一组固定数据

基本语法格式

cy.fixture(filePath)
cy.fixture(filePath, encoding)
cy.fixture(filePath, options)
cy.fixture(filePath, encoding, options)

参数解释

  1. filepath,文件路径默认会从cypress/fixtures 文件夹下找文件
  2. encoding
    1. ascii
    2. base64
    3. binary
    4. hex
    5. latin1
    6. utf8
    7. utf-8
    8. ucs2
    9. ucs-2
    10. utf16le
    11. 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有自己的读取规则,读取顺序如下:

  1. cypress/fixtures/admin.json
  2. cypress/fixtures/admin.js
  3. cypress/fixtures/admin.coffee
  4. cypress/fixtures/admin.html
  5. cypress/fixtures/admin.txt
  6. cypress/fixtures/admin.csv、
  7. cypress/fixtures/admin.png
  8. cypress/fixtures/admin.jpg
  9. cypress/fixtures/admin.jpeg
  10. cypress/fixtures/admin.gif
  11. cypress/fixtures/admin.tif
  12. cypress/fixtures/admin.tiff
  13. 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()
            })
        });

    });

});

.readFile()

将读取文件并返回内容

基本语法格式

cy.readFile(filePath)
cy.readFile(filePath,encoding)
cy.readFile(filePath,options)
cy.readFile(filePath,encoding,options)

相关参数

  1. filePath 项目根目录(包含默认cypress.json配置文件的目录)中需要读取的文件路径
  2. encoding 读取时需要使用的编码
    1. ascii
    2. base64
    3. binary
    4. hex
    5. latin1
    6. utf8
    7. utf-8
    8. ucs2
    9. ucs-2
    10. utf16le
    11. utf-16le
  3. options
    1. log 是否将命令显示到命令日志中,默认true
    2. timeout 命令超时时间

操作实例代码

describe('测试读取', function () {
    it('读取text文件', function () {
        cy.readFile('sss.text').then(function (el) {
            cy.log(el)
        })
    });
    it('读取json文件', function () {
        cy.readFile('sss.json').then(function (el) {
            cy.log(el.name)
        })
    });
    /*
     * yaml扩展使用
     * YAML = require('yamljs');
     *
     * // 解析 YAML 文件
     * nativeObject = YAML.parse(yamlString);
     *
     * // 生成 YAML 字符串
     * yamlString = YAML.stringify(nativeObject, 4);
     *
     * // 加载 YAML 文件
     * nativeObject = YAML.load('myfile.yml');
     */
    it('读取yaml文件', function () {
        cy.readFile('sss.yaml').then(function (yaml) {
            // 导入模块(需要在node_modules目录下安装yamljs模块)
            const YAML = require('yamljs')
            // 解析YAML字符串
            const yl = YAML.parse(yaml)
            // 查看内容
            cy.log(yl)
        })
    });
    it('读取图片 ', function () {
        cy.readFile('sss.png','base64').then(function (png) {
            cy.log(png)
        })
    });
    it('读取mp3', function () {
        cy.readFile('sss.mp3','base64').then(function (mp3) {
            const uri='data:audio/mp3;base64,'+mp3
            const audio=new Audio(uri)
            audio.play()
        })
    });
    it('读取mp4文件', function () {
        cy.readFile('sss.mp4','base64').then(function (mp4) {
            cy.log(mp4)
        })
    });

});

.writeFile()

写入具体制定内容的文件

基本语法格式

cy.writeFile(filePath, contents)
cy.writeFile(filePath, contents, encoding)
cy.writeFile(filePath, contents, options)

相关参数

  1. filePath 项目根目录(包含默认cypress.json配置文件的目录)中需要读取的文件路径
  2. encoding 读取时需要使用的编码
    1. ascii
    2. base64
    3. binary
    4. hex
    5. latin1
    6. utf8
    7. utf-8
    8. ucs2
    9. ucs-2
    10. utf16le
    11. utf-16le
  3. options
    1. log 是否将命令显示到命令日志中,默认true
    2. flag 文件系统标志,默认w
    3. encoding 写入文件时要使用的编码,默认utf8

flag文件系统详细标志

选项 描述
a 在结尾进行追加,但不可读,如果文件不存在,则创建该文件
ax 和a一样,但如果路径存在则失败
a+ 可读,也可以在结尾处进行追加,如果文件不存在,则创建该文件
ax+ 和a+一样,但如果路径存在则失败
as 以同步方式添加,但不可读,如果文件不存在,则创建该文件
as+ 可读,也可以同步方式添加,如果文件不存在,则创建该文件
r 只读,如果文件不存在,则会发生异常
r+ 可读写,如果文件不存在,则会发生异常
rs+ 以同步方式进行读写,操作系统绕过本地文件系统缓存(不推荐)
w 只写,如果文件不存在则创建,文件存在则覆盖内容
wx 和w一样,但如果路径存在则失败
w+ 可读写,如果文件不存在则创建爱你,文件存在则覆盖内容
wx+ 和w+一样,但如果路径存在则失败

操作实例代码

describe('测试写入文件', function () {
    it('写入txt文件', function () {
        cy.writeFile('D:/cypresses/uuu.txt','hello world1')
    });
    it('写入json文件', function () {
        cy.writeFile('D:/cypresses/uuu.json',{'age':18})
    });
    it('写入json文件,a+模式', function () {
        cy.writeFile('D:/cypresses/uuu.json',{'name':'wupeng'},{flag:'a+'})
    });
});

其他命令

.wrap()

作用

  1. 返回传递给它的对象
  2. 返回的是一个Promise对象,可以直接接Cypress其他命令
  3. 如果传递给它的就是一个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

  1. 为了绕过同源策略,当 Cypress 开始运行测试时,会在 localhost 上打开一个随机端口进行初始化
  2. 直到遇见第一个 cy.visit() 命令里的 URL 才匹配被测应用程序的 URL
  3. 当 Cypress 以交互模式启动时,会看到 Cypress 先运行在 localhost 上然后又切换到 URL 重新运行(也就多消耗了时间)
  4. 在cypress.json中设置baseUrl
    1. 可以在运行时节省 Cypress 匹配被测应用程序 URl 的时间
    2. 还可以在编写待访问的 URL 时,忽略 baseUrl,直接写后面的路径
设置全局实例
// 不加 baseUrl 的写法
cy.visit('https://example.cypress.io/commands/actions')

// 加了上面 baseUrl 的写法
cy.visit('/commands/actions')

避免访问多个站点

  1. 为了绕开同源策略的限制而实现的方案,使得 Cypress 不能支持在一个测试用例文件里访问多个不同域名的 URL
  2. 如果访问了多个不同域名的站点,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')
});

删除等待代码

  1. 在其他的自动化测试框架中,很大概率会用到强制等待及隐式等待
  2. 在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)

参数解释

  1. fileName
    1. 代保存图片的名称
    2. 图片默认保存在 cypress/screenshots 文件夹下,可以在 cypress.json 修改默认文件夹路径(配置项 screenshotsFolder )
  2. options
参数 默认 作用
log true 在命令行中显示
blackout [ ] 接收一个数组类型的字符选择器,此选择器匹配的元素会被涂黑
这个选项在capture是runner时不起作用
capture fullPage 决定截图截取测试运行器的哪个部分
此参数仅限在cy.后使用游戏哦啊,针对某个元素截图则不起作用
有三个可选项:
1. viewport:截图大小是被测应用程序当前视窗大小
2. fullpage:整个被测程序界面(整个页面)
3. runner:截图测试运行器的整个部分
clip null 用于裁剪最终屏幕截图图像的位置和尺寸(以px为单位)
格式:
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()
});

断言最佳实践

介绍

  1. Cypress的断言库是基于Chai断言库
  2. 并且增加了对 Sinon-Chai,Chai-jQuery 断言库的支持,带来了强大的断言功能
  3. 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() 期望能从当前对象找到一个属性

隐性断言

  1. should()、and()是Cypress推崇的方式
  2. and()和should()其实是使用方式和效果是完全一样的
cy
.get('form')
.should('be.visible')
.and('have.class', 'open')

显性断言

expect允许传入一个特定的对象并且对它进行断言

expect(true).to.be.true

po模式(不推荐使用)

数据驱动策略

通过js的方式创建

具体操作,详情点开动态生成测试用例一栏。

使用fixture(推荐)

参考.fixture()一栏。

数据保存到自定义文件中

第一步:创建自定义json文件放入测试套件的data目录下,并编写需要数据
[
  {
    "username": "admin",
    "password": "123456"
  },
  {
    "username": "admin",
    "password": "1123456"
  }
]

第二步:编写测试用例(需要先引入外部的数据文件)
import data from "../integration/data/te.json"

describe('登录', function () {
    beforeEach(function () {
        cy.visit("http://192.168.102.49:5001/#/")
    });
    // 循环测试数据
    for (const datas of data) {
        it("sss", function () {
            cy.get("#username").type(datas.username)
            cy.get("#password").type(datas.password)
            cy.get("button[type=submit]").click()
        });
    }
});

环境变量设置指南

  1. 在实际项目可能存在多个环境(开发、测试、预发、生产),不同环境的环境变量就会不一样。
  2. 如果还是单纯只用上面讲到的方式,切换不同环境时,还得手动修改环境便令,及其不方便。

第一种解决方案

  1. 在cypress安装目录下创建爱你一个config文件夹
  2. 文件夹下建立两个文件,分别命令为cypress.dev.json,cypress.qa.json
  3. 在cypress安装目录/plugins/index.js中更改配置
  4. 命令行输入npm run cypress:open --env configFile=qa
// 第一步、第二步(完成)
// 第三步,更改配置
const fs = require("fs-extra")
const path = require("path")

/**
 * @type {Cypress.PluginConfig}
 */
function getConfigurationByFile(file) {
    const pathToConfigFile = path.resolve("..", "cypresses/cypress/config", `cypress.${file}.json`)
    console.log(pathToConfigFile)
    return fs.readJson(pathToConfigFile)
}

module.exports = (on, config) => {
    const file = config.env.configFile = "qa" || "dev"
    return getConfigurationByFile(file)
}

// 第四步,新建测试用例
describe('测试环境变量设置', function () {
    it('开始测试', function () {
        cy.log(`${Cypress.env('username')}`)
        cy.log(`${Cypress.env('password')}`)
    });
});

第二种解决方案

  1. cypress.json添加内容
  2. 在support/index.js中添加代码
  3. 输入命令行:npm run cypress:open --env testEnv=qa
第一步:
{
  "targetEnv": "dev",
  "env": {
    "dev": {
      "username": "admin",
      "password": "123456",
      "Url": "http://192.168.102.49:5001/#/"
    },
    "qa": {
      "username": "admin",
      "password": "123456",
      "Url": "http://192.168.102.49:5001/#/"
    }
  }
}
第二步:
beforeEach(function () {
    const targetEnv=Cypress.env('targetEnv')||Cypress.config('targetEnv')
    cy.log(`${JSON.stringify(Cypress.env(targetEnv).username)}`)
});

接口测试网络相关命令

.request()

用来发送HTTP请求

基本语法格式

cy.request(url)
cy.request(url, body)
cy.request(method, url)
cy.request(method, url, body)
cy.request(options)

相关参数说明

  1. url 用来请求url
  2. body
    1. 请求正文,不同接口内容,body会有不同的形式。
    2. Cypress设置了Accepts请求头,并通过encoding选项序列化响应体。
  3. method 请求方法,默认是get
  4. options
参数 默认 作用
log true 在命令日志中显示命令
url null 请求url
method get 请求方法
auth null 添加鉴权标头
body null 请求体
failOnStatusCode true 返回值不是2xx或3xx时,是否直接返回失败
followRedirect true 是否自动重定向
form false 是否将body的值转换为rul-encoded并设置x-www-form-urlencoded标头(表单格式)
encoding utf8 1. 徐丽华响应体时要用的编码
2. 支持:ascli,base64,binary,hex,latin1,utf8,utf-8,ucs2,ucs-2,utf16le,utf-16le
gzip true 是否接受gzip编码
headers null 要发送的额外请求头
qs null params把查询参数追加到请求url后
retryOnStatusCodeFailure false StatusCode失败后是否自动重试,若ture则自动重试4次
retryOnNetworkFailure ture 网络问题导致失败后是否自动重试,若true则自动重试4次

代码实例

describe('接口测试', function () {
    beforeEach(function () {
        cy.visit("https://api.apiopen.top")
    });
    it('测试request', function () {
        cy.request({
            method:"get",
            url:'/getSingleJoke',
            qs:{
                sid: "28654780"
            }
        }).as("comments")
        cy.get("@comments").then(function (response) {
            expect(response.status).to.be.eq(200)
        })
    });
});

注意事项

  1. 通过.request()发出的请求不会出现在开发者工具,开发人员工具中看不到该请求。
  2. cypress实际上并未从浏览器发出XHR请求
  3. 实际上是从Cypress Test Runner发出HTTP请求

关于cookie

  1. 通过 .request()发出的请求,Cypress会自动发送和接收Cookie
  2. 在发送 HTTP 请求之前,如果请求来自浏览器,Cypress会自动附加本应附加的 Cookie
  3. 此外,如果响应具有 Set-Cookie 标头,则这些标头将自动在浏览器Cookie上重新设置
  4. 换句话说,cy.request()透明地执行所有基础功能,就好像它来自浏览器一样

.route()

用来管理控制整个网络请求

前言

  1. cypress6.0开始不推荐使用cy.server()和cy.route()
  2. 在将来的版本中,对cy.server()和cy.route()
  3. 现在有限考虑使用cy.intercept()

注意事项

  1. cypress目前支持拦截XMLHttpRequest,可在开发者工具看到请求type是xhr,或者直接点击xhr进行筛选。
  2. 同样是login请求,有些是xhr,有些却是document,对于type=document的请求,.route()默认不会拦截到的。

基本语法格式

cy.route(url)
cy.route(url, response)
cy.route(method, url)
cy.route(method, url, response)
cy.route(callbackFn)
cy.route(options)

参数说明

  1. url 需要监听的URL,遵循minimatch模式。
  2. response 为匹配上的URL提供自定义响应体
  3. method 待匹配监听URL的请求方法
  4. callbackFn 回调函数
  5. options 可选项(具体如下表格)
参数 默认 作用
delay 0 设定stubbed返回的延迟时间,ms为单位
force404 false 当XHR请求没有匹配到任何cy.route()定义的时候,强制返回404
headers null 设定stubbed路由的header
method GET 设定待匹配的请求方法,默认GET
onAbort null 回调函数,当一个XHR被终止的时候被调用
onRequest null 设定请求被发送时的回调函数
onResponse null 设定服务器返回时的回调函数
response null 设定stubbed路由的返回响应体
status 200 设定stubbed路由的返回的状态码
url null 匹配请求url的字符串或者正则表达式

使用总结

  1. 当发出请求的url+method匹配上路由的url+method,就会被该路由监听到
  2. 简单理解:response 是自定义响应体,status是自定义响应状态码,headers是自定义响应头
  3. 如果设置了response、status、headers 参数,则被监听到的请求会获取到这三个参数

基本操作

例一:
cy.server()
cy.route('**/users/*/comments')

// https://localhost:7777/users/123/comments     <-- 匹配
// https://localhost:7777/users/123/comments/465 <-- 不匹配

例二:
cy.server()
cy.route('**/posts/**')

// https://localhost:7777/posts/1            <-- 匹配
// https://localhost:7777/posts/foo/bar/baz  <-- 匹配
// https://localhost:7777/posts/quuz?a=b&1=2 <-- 匹配
// https://localhost:7777/posts <-- 不匹配

例三:
cy.route('**/users/*')

// 下面的都匹配
/users/1
http://localhost:2020/users/2
https://google.com/users/3

// 下面的都不匹配
/users/4/foo
http://localhost:2020/users/5/foo

基本实例代码

    const username = 'jane.lane'
    const password = 'password123'

    before(function () {
        cy.visit('http://localhost:7079/')
    })

    it('正常登录,修改登录请求的status、response', function () {
        cy.server()
        cy.route({
            url: '**/login',
            method: 'POST',
            status: 503,
            delay: 1000,
            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) => {
            cy.log(res)
            expect(res.status).to.eq(503)
            expect(res.responseBody.data).to.eq('Not success')
        })
    });

路由结果日志

  1. 每当启动服务器(cy.server())并添加路由(cy.route())时,Cypress都会显示一个名为ROUTES(n)的新模块日志。
  2. 将在日志中列出路由表,包括方法,URL,是否Stubbed,别名和成功匹配请求的数量。
  3. 当发出XHR请求后,cypress会记录此请求是否匹配到某个路由的别名。

强制返回404实例

 cy.server({ force404: true })
        cy.route({
            url: '**/logins',
            method: 'POST',
            status: 503,
            delay: 1000,
            response: {
                success: false,
                data: 'Not success'
            },
        }).as("login")

// 伪代码
// 发出 /login 请求的操作

当/login没有匹配到任意路由的时候,会返回404

官方实例

    it('cy.route() - route responses to matching requests', () => {
        // https://on.cypress.io/route
        // 访问
        cy.visit('https://example.cypress.io/commands/network-requests')

        // 预置变量
        let message = 'whoa, this comment does not exist'

        // 启动 Mock 服务器
        cy.server()

        // 路由1:监听 url 是 comments/* 且 请求方法是 GET 的请求
        cy.route('GET', 'comments/*').as('getComment')

        // 点击按钮触发请求
        cy.get('.network-btn').click()

        // 等待请求响应成功后获取 status 进行断言
        cy.wait('@getComment').its('status').should('eq', 200)

        // 路由2:监听 url 是 /commets 且 请求方法是 POST 的请求
        cy.route('POST', '/comments').as('postComment')

        // 点击按钮触发请求
        cy.get('.network-post').click()

        // 等待请求响应成功后进行断言
        cy.wait('@postComment').should((xhr) => {
            expect(xhr.requestBody).to.include('email')
            expect(xhr.requestHeaders).to.have.property('Content-Type')
            expect(xhr.responseBody).to.have.property('name', 'Using POST in cy.route()')
        })

        /*
        路由3:监听 url 是 comments/* 且 请求方法是 POST 的请求
        自定义 status、response、delay 并返回给监听到的请求
         */
        cy.route({
            method: 'PUT',
            url: 'comments/*',
            status: 503,
            response: {error: message},
            delay: 500,
        }).as('putComment')

        // // 等待请求响应成功后进行断言
        cy.get('.network-put').click()

        cy.wait('@putComment')

        // 出现 404 之后断言文案
        cy.get('.network-put-comment').should('contain', message)
    })

.server()

启动服务器以开始讲响应路由到cy.route()并更改网络请求的行为。cyprss提供的mock方案,使用自带的命令cy.server()、cy.route(),可以进行接口测试,还可以截获、控制及修改接口返回的行为。

基本语法格式

cy.server()
cy.server(options)

options参数

用于控制服务器,将会影响所有请求的行为。

参数 默认 作用
enable true 若为false,则表明禁用已有的route stubs
force404 false 当XHR请求没有匹配到任何cy.route()定义的时候,强制返回404
onAnyAbort undefined 回调函数,当一个XHR被中止的时候被调用
onAnyRequest undefined 设定请求被发送时的回调函数
onAnyResponse undefined 设定服务端返回时的回调函数
urlMatchingOptions 使用全局字符串匹配URL时,传递给minimatch的默认选项
ignore function 1. 回调功能,可将被logged或stubbed的请求列入白名单
2. 默认匹配:.js、.html、.css文件

不带参数实例

  1. 任何与cy.route()不匹配的请求都讲传递到服务器,除非设置了force404,这样请求变成404和拿到一个空response
  2. 与options.ignore函数匹配的任何请求都不会记录或存根(logged、stubbed)
  3. 将在命令日志中看到名为(XHR Stub)或(XHR Stub)或(XHR)的请求。

带参数实例

context('route 的栗子', function () {

    const username = 'jane.lane'
    const password = 'password123'

    before(function () {
        cy.visit('http://localhost:7079/')
    })

    it('栗子1', function () {
        cy.server({
            method: 'POST',
            status: 503,
            delay: 1000,
            headers: {
                'x-token': 'abc-123-foo-bar'
            }
        })
        cy.route({
            url: '**/login',
            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) => {
            cy.log(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')
        })
    });
})

启动服务器,关闭服务器实例

it('栗子2', function () {
    cy.server()
    cy.route({
        url: '**/login',
        method: 'POST',
        status: 503,
        response: {
            data:"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)
        
        // 关闭服务器
        cy.server({
            enable: false
        })
    })

    cy.visit('http://localhost:7079/')
    cy.get("input[name=username]").type(username)

    //第二次发出请求
    cy.get("input[name=password]").type(`${password}{enter}`)
});

// 第二个请求虽然被路由监听到了,但是因为服务器关闭了,所有并没有获取路由的status、response

注意事项

  1. 可以在启动cy.visi()之前启动服务器cy.server()
  2. 应用程序在加载时可能会立即发出初始请求(例如对用户进行身份验证)
  3. cypress可以在cy.visit()之前启动服务器并定义路由(cy.route())
  4. 下次访问时,服务器+路由将在应用程序加载之前立即应用。

.intercept()

使用该命令在网络层管理HTTP请求的行为,在Cypress6.0之后才支持该方法。

具体功能

  1. 对任何类型的HTTP请求进行stub或spy
  2. 在HTTP请求发送到目标服务器前,可以修改HTTP请求body、headers、URL(类似抓包工具对请求进行打断点然后修改)
  3. 动态或静态的对HTTP请求的相应进行stub
  4. 接收HTTP相应后可对HTTP响应body、headers、status、code进行修改(类似抓包工具对响应进行打断点,然后修改)
  5. 在所有阶段都可以完全访问所有HTTP请求

对比cy.route()

  1. 可以拦截所有类型的网络请求,包括Fetch API,页面加载,XMLHttpRequest,资源加载等
  2. 不需要在使用前调用cy.server(),实际上cy.server()根本不影响cy.intercept()
  3. 默认情况下没有将请求方法设置为GET

基本语法格式

cy.intercept(url, routeHandler?)
cy.intercept(method, url, routeHandler?)
cy.intercept(routeMatcher, routeHandler?)

相关参数

url
要匹配的请求URL,可以是字符串也可以是正则表达式。
method
请求方法
routeMatcher

  1. 一个对象,用于匹配此路由将处理那些传入的HTTP请求
  2. 所有对象属性都是可选的,不是必填的
  3. 设置的所有属性必须与路由匹配才能处理请求
  4. 如果将字符串传递给任何属性,则将使用minimatch将与请求进行全局匹配
    routeMatcher如下属性
{
  /**
   * 与 HTTP Basic身份验证中使用的用户名和密码匹配
   */
  auth?: { username: string | RegExp, password: string | RegExp }

  /**
   * 与请求上的 HTTP Headers 匹配
   */
  headers?: {
    [name: string]: string | RegExp
  }

  /**
   * 与请求上的 hostname 匹配
   */
  hostname?: string | RegExp

  /**
   * If 'true', 只有 https 的请求会被匹配
   * If 'false', 只有 http 的请求会被匹配
   */
  https?: boolean

  /**
   * 与请求上的 method 请求方法匹配
   * 默认 '*', 匹配全部类型的 method
   */
  method?: string | RegExp

  /**
   * 主机名后的路径, 包括了 ? 后面的查询参数
   * www.baidu.com/s?wd=2
   */
  path?: string | RegExp

  /**
   * 和 path 一样, 不过不管 ? 后面的查询参数
   * www.baidu.com/s
   */
  pathname?: string | RegExp

  /**
   * 与指定的端口匹配, 或者传递多个端口组成的数组, 其中一个匹配上就行了
   */
  port?: number | number[]

  /**
   * 与请求路径 ? 后面跟的查询参数匹配上
   * wd=2
   */
  query?: {
    [key: string]: string | RegExp
  }

  /**
   * 完整的请求 url
   * http://www.baidu.com/s?wd=2
   */
  url?: string | RegExp
}

routeHander

  1. routeHandler定义了如果请求和routeMatcher匹配将对请求进行指定的处理。
  2. 可接收的数据类型:string、object、Function、StaticResponse
    StaticResponse
  3. 相当于一个自定义响应体对象
  4. 可以自定义Response Headers、HTTP状态码、Response body等
    StaticResponse对象的属性
{
  /**
   * 将 fixture 文件作为响应主体, 以 cypress/fixtures 为根目录
   */
  fixture?: string
  /**
   * 将字符串或 JSON 对象作为响应主体
   */
  body?: string | object | object[]
  /**
   * 响应 headers
   * @default {}
   */
  headers?: { [key: string]: string }
  /**
   * 响应状态码
   * @default 200
   */
  statusCode?: number
  /**
   * 如果 true, Cypress 将破坏网络连接, 并且不发送任何响应
   * 主要用于模拟无法访问的服务器
   * 请勿与其他选项结合使用
   */
  forceNetworkError?: boolean
  /**
   * 发送响应前要延迟的毫秒数
   */
  delayMs?: number
  /**
   * 以多少 kbps 发送响应体
   */
  throttleKbps?: number
}
**string**
1. 如果传递一个字符串,这个值相当于响应body的值
2. 等价于StaticResponse对象{body:"dev"}
**object**
1. 如果传递了没有StaticResponse秘钥的对象,则它将作为JSON响应Body发送
2. 例:{dev:"say"}等价于StaticResponse对象{body:{dev:"say}}
**function**
1. 如果传递了一个回调函数,当一个请求匹配上了该路由将会自动调用这个函数
2. 函数第一个参数是请求对象
3. 在回调函数内部,可以修改外发请求、发送响应、访问实际响应
**命令返回结果**
1. 返回null
2. 可以连接as()进行别名,但不可链接其他命令
3. 可以使用cy.wait()和等待cy.intercept()路由匹配上请求,这将会产生一个独享,包含匹配上的请求/响应相关信息。

通过URL路由匹配请求实例

describe('发送请求', function () {
    it('should ', function () {
        cy.visit("http://192.168.102.49:5001")
        cy.intercept("http://192.168.102.49:5001").as('login')
        cy.login("admin","123456")
        cy.wait('@login').then(function (result) {
            cy.log(result)
        })
    });
});

通过RouteMatcher路由匹配实例

describe('通过routeMatcher发送请求', function () {
    it('should ', function () {
        cy.visit("http://192.168.102.49:5001")
        cy.intercept({
            hostname:"192.168.102.49",
            pathname:'',
            method:'post',
            https:false
        }).as('login2')
        cy.login("admin","123456")
        cy.wait("@login2").then(function (result) {
            cy.log(result)
            expect(result.response.body).to.have.property('code')
            expect(result.response.statusCode).to.be.eq(200)

        })
    });
});

自定义不同类型响应体实例

describe('自定义响应内容', function () {
    beforeEach(function () {
        cy.visit("http://192.168.102.49:5001/#/")
    });
    it('自定义字符串响应体 ', function () {
        cy.intercept("/login", 'successes').as('login_str')
        cy.login("admin", "123456")
        cy.wait("@login_str").then(function (str) {
            cy.log(str)
        })
    });
    it('自定义json响应体', function () {
        cy.intercept("/login", {fixture: 'test.json'}).as("login_json")
        cy.login("admin", "123456")
        cy.wait("@login_json").then(function (to_json) {
            cy.log(to_json)

        })
    });
    it('自定义一个StaticResponse响应体', function () {
        cy.intercept("/login",{
            body:{
                "name":"wujuan"
            },
            statusCode:201,
            delayMs: 2000
        }).as("login_sr")
        cy.login("admin", "123456")
        cy.wait("@login_sr").then(function (sr) {
            cy.log(sr)
        })
    });
});

自定义拦截请求实例

后续补充。。。。
后续补充。。。。
后续补充。。。。
后续补充。。。。
后续补充。。。。
后续补充。。。。

接口测试操作cookie命令

.getCookies()、.getCookie()

获取所有的cookie,返回一个cookie对象数组。如果只是获取单个cookie,需要添加name值(必传)。

基本语法

cy.getCookies(name)
cy.getCookies(name,options)

option参数

  1. log:是否将命令显示到命令日志中,默认true
  2. timeout:命令超时时间

基本代码实例

context('登录测试', function () {

    const username = 'wupeng'
    const password = 'bb961202'

    beforeEach(function () {
        cy.visit('http://192.168.102.210:10001/zentao/user-login.html')
        cy.get('input[name=account]').type(username)
        cy.get('input[name=password]').type(password)
        cy.get('#submit').click()
    })

    it('getCookie', function () {
        cy.getCookies().each(function (cookie) {
            cy.log(cookie)
        })
    });
})

.setCookie()

基本语法格式

cy.setCookie(name, value)
cy.setCookie(name, value, options)

基本参数

  1. name cookie的名称
  2. value cookie的值
  3. options可选值
option 默认值 描述
log true 命令是否显示在命令日志中
domain window.location.hostname cookie设置在哪个域下
expiry 20 years into the future cookie到期时间
httpOnly false 是否是仅仅HTTP cookie
path / cookie路径
secure false 是否为安全Cookie
timeout responseTimeout 命令超时时间
sameSite undefined 1. 如果设置,则应为lax,strict或no_restriction之一。
2. 传递undefined以使用浏览器的默认值。
3. 注意:仅当安全标志设置为true时,才能使用no_restriction

基本用法

cy.setCookie('auth_key', '123key')

命令返回结果

返回设置的cookie对象并且包含以下属性

  1. domain
  2. expiry(如果有)
  3. httpOnly
  4. name
  5. path
  6. sameSite(如果有)
  7. secure
  8. value

.clearCookies()、.clearCookie()

cypress会在每次测试之前自动清除所有Cookie,以防止在测试用例之间共享状态,除非在测试用例中需要调用此命令清除某个Cookie,否则不要使用该命令。如果清除的不是所有,需要穿cookie的name值。

基本语法格式

cy.clearCookie(name)
cy.clearCookie(name, options)

option参数

  1. log 是否将命令显示到命令日志中,默认是true
  2. timeout 命令超时时间

基本代码实例

context('登录测试', function () {

    const username = 'wupeng'
    const password = 'bb961202'

    beforeEach(function () {
        cy.visit('http://192.168.102.210:10001/zentao/user-login.html')
        cy.get('input[name=account]').type(username)
        cy.get('input[name=password]').type(password)
        cy.get('#submit').click()
    })

    it('getCookie', function () {
        cy.clearCookies()
        cy.getCookies().each(function (cookie) {
            cy.log(cookie)
        })
    });
})

// 先清除cookie之后,在进行获取,值就为空了。

Cypress API相关

Cypress.Commands

介绍

  1. Custom Commands 被认为是替代 PageObject 的良好选择
  2. 使用 Custom Commands 可以创建自定义命令和替换现有命令
  3. 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

正确用法

Cypress.Commands.add('login', (email, pw) => {})
Cypress.Commands.overwrite('visit', (orig, url, options) => {}) 

基本代码实例

第一步:编写commands.js的内容
Cypress.Commands.add('login', function (username, password) {
    cy.visit("http://192.168.102.49:5001")
    cy.get("#username").type(username)
    cy.get("#password").type(password)
    cy.get("button[type=submit]").click()
})

第二步:编写测试用例代码
describe('测试commands', function () {
    const username='admin'
    const password='123456'
    beforeEach(function () {
        cy.login(username,password)
    });
    it('用来测试断言是否可行', function () {
        cy.url().should('include','/stationManage')
    });
});

重写visit实例代码

// 第一个参数代表需要覆盖的命令
Cypress.Commands.overwrite('visit', (originalFn, url, options) => {
    const domain = Cypress.env('BASE_DOMAIN')

    if (domain === '...') {
        url = '...'
    }

    if (options.something === 'else') {
        url = '...'
    }

    // originalFn 代表传入进来的原 visit 命令
    //
    // 记得需要在最后 return
    return originalFn(url, options)
})

重写type实例代码

// 第一步:commands.js编写方法
Cypress.Commands.overwrite('type', (originalFn, element, text, options) => {
    if (options && options.sensitive) {
        options.log = false
        // 创建自定义命令的日志
        Cypress.log({
            $el: element,
            name: 'type',
            message: '*'.repeat(text.length),
        })
    }

    return originalFn(element, text, options)
})

// 第二步:编写测试用例
cy.get('input[name=username]').type(username)
cy.get('input[name=password]').type(pwd, {sensitive: true})

使用Customn Commands的益处

  1. 定义在 cypress/support/command.js 中的命令可以像 Cypress 内置命令那样直接使用,无须 import 对应的 page(实际上 PageObject 模式在 Cypress 看来无非是数据/操作函数的共享)
  2. 自定义命令可以比 PageObject 模式运行更快,Cypress 和应用程序运行在同一个浏览器中,意味着 Cypress 可以直接发送请求到应用程序并设置运行测试所需要的用户状态,而这一切通常无须通过页面操作,这使得使用了自定义命令的测试会更加稳定
  3. 自定义命令允许重写 Cypress 内置命令,意味着可以自定义测试框架并立刻全局应用

Cypress.Cookies

基本语法

Cypress.Cookies.debug(enable, options)
Cypress.Cookies.preserveOnce(names...)
Cypress.Cookies.defaults(options)

Cypress.Cookies.debug

  1. 作用 是否启用cookie调试功能,更加易于了解cypress是如何操作cookie。
  2. 参数enable
    1. true 默认启用,启用后在卡覅这工具的console中可以看到详细的cookie操作日志。
    2. false 不启用,console不会显示cookie操作日志。
  3. 参数options verbose:是否详细打印cookie操作日志,默认true。
// 实例代码一:
describe('Cypress.Cookies', function () {
    it('debug', function () {
        Cypress.Cookies.debug(true)
        cy.setCookie('faceCookie','12345BC')
    });
});

// 实例代码二:
describe('Cypress.Cookies', function () {
    it('debug', function () {
        Cypress.Cookies.debug(true,{verbose:false})
        cy.setCookie('faceCookie','12345BC')
    });
});

// 实例代码三:
describe('Cypress.Cookies', function () {
    it('debug', function () {
        Cypress.Cookies.debug(false)
        cy.setCookie('faceCookie','12345BC')
    });
});

Cypress.Cookies.preserveOnce

实际应用场景就是如果不保存cookie,则每次测试前都需要登录一次,这将大大浪费不必要的测试时间。
Cypress如何保存Cookie

  1. Cypress.Cookies.preserveOnce()命令可以保存cookie,使它在多个测试用例间共享。
  2. 目前只有使用的是基于session的cookie,此命令有效。
    操作实例代码
// 第一步:编写commands.js代码,用于实现登录,产生登录的cookie.
Cypress.Commands.add('login', function (username, password) {
   cy.visit("http://192.168.102.49:5001")
   cy.get("#username").type(username)
   cy.get("#password").type(password)
   cy.get("button[type=submit]").click()
})

// 第二步:编写测试用例
describe('保存cookie', function () {
   const username="wupeng"
   const password="bb961202"
   before(function () {
       cy.visit('http://192.168.102.210:10001/zentao/user-login.html')
       cy.get('input[name=account]').type(username)
       cy.get('input[name=password]').type(password)
       cy.get('#submit').click()
   });
   beforeEach(function () {
       Cypress.Cookies.preserveOnce("cypress-session-cookie")
   });
   it('should 1', function () {
       cy.getCookies().each(function (cookie) {
           cy.log(cookie.name, cookie.value)

       })
   });
   it('should 2', function () {
       cy.getCookies().each(function (cookie) {
           cy.log(cookie.name, cookie.value)

       })
   });
});

// 两个测试用例,主要校验cookie是否可以共享。

Cypress.Cookies.defaults

作用

  1. 设置全局默认Cookie
  2. 可以修改全局默认值并保留一组Cookie,这些Cookie将始终在测试用例之间保留。
  3. 只要调用了这个方法,将在其他测试用例中都会生效。
    重点
  4. 在cypress/support/index.js中配置此命令是绝佳选择。
  5. 因为它会在所有测试文件之前加载。
    可选参数
    只有一个preserve参数,接收下面四种数据类型
  6. String
  7. Array
  8. RegExp
  9. Function
    正确使用方式
// 所有名为 cypress-session-cookies 将不会被清除
Cypress.Cookies.defaults({
    preserve: 'cypress-session-cookies'
})

// 所有名为 cypress-session-cookies 或 sessions_id 将不会被清除
// 多个 Cookie 可以用数组来存储
Cypress.Cookies.defaults({
  preserve: ['sessions_id', 'cypress-session-cookies']
})

// 满足此正则表达式的 Cookie 将不会被清除
Cypress.Cookies.defaults({
  preserve: /session|cookie/
})

Cypress.Cookies.defaults({
  preserve: (cookie) => {
    // 可以在这里实现自己的逻辑
    // 如果函数返回 true, 那 Cookie 则不会被清除
  }
})

操作代码实例

// 第一步:编写support/index.js
Cypress.Cookies.defaults({
    preserve: /sid|session/
})

// 第二步:编写测试用例
describe('设置全局默认cookie', function () {
    const username="wupeng"
    const password="bb961202"
    before(function () {
        cy.setCookie("session_id","test_cookie")
        cy.login_zd(username,password)
    });
    it('should 1', function () {
        cy.getCookies().each(function (cookie) {
            cy.log(cookie.name, cookie.value)

        })
    });
    it('should 2', function () {
        cy.getCookies().each(function (cookie) {
            cy.log(cookie.name, cookie.value)

        })
    });
});

总结

  1. 这种方式在项目中体现会有绝佳的效果。
  2. 一般我们需要提前知道需要的cookie是什么,但是在这里我们只需要提前在support/index.js中调用该命令去设置cookie就可以了。

Cypress.config

作用

在测试中获取并设置配置选项
作用范围

  1. 使用Cypress.config设置的配置项仅仅在当前规范文件(js测试文件)的范围内生效。
  2. Cypress隔离运行每个测试文件:在一个测试文件中更改的配置在其他测试文件中不可见。

基本语法格式

Cypress.config()
Cypress.config(name)
Cypress.config(name, value)
Cypress.config(object)

相关参数

  1. name 要获取或设置的配置名称
  2. value 要设置的配置值
  3. object 使用对象属性({}的格式)设置多个配置项。

操作实例代码

describe('config', function () {
    it('获取配置项', function () {
        // 查看所有配置
        cy.log(Cypress.config())
        // 设置配置项
        Cypress.config('viewportWidth', 1888)
        // 设置多个配置项(该操作会覆盖原有的配置项)
        Cypress.config({
            'animationDistanceThreshold': 5,
            'arch': "x64",
            'autoOpen': false,
            'baseUrl': null,
            'blockHosts': null
        })
    });
});

Cypress.env()

作用

在测试中获取并设置环境变量
作用范围

  1. 使用Cypress.env设置的环境变量仅在当前规范文件(js测试文件)的范围内生效
  2. Cypress隔离运行每个测试文件:在一个测试文件中更改的环境变量在其他测试文件中不可见

基本语法格式

Cypress.env()
Cypress.env(name)
Cypress.env(name, value)
Cypress.env(object)

相关参数

  1. name 要获取或设置的环境变量名称
  2. value 要设置的环境变量值
  3. object 使用对象属性({}的格式)设置多个环境变量

操作实例代码

describe('config', function () {
    it('获取配置项', function () {
        // 查看所有配置
        cy.log(Cypress.env())
        // 设置配置项
        Cypress.env('viewportWidth', 1888)
        // 设置多个配置项(该操作会覆盖原有的配置项)
        Cypress.env({
            'age': 5,
            'sex': "x64",

        })
    });
});

总结

只有stop和手动关闭浏览器然后再次开始测试才会重置环境变量。

Cypress.dom

作用

与DOM元素相关的helper方法结合

基本语法格式

Cypress.dom.isHidden(element)

操作实例代码

describe('设置全局默认cookie', function () {
    const username = "wupeng"
    const password = "bb961202"
    beforeEach(function () {
        cy.visit("http://192.168.102.49:5001")
    });
    it('测试元素是否为隐藏的', function () {
        cy.get("#username").type(username).then(function (el) {
            // 断言失败
            expect(Cypress.dom.isHidden(el)).to.be.true
        })
    });
    it('测试元素是否附加在DOM树上', function () {
        cy.get("#username").type(username).then(function (el) {
            // 断言成功
            expect(Cypress.dom.isAttached(el)).to.be.true
        })
    });
    it('判断一个元素是否是另一个元素的后代', function () {
        cy.get("#username").type(username).then(function (el) {
            // 断言成功
            expect(Cypress.dom.isDescendent(el.parent(),el)).to.be.true
        })
    });
    it('判断一个元素是否与DOM树分离', function () {
        cy.get("#username").type(username).then(function (el) {
            // 断言失败
            expect(Cypress.dom.isdetached(el)).to.be.true
        })
    });
    it('判断一个元素是否是document文档类型', function () {
        cy.get("#username").type(username).then(function (el) {
            // 断言失败
            expect(Cypress.dom.isdocument(el)).to.be.true
        })
    });
    it('判断一个元素是否是DOM对象', function () {
        cy.get("#username").type(username).then(function (el) {
            // 断言成功
            expect(Cypress.dom.isDom(el)).to.be.true
        })
    });
    it('判断一个元素是否可以接收焦点', function () {
        cy.get("#username").type(username).then(function (el) {
            // 断言成功
            expect(Cypress.dom.isFocusable(el)).to.be.true
        })
    });
    it('判断一个元素当前是否有焦点', function () {
        cy.get("#username").type(username).then(function (el) {
            // 断言失败
            expect(Cypress.dom.isFocused(el)).to.be.true
        })
    });
     it('判断一个元素元素是否可见', function () {
        cy.get("#username").type(username).then(function (el) {
            // 断言成功
            expect(Cypress.dom.isVisible(el)).to.be.true
        })
    });
     it('判断一个对象是否为jQuery对象', function () {
        cy.get("#username").type(username).then(function (el) {
            // 断言成功
            expect(Cypress.dom.isJquery(el)).to.be.true
        })
    });
     it('判断一个元素是否可滚动', function () {
        cy.get("#username").type(username).then(function (el) {
            // 断言失败
            expect(Cypress.dom.isScrollable(el)).to.be.true
        })
    });
     it('判断一个对象是否为Window对象', function () {
        cy.get("#username").type(username).then(function (el) {
            // 断言失败
            expect(Cypress.dom.isWindow(el)).to.be.true
        })
    });


});

Cypress.platform

作用

  1. 返回基础的操作系统名称
  2. 即使Cypress在浏览器中运行,它也会自动使用该属性可用于测试。

操作实例代码

describe('cypress.platform', function () {
    it('测试开始', function () {
      expect(Cypress.platform).to.be.oneOf(['win32','ios'])
    });
});

Cypress.version

作用

返回正在运行的Cypress的当前版本

操作实例代码

describe('测试版本', function () {
    it('测试开始', function () {
        expect(Cypress.version).to.be.eq('6.5.0')
    });
});

Cypress.arch

作用

返回基础操作系统的CPU体系结构名称

操作实例代码

describe('测试Cpu体系结构', function () {
    it('测试开始', function () {
        expect(Cypress.arch).to.be.oneOf(['x64','ia32'])
    });
});

Cypress.browser

作用

返回浏览器的属性

属性

选项 数据类型 描述
channel string 浏览器发布渠道,如:stable,dev,canary
displayName string 浏览器的可读名称
family string 正在使用的渲染引擎,如:chromium,firefox
isChosen boolean 是否在测试运行器的浏览器选择器中选择了浏览器
majorVersion number 浏览器的主要版本号
name string 浏览器名称,如:chrome,electron,firefox
path string 浏览器在磁盘上的路径
version string 完整的版本号
isHeadless boolean 浏览器是否无头运行
isHeaded boolean 浏览器是否有头运行

操作实例代码

describe('测试浏览器属性', function () {
    it('测试开始', function () {
        cy.wrap(Cypress.browser).should(function (br) {
            expect(br).to.have.property('name','electron')
            expect(br).to.have.property('channel','stable')
            expect(br).to.have.property('isChosen',true)
            expect(br).to.have.property('isHeaded',true)
        })
    });
});
posted @ 2021-02-18 15:23  SunFree  阅读(1266)  评论(0编辑  收藏  举报