使用 Cypress 進行端對端測試

Metabase 使用 Cypress 進行「端對端測試」,也就是針對整個應用程式(包括前端、後端和應用程式資料庫)執行的測試。這些測試本質上是以 JavaScript 撰寫的腳本,可在網頁瀏覽器中執行:造訪不同的 URL、點擊各種 UI 元素、輸入文字,並斷言事情是否如預期般發生 (例如,螢幕上是否出現元素,或是否發生網路請求)。

開始使用

Metabase 的 Cypress 測試位於 e2e/test/scenarios 原始碼樹狀結構中,其結構大致反映 Metabase 的 URL 結構。例如,管理「資料模型」頁面的測試位於 e2e/test/scenarios/admin/datamodel

我們的自訂 Cypress 執行器會建立自己的後端,並建立暫時的 H2 應用程式資料庫。當此程序終止時,兩者都會被銷毀。保留的預設連接埠是本機主機上的 4000。沒有任何因素阻止您同時在 localhost:3000 上執行本機 Metabase 實例。這甚至可能有助於偵錯。

標準開發流程

  1. 持續建置前端

    a. 如果您只需要前端,請執行 yarn build-hot

    b. 如果您想要在 Cypress 旁邊執行本機 Metabase 實例,最簡單的方法是使用 yarn devyarn dev-ee (兩者都依賴底層的前端熱重載)

  2. 在另一個終端機工作階段中 (不終止前一個工作階段),執行 yarn test-cypress。這將開啟 Cypress GUI,讓您可以選擇要執行的測試。或者,查看 run_cypress_local.jse2e/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/cypresscy.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 測試設定來設定視窗寬度/高度。此設定適用於 describeit 區塊。

describe("foo", { viewportWidth: 1400 }, () => {});

it("bar", { viewportWidth: 1600, viewportHeight: 1200 }, () => {});

程式碼重新載入 vs 測試重新載入

當您編輯 Cypress 測試檔案時,測試將會重新整理並再次執行。但是,當您編輯程式碼檔案時,Cypress 不會偵測到該變更。如果您正在執行 yarn build-hot,則程式碼將會重建並在 Cypress 內更新。您必須在新程式碼載入後手動點擊重新執行。

在「contains helper」開啟時檢查

Cypress 的一個很棒的功能是您可以在測試的每個步驟之後使用 Chrome 檢查器。它們還很有幫助地提供了一個輔助程式,可以測試 containsget 呼叫。此輔助程式會建立新的 UI,以防止檢查從目標正確的元素。如果您想要在 Chrome 中檢查 DOM,則應關閉此輔助程式。

將錯誤的 HTML 範本放入 Uberjar 中

yarn buildyarn build-hot 各自覆寫 HTML 範本以參考正確的 JavaScript 檔案。如果您在為 Cypress 測試建置 Uberjar 之前執行 yarn build,即使您稍後啟動 yarn build-hot,您也不會看到 JavaScript 的變更反映出來。

在 M1 機器上執行 Cypress

您在 M1 機器上執行 Cypress 時可能會遇到問題。這是由 @bahmutov/cypress-esbuild-preprocessor 引起的,它使用 esbuild 作為依賴項。錯誤可能看起來像這樣解決方案是使用其中一個 Node 版本管理器 (例如 nvmn) 安裝 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 的測試

  1. 您可以使用 describeWithSnowplow (或 EE 版本的 describeWithSnowplowEE) 方法來定義僅在 Snowplow 實例執行時執行的測試
  2. 在每次測試之前使用 resetSnowplow() 測試輔助程式來清除已處理事件的佇列。
  3. 使用 expectGoodSnowPlowEvent({ ...payload}) 來斷言 snowplow 事件的內容。使用 expectGoodSnowplowEvents(count) 來斷言事件已正確傳送和處理。相較於僅僅計算事件數量,更偏好對實際酬載進行更精確的斷言。
  4. 在每次測試之後使用 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 範例:GitHub Actions artifacts section

針對 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觸發器。
  1. 在第一個欄位「Use workflow from」中選擇您自己的分支(這部分至關重要!)
  2. 複製並貼上您要測試的 spec 的相對路徑(例如 e2e/test/scenarios/onboarding/urls.cy.spec.js)- 您不必將其用引號括起來
  3. 設定測試執行的次數
  4. 可選地提供 grep 過濾器,根據 文件
  5. 點擊綠色的「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 版本的文件。