Skip to main content
Cypress应用

编写和组织测试

info
学习内容
  • 如何在Cypress中组织测试及支持的文件类型
  • 如何编写Cypress测试(含钩子函数、排除项和配置)
  • 测试状态及其解读方法
  • 编写Cypress测试时自动监视的文件

文件夹结构

添加新项目后,Cypress会自动生成推荐的文件夹结构。默认创建如下:

端到端测试:
/cypress.config.js
/cypress/fixtures/example.json
/cypress/support/commands.js
/cypress/support/e2e.js

组件测试:
/cypress.config.js
/cypress/fixtures/example.json
/cypress/support/commands.js
/cypress/support/component.js
/cypress/support/component-index.html

两者兼备:
/cypress.config.js
/cypress/fixtures/example.json
/cypress/support/commands.js
/cypress/support/e2e.js
/cypress/support/component.js
/cypress/support/component-index.html

配置文件夹结构

虽然Cypress允许配置测试、夹具和支持文件的存放位置,但如果是首次创建项目,我们建议使用上述结构。

可以通过配置文件修改文件夹配置。详见Cypress配置

info
哪些文件应添加到.gitignore?

Cypress会创建screenshotsFoldervideosFolder来存储测试过程中生成的截图和视频。建议将这些文件夹加入.gitignore。如果配置文件中包含敏感环境变量或使用了cypress.env.json,也应忽略这些文件。

测试规范文件

测试文件默认位于cypress/e2e,但可通过配置修改路径。支持以下格式:

  • .js
  • .jsx
  • .ts
  • .tsx
  • .coffee
  • .cjsx

Cypress原生支持ES2015,可使用ES2015模块CommonJS模块,支持importrequire引入npm包和本地模块。

info
示例参考

查看我们使用ES2015和CommonJS模块的示例。

要查看所有Cypress命令的示例,请打开cypress/e2e文件夹中的2-advanced-examples文件夹

夹具文件

夹具文件是测试使用的静态数据,默认位于cypress/fixtures,可通过配置修改路径。通常与cy.fixture()命令配合使用,常用于模拟网络请求

资源文件

测试运行后会生成一些包含资源的文件夹,建议将这些文件夹加入.gitignore。

下载文件

测试文件下载功能时下载的文件默认存储在downloadsFolder(默认为cypress/downloads)。

/cypress
/downloads
- records.csv

截图文件

通过cy.screenshot()命令或测试失败时自动生成的截图默认存储在screenshotsFolder(默认为cypress/screenshots)。

/cypress
/screenshots
/app.cy.js
- Navigates to main menu (failures).png

更多截图设置请参阅截图和视频

视频文件

测试运行的录像默认存储在videosFolder(默认为cypress/videos)。

/cypress
/videos
- app.cy.js.mp4

资源文件路径

生成的截图和视频会保存在各自文件夹中。文件路径会根据specPattern选项(或通过--spec命令行选项或spec模块API选项)去除所有规范文件共享的公共祖先路径。

示例1:

  • 规范文件:cypress/e2e/path/to/file/one.cy.js
  • 公共祖先路径:cypress/e2e/path/to/file
  • 生成截图:cypress/screenshots/one.cy.js/your-screenshot.png
  • 生成视频:cypress/videos/one.cy.js.mp4

示例2:

  • 规范文件:
    • cypress/e2e/path/to/file/one.cy.js
    • cypress/e2e/path/to/two.cy.js
  • 公共祖先路径:cypress/e2e/path/to/
  • 生成截图:
    • cypress/screenshots/file/one.cy.js/your-screenshot.png
    • cypress/screenshots/two.cy.js/your-screenshot.png
  • 生成视频:
    • cypress/videos/file/one.cy.js.mp4
    • cypress/videos/two.cy.js.mp4

Cypress云中的资源

测试回放界面

无需手动管理资源,可通过Cypress云保存。使用 Cypress测试回放功能可以回放测试执行过程。

