天天看點

Cypress 的學習筆記使用 then 來對前一個指令 yield 的目标進行操作Cypress 的異步執行特性command 執行過程中背後發生的事情Cypress 指令自帶的逾時設定Queries for the element .mobile-nav, 然後停頓 4 秒,直至元素出現在 DOM 裡。

https://docs.cypress.io/guides/core-concepts/introduction-to-cypress#Subject-Management
Cypress 的學習筆記使用 then 來對前一個指令 yield 的目标進行操作Cypress 的異步執行特性command 執行過程中背後發生的事情Cypress 指令自帶的逾時設定Queries for the element .mobile-nav, 然後停頓 4 秒,直至元素出現在 DOM 裡。

上述 cypress 代碼,很像自然語言。

cypress 的 文法,cy.get(’.my-selector’),很像jQuery: cy.get(’.my-selector’)

事實上,cypress 本身就 bundle 了 jQuery:

Cypress 的學習筆記使用 then 來對前一個指令 yield 的目标進行操作Cypress 的異步執行特性command 執行過程中背後發生的事情Cypress 指令自帶的逾時設定Queries for the element .mobile-nav, 然後停頓 4 秒,直至元素出現在 DOM 裡。

支援類似 jQuery 的鍊式調用:

Cypress 的學習筆記使用 then 來對前一個指令 yield 的目标進行操作Cypress 的異步執行特性command 執行過程中背後發生的事情Cypress 指令自帶的逾時設定Queries for the element .mobile-nav, 然後停頓 4 秒,直至元素出現在 DOM 裡。

隻是有一點需要特别注意:

Cypress 的學習筆記使用 then 來對前一個指令 yield 的目标進行操作Cypress 的異步執行特性command 執行過程中背後發生的事情Cypress 指令自帶的逾時設定Queries for the element .mobile-nav, 然後停頓 4 秒,直至元素出現在 DOM 裡。

ct.get 并不會像 jQuery 那樣,采用同步的方式傳回待讀取的元素。Cypress 的元素通路,采取

異步方式

完成。

因為 jQuery 的同步通路機制,我們在調用元素查詢 API 之後,需要手動查詢其結果是否為空:

Cypress 的學習筆記使用 then 來對前一個指令 yield 的目标進行操作Cypress 的異步執行特性command 執行過程中背後發生的事情Cypress 指令自帶的逾時設定Queries for the element .mobile-nav, 然後停頓 4 秒,直至元素出現在 DOM 裡。

而 Cypress 的異步操作,導緻待讀取的元素真正可用時,其結果才會被作為參數,傳入回調函數:

Cypress 的學習筆記使用 then 來對前一個指令 yield 的目标進行操作Cypress 的異步執行特性command 執行過程中背後發生的事情Cypress 指令自帶的逾時設定Queries for the element .mobile-nav, 然後停頓 4 秒,直至元素出現在 DOM 裡。

也就是說,Cypress 内部幫我們封裝了 retry 和 timeout 重試機制。

Cypress 的學習筆記使用 then 來對前一個指令 yield 的目标進行操作Cypress 的異步執行特性command 執行過程中背後發生的事情Cypress 指令自帶的逾時設定Queries for the element .mobile-nav, 然後停頓 4 秒,直至元素出現在 DOM 裡。

如果想回歸到 jQuery 那種同步讀取元素的風格,使用 Cypress.$ 即可。

Cypress 的學習筆記使用 then 來對前一個指令 yield 的目标進行操作Cypress 的異步執行特性command 執行過程中背後發生的事情Cypress 指令自帶的逾時設定Queries for the element .mobile-nav, 然後停頓 4 秒,直至元素出現在 DOM 裡。

Cypress 指令并不會直接傳回其工作的目标,而是 yield 這些目标。Cypress 指令以異步的方式執行,指令被插入到隊列裡,并不會立即執行,而是等待排程。當指令真正執行時,目标對象經由前一個指令生成,然後傳入下一個指令裡。指令與指令之間,執行了很多有用的 Cypress 代碼,以確定指令執行順序和其在 Cypress 測試代碼裡調用的順序一緻。

