E2E 測試之旅第 2 部分:從 WebdriverIO 到 Cypress
已發表: 2019-11-21注意:這是來自#frontend@twiliosendgrid 的帖子。 對於其他工程帖子,請轉到技術博客卷。
在我們所有的前端應用程序中,我們擁有並且仍然擁有以下目標:提供一種方法來為我們的前端應用程序編寫一致、可調試、可維護和有價值的 E2E(端到端)自動化測試,並與 CICD(持續集成和持續部署)。
為了達到我們今天在 SendGrid 的所有前端團隊中可能觸發或按計劃運行數百個 E2E 測試的狀態,我們必須在此過程中研究和試驗許多潛在的解決方案,直到我們完成主要任務目標。
我們嘗試推出由專門的測試工程師開發的自定義 Ruby Selenium 解決方案,稱為 SiteTestUI aka STUI,在該解決方案中,所有團隊都可以貢獻一個 repo 並運行跨瀏覽器自動化測試。 可悲的是,它屈服於緩慢、不穩定的測試、誤報、repos 和語言之間缺乏上下文、一個籃子裡有太多的手、痛苦的調試經歷,以及花費在維護上的時間多於提供價值的時間。
然後,我們在 WebdriverIO 中嘗試了另一個有前途的庫,用 JavaScript 編寫測試,這些測試與每個團隊的應用程序存儲庫位於同一位置。 雖然這解決了一些問題,賦予了更多的團隊所有權,並允許我們將測試與我們的 CICD 提供商 Buildkite 集成,但除了處理所有太相似的 Selenium 錯誤和怪癖之外,我們仍然有一些難以調試和難以編寫的不穩定測試.
我們想避免另一個 STUI 2.0 並開始探索其他選項。 如果您想了解更多關於我們從 STUI 遷移到 WebdriverIO 過程中的經驗教訓和發現的策略,請查看博客文章系列的第 1 部分。
在博文系列的最後第二部分,我們將介紹從 STUI 和 WebdriverIO 到 Cypress 的旅程,以及我們如何在設置整體基礎架構、編寫有組織的 E2E 測試、與我們的 Buildkite 管道集成以及擴展到組織中的其他前端團隊。
TLDR:我們在 STUI 和 WebdriverIO 上採用了 Cypress,並且我們完成了編寫有價值的 E2E 測試以與 CICD 集成的所有目標。 我們從 WebdriverIO 和 STUI 中學到的大量工作和經驗很好地延續到了我們今天如何使用和集成賽普拉斯測試。
目錄
探索並登陸 Cypress
從 STUI 和 WebdriverIO 切換到 Cypress
第 1 步:為 Cypress 安裝依賴項
第 2 步:環境配置和腳本
環境配置和腳本的第一遍
環境配置和腳本的演變
第 3 步:在本地實施 E2E 測試
第 4 步:對測試進行 Docker 化
第 5 步:與 CICD 集成
第 6 步:比較 Cypress 與 WebdriverIO/STUI
第 7 步:擴展到其他前端團隊
我們對賽普拉斯的期待
採用賽普拉斯走向未來
探索並登陸 Cypress
當我們搜索 WebdriverIO 的替代品時,我們看到了其他 Selenium 包裝器,例如 Protractor 和 Nightwatch,它們具有與 WebdriverIO 類似的功能集,但我們認為我們很可能會遇到冗長的設置、不穩定的測試以及繁瑣的調試和維護。
幸運的是,我們偶然發現了一個名為 Cypress 的新 E2E 測試框架,它展示了快速設置、在瀏覽器中運行的快速且可調試的測試、網絡層請求存根,最重要的是,它沒有在後台使用 Selenium。
我們驚嘆於視頻錄製、賽普拉斯 GUI、付費儀表板服務和並行化等令人驚嘆的功能。 我們願意在跨瀏覽器支持上做出妥協,以支持有價值的測試持續通過 Chrome,我們可以使用一系列工具來為我們的開發人員和 QA 實施、調試和維護測試。
我們還讚賞為我們選擇的測試框架、斷言庫和所有其他工具,以便在我們所有的前端團隊中擁有更標準化的測試方法。 下面,我們提供了 WebdriverIO 和 Cypress 等解決方案之間差異的屏幕截圖,如果您想了解 Cypress 和 Selenium 解決方案之間的更多差異,您可以查看他們的文檔以了解其工作原理。
考慮到這一點,我們承諾測試 Cypress 作為另一種解決方案,用於編寫快速、一致和可調試的 E2E 測試,最終在 CICD 期間與 Buildkite 集成。 我們還特意設定了另一個目標,將 Cypress 與之前基於 Selenium 的解決方案進行比較,最終通過數據點確定我們是否應該繼續尋找或使用 Cypress 構建我們的測試套件。 我們計劃將現有的 WebdriverIO 測試和仍在 STUI 中的任何其他高優先級測試轉換為賽普拉斯測試,並比較我們的開發人員體驗、速度、穩定性、測試運行時間和測試維護。
從 STUI 和 WebdriverIO 切換到 Cypress
當從 STUI 和 WebdriverIO 切換到 Cypress 時,我們通過我們在前端應用程序存儲庫中嘗試從 STUI 遷移到 WebdriverIO 時使用的相同高級策略系統地解決了這個問題。 有關我們如何為 WebdriverIO 完成這些步驟的更多詳細信息,請參閱博客文章系列的第 1 部分。 過渡到賽普拉斯的一般步驟包括以下內容:
- 安裝和設置依賴項以連接 Cypress
- 建立環境配置和腳本命令
- 實施針對不同環境在本地通過的端到端測試
- Docker 化測試
- 將 Dockerized 測試與我們的 CICD 提供商 Buildkite 集成
為了實現我們的次要目標,我們還添加了額外的步驟來比較 Cypress 與之前的 Selenium 解決方案,並最終在組織中的所有前端團隊中擴展 Cypress:
6. 在開發人員體驗、速度和測試穩定性方面比較 Cypress 與 WebdriverIO 和 STUI
7. 擴展到其他前端團隊
第 1 步:為 Cypress 安裝依賴項
為了快速啟動和運行賽普拉斯,我們所要做的就是在我們的項目中使用“npm install cypress”並首次啟動賽普拉斯,以便使用“cypress.json”配置文件和cypress
文件夾自動佈局帶有用於命令和插件的啟動裝置、測試和其他設置文件。 我們很欣賞 Cypress 如何與 Mocha 捆綁在一起作為測試運行程序,Chai 用於斷言,Chai-jQuery 和 Sinon-Chai 用於更多的斷言以供使用和鏈接。 與剛開始使用 WebdriverIO 或 STUI 時相比,我們不再需要花費大量時間來研究要安裝和使用哪些測試運行程序、報告程序、斷言和服務庫。 我們立即使用他們的 Cypress GUI 運行了一些生成的測試,並探索了我們可以使用的許多調試功能,例如時間旅行調試、選擇器遊樂場、錄製的視頻、屏幕截圖、命令日誌、瀏覽器開發工具等。
我們稍後還使用 Eslint 和 TypeScript 對其進行了設置,以便在提交新的賽普拉斯測試代碼時遵循額外的靜態類型檢查和格式化規則。 我們最初在 TypeScript 支持方面遇到了一些問題,有些文件需要是 JavaScript 文件,比如那些以插件文件為中心的文件,但在大多數情況下,我們能夠對我們的大部分文件進行類型檢查,以獲取我們的測試、頁面對象和命令。
這是我們的一個前端團隊遵循的示例文件夾結構,用於合併頁面對象、插件和命令:
第 2 步:環境配置和腳本
在快速安裝和設置賽普拉斯以在本地運行後,我們需要一種方法讓賽普拉斯測試在每個環境的不同設置下運行,並希望支持我們的 WebdriverIO 命令允許我們執行的相同用例。 這是一個列表,說明了我們希望如何執行這些測試以及為什麼我們希望支持它們的大多數用例:
- 針對在 localhost(即 http://localhost:8000)上運行的 Webpack 開發服務器,該開發服務器將指向某個環境 API(即 https://testing.api.com 或 https://staging.api。 com) 喜歡測試或分期。
為什麼? 有時我們需要對本地 Web 應用程序進行更改,例如為我們的測試添加更多特定的選擇器,以便以更健壯的方式與元素交互,或者我們正在開發新功能並需要調整和驗證現有的自動化測試將針對我們的新代碼更改在本地傳遞。 每當應用程序代碼更改並且我們還沒有推送到已部署的環境時,我們使用此命令針對我們的本地 Web 應用程序運行我們的測試。 - 針對特定環境(即 https://testing.app.com 或 https://staging.app.com)的已部署應用程序,例如測試或登台
為什麼? 其他時候應用程序代碼不會改變,但我們可能不得不改變我們的測試代碼來修復一些脆弱性,或者我們有足夠的信心在不進行任何前端更改的情況下完全添加或刪除測試。 我們大量使用此命令來針對已部署的應用程序在本地更新或調試測試,以更接近地模擬我們的測試在 CICD 中的運行方式。 - 在 Docker 容器中針對特定環境(如測試或登台)部署的應用程序運行
為什麼? 這適用於 CICD,因此我們可以觸發 E2E 測試在 Docker 容器中運行,例如針對暫存部署的應用程序,並確保它們在將代碼部署到生產之前或在專用管道中的預定測試運行中通過。 最初設置這些命令時,我們進行了大量試驗和錯誤,以使用不同的環境變量值啟動 Docker 容器,並在將其與我們的 CICD 提供程序 Buildkite 掛鉤之前,測試是否成功執行了正確的測試。
環境配置和腳本的第一遍
當我們第一次嘗試設置 Cypress 時,我們在包含 https://app.sendgrid.com 的存儲庫中進行了設置,這是一個包含發件人身份驗證、電子郵件活動和電子郵件驗證等功能頁面的 Web 應用程序,我們不可避免地分享了我們與營銷活動網絡應用程序背後的團隊的發現和學習,其中包含 https://mc.sendgrid.com 域。 我們希望針對我們的暫存環境運行 E2E 測試,並利用賽普拉斯的命令行界面和--config
或--env
等選項來完成我們的用例。
為了在http://127.0.0.1:8000
或部署的暫存應用程序 URL 上對本地 Web 應用程序運行賽普拉斯測試,我們調整了命令中的baseUrl
配置標誌並添加了額外的環境變量,例如testEnv
以提供幫助在我們的賽普拉斯測試中加載某些固定裝置或特定環境的測試數據。 例如,使用的 API 密鑰、創建的用戶和其他資源可能因環境而異。 如果環境中不支持某些功能或測試設置不同,我們會使用testEnv
來切換這些固定裝置或添加特殊的條件邏輯,並且我們將通過我們規範中的Cypress.env(“testEnv”)
類的調用來訪問環境。
然後,我們組織了命令cypress:open:*
來表示打開 Cypress GUI,以便我們在本地開發時選擇要通過 UI 運行的測試,並組織cypress:run:*
來表示在無頭模式下執行測試,這更加定制用於在 CICD 期間在 Docker 容器中運行。 在open
或run
之後出現的是環境,因此我們的命令可以像npm run cypress:open:localhost:staging
一樣輕鬆讀取,以打開 GUI 並針對指向 staging API 或npm run cypress:run:staging
的本地 Webpack 開發服務器運行測試npm run cypress:run:staging
以無頭模式針對已部署的登台應用程序和 API 運行測試。 package.json
賽普拉斯腳本是這樣出來的:
環境配置和腳本的演變
在另一個項目中,我們改進了賽普拉斯命令和配置,以利用cypress/plugins/index.js
文件中的一些節點邏輯來擁有一個基本的cypress.json
文件和單獨的配置文件,這些文件將基於名為的環境變量讀取configFile
加載特定的配置文件。 然後將加載的配置文件與基本文件合併,最終指向登台或模擬後端服務器。
如果您想了解更多關於模擬後端服務器的信息,我們開發了一個 Express 服務器,其後端端點僅根據請求中傳遞的查詢參數返回靜態 JSON 數據和狀態代碼(即 200、4XX、5XX)的不同響應。 這使前端暢通無阻,可以繼續通過對模擬後端服務器的實際網絡調用來開發頁面流,並通過響應模擬實際 API 在未來可用時的樣子。 我們還可以針對我們不同的 UI 狀態輕鬆模擬不同級別的成功和錯誤響應,否則這些狀態將難以在生產中重現,並且由於我們將進行確定性網絡調用,因此我們的 Cypress 測試在啟動相同網絡時不會那麼不穩定每次請求和響應。
我們有一個基本的cypress.json
文件,其中包括用於一般超時的共享屬性、用於連接 Cypress Dashboard Service 的項目 ID(我們將在稍後討論)以及其他設置,如下所示:
我們在cypress
文件夾中創建了一個config
文件夾來保存我們的每個配置文件,例如localhostMock.json
以針對本地模擬 API 服務器運行我們的本地 Webpack 開發服務器或staging.json
以針對已部署的登台應用程序和 API 運行。 這些要與基本配置進行比較和合併的配置文件如下所示:
CICD 配置文件有一個更簡單的 JSON 文件,因為我們需要動態設置環境變量以考慮我們稍後將深入研究的不同 Docker 服務前端基本 URL 和模擬服務器 API 主機。
在我們的cypress/plugins/index.js
文件中,我們添加了從 Cypress 命令讀取名為configFile
的環境變量的邏輯,該命令最終將讀取config
文件夾中的相應文件並將其與基本cypress.json
合併,如下所示:
為了使用為我們的用例設置的環境變量編寫合理的賽普拉斯命令,我們利用了類似於以下內容的Makefile
:
將這些命令整齊地排列在 Makefile 中,我們可以在我們的 `package.json` npm 腳本中快速執行諸如make cypress_open_staging
或make cypress_run_staging
類的操作。
以前,我們習慣將一些命令放在一行很長的行中,這樣很難在沒有錯誤的情況下進行編輯。 值得慶幸的是,Makefile 通過將環境變量的可讀內插到多行的賽普拉斯命令中,幫助更好地分散了內容。 我們可以快速設置或導出環境變量,例如configFile
用於加載哪個環境配置文件, BASE_URL
用於訪問我們的頁面, API_HOST
用於不同的後端環境,或者SPECS
以確定在我們啟動任何 Makefile 命令之前運行哪些測試。
我們還將 Makefile 命令用於其他冗長的 npm 腳本和 Docker 命令,例如構建我們的 Webpack 資產、安裝依賴項或與其他命令同時運行。 最後,我們會將一些 Makefile 命令翻譯到package.json
腳本部分,但如果有人只想使用 Makefile,這不是必需的,它看起來如下所示:
我們故意省略了很多 Cypress CICD 命令,因為它們不是日常開發中使用的命令,因此使package.json
更加精簡。 最重要的是,我們可以一目了然地看到所有與模擬服務器和本地 Webpack 開發服務器相關的賽普拉斯命令與暫存環境,以及哪些是“打開”GUI 而不是在無頭模式下“運行”。
第 3 步:在本地實施 E2E 測試
當我們開始使用 Cypress 實施 E2E 測試時,我們引用了來自 WebdriverIO 和 STUI 的現有測試來轉換並為其他高優先級功能添加了更新的測試,從簡單的健康檢查到復雜的快樂路徑流。 將現有的頁面對象和測試文件從 WebdriverIO 或 STUI 轉換為 Cypress 中的等效頁面對象和規範被證明是輕而易舉的事。 它實際上產生了比以前更簡潔的代碼,更少的明確等待元素以及更好的斷言和其他賽普拉斯命令的鏈接性。
例如,從最終用戶的角度來看,測試的一般步驟保持不變,因此轉換工作涉及通過以下方式將 WebdriverIO 或 STUI API 映射到 Cypress API:
- 許多命令本質上出現並且工作起來類似於我們幾乎只是用
cy
或Cypress
替換$
或browser
即通過$(“.button”).click()
到cy.get(“.button”).click()
訪問頁面.clickcy.get(“.button”).click()
,browser.url()
到cy.visit()
,或$(“.input”).setValue()
到cy.get(“.input”).type()
- 使用
$
或$$
通常會變成cy.get(...)
或cy.contains(...)
即$$(“.multiple-elements-selector”)
或$(“.single-element-selector”)
變成cy.get(“.any-element-selector”)
、cy.contains(“text”)
或cy.contains(“.any-selector”)
- 刪除無關
$(“.selector”).waitForVisible(timeoutInMs)
、$(“.selector”).waitUntil(...)
或$(“.selector”).waitForExist()
調用,以支持默認情況下讓 Cypress使用cy.get('.selector')
和cy.contains(textInElement)
處理重試和檢索元素。 如果我們需要比默認更長的超時時間,我們將完全使用cy.get('.selector', { timeout: longTimeoutInMs })
,然後在檢索元素後,我們將鏈接下一個操作命令以對元素執行某些操作,即cy.get(“.selector”).click()
。 - 使用瀏覽器的自定義命令。
addCommand('customCommand, () => {})` turned into `Cypress.Commands.add('customCommand', () => {})
並執行 `cy.customCommand()` - 使用名為
node-fetch
的庫通過 API 發出網絡請求以進行設置或拆卸,並將其包裝在browser.call(() => return fetch(...))
和/或browser.waitUntil(...)
中導致通過cy.request(endpoint)
或我們定義的自定義插件在賽普拉斯節點服務器中發出 HTTP 請求,並發出類似cy.task(taskToHitAPIOrService)
的調用。 - 在我們不得不等待一個重要的網絡請求可能在沒有任何顯著 UI 更改的情況下完成之前,我們有時不得不求助於使用
browser.pause(timeoutInMs)
,但是使用 Cypress,我們通過網絡存根功能改進了它並且能夠監聽並使用cy.server()
、cy.route(“method”, “/endpoint/we/are/waiting/for).as(“endpoint”)`, and `cy.wait(“@endpoint”)
在啟動將觸發請求的操作之前。
在將大量 WebdriverIO 語法和命令轉換為賽普拉斯命令後,我們引入了相同的概念,即為通用共享功能提供基本頁面對象,並為測試所需的每個頁面提供擴展頁面對象。 這是一個基本頁面對象的示例,該對象具有可在所有頁面之間共享的通用open()
功能。
擴展的頁面對象將為元素選擇器添加 getter,使用其頁面路由實現open()
功能,並提供任何幫助器功能,如下所示。
我們實際的擴展頁面對像還使用了一個簡單的對象映射來將我們所有的 CSS 元素選擇器保存在一個地方,我們可以將其作為數據屬性插入到 React 組件中,在單元測試中引用,並在 Cypress 頁面對像中用作我們的選擇器。 此外,我們的頁面對像類有時會在利用類構造函數方面有所不同,如果說一個頁面對像被重用於一堆外觀和功能相似的頁面,比如我們的 Suppressions 頁面,我們會傳遞參數來更改路由或特定屬性。
附帶說明一下,團隊不需要使用頁面對象,但我們讚賞將頁面功能和 DOM 元素選擇器引用與標準類對象結構保持在一起的模式的一致性,以便在所有頁面之間共享通用功能。 其他團隊更喜歡使用很少的實用功能和不使用 ES6 類來創建許多不同的文件,但重要的是要提供一種有組織的、可預測的方式來封裝所有內容並編寫測試以提高開發人員的效率和可維護性。
我們堅持使用與舊 WebdriverIO 測試相同的通用測試策略,並嘗試通過 API 盡可能多地設置測試。 我們特別希望避免通過 UI 建立我們的設置狀態,以免為我們不打算測試的部分引入片狀和浪費時間。 大多數測試都涉及此策略:
- 通過 API 設置或刪除 - 如果我們需要通過 UI 測試創建實體,我們將確保首先通過 API 刪除實體。 無論之前的測試運行如何以成功或失敗告終,都需要通過 API 正確設置或拆除測試,以確保測試以一致的方式運行並以正確的條件開始。
- 通過 API 登錄到專門的測試用戶——我們為每個頁面甚至每個自動化測試創建了專門的測試用戶,這樣我們的測試將被隔離,並且在並行運行時不會佔用彼此的資源。 我們通過 API 發出與登錄頁面相同的請求,並在測試開始之前存儲 cookie,以便我們可以直接訪問經過身份驗證的頁面並開始實際的測試步驟。
- 從最終用戶的角度自動執行步驟——通過 API 登錄用戶後,我們直接訪問頁面並自動執行最終用戶完成功能流程並驗證用戶看到並與正確的事物交互的步驟一路上。
為了將測試重置為其預期的原始狀態,我們將使用全局cy.login
命令通過 API 登錄到專用測試用戶,設置 cookie 以保持用戶登錄,進行必要的 API 調用以返回用戶通過cy.request(“endpoint”)
或cy.task(“pluginAction”)
調用到達所需的起始狀態,並訪問我們試圖直接測試的經過身份驗證的頁面。 然後,我們將自動執行這些步驟來完成用戶功能流程,如下面的測試佈局所示。
還記得我們談到的登錄自定義命令cy.login()
和註銷cy.logout()
嗎? 我們以這種方式在 Cypress 中輕鬆實現了它們,因此我們所有的測試都將以相同的方式通過 API 登錄到用戶。
此外,我們希望自動化和驗證某些涉及電子郵件的複雜流程,而這些流程在以前使用 WebdriverIO 或 STUI 無法很好地完成。 一些示例包括將電子郵件活動導出到 CSV、通過“發送給同事”流程進行發件人身份驗證,或將電子郵件驗證結果導出到 CSV。 賽普拉斯阻止一個人在一次測試中訪問多個超級域,因此通過我們不擁有的 UI 導航到電子郵件客戶端是不穩定的,不是一種選擇。
相反,我們通過他們的cy.task(“pluginAction”)
命令開發了賽普拉斯插件,以使用賽普拉斯節點服務器中的一些庫連接到測試電子郵件 IMAP 客戶端/收件箱,例如 SquirrelMail,以便在提示操作後檢查收件箱中的匹配電子郵件在 UI 中,並將這些電子郵件中的重定向鏈接返回到我們的 Web 應用程序域,以驗證某些下載頁面是否出現並有效地完成整個客戶流程。 我們實現了插件,這些插件會在給定特定主題行的情況下等待電子郵件到達 SquirrelMail 收件箱,刪除電子郵件,發送電子郵件,觸發電子郵件事件,輪詢後端服務,並通過 API 進行更有用的設置和拆卸以供我們的測試使用。
為了更深入地了解我們使用賽普拉斯實際測試的內容,我們介紹了許多高價值案例,例如:
- 對我們所有頁面的健康檢查,也就是瀏覽應用程序——我們希望確保加載了某些內容的頁面有時會導致某些後端服務或前端託管關閉。 我們還建議首先進行這些測試,以建立使用選擇器和輔助函數構建頁面對象的心理肌肉記憶,並針對環境進行快速、有效的測試。
- 頁面上的 CRUD 操作——我們將始終通過 API 相應地重置測試,然後專門測試 UI 中的創建、讀取、更新或刪除。 例如,如果我們測試能夠通過 UI 創建域身份驗證,無論最後一次測試運行如何結束,我們都需要確保我們要通過 UI 創建的域首先通過 API 刪除,然後再繼續使用自動化 UI 步驟來創建域並避免衝突。 如果我們測試了能夠通過 UI 刪除抑制,我們確保首先通過 API 創建抑制,然後繼續執行這些步驟。
- 在頁面上測試搜索過濾器——我們測試了使用電子郵件活動設置一堆高級搜索過濾器,並使用查詢參數訪問頁面,以確保過濾器是自動填充的。 我們還通過電子郵件驗證 API 添加了數據,並再次啟動了不同的搜索過濾器並驗證了該表與該頁面中的搜索過濾器匹配。
- 不同的用戶訪問權限——在 Twilio SendGrid,我們的父帳戶可以擁有具有不同範圍或訪問權限的隊友,或者其下的子用戶也具有不同程度的訪問權限,並且行為與父帳戶有些相似。 對某些頁面和子用戶具有隻讀權限和管理員權限的隊友會看到或看不到頁面上的某些內容,這使得自動登錄這些類型的用戶並檢查他們在賽普拉斯測試中看到或看不到的內容變得容易。
- 不同的用戶包——我們的用戶也可以在免費到付費包的類型上有所不同,例如 Essentials、Pro 和 Premier,這些包也可以在頁面上看到或看不到某些內容。 我們將使用不同的軟件包登錄用戶,并快速驗證用戶在賽普拉斯測試中可以訪問的功能、副本或頁面。
第 4 步:對測試進行 Docker 化
在雲中的新 AWS 機器上執行每個 Buildkite 管道步驟時,我們不能簡單地調用npm run cypress:run:staging
,因為這些機器沒有 Node、瀏覽器、我們的應用程序代碼或任何其他依賴項來實際運行 Cypress測試。 當我們之前設置 WebdriverIO 時,我們需要在 Docker Compose 文件中組裝三個獨立的服務,以使適當的 Selenium、Chrome 和應用程序代碼服務一起運行以運行測試。
使用賽普拉斯,這要簡單得多,因為我們只需要賽普拉斯基礎 Docker 映像cypress/base
來在 Dockerfile 中設置環境,並且在Dockerfile
docker-compose.yml
文件中只需要一個服務以及我們的應用程序代碼來運行賽普拉斯測試。 我們將介紹一種方法,因為還有其他賽普拉斯 Docker 映像可供使用,以及在 Docker 中設置賽普拉斯測試的其他方法。 我們鼓勵您查看賽普拉斯文檔以獲取替代方案
為了提供運行賽普拉斯測試所需的所有應用程序和測試代碼的服務,我們製作了一個名為Dockerfile
的Dockerfile.cypress
並安裝了所有node_modules
並將代碼複製到 Node 環境中映像的工作目錄中。 這將被我們的cypress
Docker Compose 服務使用,我們通過以下方式實現了Dockerfile
設置:
有了這個Dockerfile.cypress
,我們可以集成 Cypress 以針對特定環境 API 運行選定的規範,並通過一個名為cypress
的 Docker Compose 服務部署應用程序。 我們所要做的就是插入一些環境變量,例如SPECS
和BASE_URL
,以通過npm run cypress:run:cicd:staging
命令對某個基本 URL 運行選定的賽普拉斯測試,該命令如下所示, ”cypress:run:cicd:staging”: “cypress run --record --key --config baseUrl=$BASE_URL --env testEnv=staging”
。
這些環境變量將通過 Buildkite 管道的設置/配置文件進行設置,或者在觸發賽普拉斯測試以從我們的部署管道運行時動態導出。 一個示例docker-compose.cypress.yml
文件看起來類似於:
還有一些其他的事情需要觀察。 例如,您可以看到VERSION
環境變量,它允許我們引用特定標記的 Docker 映像。 稍後我們將演示如何標記 Docker 映像,然後為該構建拉取相同的 Docker 映像,以針對賽普拉斯測試的正確代碼運行。
此外,您還會注意到傳遞的BUILDKITE_BUILD_ID
,它與我們啟動的每個構建的其他 Buildkite 環境變量以及ci-build-id
標誌一起免費提供。 這啟用了賽普拉斯的並行化功能,當我們為賽普拉斯測試分配一定數量的機器時,它會自動神奇地知道如何啟動這些機器並將我們的測試分開以在所有這些機器節點上運行,以優化和加速我們的測試運行時間。
我們最終還利用了卷安裝和 Buildkite 的工件功能。 我們上傳視頻和屏幕截圖,以便通過 Buildkite UI 的“Artifacts”選項卡直接訪問,以防我們用完本月分配的付費測試記錄或無法訪問 Dashboard 服務。 每當在無頭模式下運行賽普拉斯“運行”命令時, cypress/videos
和cypress/screenshots
文件夾中都會有輸出供人們在本地查看,我們只需掛載這些文件夾並將它們上傳到 Buildkite 以防萬一。
第 5 步:與 CICD 集成
一旦我們讓賽普拉斯測試在 Docker 容器中針對不同環境成功運行,我們就開始與我們的 CICD 提供商 Buildkite 集成。 Buildkite 提供了在我們的 AWS 機器上執行.yml
文件中的步驟的方法,其中 Bash 腳本和環境變量在代碼中或通過 Web UI 中的 repo 的 Buildkite 管道設置設置。 Buildkite 還允許我們使用導出的環境變量從我們的主部署管道觸發這個測試管道,我們可以將這些測試步驟重用於其他隔離的測試管道,這些測試管道將按計劃運行,供我們的 QA 監控和查看。
在高層次上,我們為 Cypress 測試的 Buildkite 管道以及我們之前的 WebdriverIO 管道共享以下類似步驟:
- 設置 Docker 映像。 構建、標記測試所需的 Docker 映像並將其推送到註冊表,以便我們可以在後面的步驟中將其拉下。
- 根據環境變量配置運行測試。 為特定構建拉下標記的 Docker 映像,並針對已部署的環境執行正確的命令,以從設置的環境變量中運行選定的測試套件。
這是一個pipeline.cypress.yml
文件的示例,該文件演示了在“構建賽普拉斯 Docker 映像”步驟中設置 Docker 映像並在“運行賽普拉斯測試”步驟中運行測試:
需要注意的一點是第一步,“構建 Cypress Docker 映像”,以及它如何為測試設置 Docker 映像。 它使用 Docker Compose build
命令使用所有應用程序測試代碼構建cypress
服務,並使用latest
和${VERSION}
環境變量對其進行標記,因此我們最終可以在未來的一步。 每個步驟都可能在 AWS 雲中某處的不同機器上執行,因此標籤唯一地標識特定 Buildkite 運行的映像。 標記圖像後,我們將最新和版本標記的圖像推送到我們的私有 Docker 註冊表以供重用。
在“運行賽普拉斯測試”步驟中,我們拉下我們在第一步中構建、標記和推送的映像,並啟動賽普拉斯服務以執行測試。 基於SPECS
和BASE_URL
等環境變量,我們將針對特定的已部署應用程序環境運行特定的測試文件,以實現特定的 Buildkite 構建。 這些環境變量將通過 Buildkite 管道設置進行設置,或者從 Bash 腳本動態觸發,該腳本將解析 Buildkite 選擇字段以確定要運行哪些測試套件以及針對哪個環境運行。
當我們選擇在 Buildkite CICD 部署管道期間運行哪些測試並使用某些導出的環境變量觸發專用觸發測試管道時,我們按照pipeline.cypress.yml
文件中的步驟來實現它。 將一些新代碼從部署管道部署到功能分支環境後觸發測試的示例如下所示:
觸發測試將在單獨的管道中運行,並且在點擊“Build #639”鏈接後,它將帶我們進入觸發測試運行的構建步驟,如下所示:
為按計劃運行的專用賽普拉斯 Buildkite 管道重複使用相同的pipeline.cypress.yml
文件,我們有構建,例如運行我們的“P1”,最高優先級 E2E 測試的構建,如下圖所示:
我們所要做的就是為諸如要運行的規範以及要在 Buildkite 管道設置中命中的後端環境等設置適當的環境變量。 然後,我們可以配置一個 Cron 計劃構建,它也在管道設置中,每隔一定小時啟動一次,我們就可以開始了。 We would then create many other separate pipelines for specific feature pages as needed to run on a schedule in a similar way and we would only vary the Cron schedule and environment variables while once again uploading the same `pipeline.cypress.yml` file to execute.
In each of those “Run Cypress tests” steps, we can see the console output with a link to the recorded test run in the paid Dashboard Service, the central place to manage your team's test recordings, billing, and other Cypress stats. Following the Dashboard Service link would take us to a results view for developers and QAs to take a look at the console output, screenshots, video recordings, and other metadata if required such as this:
Step 6: Comparing Cypress vs. WebdriverIO/STUI
After diving into our own custom Ruby Selenium solution in STUI, WebdriverIO, and finally Cypress tests, we recorded our tradeoffs between Cypress and Selenium wrapper solutions.
優點
- It's not another Selenium wrapper – Our previous solutions came with a lot of Selenium quirks, bugs, and crashes to work around and resolve, whereas Cypress arrived without the same baggage and troubles to deal with in allowing us full access to the browser.
- More resilient selectors – We no longer had to explicitly wait for everything like in WebdriverIO with all the
$(.selector).waitForVisible()
calls and now rely oncy.get(...)
and cy.contains(...)
commands with their default timeout. It will automatically keep on retrying to retrieve the DOM elements and if the test demanded a longer timeout, it is also configurable per command. With less worrying about the waiting logic, our tests became way more readable and easier to chain. - Vastly improved developer experience – Cypress provides a large toolkit with better and more extensive documentation for assertions, commands, and setup. We loved the options of using the Cypress GUI, running in headless mode, executing in the command-line, and chaining more intuitive Cypress commands.
- Significantly better developer efficiency and debugging – When running the Cypress GUI, one has access to all of the browser console to see some helpful output, time travel debug and pause at certain commands in the command log to see before and after screenshots, inspect the DOM with the selector playground, and discern right away at which command the test failed. In WebdriverIO or STUI we struggled with observing the tests run over and over in a browser and then the console errors would not point us toward and would sometimes even lead us astray from where the test really failed in the code. When we opted to run the Cypress tests in headless mode, we got console errors, screenshots, and video recordings. With WebdriverIO we only had some screenshots and confusing console errors. These benefits resulted in us cranking out E2E tests much faster and with less overall time spent wondering why things went wrong. We recorded it took less developers and often around 2 to 3 times less days to write the same level of complicated tests with Cypress than with WebdriverIO or STUI.
- Network stubbing and mocking – With WebdriverIO or STUI, there was no such thing as network stubbing or mocking in comparison to Cypress. Now we can have endpoints return certain values or we can wait for certain endpoints to finish through
cy.server()
andcy.route()
. - Less time to set up locally – With WebdriverIO or STUI, there was a lot of time spent up front researching which reporters, test runners, assertions, and services to use, but with Cypress, it came bundled with everything and started working after just doing an
npm install cypress.
- Less time to set up with Docker – There are a bunch of ways to set up WebdriverIO with Selenium, browser, and application images that took us considerably more time and frustration to figure out in comparison to Cypress's Docker images to use right out of the gate.
- Parallelization with various CICD providers – We were able to configure our Buildkite pipelines to spin up a certain number of AWS machines to run our Cypress tests in parallel to dramatically speed up the overall test run time and uncover any flakiness in tests using the same resources. The Dashboard Service would also recommend to us the optimal number of machines to spin up in parallel for the best test run times.
- Paid Dashboard Service – When we run our Cypress tests in a Docker container in a Buildkite pipeline during CICD, our tests are recorded and stored for us to look at within the past month through a paid Dashboard Service. We have a parent organization for billing and separate projects for each frontend application to check out console output, screenshots, and recordings of all of our test runs.
- Tests are way more consistent and maintainable – Tests passed way more consistently with Cypress in comparison to WebdriverIO and STUI where the tests kept on failing so much to the point where they were often ignored. Cypress tests failing more often signaled actual issues and bugs to look into or suggested better ways to refactor our tests to be less flaky. With WebdriverIO and STUI, we wasted a lot more time in maintaining those tests to be somewhat useful, whereas with Cypress, we would every now and then adjust the tests in response to changes in the backend services or minor changes in the UI.
- Tests are faster – Builds passed way more consistently and overall test run times would be around 2 to 3 times faster when run serially without parallelization. We used to have overall test runs that would take hours with STUI and around 40 minutes with WebdriverIO, but now with way more tests and with the help of parallelization across many machine nodes, we can run over 200 tests in under 5 minutes .
- Room to grow with added features in the future – With a steady open-source presence and dedicated Cypress team working towards releasing way more features and improvements to the Cypress infrastructure, we viewed Cypress as a safer bet to invest in rather than STUI, which would require us to engineer and solve a lot of the headaches ourselves, and WebdriverIO, which appeared to feel more stagnant in new features added but with the same baggage as other Selenium wrappers.
缺點
- Lack of cross-browser support – As of this writing, we can only run our tests against Chrome. With WebdriverIO, we could run tests against Chrome, Firefox, Safari, and Opera. STUI also provided some cross-browser testing, though in a much limited form since we created a custom in-house solution
- Cannot integrate with some third-party services – With WebdriverIO, we had the option to integrate with services like BrowserStack and Sauce Labs for cross-browser and device testing. However, with Cypress there are no such third-party integrations but there are some plugins with services like Applitools for visual regression testing available. STUI, on the other hand, also had some small integrations with TestRail , but as a compromise, we log out the TestRail links in our Cypress tests so we can refer back to them if we needed to.
- Requires workarounds to test with iframes – There are some issues around handling iframes with Cypress. We ended up creating a global Cypress command to wrap how to deal with retrieving an iframe's contents as there is no specific API to deal with iframes like how WebdriverIO does.
To summarize our STUI, WebdriverIO, and Cypress comparison, we analyzed the overall developer experience (related to tools, writing tests, debugging, API, documentation, etc.), test run times, test passing rates, and maintenance as displayed in this table:
Following our analysis of the pros and cons of Cypress versus our previous solutions, it was pretty clear Cypress would be our best bet to accomplish our goal of writing fast, valuable, maintainable, and debuggable E2E tests we could integrate with CICD.
Though Cypress lacked features such as cross-browser testing and other integrations with third-party services that we could have had with STUI or WebdriverIO, we most importantly need tests that work more often than not and with the right tools to confidently fix broken ones. If we ever needed cross-browser testing or other integrations we could always still circle back and use our knowledge from our trials and experiences with WebdriverIO and STUI to still run a subset of tests with those frameworks.
We finally presented our findings to the rest of the frontend organization, engineering management, architects, and product. Upon demoing the Cypress test tools and showcasing our results between WebdriverIO/STUI and Cypress, we eventually received approval to standardize and adopt Cypress as our E2E testing library of choice for our frontend teams.
Step 7: Scaling to Other Frontend Teams
After successfully proving that using Cypress was the way to go for our use cases, we then focused on scaling it across all of our frontend teams' repos. We shared lessons learned and patterns of how to get up and running, how to write consistent, maintainable Cypress tests, and of how to hook those tests up during CICD or in scheduled Cypress Buildkite pipelines.
To promote greater visibility of test runs and gain access to a private monthly history of recordings, we established our own organization to be under one billing method to pay for the Dashboard Service with a certain recorded test run limit and maximum number of users in the organization to suit our needs.
Once we set up an umbrella organization, we invited developers and QAs from different frontend teams and each team would install Cypress, open up the Cypress GUI, and inspect the “Runs” and “Settings” tab to get the “Project ID” to place in their `cypress.json` configuration and “Record Key” to provide in their command options to start recording tests to the Dashboard Service. Finally, upon successfully setting up the project and recording tests to the Dashboard Service for the first time, logging into the Dashboard Service would show that team's repo under the “Projects” tab like this:
When we clicked a project like “mako”, we then had access to all of the test runs for that team's repo with quick access to console output, screenshots, and video recordings per test run upon clicking each row as shown below:
For more insights into our integration, we set up many separate dedicated test pipelines to run specific, crucial page tests on a schedule like say every couple hours to once per day. We also added functionality in our main Buildkite CICD deploy pipeline to select and trigger some tests against our feature branch environment and staging environment.
正如人們所預料的那樣,這很快就通過了我們分配的本月記錄的測試運行,特別是因為有多個團隊以各種方式貢獻和触發測試。 根據經驗,我們建議注意計劃運行的測試數量、運行這些測試的頻率以及在 CICD 期間運行的測試。 可能有一些冗餘的測試運行和其他更節儉的領域,例如回撥預定測試運行的頻率,或者可能完全擺脫一些僅在 CICD 期間觸發測試。
節儉的規則同樣適用於添加用戶,因為我們強調只向前端團隊中的開發人員和質量保證人員提供訪問權限,他們將大量使用儀表板服務,而不是高層管理人員和這些團隊之外的其他人來填補這些有限的位置。
我們對賽普拉斯的期待
正如我們之前提到的,賽普拉斯在開源社區中展示了巨大的增長潛力和潛力,並且其專門的團隊負責為我們提供更多有用的功能,以供我們在 E2E 測試中使用。 我們強調的許多缺點目前正在得到解決,我們期待以下事情:
- 跨瀏覽器支持——這是一個很大的支持,因為我們採用 Cypress 的很多阻力來自於它只使用 Chrome,而基於 Selenium 的解決方案支持 Firefox、Chrome 和 Safari 等瀏覽器。 值得慶幸的是,更可靠、可維護和可調試的測試為我們的組織贏得了勝利,我們希望在未來賽普拉斯團隊發布此類跨瀏覽器支持時,通過更多跨瀏覽器測試來提升我們的測試套件。
- 網絡層重寫——這也是一個巨大的問題,因為我們傾向於在較新的 React 領域大量使用 Fetch API,而在較舊的 Backbone/Marionette 應用程序領域,我們仍然使用 jQuery AJAX 和正常的基於 XHR 的調用。 我們可以輕鬆地在 XHR 區域中截取或監聽請求,但必須使用 fetch polyfill 做一些變通的變通方法才能達到相同的效果。 網絡層重寫應該有助於減輕這些痛苦。
- 儀表板服務的增量改進——我們最近已經看到儀表板服務的一些新的 UI 更改,我們希望繼續看到它隨著更多的統計可視化和有用數據的分解而增長。 我們還大量使用並行化功能,並經常在儀表板服務中查看我們失敗的測試記錄,因此對佈局和/或功能的任何迭代改進都會很高興看到。
採用賽普拉斯走向未來
對於我們的組織,我們重視在 Chrome 瀏覽器下運行賽普拉斯測試時的開發人員效率、調試和穩定性。 我們實現了一致的、有價值的測試,並且在未來減少了維護,並使用了許多工具來開發新的測試和修復現有的測試。
總的來說,我們可以使用的文檔、API 和工具遠遠超過了任何缺點。 在體驗了 Cypress GUI 和付費儀表板服務之後,我們絕對不想回到 WebdriverIO 或我們定制的 Ruby Selenium 解決方案。
我們將我們的測試與 Buildkite 連接起來,並實現了我們的目標,即為我們的前端應用程序提供一種編寫一致、可調試、可維護和有價值的 E2E 自動化測試以與 CICD 集成的方法。 我們向其他前端團隊、工程高層和產品所有者證明了採用賽普拉斯並放棄 WebdriverIO 和 STUI 的好處。
後來在 Twilio SendGrid 的前端團隊中進行了數百次測試,我們在暫存環境中發現了許多錯誤,並且能夠比以往更加自信地快速修復我們這邊的任何不穩定的測試。 開發人員和 QA 不再害怕編寫 E2E 測試,但現在期待為我們發布的每個新功能或每個可以使用更多覆蓋範圍的舊功能編寫它們。