截图和视频会永久存储,附加到相应的测试结果中,便于通过网页界面浏览或分享。更多视频设置请参阅截图和视频

插件文件

插件文件是一个特殊的Node文件,在项目加载前、浏览器启动前和测试执行期间运行。虽然Cypress测试在浏览器中执行,但插件文件在后台Node进程中运行,使测试能够通过cy.task()命令访问文件系统和操作系统。

插件文件适合定义如何通过预处理器打包规范文件,如何通过浏览器启动API查找和启动浏览器等功能。详见插件指南

初始导入的插件文件可配置为其他文件

支持文件

要包含在测试文件之前运行的代码,可设置supportFile路径。默认查找以下文件:

组件测试:

  • cypress/support/component.js
  • cypress/support/component.jsx
  • cypress/support/component.ts
  • cypress/support/component.tsx

端到端测试:

  • cypress/support/e2e.js
  • cypress/support/e2e.jsx
  • cypress/support/e2e.ts
  • cypress/support/e2e.tsx
danger

对于给定的测试类型,存在多个匹配的supportFile文件会导致Cypress加载时报错。

info

按测试类型配置supportFile

根据使用的测试类型,可相应配置supportFile

Cypress会为每个配置的测试类型自动创建示例支持文件,其中包含多个注释掉的示例。

该文件在每个规范文件之前运行,作为便捷机制避免手动导入。默认情况下,Cypress会自动包含特定类型的支持文件。

支持文件适合放置可重用行为,如自定义命令或要应用于所有规范文件的全局覆盖。初始导入的支持文件可配置为其他文件或完全关闭,使用supportFile配置。可从支持文件中importrequire其他文件以保持组织有序。

可在任何cypress/support文件中定义beforebeforeEach行为:

beforeEach(() => {
cy.log('我在每个规范文件的每个测试前运行!!!!!!')
})
测试的全局钩子
info

注意: 此示例假设您已熟悉Mocha钩子

执行顺序

Cypress在规范文件之前执行支持文件。例如:

端到端测试示例:

  1. support/e2e.js(支持文件)
  2. e2e/spec-a.cy.js(规范文件)

组件测试示例:

  1. support/component.js(支持文件)
  2. components/Button/Button.cy.js(规范文件)

故障排查

如果Cypress找不到规范文件,可通过启用调试日志进行排查:

DEBUG=cypress:cli,cypress:data-context:sources:FileDataSource,cypress:data-context:sources:ProjectDataSource npx cypress open
## 或
DEBUG=cypress:cli,cypress:data-context:sources:FileDataSource,cypress:data-context:sources:ProjectDataSource npx cypress run

编写测试

Cypress基于MochaChai构建,支持Chai的BDDTDD断言风格。编写的测试大多遵循这种风格。

如果您熟悉JavaScript测试编写,那么编写Cypress测试将非常简单。

info

要开始为应用编写测试,请遵循组件测试端到端测试指南。

info

需要低代码方式创建测试?使用Cypress Studio记录浏览器交互。

测试结构

测试接口借鉴Mocha,提供describe()context()it()specify()

context()等同于describe()specify()等同于it(),可根据喜好选择术语。

// -- 应用代码 --
function add(a, b) {
return a + b
}

function subtract(a, b) {
return a - b
}

function divide(a, b) {
return a / b
}

function multiply(a, b) {
return a * b
}
// -- 应用代码结束 --

// -- Cypress测试 --
describe('数学函数单元测试', () => {
context('数学运算', () => {
it('可以相加数字', () => {
expect(add(1, 2)).to.eq(3)
})

it('可以相减数字', () => {
expect(subtract(5, 12)).to.eq(-7)
})

specify('可以相除数字', () => {
expect(divide(27, 9)).to.eq(3)
})

specify('可以相乘数字', () => {
expect(multiply(5, 4)).to.eq(20)
})
})
})
// -- 测试结束 --

钩子函数

Cypress还提供钩子函数(借鉴自Mocha)。

这些钩子有助于在测试集之前或每个测试之前设置条件,也便于在测试集之后或每个测试之后清理条件。

