使用Cypress需要避免的5個E2E測試錯誤
譯文【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】