配置、组织和整合 Cypress 测试的想法#frontend@twiliosendgrid

已发表: 2020-12-15

我们已经在 Twilio SendGrid 为不同的 Web 应用程序和前端团队编写了许多赛普拉斯测试。 随着我们的测试扩展到许多功能和页面,我们偶然发现了一些有用的配置选项并开发了一些方法来更好地维护我们不断增长的文件数量、页面元素的选择器以及沿途的超时值。

我们的目标是向您展示这些关于配置、组织和整合您自己的赛普拉斯相关事物的技巧和想法,因此请随意使用最适合您和您的团队的方法。

这篇博文假设您对赛普拉斯测试有一定的应用知识,并且正在寻找更好地维护和改进赛普拉斯测试的想法。 但是,如果您想了解更多关于常见函数、断言和模式的信息,您可能会发现针对不同环境编写赛普拉斯测试很有用,您可以查看这篇 1000 英尺概述博客文章。

否则,让我们继续并首先向您介绍赛普拉斯的一些配置技巧。

配置你的 cypress.json

您可以在cypress.json文件中设置赛普拉斯测试的所有配置,例如赛普拉斯命令的基本超时、环境变量和其他属性。

以下是一些提示,供您在配置中尝试:

  • 微调您的基本命令超时,因此您不必总是在整个赛普拉斯命令中添加{ timeout: timeoutInMs } 修改数字,直到找到“defaultCommandTimeout”、“requestTimeout”和“responseTimeout”等设置的正确平衡。
  • 如果您的测试涉及 iframe,您很可能需要将“chromeWebSecurity”设置为 false,以便您可以在应用程序中访问跨域 iframe。
  • 尝试在执行赛普拉斯测试时阻止第三方营销、分析和日志记录脚本,以提高速度而不触发不需要的事件。 您可以使用其“blacklistHosts”属性(或赛普拉斯 5.0.0 中的“blockHosts”属性)轻松设置拒绝列表,获取与第三方主机路径匹配的字符串 glob 数组。
  • 当您打开赛普拉斯 GUI 时,使用“viewportWidth”和“viewportHeight”调整默认视口尺寸,以使事情更容易看到。
  • 如果 Docker 中存在内存问题以进行繁重的测试,或者您希望帮助提高效率,您可以尝试将“numTestsKeptInMemory”修改为比默认值更小的数字,并将“videoUploadOnPasses”设置为 false,以专注于为失败的测试上传视频只运行。