before(() => {
// 根级钩子
// 所有测试前运行一次
})

beforeEach(() => {
// 根级钩子
// 每个测试块前运行
})

afterEach(() => {
// 每个测试块后运行
})

after(() => {
// 所有测试完成后运行一次
})

describe('钩子函数', () => {
before(() => {
// 该区块所有测试前运行一次
})

beforeEach(() => {
// 该区块每个测试前运行
})

afterEach(() => {
// 该区块每个测试后运行
})

after(() => {
// 该区块所有测试后运行一次
})
})

钩子和测试执行顺序:

  1. 所有before()钩子运行(一次)
  2. 任何beforeEach()钩子运行
  3. 测试运行
  4. 任何afterEach()钩子运行
  5. 所有after()钩子运行(一次)
danger

🚨 在编写after()afterEach()钩子前,请阅读关于使用after()或afterEach()清理状态的反模式思考

排除和包含测试

要运行特定套件或测试,在函数后追加.only。嵌套套件也会执行。这使我们能够一次运行一个测试,是推荐的测试套件编写方式。

// -- 应用代码 --
function fizzbuzz(num) {
if (num % 3 === 0 && num % 5 === 0) {
return 'fizzbuzz'
}

if (num % 3 === 0) {
return 'fizz'
}

if (num % 5 === 0) {
return 'buzz'
}
}
// -- 应用代码结束 --

// -- Cypress测试 --
describe('FizzBuzz单元测试', () => {
function numsExpectedToEq(arr, expected) {
arr.forEach((num) => {
expect(fizzbuzz(num)).to.eq(expected)
})
}

// 仅运行此测试
it.only('当数字是3的倍数时返回"fizz"', () => {
numsExpectedToEq([9, 12, 18], 'fizz')
})

it('当数字是5的倍数时返回"buzz"', () => {
numsExpectedToEq([10, 20, 25], 'buzz')
})

it('当数字是3和5的倍数时返回"fizzbuzz"', () => {
numsExpectedToEq([15, 30, 60], 'fizzbuzz')
})
})

要跳过特定套件或测试,在函数后追加.skip()。嵌套套件也会跳过。

it.skip('当数字是3的倍数时返回"fizz"', () => {
numsExpectedToEq([9, 12, 18], 'fizz')
})

测试隔离

tip

最佳实践: 测试应始终能够独立运行且仍能通过

正如我们的使命所述,我们致力于推广真正有效的测试流程,并构建Cypress以引导开发人员从一开始就编写独立测试。

我们通过在每次测试前清理测试状态和浏览器上下文来确保一个测试的操作不会影响后续测试。每个测试的目标应该是可靠地通过,无论是独立运行还是与其他测试连续运行。依赖于先前测试状态的测试可能导致不确定的测试失败,增加调试难度。

在干净的浏览器上下文中运行测试的行为称为testIsolation

测试隔离是全局配置,可通过testIsolation选项在端到端测试的describe级别覆盖。

要了解更多关于此行为及禁用它的权衡,请参阅测试隔离指南

测试配置

可以为套件或测试应用测试配置值。将配置对象作为第二个参数传递给测试或套件函数。

这些配置将在设置它们的套件或测试期间生效,完成后恢复为之前的默认值。

语法

describe(name, config, fn)
context(name, config, fn)
it(name, config, fn)
specify(name, config, fn)

允许的配置值

caution

注意: 某些配置值是只读的,无法通过测试配置更改。请查看测试配置选项列表。

套件配置

如果想针对特定浏览器运行或排除一组测试,可以在套件配置中覆盖browser配置。browser选项接受与Cypress.isBrowser()相同的参数。

以下套件测试在Chrome浏览器中运行时会被跳过。

describe('非Chrome环境', { browser: '!chrome' }, () => {
it('显示警告', () => {
cy.get('[data-testid="browser-warning"]').should(
'contain',
'为了最佳浏览体验,请使用Chrome浏览器'
)
})

it('链接到浏览器兼容性文档', () => {
cy.get('a.browser-compat')
.should('have.attr', 'href')
.and('include', 'browser-compatibility')
})
})