Cypress 的學習筆記使用 then 來對前一個指令 yield 的目标進行操作Cypress 的異步執行特性command 執行過程中背後發生的事情Cypress 指令自帶的逾時設定Queries for the element .mobile-nav, 然後停頓 4 秒,直至元素出現在 DOM 裡。

Cypress 提供了一種叫做 aliasing 的機制,能将元素引用儲存下來,以備将來之用。

看一個例子:

Cypress 的學習筆記使用 then 來對前一個指令 yield 的目标進行操作Cypress 的異步執行特性command 執行過程中背後發生的事情Cypress 指令自帶的逾時設定Queries for the element .mobile-nav, 然後停頓 4 秒,直至元素出現在 DOM 裡。

使用 then 來對前一個指令 yield 的目标進行操作
Cypress 的學習筆記使用 then 來對前一個指令 yield 的目标進行操作Cypress 的異步執行特性command 執行過程中背後發生的事情Cypress 指令自帶的逾時設定Queries for the element .mobile-nav, 然後停頓 4 秒,直至元素出現在 DOM 裡。

Cypress 的異步執行特性
Cypress 的學習筆記使用 then 來對前一個指令 yield 的目标進行操作Cypress 的異步執行特性command 執行過程中背後發生的事情Cypress 指令自帶的逾時設定Queries for the element .mobile-nav, 然後停頓 4 秒,直至元素出現在 DOM 裡。

這是 Cypress 不同于其他前端自動測試架構的特别之處:直到測試函數退出,Cypress 才會觸發浏覽器的自動執行邏輯。

Cypress 的學習筆記使用 then 來對前一個指令 yield 的目标進行操作Cypress 的異步執行特性command 執行過程中背後發生的事情Cypress 指令自帶的逾時設定Queries for the element .mobile-nav, 然後停頓 4 秒,直至元素出現在 DOM 裡。
Cypress 的學習筆記使用 then 來對前一個指令 yield 的目标進行操作Cypress 的異步執行特性command 執行過程中背後發生的事情Cypress 指令自帶的逾時設定Queries for the element .mobile-nav, 然後停頓 4 秒,直至元素出現在 DOM 裡。

正确的做法,把 html 元素 evaluation 的代碼放在 then 的callback裡:

Cypress 的學習筆記使用 then 來對前一個指令 yield 的目标進行操作Cypress 的異步執行特性command 執行過程中背後發生的事情Cypress 指令自帶的逾時設定Queries for the element .mobile-nav, 然後停頓 4 秒,直至元素出現在 DOM 裡。

Each Cypress command (and chain of commands) returns immediately

每個 Cypress 指令(包含指令鍊)調用後立即傳回,不會阻塞住以達到同步運作的效果。

Having only been appended to a queue of commands to be executed at a later time.

這些 command 隻是被添加到一個指令隊列裡,等待 Cypress 架構稍後統一排程執行。

You purposefully cannot do anything useful with the return value from a command. Commands are enqueued and managed entirely behind the scenes.

對于 Cypress 直接傳回的指令的執行結果,我們無法對其實行任何有效的操作,因為代碼裡指令的調用,實際上隻是加入到待執行隊列裡。至于何時執行,由 Cypress 統一排程,對Cypress 測試開發人員來說是黑盒子。

We’ve designed our API this way because the DOM is a highly mutable object that constantly goes stale. For Cypress to prevent flake, and know when to proceed, we manage commands in a highly controlled deterministic way.

Cypress API 如此設計的原因是,DOM 是一種易變對象,随着使用者操作或者互動,狀态經常會 go stale. 為了避免出現 flake 情形,Cypress 遵循了上文描述的思路,以一種高度可控,确定性的方式來管理指令執行。

下面一個例子:網頁顯示随機數,當随機數跳到數字 7 時,讓測試停下來。 如果随機數不是數字 7,重新加載頁面,繼續測試。

下列是錯誤的 Cypress 代碼,會導緻浏覽器崩潰:

Cypress 的學習筆記使用 then 來對前一個指令 yield 的目标進行操作Cypress 的異步執行特性command 執行過程中背後發生的事情Cypress 指令自帶的逾時設定Queries for the element .mobile-nav, 然後停頓 4 秒,直至元素出現在 DOM 裡。

原因就是:在 while 循環裡迅速将巨量的 get command 插入到任務隊列(準确的說是 test chain)裡,而根本沒有機會得到執行。

The above test keeps adding more cy.get(’#result’) commands to the test chain without executing any!

上面的代碼,起到的效果就是,在 while 循環裡,不斷地将 cy.get 指令,加入到 test chain裡,但是任何一個指令,都不會有得到執行的機會!

The chain of commands keeps growing, but never executes - since the test function never finishes running.

指令隊列裡的元素個數持續增長,但是永遠得不到執行的機會,因為 Cypress 代碼本身一直在 while 循環裡,沒有執行完畢。

The while loop never allows Cypress to start executing even the very first cy.get(…) command.

即使是任務隊列裡第一個 cy.get 語句,因為 while 循環,也得不到執行的機會。

正确的寫法:

利用遞歸

在 callback 裡書寫找到 7 之後 return 的邏輯。

Cypress 的學習筆記使用 then 來對前一個指令 yield 的目标進行操作Cypress 的異步執行特性command 執行過程中背後發生的事情Cypress 指令自帶的逾時設定Queries for the element .mobile-nav, 然後停頓 4 秒,直至元素出現在 DOM 裡。

command 執行過程中背後發生的事情

下列這段代碼,包含了 5 部分邏輯:

Cypress 的學習筆記使用 then 來對前一個指令 yield 的目标進行操作Cypress 的異步執行特性command 執行過程中背後發生的事情Cypress 指令自帶的逾時設定Queries for the element .mobile-nav, 然後停頓 4 秒,直至元素出現在 DOM 裡。

5 個 步驟的例子:

Visit a URL.

Find an element by its selector.

Perform a click action on that element.

Grab the URL.

Assert the URL to include a specific string.

上述 5 步驟 是 串行執行的,而不是并發執行。每個步驟背後,Cypress 架構都悄悄執行了一些“魔法”:

Visit a URL

魔法:Cypress wait for the page load event to fire after all external resources have loaded

該指令執行時,Cypress 等待頁面所有外部資源加載,然後頁面抛出 page load 事件。

Find an element by its selector

魔法:如果 find 指令沒找到 DOM element,就執行重試機制,直到找到位置。

Perform a click action on that element

魔法:after we wait for the element to reach an actionable state

在 點選元素之前,先等待其成為可以點選狀态。

每個 cy 指令都有特定的逾時時間,記錄在文檔裡:

https://docs.cypress.io/guides/references/configuration

Commands are promise

This is the big secret of Cypress: we’ve taken our favorite pattern for composing JavaScript code, Promises, and built them right into the fabric of Cypress. Above, when we say we’re enqueuing actions to be taken later, we could restate that as “adding Promises to a chain of Promises”.

Cypress 在 promise 程式設計模式的基礎上,增添了 retry 機制。

下列這段代碼:

Cypress 的學習筆記使用 then 來對前一個指令 yield 的目标進行操作Cypress 的異步執行特性command 執行過程中背後發生的事情Cypress 指令自帶的逾時設定Queries for the element .mobile-nav, 然後停頓 4 秒,直至元素出現在 DOM 裡。

翻譯成 promise 風格的 JavaScript 代碼為:

Cypress 的學習筆記使用 then 來對前一個指令 yield 的目标進行操作Cypress 的異步執行特性command 執行過程中背後發生的事情Cypress 指令自帶的逾時設定Queries for the element .mobile-nav, 然後停頓 4 秒,直至元素出現在 DOM 裡。

Without retry-ability, assertions would randomly fail. This would lead to flaky, inconsistent results. This is also why we cannot use new JS features like async / await.

缺少重試機制,後果就是造成 flaky 和不一緻的測試結果,這就是 Cypress 沒有選擇 async / await 的原因。

You can think of Cypress as “queueing” every command. Eventually they’ll get run and in the exact order they were used, 100% of the time.

Cypress 的指令執行順序和其被插入 test chain 隊列的順序完全一緻。

How do I create conditional control flow, using if/else? So that if an element does (or doesn’t) exist, I choose what to do?

有的開發人員可能會産生疑問,如何編寫條件式控制流,比如在 IF / ELSE 分支裡,執行不同的測試邏輯?

The problem with this question is that this type of conditional control flow ends up being non-deterministic. This means it’s impossible for a script (or robot), to follow it 100% consistently.

事實上,這種條件式的控制邏輯,會使測試流失去确定性(non-deterministic). 這意味着測試腳本揮着機器人,無法 100% 嚴格按照測試程式去執行。

下列這行代碼:

Cypress 的學習筆記使用 then 來對前一個指令 yield 的目标進行操作Cypress 的異步執行特性command 執行過程中背後發生的事情Cypress 指令自帶的逾時設定Queries for the element .mobile-nav, 然後停頓 4 秒,直至元素出現在 DOM 裡。

翻譯成自然語言就是:

After clicking on this , I expect its class to eventually be active.

注意其中的eventually.

This above test will pass even if the .active class is applied to the button asynchronously - or after a indeterminate period of time.

Cypress 會不斷重試上述的 assertion,直至 .active class 被添加到 button 上,不管是通過異步添加,還是在一段未知長度的時間段後。

What makes Cypress unique from other testing tools is that commands automatically retry their assertions. In fact, they will look “downstream” at what you’re expressing and modify their behavior to make your assertions pass.

You should think of assertions as guards.

Use your guards to describe what your application should look like, and Cypress will automatically block, wait, and retry until it reaches that state.

Cypress 指令預設的 assertion 機制

With Cypress, you don’t have to assert to have a useful test. Even without assertions, a few lines of Cypress can ensure thousands of lines of code are working properly across the client and server!

This is because many commands have a built in Default Assertion which offer you a high level of guarantee.

很多 cy 指令都有預設的 assertion 機制。

cy.visit() expects the page to send text/html content with a 200 status code. 確定 頁面發出 text/html 内容後,收到200 的狀态碼。

cy.request() expects the remote server to exist and provide a response.

確定遠端系統存在,并且提供響應。

cy.contains() expects the element with content to eventually exist in the DOM.

確定制訂的 content 最終在 DOM 中存在。

cy.get() expects the element to eventually exist in the DOM.

確定請求的 element 最終在 DOM 中存在。

.find() also expects the element to eventually exist in the DOM. - 同 cy.get

.type() expects the element to eventually be in a typeable state.

確定元素處于可輸入狀态。

.click() expects the element to eventually be in an actionable state.

確定元素處于可點選狀态。

.its() expects to eventually find a property on the current subject.

確定目前對象上能夠找到對應的 property

All DOM based commands automatically wait for their elements to exist in the DOM.

所有基于 DOM 的指令,都會自動阻塞,直至其元素存在于 DOM 樹為止。

Cypress 的學習筆記使用 then 來對前一個指令 yield 的目标進行操作Cypress 的異步執行特性command 執行過程中背後發生的事情Cypress 指令自帶的逾時設定Queries for the element .mobile-nav, 然後停頓 4 秒,直至元素出現在 DOM 裡。

在執行 click 指令之前,button 必須成為可點選狀态,否則 click 指令不會得到執行。可點選狀态(actionable),意思是 button 不能是 disabled,covered,或者 hidden 狀态。

Cypress 指令自帶的逾時設定
Cypress 的學習筆記使用 then 來對前一個指令 yield 的目标進行操作Cypress 的異步執行特性command 執行過程中背後發生的事情Cypress 指令自帶的逾時設定Queries for the element .mobile-nav, 然後停頓 4 秒,直至元素出現在 DOM 裡。
Queries for the element .mobile-nav, 然後停頓 4 秒,直至元素出現在 DOM 裡。

再停頓 4 秒,等待元素出現在頁面上。

再等待 4 秒,等待元素包含 home 的 text 屬性。

一段測試程式裡的所有 Cypress 指令,共享同一個逾時值。

繼續閱讀