另一方面,在调整赛普拉斯配置后,您还可以添加 TypeScript 并更好地键入赛普拉斯测试,就像我们在这篇博文中所做的那样。 这对于调用和链接函数(如cy.task('someTaskPluginFunction)Cypress.env('someEnvVariable')cy.customCommand() )时的自动完成、类型警告或错误特别有用。

您可能还想尝试设置基本的cypress.json文件并为每个测试环境加载单独的赛普拉斯配置文件,例如当您在package.json中运行不同的赛普拉斯脚本时的staging.json 。 赛普拉斯文档在引导您完成这种方法方面做得很好。

组织 Cypress 文件夹

当您首次在代码库中启动 Cypress 时,Cypress 已经设置了一个具有引导结构的顶级cypress文件夹。 这包括其他文件夹,例如规范文件的integration 、JSON 数据文件的fixturescy.task(...)函数和其他配置的plugins ,以及自定义命令和类型的support文件夹。

在 Cypress 文件夹、React 组件或任何一般代码中进行组织时,一个好的经验法则是将可能一起改变的东西放在一起。 在这种情况下,由于我们在浏览器中处理 Web 应用程序,因此一种可以很好地扩展的方法是按页面或总体功能将文件夹中的内容分组。

我们创建了一个单独的pages文件夹,按功能名称(例如“SenderAuthentication”)来保存/settings/sender_auth路由下的所有页面对象,例如“DomainAuthentication”(/settings/sender_auth/domains/**/*)和“LinkBranding” (/settings/sender_auth/links/**/*)。 在我们的plugins文件夹中,我们也做了同样的事情,将某个功能或页面的所有cy.task(...)插件文件组织在同一个文件夹中以进行发件人身份验证,我们对integration文件夹采用相同的方法规范文件。 我们已经将我们的测试扩展到我们的一个代码库中的数百个规范文件、页面对象、插件和其他文件——并且浏览起来既简单又方便。

这是我们如何组织cypress文件夹的概述。

组织integration文件夹(所有规范所在的位置)时要考虑的另一件事是可能根据测试的优先级拆分规范文件。 例如,您可能在“P1”文件夹中拥有所有最高优先级和价值测试,在“P2”文件夹中拥有第二优先级测试,因此您可以通过设置--spec选项轻松运行所有“P1”测试,例如--spec 'cypress/integration/P1/**/*'

创建一个适合您的规范文件夹层次结构,以便您可以轻松地将规范组合在一起,不仅可以按页面或功能(例如--spec 'cypress/integration/SomePage/**/*' ,还可以按其他一些标准(例如优先级、产品) ,或环境。

合并元素选择器

在开发我们页面的 React 组件时,我们通常会使用 Jest 和 Enzyme 添加一些级别的单元和集成测试。 在功能开发结束时,我们添加了另一层赛普拉斯 E2E 测试,以确保一切都与后端一起工作。 我们的 React 组件的单元测试和 Cypress E2E 测试的页面对象都需要选择器来选择我们希望与之交互和断言的页面上的组件/DOM 元素。

当我们更新这些页面和组件时,可能会出现偏差和错误,因为必须将多个位置从单元测试选择器同步到赛普拉斯页面对象再到实际组件代码本身。 如果我们只依赖与样式相关的类名,那么记住更新所有可能损坏的地方会很痛苦。 相反,我们将“data-hook”、“data-testid”或任何其他一致命名的“data-*”属性添加到我们希望记住的页面上的特定组件和元素,并在我们的测试层中为其编写选择器。

我们可以将“数据挂钩”属性附加到我们的许多元素中,但我们仍然需要一种方法将它们全部组合在一个地方,以便在其他文件中更新和重用。 我们找到了一种类型化的方法来管理所有这些“数据挂钩”选择器,这些选择器被分散到我们的 React 组件中,并在我们的单元测试和页面对象选择器中使用,以便在导出对象中实现更多重用和更容易维护。

对于每个页面的顶级文件夹,我们将创建一个hooks.ts文件,该文件管理和导出一个对象,该对象具有元素的可读键名和元素的实际字符串 CSS 选择器作为值。 我们将这些称为“读取选择器”,因为我们需要在单元测试或赛普拉斯的调用中读取和使用 CSS 选择器表单来调用元素,例如 Enzyme 的wrapper.find(“[data-hook='selector']”) cy.get(“[data-hook='selector']”)在我们的页面对象中。 然后这些调用看起来像wrapper.find(Selectors.someElement)cy.get(Selectors.someElement)一样干净。

以下代码片段详细介绍了我们在实践中使用这些读取选择器的原因和方式。

类似地,我们还使用元素的可读键名和像{ “data-hook”: “selector” }这样的对象作为值导出对象。 我们将这些称为“写入选择器”,因为我们需要将这些对象作为道具写入或传播到 React 组件上,以便成功地将“数据挂钩”属性添加到底层元素。 目标是做这样的事情下面的实际 DOM 元素——假设 props 被正确地传递给 JSX 元素——也将设置“data-hook=”属性。

以下代码片段详细介绍了我们在实践中使用这些写选择器的原因和方式。

我们可以创建对象来整合我们的读取选择器和写入选择器来更新更少的位置,但是如果我们必须为一些更复杂的页面编写许多选择器怎么办? 这可能更容易出错,因此让我们创建函数来轻松生成这些读取选择器和写入选择器以最终导出到某个页面。

对于读取选择器生成器函数,我们将遍历输入对象的属性并为每个元素名称形成[data-hook=”selector”] CSS 选择器字符串。 如果键的对应值为null ,我们将假设输入对象中的元素名称将与“data-hook”值相同,例如{ someElement: null } => { someElement: '[data-hook=”someElement”] } . 否则,如果键的对应值不为空,我们可以选择覆盖该“数据挂钩”值,例如{ someElement: “newSelector” } => { someElement: '[data-hook=”newSelector”]' }

对于写入选择器生成器函数,我们将遍历输入对象的属性并为每个元素名称形成{ “data-hook”: “selector” }对象。 如果键的对应值为null ,我们将假设输入对象中的元素名称将与“data-hook”值相同,例如{ someElement: null } => { someElement: { “data-hook”: “someElement” } } 。 否则,如果一个键的对应值不是null ,我们可以选择覆盖那个“data-hook”值,例如{ someElement: “newSelector” } => { someElement: { “data-hook”: “newSelector” }' } .

使用这两个生成器函数,我们为页面构建读取选择器和写入选择器对象,并将它们导出以在我们的单元测试和赛普拉斯页面对象中重用。 另一个好处是这些对象的类型是这样的,我们不会在我们的 TypeScript 文件中意外地执行Selectors.unknownElementWriteSelectors.unknownElement 。 在导出我们的读取选择器之前,我们还允许为我们无法控制的第三方组件添加额外的元素和 CSS 选择器映射。 在某些情况下,我们无法为某些元素添加“data-hook”属性,因此我们仍然需要通过其他属性、id 和类进行选择,并为读取的选择器对象添加更多属性,如下所示。

这种模式可以帮助我们保持对页面的所有选择器以及何时需要更新内容的组织。 我们建议您研究在某种对象中或通过任何其他方式管理所有这些选择器的方法,以最大限度地减少您在未来更改时需要触及的文件数量。

合并超时

在编写了许多赛普拉斯测试后,我们注意到的一件事是,我们的某些元素的选择器会超时,并且比在cypress.json中设置的默认超时值(例如“defaultCommandTimeout”和“responseTimeout”)花费的时间更长。 我们会回过头来调整需要更长超时时间的某些页面,但是随着时间的推移,任意超时值的数量会增加,并且对于大规模更改而言,维护它变得更加困难。

因此,我们将超时合并到一个从“defaultCommandTimeout”开始的对象中,该对象在 5 到 10 秒的范围内,以涵盖我们选择器的大多数一般超时,例如cy.get(...)cy.contains(...) 。 除了该默认超时,我们将在超时对象内扩展为“short”、“medium”、“long”、“xlong”和“xxlong”,我们可以在文件中的任何位置导入以在 Cypress 命令中使用,例如cy.get(“someElement”, { timeout: timeouts.short })cy.task('pluginName', {}, { timeout: timeouts.xlong }) 。 在用我们导入的对象中的这些值替换我们的超时后,我们有一个地方可以更新以扩大或缩小某些超时所花费的时间。

下面显示了一个如何轻松开始的示例。

包起来

随着您的赛普拉斯测试套件的增长,您可能会遇到我们在确定如何最好地扩展和维护赛普拉斯测试时遇到的一些相同问题。 您可以选择根据页面、功能或其他一些分组约定来组织文件,因此随着更多开发人员为您的代码库做出贡献,您始终知道在哪里查找现有测试文件以及在哪里添加新文件。

随着 UI 的变化,您可以使用某种类型的对象(例如读取和写入选择器)来维护您的“数据”属性选择器,以便您在单元测试和赛普拉斯中断言或交互的每个页面的关键元素测试。 如果您发现自己开始为赛普拉斯命令的超时值等应用太多任意值,则可能是时候设置一个填充了缩放值的对象,以便您可以在一个地方更新这些值。

随着前端 UI、后端 API 和 Cypress 测试的变化,您应该始终考虑如何更轻松地维护这些测试。 更少的更新和出错的地方以及更少的不确定性会产生巨大的差异,因为许多开发人员会添加新的页面、功能和(不可避免地)赛普拉斯测试。

对赛普拉斯的更多帖子感兴趣? 看看以下文章:

  • 编写 E2E 测试时要考虑什么
  • 编写柏树测试的 1,000 英尺概述
  • TypeScript 包含 Cypress 测试中的所有内容
  • 在 Cypress 测试中处理电子邮件流
  • 将 Cypress 测试与 Docker、Buildkite 和 CICD 集成