以下套件测试仅在Firefox浏览器中执行。其中一个测试会覆盖视口分辨率,并将当前环境变量与提供的环境变量合并。

describe(
'Firefox环境',
{
browser: 'firefox',
viewportWidth: 1024,
viewportHeight: 700,
env: {
DEMO: true,
API: 'http://localhost:9000',
},
},
() => {
it('设置预期视口和API URL', () => {
expect(cy.config('viewportWidth')).to.equal(1024)
expect(cy.config('viewportHeight')).to.equal(700)
expect(cy.env('API')).to.equal('http://localhost:9000')
})

it(
'使用最近的API环境变量',
{
env: {
API: 'http://localhost:3003',
},
},
() => {
expect(cy.env('API')).to.equal('http://localhost:3003')
// 其他环境变量保持不变
expect(cy.env('DEMO')).to.be.true
}
)
}
)

单个测试配置

可以配置cypress runcypress open期间的重试次数。详见测试重试

it('未认证用户应重定向到登录页', {
retries: {
runMode: 3,
openMode: 2
}
} () => {
// 测试代码...
})
})

动态生成测试

可以使用JavaScript动态生成测试。

describe('如果应用使用jQuery', () => {
;['mouseover', 'mouseout', 'mouseenter', 'mouseleave'].forEach((event) => {
it('触发事件: ' + event, () => {
// 如果应用使用jQuery,可以触发jQuery事件
cy.get('#with-jquery')
.invoke('trigger', event)
.get('[data-testid="messages"]')
.should('contain', '事件 ' + event + '已触发')
})
})
})

上述代码将生成包含4个测试的套件:

> 如果应用使用jQuery
> 触发事件: 'mouseover'
> 触发事件: 'mouseout'
> 触发事件: 'mouseenter'
> 触发事件: 'mouseleave'

断言风格

Cypress支持BDD(expect/should)和TDD(assert)风格的普通断言。阅读更多关于普通断言的内容。

it('可以相加数字', () => {
expect(add(1, 2)).to.eq(3)
})

it('可以相减数字', () => {
assert.equal(subtract(5, 12), -7, '这些数字相等')
})

.should()命令及其别名.and()也可用于更轻松地从Cypress命令链中断言。阅读更多关于断言的内容。

cy.wrap(add(1, 2)).should('equal', 3)

运行测试

可以通过点击规范文件名运行测试。例如Cypress RealWorld App有多个测试文件,下面我们通过点击"new-transaction.spec.ts"测试文件来运行它。

运行单个规范文件

测试状态

Cypress规范文件完成后,每个测试有四种状态之一:通过失败待定跳过。这些状态的行为继承自Mocha,因为这是Cypress使用的测试运行器。

通过

通过的测试成功完成了所有钩子和命令,没有断言失败。下面的测试截图显示了一个通过的测试:

Cypress显示单个通过测试

注意,测试可能在几次测试重试后通过。此时命令日志会显示一些失败的尝试,但最终整个测试会成功完成。

失败

好消息 - 失败的钩子或测试发现了问题。更糟的情况可能是用户遇到这个错误!

Cypress显示单个失败测试

测试失败后,测试回放或Cypress云的截图和视频可以帮助找到问题以便修复。

待定

可以通过以下几种方式编写_占位符_测试,Cypress知道不要运行它们。此外,可以条件性地指定应运行的浏览器和测试,包括如果测试不应在当前测试的浏览器中运行,则标记为_待定_。

Cypress将以下所有测试标记为_待定_。

describe('TodoMVC', () => {
it('尚未编写')

it.skip('添加2个待办事项', function () {
cy.visit('/')
cy.get('[data-testid="new-todo"]').as('new').type('学习测试{enter}')

cy.get('@new').type('保持酷{enter}')

cy.get('[data-testid="todo-list"] li').should('have.length', 100)
})

xit('另一个测试', () => {
expect(false).to.true
})

it('仅测试chrome', { browser: 'chrome' }, () => {
cy.visit('/')
cy.contains('待办事项')
})
})

