使用 Cypress 進行端對端測試
Metabase 使用 Cypress 進行「端對端測試」,也就是針對整個應用程式(包括前端、後端和應用程式資料庫)執行的測試。這些測試本質上是以 JavaScript 撰寫的腳本,可在網頁瀏覽器中執行:造訪不同的 URL、點擊各種 UI 元素、輸入文字,並斷言事情是否如預期般發生 (例如,螢幕上是否出現元素,或是否發生網路請求)。
開始使用
Metabase 的 Cypress 測試位於 e2e/test/scenarios
原始碼樹狀結構中,其結構大致反映 Metabase 的 URL 結構。例如,管理「資料模型」頁面的測試位於 e2e/test/scenarios/admin/datamodel
。
我們的自訂 Cypress 執行器會建立自己的後端,並建立暫時的 H2 應用程式資料庫。當此程序終止時,兩者都會被銷毀。保留的預設連接埠是本機主機上的 4000
。沒有任何因素阻止您同時在 localhost:3000
上執行本機 Metabase 實例。這甚至可能有助於偵錯。
標準開發流程
-
持續建置前端
a. 如果您只需要前端,請執行
yarn build-hot
b. 如果您想要在 Cypress 旁邊執行本機 Metabase 實例,最簡單的方法是使用
yarn dev
或yarn dev-ee
(兩者都依賴底層的前端熱重載) -
在另一個終端機工作階段中 (不終止前一個工作階段),執行
yarn test-cypress
。這將開啟 Cypress GUI,讓您可以選擇要執行的測試。或者,查看run_cypress_local.js
和e2e/test/scenarios/docker-compose.yml
以取得所有可能的選項。
執行選項
若要在終端機中以無頭模式執行所有 Cypress 測試
OPEN_UI=false yarn run test-cypress
您可以使用官方的 --spec
旗標快速測試單一檔案。此旗標可用於執行資料夾內的所有規格,或執行多個不同的規格。請參閱 官方文件 以取得說明。
OPEN_UI=false yarn test-cypress --spec e2e/test/scenarios/question/new.cy.spec.js
您可以使用 --browser
旗標指定要在其中執行 Cypress 測試的瀏覽器。如需更多詳細資訊,請參閱 官方文件。
在執行模式下執行 Cypress 時,指定瀏覽器最有意義。另一方面,Cypress 開啟模式 (GUI) 讓使用者可以輕鬆地在系統上的所有可用瀏覽器之間切換。但是,有些人甚至在這種情況下也喜歡指定瀏覽器。如果您這樣做,請記住,您僅僅是為 Cypress 預先選取初始瀏覽器,但您仍然可以選擇其他瀏覽器。
測試的結構
Cypress 測試檔案的結構類似 Mocha 測試,其中 describe
區塊用於將相關測試分組,而 it
區塊本身就是測試。
describe("homepage", () => {
it("should load the homepage and...", () => {
cy.visit("/metabase/url");
// ...
});
});
我們強烈建議使用選取器,例如來自 @testing-library/cypress
的 cy.findByText()
和 cy.findByLabelText()
,因為它們鼓勵撰寫不依賴 CSS 類別名稱等實作詳細資訊的測試。
盡量避免重複測試應用程式的片段。例如,如果您想要測試關於查詢產生器的某些內容,請使用 openOrdersTable()
等輔助程式直接跳到那裡,而不是從首頁開始,點擊「新增」,然後點擊「問題」等等。
Cypress 文件
- 簡介:https://docs.cypress.io/guides/core-concepts/introduction-to-cypress.html
- 命令:https://docs.cypress.io/api/api/table-of-contents.html
- 斷言:https://docs.cypress.io/guides/references/assertions.html
提示/注意事項
contains
vs find
vs get
Cypress 有一組類似的命令,用於選取元素。以下是一些使用它們的提示
contains
(預設) 對 DOM 中的文字區分大小寫。如果它與您預期的文字不符,請檢查 CSS 是否已更新大小寫。您可以使用以下選項明確告知它忽略大小寫:{ matchCase: false }
。contains
比對子字串。給定兩個字串「filter by」和「Add a filter」,cy.contains(“filter”);
將比對兩者。為了避免這些問題,您可以傳遞一個固定字串開始/結束的 regexp,或將字串範圍限定為特定的選取器:cy.contains(selector, content);
。
find
可讓您在先前的選取範圍內搜尋。get
將搜尋整個頁面,即使是鏈接,除非您明確調整withinSubject
選項。
如何存取範例資料庫表格和欄位 ID?
我們在 E2E 測試中使用的範例資料庫可能隨時變更,其表格和欄位的參考也會隨之變更。絕對不要使用對這些 ID 進行硬式編碼的數字參考。我們提供了一個有用的機制來實現這一點,保證產生正確的結果。每次您啟動 Cypress 時,它都會擷取關於範例資料庫的資訊,擷取表格和欄位 ID,並將其寫入 e2e/support/cypress_sample_database
JSON,然後我們重新匯出並提供給所有測試使用。
// Don't
const query = {
"source-table": 1,
aggregation: [["count"]],
breakout: [["field", 7, null]],
};
// Do this instead
import { SAMPLE_DATABASE } from "e2e/support/cypress_sample_database";
const { PRODUCTS, PRODUCTS_ID } = SAMPLE_DATABASE;
const query = {
"source-table": PRODUCTS_ID,
aggregation: [["count"]],
breakout: [["field", PRODUCTS.CATEGORY, null]],
};
增加視窗大小以避免捲動
有時 Metabase 檢視對於 Cypress 的預設 1280x800 視窗來說有點大。這可能需要您捲動才能使測試正常運作。例如,虛擬化表格甚至不會在視窗外呈現內容。為了避免這些問題,請針對特定測試增加視窗大小。除非您特別測試應用程式在視窗調整大小時的行為,否則請避免在測試中間使用 cy.viewport(width, height);
。請改用選用的 Cypress 測試設定來設定視窗寬度/高度。此設定適用於 describe
和 it
區塊。
describe("foo", { viewportWidth: 1400 }, () => {});
it("bar", { viewportWidth: 1600, viewportHeight: 1200 }, () => {});
程式碼重新載入 vs 測試重新載入
當您編輯 Cypress 測試檔案時,測試將會重新整理並再次執行。但是,當您編輯程式碼檔案時,Cypress 不會偵測到該變更。如果您正在執行 yarn build-hot
,則程式碼將會重建並在 Cypress 內更新。您必須在新程式碼載入後手動點擊重新執行。
在「contains helper」開啟時檢查
Cypress 的一個很棒的功能是您可以在測試的每個步驟之後使用 Chrome 檢查器。它們還很有幫助地提供了一個輔助程式,可以測試 contains
和 get
呼叫。此輔助程式會建立新的 UI,以防止檢查從目標正確的元素。如果您想要在 Chrome 中檢查 DOM,則應關閉此輔助程式。
將錯誤的 HTML 範本放入 Uberjar 中
yarn build
和 yarn build-hot
各自覆寫 HTML 範本以參考正確的 JavaScript 檔案。如果您在為 Cypress 測試建置 Uberjar 之前執行 yarn build
,即使您稍後啟動 yarn build-hot
,您也不會看到 JavaScript 的變更反映出來。
在 M1 機器上執行 Cypress
您在 M1 機器上執行 Cypress 時可能會遇到問題。這是由 @bahmutov/cypress-esbuild-preprocessor
引起的,它使用 esbuild
作為依賴項。錯誤可能看起來像這樣。解決方案是使用其中一個 Node 版本管理器 (例如 nvm 或 n) 安裝 NodeJS。
您幾乎肯定會面臨的另一個問題是無法連線到我們的 Mongo QA 資料庫。您可以透過提供以下 env 來解決此問題
export EXPERIMENTAL_DOCKER_DESKTOP_FORCE_QEMU=1
執行依賴 Docker 映像的測試
我們測試的一個子集依賴於可透過 Docker 映像取得的外部服務。在撰寫本文時,這些是三個支援的外部 QA 資料庫、Webmail、Snowplow 和 LDAP 伺服器。預設的 cypress 命令將啟動所有必要的 docker 容器,以使這些測試正常運作,但如果您願意,可以關閉它們
START_CONTAINERS=false yarn test-cypress
執行涉及 Snowplow 的測試
依賴 Snowplow 的測試預期伺服器正在執行。預設情況下會啟用此功能。您也可以手動啟用它們,方法是啟動 snowplow micro docker 容器並設定適當的環境變數
docker-compose -f ./snowplow/docker-compose.yml up -d
export MB_SNOWPLOW_AVAILABLE=true
export MB_SNOWPLOW_URL=https://127.0.0.1:9090
使用 Snowplow 進行測試
我們有一些輔助程式可用於處理涉及 snowplow 的測試
- 您可以使用
describeWithSnowplow
(或 EE 版本的describeWithSnowplowEE
) 方法來定義僅在 Snowplow 實例執行時執行的測試 - 在每次測試之前使用
resetSnowplow()
測試輔助程式來清除已處理事件的佇列。 - 使用
expectGoodSnowPlowEvent({ ...payload})
來斷言 snowplow 事件的內容。使用expectGoodSnowplowEvents(count)
來斷言事件已正確傳送和處理。相較於僅僅計算事件數量,更偏好對實際酬載進行更精確的斷言。 - 在每次測試之後使用
expectNoBadSnowplowEvents()
來斷言未傳送任何無效事件。
執行需要 SMTP 伺服器的測試
我們的一些測試依賴於已設定的電子郵件,並且需要本機 SMTP 伺服器。我們為此目的使用 maildev
Docker 映像。在撰寫本文時,我們使用的映像是 maildev/maildev:2.1.0
。本機開發的預設 cypress 設定將為您處理此問題。如果您想要手動設定,可以使用此命令
docker run -d -p 1080:1080 -p 1025:1025 maildev/maildev:latest
Cypress 免費提供 Lodash
我們不需要在直接依賴項中加入 Lodash 即可搭配 Cypress 使用它。它以底線別名,其方法可以使用 Cypress._.method()
存取。我們可以將 _.times
方法用於在本機壓力測試特定測試 (或一組測試)。
// Run the test N times
Cypress._.times(N, () => {
it("should foo", () => {
// ...
});
});
嵌入 SDK 測試
位於 e2e/test/scenarios/embedding-sdk
中的測試用於執行嵌入 SDK 的自動檢查。
嵌入 SDK 是程式庫,而不是應用程式。我們使用 Storybook 來託管公用元件,並針對其執行測試。
為了在本機執行用於測試的故事,請查看 storybook 設定文件
資料庫快照
在每個測試套件開始時,我們會清除後端的資料庫和設定快取。這可確保測試套件以可預測的狀態啟動。
通常,我們會在第一個 describe
區塊內新增 before(restore)
,以在執行整個測試套件之前還原預設快照。如果您想要使用預設快照以外的快照,請將名稱指定為 restore
的引數,如下所示:before(() => restore("blank"))
。您也可以在 beforeEach()
內呼叫 restore()
以在每次測試之前重設,或在特定測試內呼叫。
快照是使用一組獨立的 Cypress 測試建立的。這些測試以空白資料庫開始,並執行特定動作以將資料庫置於可預測的狀態。例如:以 bob@metabase.com 註冊、新增問題、開啟設定 ABC。
這些快照產生測試具有 .cy.snap.js
副檔名。當這些測試執行時,它們會在 frontend/tests/snapshots/*.sql
中建立資料庫傾印。它們會在測試開始之前執行,並且不會提交到 git。
在 CI 中執行
Cypress 記錄每次測試執行的影片,這有助於偵錯。此外,失敗的測試會儲存更高品質的影像。
這些檔案可以在 GitHub Actions 中每個執行摘要的「Artifacts」區段下找到。在「Onboarding」目錄中失敗測試的 Artifacts 範例:
針對 Metabase® Enterprise Edition™ 執行 Cypress 測試
在針對 Metabase® Enterprise Edition™ 執行 Cypress 之前,請設定 MB_EDITION=ee
環境變數。
Enterprise 實例將在沒有 premium 權杖的情況下啟動!
如果您想要測試 premium 功能 (功能旗標),則所有 Cypress 測試都需要提供有效的權杖。我們透過在環境變數前面加上 CYPRESS_
來實現這一點。您應該提供兩個權杖,分別對應於 EE/PRO
自我託管 (所有功能已啟用) 和 STARTER
Cloud (未啟用任何功能) Metabase 方案。如需更多資訊,請參閱 Metabase 定價頁面。(注意:只有少數測試需要無功能權杖)
CYPRESS_ALL_FEATURES_TOKEN
CYPRESS_NO_FEATURES_TOKEN
MB_EDITION=ee ENTERPRISE_TOKEN=xxxxxx yarn test-cypress
如果您導覽至 /admin/settings/license
頁面,授權輸入欄位應顯示活動權杖。分享螢幕擷取畫面時請小心!
- 如果測試開始執行但缺少 enterprise 功能:請確定您使用的權杖已啟用對應的功能旗標。
- 如果 token 看起來沒有問題,那就採取最終手段,摧毀所有 Java 程序:執行
killall java
並重新啟動 Cypress。
標籤
Cypress 允許我們標記測試,以便輕鬆找到特定類別的標籤。例如,我們可以將所有需要外部資料庫的測試標記為 @external
,然後只使用 yarn test-cypress --env grepTags="@external"
執行這些測試。標籤應該以 @
開頭,這樣可以更容易地將它們與搜尋中的其他字串區分開來。
以下是目前使用的標籤
@external
- 需要外部 Docker 容器才能運行的測試@actions
- 使用 Metabase actions 並修改資料來源中資料的測試
如何壓力測試 flake 修復?
在本地修復不穩定的測試並不代表修復在 GitHub 的 CI 環境中有效。確保修復有效的唯一方法是在 CI 中進行壓力測試。這就是 .github/workflows/e2e-stress-test-flake-fix.yml
的用途。它允許您在分支中快速測試修復,而無需等待完整的建置完成。
請按照以下步驟操作
準備
- 建立一個包含您建議修復的新分支,並將其推送到遠端
- 完全跳過開啟 PR,或開啟草稿 pull request
手動觸發壓力測試工作流程
- 前往
https://github.com/metabase/metabase/actions/workflows/e2e-stress-test-flake-fix.yml
- 點擊「此工作流程具有 workflow_dispatch 事件觸發器」旁邊的Run workflow觸發器。
- 在第一個欄位「Use workflow from」中選擇您自己的分支(這部分至關重要!)
- 複製並貼上您要測試的 spec 的相對路徑(例如
e2e/test/scenarios/onboarding/urls.cy.spec.js
)- 您不必將其用引號括起來 - 設定測試執行的次數
- 可選地提供 grep 過濾器,根據 文件
- 點擊綠色的「Run workflow」按鈕並等待結果
使用此工作流程時要記住的事項
- 它將自動嘗試尋找並下載先前建置的 Metabase uberjar,該 uberjar 作為過去提交 / CI 運行的產物儲存。
- 它旨在用於不需要新的 Metabase uberjar 的純 E2E 修復。
- 如果修復需要原始碼變更(後端或前端),請改為開啟常規 PR,並讓 CI 首先運行所有測試。在此之後,您可以手動觸發壓力測試工作流程,如上所述,它將自動從此 CI 運行下載新建立的產物。請記住,CI 需要先完全完成運行。工作流程使用 GitHub REST API,否則看不到產物。
報告
每個 spec 都會自動產生個別的 Mocha 報告。它們儲存在 cypress/reports/mochareports
中。請記住,根目錄層級的 cypress/
資料夾已被 git 忽略!
當測試在 CI 中運行時,我們會執行一些額外步驟,合併這些個別報告(使用 mochawesome-merge
),格式化它們,然後產生自訂的 GitHub Actions 工作摘要。
如果您在本地運行測試時需要統一的測試報告,您可以透過調用 yarn generate-cypress-html-report
來實現。
閱讀其他Metabase 版本的文件。