【51CTO.com快译】不过,端到端测试是困难的、耗时的,并且有一大堆问题需要解决。但如果你用对了工具的话,效果可能会事半功倍。
Cypress测试框架提供了快速、简单、可靠的浏览器测试。能够帮助解决端到端测试的大多数痛点,并使编写测试变得有趣。但是,有一些错误是要避免的,这样就可以充分利用Cypress的优势。
在这篇博文中,我们将介绍5个常见错误,这些错误在使用Cypress测试时应该避免。
使用id和class来选择元素
使用id和class来选择元素是有问题的,因为它们主要用于行为和样式目的,因此经常会发生更改。这样做会导致你可能不想要的脆弱测试。
相反,你应该始终尝试使用data-cy或data-test-id。因为它们是专门用于测试目的的,这使得它们与行为或样式分离,因此更可靠。
例如,假设我们有一个input元素:
<input
id="main"
type="text"
class="input-box"
name="name"
data-testid="name"
/>
应该使用id或class来测试这个元素,而应该使用data- tested:
// Don't
cy.get("#main").something();
cy.get(".input-box").something();
// Do
cy.get("[data-testid=name]").something();
有时需要使用文本(如按钮标签)来进行断言或操作。尽管这很好,但请记住,如果文本更改,则会导致测试失败。
将Cypress命令视为Promise
Cypress测试由Cypress命令组成,例如Cypress .get和Cypress .visit。类似于Promise,但不是真正的Promise。
这意味着,我们不能在使用它们时像使用async-await这样的语法。例如:
// This won't work
const element = await cy.get("[data-testid=element]");
// Do something with element
如果你需要在一个命令完成后执行某些操作,你可以在cy.then命令的帮助下完成。它将保证只有在前一个命令完成后,才会运行下一个命令。
// This works
cy.get("[data-testid=element]").then($el => {
// Do something with $el
});
在使用像Promise这样的命令时,它可能不会像你期望的那样工作,因为Cypress命令类似于Promise,但不是真正的Promise。
在Cypress测试中使用任意等待
在编写Cypress测试时,我们希望在真实场景中模拟真实用户的行为。由于网络延迟和设备限制等因素,现实世界中的应用程序是异步的和缓慢的。
在为这类应用程序编写测试时,我们倾向于在cy.wait命令中使用任意值。这种方法的问题在于,虽然它在开发中运行良好,但不能保证一直稳定。因为底层系统依赖于像网络请求这样的东西,这些东西是异步的,几乎不可能预测的。
// Might work (sometimes)
cy.get("[data-testid=element]").performSomeAsyncAction();
// Wait for 1000 ms
cy.wait(1000);
// Do something else after the action is completed
相反,我们应该等待视觉元素加载完成。它不仅更接近真实世界的用例,而且给出了更可靠的结果。想想看,使用应用程序的用户很可能会等待加载之类的视觉线索来确定操作的完成,而不是等待任意时间。
// The right way
cy.get("[data-testid=element]").performSomeAsyncAction();
// Wait for loading to finish
cy.get("[data-testid=loader]").should("not.be.visible");
// Now that we know previous action has been completed; move ahead
例如,Cypress命令cy.get在断言之前等待元素,当然你可以修改预定义的超时值,timeout很酷的一点是它们只会等待条件满足,而不是像cy.wait命令那样等待整个持续时间
在Cypress测试中使用不同的域
Cypress的一个限制是,它不允许在单个测试中使用多个域名。
如果你尝试在单个测试块中使用多个域,Cypress将抛出安全警告。
如果真要在一个测试中需要访问多个域。我们可以通过将测试逻辑分割到单个测试文件中的多个测试块来实现。例如,你可以把它想象成一个多步骤测试:
describe("Test Page Builder", () => {
it("Step 1: Visit Admin app and do something", {
// ...
});
it("Step 2: Visit Website app and assert something", {
// ...
});
});
我们在Webiny使用类似的方法来测试Page Builder应用程序。
当以这种方式编写测试时需要记住的几件事:
- 你不能依赖持久存储,无论是测试块中的变量,还是本地存储。因为,当我们使用配置中定义的baseURL以外的域发出一个Cypress命令时,Cypress会执行一个拆除和完全重新加载。
- 由于上述相同问题,"before"和 "after"这样的块将会为每个这样的测试块运行。
所以在采用此方法并相应地调整测试之前,请注意这些问题。
混合异步和同步代码
Cypress命令是异步的,它们不返回值,而是生成值。
当我们运行Cypress时,它不会立即执行命令,而是串行读取它们并将它们排队。只有在它一个一个地执行它们之后。因此,如果你编写的测试混合了异步和同步代码,你将得到错误的结果。例如:
it("does not work as we expect", () => {
cy.visit("your-application") // Nothing happens yet
cy.get("[data-testid=submit]") // Still nothing happening
.click() // Nope, nothing
// Something synchronous
let el = Cypress.$("title") // evaluates immediately as []
if (el.length) {
// It will never run because "el.length" will immediately evaluates as 0
cy.get(".another-selector")
} else {
/*
* This code block will always run because "el.length" is 0 when the code executes
*/
cy.get(".optional-selector")
}
})
相反,使用cy.then命令在命令完成后运行代码。例如:
it("does work as we expect", () => {
cy.visit("your-application") // Nothing happens yet
cy.get("[data-testid=submit]") // Still nothing happening
.click() // Nope, nothing
.then(() => {
// placing this code inside the .then() ensures
// it runs after the cypress commands 'execute'
let el = Cypress.$(".new-el") // evaluates after .then()
if (el.length) {
cy.get(".another-selector")
} else {
cy.get(".optional-selector")
}
})
})
总结
Cypress是端到端测试的强大工具,但有时我们会犯一些错误,这让体验变得不有趣。通过避免常见的错误,我们可以让端到端测试过程更加顺利和有趣。
【51CTO译稿,合作站点转载请注明原文译者和出处为51CTO.com】