当Cypress完成运行规范文件时,上述四个测试都被标记为_待定_。

Cypress显示四个待定测试

所以请记住 - 如果您(测试编写者)通过上述三种方式之一有意跳过测试,Cypress会将其计为_待定_测试。

跳过

最后一个测试状态适用于您_打算_运行但由于某些运行时错误而跳过的测试。例如,想象一组测试共享相同的beforeEach钩子 - 您在beforeEach钩子中访问页面。

/// <reference types="cypress" />

describe('TodoMVC', () => {
beforeEach(() => {
cy.visit('/')
})

it('初始隐藏页脚', () => {
cy.get('[data-testid="filters"]').should('not.exist')
})

it('添加2个待办事项', () => {
cy.get('[data-testid="new-todo"]').as('new').type('学习测试{enter}')

cy.get('@new').type('保持酷{enter}')

cy.get('[data-testid="todo-list"] li').should('have.length', 2)
})
})

如果beforeEach钩子完成且两个测试都完成,则两个测试通过。

Cypress显示两个通过测试

但如果beforeEach钩子中的命令失败会发生什么?例如,假设我们想访问一个不存在的页面/does-not-exist而不是/。如果我们更改beforeEach使其失败:

beforeEach(() => {
cy.visit('/does-not-exist')
})

当Cypress开始执行第一个测试时,beforeEach钩子失败。现在第一个测试被标记为失败。但是如果beforeEach钩子已经失败一次,为什么我们要在第二个测试之前再次执行它?它会以同样的方式失败!因此Cypress_跳过_该块中的剩余测试,因为它们也会因beforeEach钩子失败而失败。

Cypress显示一个失败和一个跳过测试

如果我们折叠测试命令,可以看到标记跳过测试"添加2个待办事项"的空框。

Cypress显示一个跳过测试

那些本应执行但由于某些运行时问题而跳过的测试被Cypress标记为"跳过"。这通常在beforebeforeEachafterEach钩子失败时观察到。

监视测试

使用cypress open运行时,Cypress会监视文件系统以检测规范文件的更改。添加或更新测试后不久,Cypress会重新加载并运行该规范文件中的所有测试。

这提供了高效的开发体验,因为您可以在实现功能时添加和编辑测试,而Cypress用户界面始终反映最新编辑的结果。

info

记得使用.only来限制运行的测试:当您在一个规范文件中有许多不断编辑的测试时,这尤其有用;也可以考虑将测试拆分为每个处理逻辑相关行为的较小文件。

监视什么?

文件

文件夹

  • 端到端目录(默认为cypress/e2e/
  • 支持目录(默认为cypress/support/

监视文件夹、文件夹内的文件以及所有子文件夹及其文件(递归)。

info

这些文件夹路径指的是默认文件夹路径。如果配置了Cypress使用不同的文件夹路径,则会监视特定于配置的文件夹。

不监视什么?

其他所有内容;包括但不限于以下内容:

  • 应用程序代码
  • node_modules
  • cypress/fixtures/

如果使用现代基于JS的Web应用程序堆栈进行开发,则可能支持某种形式的热模块替换,负责监视应用程序代码(HTML、CSS、JS等)并在更改时透明地重新加载应用程序。

配置

watchForFileChanges配置属性设置为false以禁用文件监视。

caution

注意:cypress run期间不监视任何内容。

watchForFileChanges属性仅在通过cypress open运行Cypress时有效。

Cypress中负责文件监视行为的组件是webpack-preprocessor。这是Cypress打包的默认文件监视器。

如果需要进一步控制文件监视行为,可以显式配置此预处理器:它公开了允许配置行为的选项,例如_监视什么_以及更改后发出"更新"事件之前的延迟。

Cypress还提供了其他文件监视预处理器 - 如果想使用它们,必须显式配置。