【UI Testing】Cypress 強大的測試工具

週四看到同事的電腦正使用這一個工具 Cypress,聽完他簡單的介紹後,我花了一個下午加晚上產生了這篇文章

Cypress 的特色:

  • 安裝簡單
  • 不需要額外安裝 WebDriver
  • 語法直觀
  • 官方文件蠻直觀的,而且含有很多影片,這對初學的來說非常好上手
  • 官方目前有免費帳號可以: 500 個測試執行次數/月、3 個使用者帳號存取專案。其提供精美的 Dashboard 紀錄、追蹤
  • 支援影片記錄
  • 錯誤透過截圖記錄

必要元件:

安裝步驟:

  • 建立一個資料夾,裡面新增一個 node_modules 資料夾
  • 打開你的 Command line 工具
  • 執行以下指令:npm Install cypress -D      (PS:裡面老實說,node modules 蠻肥的,一定要冷靜的等他安裝完,我公司電腦跑了約5-10分鐘)
成功安裝畫面
  • 這邊推薦也一併安裝以下套件,稍後一個一個解釋:
  • 安裝後,執行 npx cypress open
  • 此時,就會在你的資料夾下建立預設的範本,並且開啟 UI 介面,目前支援三種瀏覽器: Chrome、Firefox、Edge
專案範本
  • 資料夾結構說明:
    • fixtures
      • 放情境的測試資料,例如:帳號、密碼、會員資料等等
    • integration
      • 實際的情境案例規格檔案
    • plugins
      • 放一些外掛元件、方法等
    • support
      • index.js => 載入套件會寫在這邊
      • commands.js => 每次執行測試都會執行的檔案,裡面可以放一些共用的指令等等。例如:每次都需要做登入動作,就可以把登入寫成一個 Command 的方式使用
    • cypress.json

我們先看他執行起來長什麼樣:官網介紹!超吸引人,再來說我實做的部分!!

接下來,來說一些我遇到花比較多時間的部分&值得分享的特點。

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
CI 步驟
  • 步驟其實跟我們第一次建立專案是一樣的,安裝 NPM、安裝 Cypress、安裝 Cypress 套件
  • 最後一步 Run : cypress run --record --key XXXX-OOOO-438c-a62a-AAAAA --config video=false
  • 如果執行失敗,就可以直接接 CI 的通知了
  • 目前這做法最大缺點: npm 裝超久…
    • 肯定有改善的方法,容我處理掉再分享!!
執行結果

荒廢了寫文章好久,好久沒有這樣好好玩一個東西。

Cypress 讓我眼睛一亮,在寫的時候滿滿的感受到它已經為了開發人員設想了很多架構。

哪些項目該放哪裡都有好好的規劃,套件目前也感覺齊全。

那個 Dashboard 也是非常精美的狀況,可以讓不懂技術的人也快速掌握系統狀況。

唯一可惜還沒有開放自訂 Web Hook 讓我們可以自己些一些整合通知服務,只能使用官方支援的(例如:GitHub、Slack等等)

我應該會讓這個項目在我們專案中繼續的被導入、使用。

 

參考文章: