# Cypress初探指南 Crypress為一前端自動化測試框架,使用javascript撰寫。 ## Getting Started ### 參考網站 --- :::info * [官方文件](https://docs.cypress.io/) * [鐵人賽](https://ithelp.ithome.com.tw/users/20140883/ironman/4316) ::: ### 安裝工具 --- :::info - [VS Code](https://code.visualstudio.com/) - [Cypress Recorder](https://chrome.google.com/webstore/detail/cypress-recorder/glcapdcacdfkokcmicllhcjigeodacab) ::: ### Gitlab --- :::info * [cypress-e2e-test專案](https://web-gitlab.internal.ihmi.net/weincloud/cypress-e2e-test) ::: ### 環境建立 --- - npm版本12 or 14 或以上 :::info * [系統要求參考網址](https://docs.cypress.io/guides/getting-started/installing-cypress#System-requirements) * [npm下載連結](https://nodejs.org/en/download/) ::: ### 檔案目錄結構 --- cypress ├── /downloads 下載檔案暫存目錄 ├── /e2e │ └── /partner partner 測試案例 │ └── /weincloud weincloud 測試案例 ├── /fixtures 靜態測試假資料 ├── /reports 測試結果報告 ├── /screenshots 錯誤畫面載圖 ├── /scripts 自定義 script function ├── /support 自定義 command ├── /videos 執行過程影片 ├── cypress.config.js cypress 設定檔及自定義 event handler ### 啟動project --- 1. 下載cypress-e2e-test project 2. 在project根目錄下terminal執行 ``` npm install ``` 3. 開啟GUI選擇要執行的測試案例 ``` npm run cy:open ``` 4. 或執行npm run cy:run執行所有測試案例 ``` npm run cy:run ``` 5. Command Line測試,並將測試結果上傳到Dashboard Windows: ``` set CYPRESS_API_URL="http://192.168.2.44:1234/" npx cy2 run --browser chrome --record --key XXX --parallel --ci-build-id %date:~0,4%-%date:~5,2%-%date:~8,2%_%time:~0,2%:%time:~3,2%:%time:~6,2% ``` Linux: ``` export CYPRESS_API_URL="http://192.168.2.40:1234/" npx cy2 run --browser ${BROWSER} --record --key XXX --parallel --ci-build-id `TZ='Asia/Taipei' date +"%Y-%m-%d_%H:%M:%S"` ``` #### 開啟GUI進行測試 :::info * [參考網址-Opening the App](https://docs.cypress.io/guides/getting-started/opening-the-app#What-you-ll-learn) ::: ``` npm run cy:open ``` 執行上述指令會跳出如下畫面,選擇左邊圖示E2E Testing ![](https://i.imgur.com/u6bYOJh.png) 選擇要測試的瀏覽器 ![](https://i.imgur.com/2ulIEfU.png) 選擇左邊選單Specs後,右邊列出所有測試腳本,partner資料夾為Weincloud Partner測試腳本,weincloud資料夾為Weincloud測試腳本 ![](https://i.imgur.com/pVeA2vn.jpg) 點擊測試腳本開始執行測試,左邊為腳本指令執行過程,顯示執行成功或失敗及錯誤錯息,右邊為腳本在瀏覽器執行的畫面,可按f12協助除錯 ![](https://i.imgur.com/8siUaRH.jpg) #### Command Line進行測試 :::info * [參考網址-Command Line](https://docs.cypress.io/guides/guides/command-line) ::: ``` npm run cy:run 或 npx cypress run ``` 指令Options: | Options | Description | | ----------------- |:----------------------- | | --browser, -b | 選擇測試瀏覽器,參數可以為chrome, chromium, edge, electron, firefox | | --spec, -s | 指定要運行的測試腳本 | |...|...| 在project根目錄執行npm run cy:run後會執行所有測試腳本,測試結束後顯示所有執行結果 ![](https://i.imgur.com/nIwIDi0.jpg) /screenshots目錄下存放錯誤畫面截圖 ![](https://i.imgur.com/pFs8yph.jpg) /videos目錄下存放執行過程影片 ![](https://i.imgur.com/Ev6cukM.png) /report目錄下存放執行結果報告網頁 ![](https://i.imgur.com/Wmsftwj.jpg) 執行結果報告 ![](https://i.imgur.com/XlDgvG1.jpg) #### Command Line測試,並將測試結果上傳到Dashboard :::info [Dashboard服務](http://192.168.1.251:8080/) [參考網址-sorry cypress](https://docs.sorry-cypress.dev/) ::: 在project根目錄下terminal執行(windows) ``` set CYPRESS_API_URL="http://192.168.1.251:1234/" npx cy2 run --browser chrome --record --key XXX --parallel --ci-build-id %date:~0,4%-%date:~5,2%-%date:~8,2%_%time:~0,2%:%time:~3,2%:%time:~6,2% ``` 在project根目錄下terminal執行(linux) ``` export CYPRESS_API_URL="http://192.168.1.251:1234/" npx cy2 run --browser ${BROWSER} --record --key XXX --parallel --ci-build-id `TZ='Asia/Taipei' date +"%Y-%m-%d_%H:%M:%S"` ``` 打開http://192.168.2.40:8080/,點擊Project選單 ![](https://i.imgur.com/2aAejpF.png) 點擊sw-iiot project ![](https://i.imgur.com/1YuIWmh.png) 最上方具有紅色圓點為正在執行的測試 ![](https://i.imgur.com/2YV8YNL.png) 點擊進入可看測試案例pass或fail ![](https://i.imgur.com/FX7a75Y.png) 點擊測試案例進入可回放測試過程錄影 ![](https://i.imgur.com/bTUw3bV.png) 點擊失敗測試案例可看cypress失敗log及截圖 ![](https://i.imgur.com/gZjQDZq.png) ## 撰寫第一個測試 --- ### 規劃測試情境 以Weincloud登入為例,一個測試情境需包含步驟及斷言以驗證測試是否通過 ``` 1. 進到Weincloud首面 2. 輸入Domain Name 3. 輸入Username 4. Password 5. 按下Login按鈕 6. 檢查登入後瀏覽器路徑是否為/account/users ``` ### 轉換為程式碼 ```gherkin= describe('Weincloud User Login', () => { beforeEach(() => { cy.visit('https://test.weincloud.net/') }) it('should redirect authenticated user to /account/users', () => { //選取元素,輸入Domain Name e2e_test cy.get('.el-input__inner').first().type('e2e_test') //選取元素,輸入帳號admin cy.get('.el-input__inner').eq(1).type('admin') //選取元素,輸入密碼123456 cy.get('.el-input__inner').eq(2).type('123456') //選取登入按鈕,點擊 cy.get('.login-btn').contains('Login').click({ force: true }) //斷言驗證瀏覽器路行是否為/account/users cy.location('pathname').should('equal', '/account/users') }) }) ``` ### 範例說明 * 測試腳本介面結構由describe()、it()、before()、beforeEach()、after()、afterEach()所組成 > [參考網址-Test Structure](https://docs.cypress.io/guides/core-concepts/writing-and-organizing-tests#Test-Structure) > [參考網址-Mocha](https://docs.cypress.io/guides/references/bundled-libraries#Mocha) * decribe() > 描述場景或圈出特定區塊,例如:標明測試的功能或 function,在這個例子我們標明測試Weincloud Login功能。 * it() > 撰寫測試案例(Test Case),此測試案例測試使用者成功登入後是否抵達路徑/account/users * get() > 選取DOM元素進行後續操作,參數為CSS選擇器。在此腳本中,我們選取DOM元素輸入domain_name, username,password後,再點擊登入按鈕。 [參考網址-get](https://docs.cypress.io/api/commands/get) * location() > 取得此頁面的window.location物件,此例中我們用以取得此頁面的路徑位址。 > [參考網址-location](https://docs.cypress.io/api/commands/location) * should() > 建立一個斷言,斷言會重試直到通過或者是超時,在此例中,我們建立一個斷言判斷location('pathname')取得的頁面路徑是否等於/account/users,如果斷言未能成功通過,則測試失敗。 > [參考網址-should](https://docs.cypress.io/api/commands/should) ## Cypress Concept 介紹 --- :::info [Introduction to crypress](https://docs.cypress.io/guides/core-concepts/introduction-to-cypress) ::: ### 選取元素 Cypress包進jQuery的selector engine,所以可以像JQeury一樣利過CSS選擇器來選取DOM元素。 ### 斷言 斷言讓我們可以描述應用程式所需要的狀態,如果斷言未能通過,測試案例將失敗,斷言可以用來確保某一個元素存在、具有某些屬性或CSS class或具有某些狀態。 #### 利用should()來撰寫斷言 取得subject後,利用should()來寫斷言,如: - cy.get('button').click().should('have.class', 'active') - cy.request('/users/1').its('body').should('deep.eq', { name: 'Jane' }) #### 預設斷言(or 內建斷言) 有的command具有內建預設的斷言,使得我們不用顯式地撰寫斷言,在某些條件未能達成時,一樣能使測試案例失敗。 如: - cy.visit() - cy.request() - cy.contains() - .get() - .find() - .type() - .click() - .its() #### Cypress支援的斷言 :::info [assertion references](https://docs.cypress.io/guides/references/assertions) ::: Cypress包進了Chai、Chai-jQuery及Sinon-Chai libraries來撰寫各式的斷言。 ### Commands(API) :::info [參考網址-API](https://docs.cypress.io/api/) [Commands are asynchronous](https://docs.cypress.io/guides/core-concepts/introduction-to-cypress#Commands-Are-Asynchronous) ::: 注意: Cypress的commands在呼叫當下並不會立刻執行,而是先放到queue裡以待稍後執行,所以我們會說Cypress的commands是非同步的。 常用api介紹(按字母排序): * as > 指定別名供之後在cy.get()或cy.wait()參數中使用。 > [參考網址-as](https://docs.cypress.io/api/commands/as) * check > 對複選框或單選框做選取。 > [參考網址-check](https://docs.cypress.io/api/commands/check) * contains > 選取包含指定文字的DOM元素。 > [參考網址-contains](https://docs.cypress.io/api/commands/contains) * click > 點擊DOM元素。 > [參考網址-click](https://docs.cypress.io/api/commands/click) * get > 選取DOM元素進行後續操作,參數為CSS選擇器 > [參考網址-get](https://docs.cypress.io/api/commands/get) * intercept > 對api請求及回應進行spy跟stub。 > [參考網址-intercept](https://docs.cypress.io/api/commands/intercept) * location() > 取得此頁面的window.location物件 > [參考網址-location](https://docs.cypress.io/api/commands/location) * request > 發出HTTP請求 > [參考網址-request](https://docs.cypress.io/api/commands/request) * screenshot > 對測試畫面進行截圖 > [參考網址-screenshot](https://docs.cypress.io/api/commands/screenshot) * select > 在HTML元素select中選擇其中一個option選項。 > [參考網址-select](https://docs.cypress.io/api/commands/select) * selectFile > 在HTML元素input[type=file]中選取一個檔案或是模擬拖曳檔案進瀏覽器。 > [參考網址-selectFile](https://docs.cypress.io/api/commands/selectfile) * should > 建立一個斷言,斷言會重試直到通過或者是超時。 > [參考網址-should](https://docs.cypress.io/api/commands/should) * submit > 對表單進行提交。 > [參考網址-submit](https://docs.cypress.io/api/commands/submit) * task > 透過plugin event執行程式碼。 > [參考網址-task](https://docs.cypress.io/api/commands/task) * then > 對上個api產生的物件進行後續連接操作。 > [參考網址-then](https://docs.cypress.io/api/commands/then) * type > 對DOM元素進行輸入。 > [參考網址-type](https://docs.cypress.io/api/commands/type) * uncheck > 取消選取複選框。 > [參考網址-uncheck](https://docs.cypress.io/api/commands/uncheck) * wait > 等待參數指定秒數或是等待別名的資源解析完畢。 > [參考網址-wait](https://docs.cypress.io/api/commands/wait) ### Custom Command :::info * [參考網址-Custom Commands](https://docs.cypress.io/api/cypress-api/custom-commands) ::: 我們可以定義自己的Command,也可以覆蓋現有的Command。 目前Project含有 | Command | Description | | ----------------- |:----------------------- | | weincloudLogin | 登入Weincloud | | partnerLogin | 登入Weincloud Partner | | getBySel(參數) | 選取具有data-test="參數"屬性的DOM元素 | | getBySelLike(參數) | 選取data-test屬性的值包含參數的DOM元素 | | ... | ... | ### Hooks :::info * [參考網址-Hooks](https://docs.cypress.io/guides/core-concepts/writing-and-organizing-tests#Hooks) * [參考網址-Mocha](https://docs.cypress.io/guides/references/bundled-libraries#Mocha) * [參考網址-認試軟體測試的世界 & TDD/BDD 入門](https://www.slideshare.net/wantingj/tdd-bdd-47559903) * [參考網址-單元測試:Mocha、Chai 和 Sinon](https://www.cythilya.tw/2017/09/17/unit-test-with-mocha-chai-and-sinon/) ::: Cypress採用Mocha的BDD語法,測試腳本都會基於Mocha提供的以下工具: * before() > 在所有測試開始前會執行的程式碼區塊。 * beforeEach() > 在每個 Test Case 開始前執行的程式碼區塊。 * it() > 撰寫測試案例(Test Case)。 * afterEach() > 在每個 Test Case 結束後執行的程式碼區塊 * after() >在所有測試結束後會執行的程式碼區塊。 ```mermaid graph LR before --> beforeEach --> it --> afterEach --> after ``` ## 使用Cypress Recorder錄製腳本 :::info * [Cypress Recorder](https://chrome.google.com/webstore/detail/cypress-recorder/glcapdcacdfkokcmicllhcjigeodacab) * [利用工具錄製腳本](https://ithelp.ithome.com.tw/articles/10265802) ::: 點擊上方連結下載Chrome Extension ![](https://i.imgur.com/HIgY78n.png) 打開Cypress Recorder後,按下start Recording按鈕,即可開始在網頁上開始操作畫面 ![](https://i.imgur.com/ObK7lyi.png) 錄製結束時,點擊Stop Recording按鈕停止錄製 ![](https://i.imgur.com/EqUeeak.png) 點擊Copy to Clipboard複製script至專案中,測試script運行是否如預期 ![](https://i.imgur.com/fPLK9ZV.png) :::warning 自動產生的script,選取元素的寫法通常比較不清楚易懂,建議可以再調整,或是改為官方建議的,使用data-test屬性來選取(參考最佳實踐) ::: ## 最佳實踐 :::info * [參考網址-Best Practices](https://docs.cypress.io/guides/references/best-practices) ::: * 選取元素 :x: 避免使用容易變動的selector :heavy_check_mark: 建議使用data-* attribute,使用自定義的cy.getBySel()來選取DOM元素 * 訪問外部網站 :x: 避免訪問無法控制的第三方系統 :heavy_check_mark: 只測試可以控制的部分,避免第三方系統,若有必要,通過cy.request()發出api請求與第三方通訊。 * 腳本之間的關聯 :x: 每個腳本之間都高度緊密,彼此相互依賴 :heavy_check_mark: 腳本之間是獨立運作且可以執行成功的 * 腳本控制登入及其它狀態 :x: 腳本透過操作UI進入登入狀態 :heavy_check_mark: 腳本間應該互相獨立,應該透過編程的方式進入登入或其它狀態,使用腳本不會依賴於登入測試是否成功。 * 不必要的等錯,錯誤使用cy.wait() :x:等待任意millisecond的時間 :heavy_check_mark:使用cy.intercept()取route alias(路徑別名)或是使用斷言來等待。 說明: 不需要下cy.wait(4000),因為cy.get('table tr').should('have.length', 2)斷言會讓腳本在斷言通過前不繼續往下執行或直到timeout失敗。 ![](https://i.imgur.com/h7Ifr3s.jpg) 說明: 或是設定api路徑別名,讓腳本明確等待api執行完成或直到發生timeout。 ![](https://i.imgur.com/utEdE2h.jpg) * 在before或beforeEach重置狀態 :x:在after或afterEach重置狀態 :heavy_check_mark:在測試腳本開始運行前重置狀態 * 設定global baseUrl :x:使用cy.visit(),但沒有設定 baseUrl :heavy_check_mark:在 cypress.json 檔設定 baseUrl 可以讓腳本執行時節省一些時間 * ... ## Dashboard :::info * [官方Dashboard](https://www.cypress.io/dashboard/) * [Sorry Cypress](https://docs.sorry-cypress.dev/) ::: 使用sorry cypress架設的dashboard位於(http://192.168.1.251:8080)