週四看到同事的電腦正使用這一個工具 Cypress,聽完他簡單的介紹後,我花了一個下午加晚上產生了這篇文章
Cypress 的特色:
- 安裝簡單
- 不需要額外安裝 WebDriver
- 語法直觀
- 官方文件蠻直觀的,而且含有很多影片,這對初學的來說非常好上手
- 官方目前有免費帳號可以: 500 個測試執行次數/月、3 個使用者帳號存取專案。其提供精美的 Dashboard 紀錄、追蹤
- 支援影片記錄
- 錯誤透過截圖記錄
必要元件:
- Node.js
- NPM
- NPM Cypress
安裝步驟:
- 建立一個資料夾,裡面新增一個 node_modules 資料夾
- 打開你的 Command line 工具
- 執行以下指令:
npm Install cypress -D
(PS:裡面老實說,node modules 蠻肥的,一定要冷靜的等他安裝完,我公司電腦跑了約5-10分鐘)
- 這邊推薦也一併安裝以下套件,稍後一個一個解釋:
- cypress-wait-until 用來等待一些物件的時候,例如:AJAX後物件改變
- cypress-xpath 透過 XPath 作為 Selector
- 安裝後,執行
npx cypress open
- 此時,就會在你的資料夾下建立預設的範本,並且開啟 UI 介面,目前支援三種瀏覽器: Chrome、Firefox、Edge
- 資料夾結構說明:
- fixtures
- 放情境的測試資料,例如:帳號、密碼、會員資料等等
- integration
- 實際的情境案例規格檔案
- plugins
- 放一些外掛元件、方法等
- support
- index.js => 載入套件會寫在這邊
- commands.js => 每次執行測試都會執行的檔案,裡面可以放一些共用的指令等等。例如:每次都需要做登入動作,就可以把登入寫成一個 Command 的方式使用
- cypress.json
- fixtures
我們先看他執行起來長什麼樣:官網介紹!超吸引人,再來說我實做的部分!!
接下來,來說一些我遇到花比較多時間的部分&值得分享的特點。
Customer Command,寫在 support/command.js
中
Cypress.Commands.add("UserLogin", (user, pw) => {
let username;
let password;
cy.fixture('profile.json') // <-- fixture in a separate file, default-user.js
.then((profile) => {
username = user || profile.userName;
password = pw || profile.userPwd;
cy.visit('/');
cy.get('li:nth-child(2) > .eStore_MyAccount').click();
cy.get('#Header_HeadLoginView_UserLogin1_LoginUser_UserName').type(username);
cy.get('#Header_HeadLoginView_UserLogin1_LoginUser_Password').type(password, { log: false });
cy.get('#LoginButton').click();
cy.xpath('/html/body/form/div[2]/header/div[1]/div[2]/div/ul[2]/li[2]/a').invoke('text').should('contains', 'kkman021');
});
});
//呼叫方法:
// cy.UserLogin();
// cy.UserLogin("kkman021","Password");
使用到的技巧:
cy.fixture('profile.json').then((profile) => {});
=> 將帳號、密碼存放在 profile.json 中,動態的進行讀取cy.visit('/')
使用 baseUrl 參數,進行相對路徑瀏覽。.type(password, { log: false }
cy.xpath('')
XPath 是 UI Testing 很常使用鎖定 Dom 物件的方式,特是透過 cypress-xpath 套件做到的,內建沒有
Cypress.on('uncaught:exception', (err, runnable) => {
return false
});
上面這段指令,因為 Cypress 執行時,如果妳的網頁的 Console 有出錯,也會被認定有異常。這時候或許不影響真實想要測試內容,透過上面指令就會忽略網頁 Console 的錯誤。
實際測試案例,案例說明:
- 我先呼叫了使用者登入的 Command
- 預期使用者在首頁進行搜尋 "XXXX" 四個字
- 搜尋後會跳轉到 Search.aspx 網頁
- 該網頁會透過 Ajax 進行查詢,在 XHR 期間會有 Loading 畫面,該 Loading 畫面的 Class 為 .isProcessing
- 在 Ajax 取得後,預期前往第一個搜尋結果,並且把搜尋結果的產品名稱記下來
- 點擊第一個搜尋結果,驗證新的網址內容有符合同時,產品頁面的名稱也符合搜尋
- 案例也可以透過 Chrome 套件進行錄製,相當簡單。(Cypress Scenario Recorder)
describe('SearchProduct ', () => {
let keyword = "XXXX";
it('successfully loads', () => {
cy.UserLogin();
cy.get('#tbxKeyword').click();
cy.get('#tbxKeyword').type(keyword);
cy.get('#btnHeadSearch > .fa').click();
cy.url().should('contains', 'Search.aspx');
cy.waitUntil(function () {
return cy.get('.isProcessing').should('have.css', 'display', 'none');
});
//Save the PartNo Object from element.
cy.xpath('/html/body/form/div[2]/div[3]/div/div/div[1]/div[3]/div/div[2]/div[1]/div/div[2]/h3/a')
.invoke('text')
.then((x => {
cy.wrap(x).as('partNo');
}));
cy.get('.product-item:nth-child(1) h3 > a').click();
//Assert part No
cy.get('@partNo')
.then(partNo => {
cy.url().should('contains', partNo);
cy.xpath('/html/body/form/div[2]/div[2]/div/div[1]/div/div[4]/h1/span[1]').invoke('text').should('contains', partNo);
});
})
})
技巧:
cy.waitUntil
因為這時候頁面實際的 Assert 對象是透過 Ajax 取得,Ajax 不一定什麼時候有回應。解決方案有二:cy.wait(1000 * N)
豪秒為單位,我會習慣直接寫成 1000 * N 就看你想等幾秒,這個缺點是:你就是硬生生的等那個秒數cy.waitUntil cypress-wait-until
套件提供功能,多種使用情境,我這邊因為 Ajax 取得結果後,Dom 物件會有變化,所以,我直接等到 Loading 畫面隱藏起來就認定完成
cy.xpath('').invoke('text')
這真的卡我最久,cypress 有屬於自己的 promise 的設計,所以呢!這邊我們是透過 .as() Alias 進行資料共享- 注意即便我們儲存了 as('partNo') 這個資料該物件還是物件而非文字,要取得裡面的文字還是需要透過 .then 進行實際的文字取得
寫到這邊,我覺得測試的寫法&我會需要用到的場景應該都已經涵蓋了。
還有一些我覺得實用的資訊內容:
- Viewport 測試不同大小必備
- Environment Variables 不同環境,改變變數必備良藥
- Dashboard
cypress run --record --key XXXXXXXX-4197-AAAAAA-BBBBB-60cd787e3024 --config video=false
- 發佈到 Dashboard 也超簡單,跟著步驟按一按就申請好了。有 Key 以後,上面指令就可以直接發佈了。上面指令設定錄影檔不會往平台丟
- 支援 TypeScript
- 整合多個開發工具,這邊介紹 Visual Studio Code
如果要設定 CI 進行單元測試就變簡單了,因為所有事情我們透過指令都可以做到,以下簡單說我在 Azure DevOps 上的設定。
- 設定 Git 版控,可以將 node_modules 資料夾設定 ignore
- Agent 選擇 windows-2019 內建 Node.js 14.16.1
- 步驟其實跟我們第一次建立專案是一樣的,安裝 NPM、安裝 Cypress、安裝 Cypress 套件
- 最後一步 Run :
cypress run --record --key XXXX-OOOO-438c-a62a-AAAAA --config video=false
- 如果執行失敗,就可以直接接 CI 的通知了
- 目前這做法最大缺點: npm 裝超久…
- 肯定有改善的方法,容我處理掉再分享!!
荒廢了寫文章好久,好久沒有這樣好好玩一個東西。
Cypress 讓我眼睛一亮,在寫的時候滿滿的感受到它已經為了開發人員設想了很多架構。
哪些項目該放哪裡都有好好的規劃,套件目前也感覺齊全。
那個 Dashboard 也是非常精美的狀況,可以讓不懂技術的人也快速掌握系統狀況。
唯一可惜還沒有開放自訂 Web Hook 讓我們可以自己些一些整合通知服務,只能使用官方支援的(例如:GitHub、Slack等等)
我應該會讓這個項目在我們專案中繼續的被導入、使用。
參考文章:
- Cypress.io E2E測試
- 種草 Cypress 和 TestCafe,QA 同學一定想了解的 Web UI 自動化測試工具
- 容器化UI自动化测试环境(Cypress&Docker)
- 這應該會是我想要解決 CI 安裝 NPM 太久的一個解套方向
- Working With Variables In Cypress Tests
- 這篇文章極為重要,我看完這邊文章才解決我對 invoke 的變數無法儲存的執著