CI (Continuous Integration) 持續性整合
聽起來很抽象,簡單來說就是針對每個變動,能持續且自動地 進行驗證
像是:

  • 建置 (build)
  • 測試 (test)
  • 程式碼分析 (source code analysis)…..

今天的目標就是利用 e2e 自動化測試工具 — Cypress 搭配 Bitbucket 的 CI/CD 整合工具 — Pipeline,完成持續性整合測試!
讓每次推 code 上去 Bitbucket 時,都將進行一次 e2e 測試
檢查 unit test 如法涵蓋到的頁面的互動是否有被改壞
來讓團隊在協作上更有信心

事不宜遲就來介紹這次選用的 e2e 測試工具 — Cypress

Why Cypress

Cypress 是一個專注 e2e 的自動化測試工具

所謂的 e2e 測試就是:
以使用者的角度出發,對真實系統(網頁、APP )進行測試
因此他會從開啟瀏覽器開始,一步步的操作與輸入,讓瀏覽器與後端 API 進行互動
透過最終的顯示狀態來診斷其結果是否符合預期

自動化測試有許多工具,Cypress 就是其中一種
像是在接觸 Cypress 之前
我有接觸過一點 selenium 做一些簡單的訪問、點擊

原本在公視的時候是希望可以做一些自動化測試,來減少手動測試的負擔
只可惜因為合約的關係沒有源碼,所以沒辦法隨意的加 testID 來輔助測試
也遇到了一些困難便無疾而終
其實還蠻可惜的,因為我覺得這是一件蠻有意義的事情
只可惜遇到了現實面的問題

後來進到公司後,蠻意外的是第一個任務就是幫助公司接手未完成的自動化測試專案並上線到 Pipeline
我很樂意接手這個專案,除了可以透過寫測試了解專案架構等等
能夠完成一年前未完成的缺憾,對我來說是個得來不易的好機會

其實在一開始的時候,公司是有一個自動化測試在跑的
當時使用的是 Testcafe
但因為執行速度太慢,所以才決定改用 Cypress 替換

Cypress 有以下幾個特點:

  1. 非 Selenium-based
  2. 只專注於 e2e 測試
  3. 只能用 JS 寫
  4. 不需安裝一堆套件可以自己寫 commands
  5. 下載容易

Cypress 與大部分的測試工具不同,他不是 Selenium-based 的測試工具
他執行的方式不是透過 WebDiver 的介面,用網路執行遠程命令來運行
而是從 node 的環境與系統交互
因此他的速度會快很多!
安裝和配置也很容易,透過 npm 下載就可以了!

不僅如此,Cypress 也提供很多很好用的工具,像是:TestRunner、DashBoard
很方便除錯、了解事發經過

Cypress 安裝與 command

首先我們先初始化 npm 或 yarn 然後在我們的目錄下載 Cypress

npm init
npm install cypress --save-dev

或如果你是使用 yarn 的話:

yarn init
yarn add cypress --dev

這邊要考量的是,Cypress 其實蠻大一包的
所以如果今天是要用在小專案可能還好
但如果是有規模的專案,可能就要考慮自己在一個資料夾,把 package.json 分開
以免再更新套件時,還要去下載不常更新又肥的 Cypress

資料夾的分層與否,也會影響到 pipeline
由於不分資料夾的範例官網已經有了,這次就選擇分層的方式

下載好之後就要了解怎麼跑測試!

如果你是使用 Vue、React 或是 Angular,第一步,也是最重要的

先把程式起起來

不然訪問不到頁面啦 XD
不過最近發現如果是用 cypress open 程式沒起起來他會貼心提醒呢

一開始,我們是沒有 Cypress 的資料夾和 cypress.json 的

├── node_modules
├── package.json
└── yarn.lock

要先下這個指令

npx cypress open

他就會彈出這個視窗

按下 got it 後,他就會自動新增檔案進去
檔案結構就會長這樣:

├── cypress
│ ├── fixtures // 透過指令可加載一些資料或是圖片等等
│ │ └── example.json
│ ├── integration // 放測試檔案的資料夾
│ │ └── examples/
│ ├── plugins
│ │ └── index.js
│ └── support
│ ├── commands.js // 寫自己的 command
│ └── index.js
├── node_modules
├── cypress.json // 環境變數設定檔
├── package.json
└── yarn.lock

官網有詳細的 資料夾介紹 可以參考

Cypress 有兩種執行指令,一種是剛剛的 GUI 介面另一種是 CLI 介面:

  1. GUI 介面指令:npx cypress open
  2. CLI 介面指令:npx cypress run

首先先來介紹 GUI 指令

cypress open

cypress open 沒什麼參數好加的
除了帶一個 --env 的環境變數,像是可以帶要輸入的帳號密碼等等
可能也會帶 --config ,他可以帶 baseUrl 等等的參數
baseUrl 就是 Cypress 要直接訪問的 url)
所以假設你起起來的 url port 是 3000,baseUrl 就是 http://localhost:3000
像是這樣:

npx cypress open --env email=xxx,pwd=xxx --config baseUrl=xxx

但其實這些變數可以 寫在 cypress.json 所以不一定要帶參數

cypress.json

{
"baseUrl": "http://localhost:3000/",
"env": {
"email": "Test@test.com",
"pwd": "test3333333"
},
}

設定、下完 cypress 指令後就會開啟 Test Runner
Test Runner 不是一個要另外下載的工具
只要下 npx cypress open 他就會打開一個圖形介面
可以選擇你要跑的單一測試項目(或)和運行的環境

https://docs.cypress.io/guides/getting-started/installing-cypress.html#Switching-browsers

假設選擇的是 Chrome 的環境,接著就會起一個 Chrome
這樣的畫面:

https://docs.cypress.io/guides/core-concepts/test-runner.html#Overview

實際運作的過程:

除了他可以看測試的過程外,其中我覺得 Command log 和 Selector 的功能十分方便

Command log

Command log 不僅可以看測試的進度以及錯誤發生在哪一個環節
當錯誤發生也能點擊該指令去看每一個點擊或選取的狀況

Selector

點擊元素產生對應語法的 CSS selector
預設是 cy.get() 方法,但也可以選擇 cy.contain()

cypress run

npx cypress run 可以加的參數很多元
若都不加的話,他預設會跑在 Electron 且所有測試都會跑一遍

每一個 case 都會顯示成功與否、跑的時間、錯誤訊息等等的細節

且每一次的測試過程都會錄影、輸出至 e2e-cypress/cypress/videos/
不用擔心用 CLI 指令就不好 debug

但這邊要注意一下,如果你是 Mac
跑第一次的時候你可能會遇到這個問題:

這個時候,點開:系統偏好設定 > 安全性與隱私權 > 一般
點擊「強制允許」

點擊「打開」

就可以使用 ffmpeg 轉檔輸出影片了

等到測試全部跑完時還會有一個測試總覽
告訴你通過的狀況和時間等等的

當然也可以加參數給 cypress run
除了剛剛講到的 —- env-- config 這邊只舉例一些常用的參數,其他參數可以 參考官方文件

  1. --browser 選擇運行的環境
cypress run --browser chrome

2. --spec 選擇跑測試的範圍
這邊要注意的是,許多人會以為 cypress open 也可以加 --spec 這個參數
但其實他是不行的,只有 cypress run 可以
我推測可能是因為 cypress open 自帶 GUI 介面
所以才不接 --spec 參數吧?

  • 單檔案
npx cypress run --spec "cypress/integration/examples/actions.spec.js"
  • 多檔案
npx cypress run --spec "cypress/integration/examples/actions.spec.js,cypress/integration/examples/files.spec.js"
  • 指定資料夾
npx cypress run --spec "cypress/integration/login/*"

Cypress 撰寫經驗談

Cypress 的指令很直觀、好理解,上手快速
大部分都是圍繞在「選擇 DOM」上,文件也寫得十分完整
因此在這裡指分享幾個重要的概念和遇到的坑

如果想要更進一步了解 Cypress 指令以及他怎麼運用的話
我會推薦閱讀這篇文章:

他寫的真的很詳細,很適合新手閱讀
這邊就不多家贅述,直接從坑開始講起!

  1. Cypress 測試腳本的核心就是固定、規律

我覺得這是 Cypress 很重要的一點
Cypress 強調測試的腳本應該要是固定、規律的事件

不能以 DOM 的有無當作條件

cypress 的選擇器有很多,像是: getfindcontain …..
他們會根據我們給的 Selector 尋找指定的 DOM 直到 time out
因此要極力避免選擇到可能因狀態改變而不存在的 DOM
我認為這樣的情形很容易發生在 Vue 或 React 上
可能因為某些條件不同就會渲染不同的 Component
這可能就要換一個方法來區分測試行為了

2. 拉出重複的行為

在撰寫每一次的測試時都會 依循這樣的架構 來精簡程式:

describe('Hooks', () => {
before(() => {
// 在測試前只跑一次
})
beforeEach(() => {
// 在測試前各跑一次
})
it('describe', () => {
// 測試內容
})
afterEach(() => {
// 在測試後各跑一次
})
after(() => {
// 在測試後只跑一次
})
})

除此之外,也可將會重複使用的測試步驟
像是:登入、選擇商品、註冊…等等
寫在 support/cammands.js 包成一個 cypress 的 cammand
就不用一直寫重複的代碼了

support/cammands.js

Cypress.Commands.add('login', (email, pw) => {
......
})

integration/xxx.spec.js

cy.login('xxx@xxx', 'xxxxxx');

3. 抓取頁面屬性或內容的值

Cypress 裡是可以寫 JS 也只能寫 JS 的,但因為運行環境問題
抓取 DOM 唯一的方法就是透過 Cypress 自己的 command
用 JS 寫取不了頁面上的 DOM的

Cypress 雖然是跟著我們的應用一起運行
雖然不是透過網路互動
但他是在 Node 環境來跟頁面互動
JS 自然無法獲取 DOM

但如果今天我不只要選擇 DOM
我還想獲得特定屬性的值,像是 href 帶的路徑
或是獲得文字內容
一般的 Cypress 選擇器就無法滿足這樣的需求
可以試試這些方法:

獲得特定屬性的值

cy.get(selector)
.invoke('attr', 'href')
.then(href => {
cy.visit(href);
});

獲得文字內容

cy.get(selector)
.invoke('text')
.then(text => {
console.log(text);
});

4. 避免撰寫原生的迴圈行為

有時候在一些邏輯上
可能會想用迴圈取值或是用迴圈搭配條件去選擇
這時候可能會直觀地想:我條件判斷都用 if 了,迴圈應該也可以寫個 for 或 each 之類的吧?
這時候可能就會踩到非同步迴圈的問題

這是因為 Cypress 的指令皆是異步執行的指令
他一樣會去跑迴圈,但他會等到迴圈都跑完了
才開始執行 Cypress 的指令
如果有要做判斷去終止迴圈,就會直接失效 QQ

因此,可以使用 cy.each() Cypress 自己的 each() 方法
他可以塞三個參數,迭代元素、索引值、陣列本身

integration/xxx.spec.js

 cy.get('ul>li')
.each(($el, index, $list) => {
// $el is a wrapped jQuery element
if ($el.someMethod() === 'something') {
// wrap this element so we can
// use cypress commands on it
cy.wrap($el).click()
} else {
// do something else
}
})

如果想提前中斷迴圈 return false 就可以中斷了

5. 減少使用cy.wait()

可能因為換頁、等資料更新或是等 component 重 render …..等等
由於不能確定網速,直覺上就會多設 cy.wait() 來確保某個元素能被 get 到
但我覺得用 cy.wait() 是一個不太好的方式

第一是你不知道你要等多久,第二是你不知道你「多」等了多久
Cypress 的好處之一就是「快速」
若每次都多了幾秒的等待時間,測試一多起來代價是很高
且有些步驟與 API response 的速度有關
如果遭遇網路問題,多了一秒就會 Test Failed 不是個好選擇

因此我覺得可以用幾個方法取代他:

  • 設 Timeout
    Cypress 有預設的 timeout ,或是在 cypress.json 定義 timeout 也行

cypress.json

{
"baseUrl": "http://localhost:3000/",
"env": {
"email": "Test_e2e@test.com",
"pwd": "testtesttest3"
},
"defaultCommandTimeout": 5000
}

但如果只是想要在特定幾行增加 timeout 時間
只要在我想要延長的 cy.get() 加上 timeout: 10000
10000 毫秒內找到 就可以 get 到了!不用等完它
要注意,不是每一個 command 都能這樣加,請查閱文件能不能加 timeout 的參數喔!

integration/xxx.spec.js

cy.get("#DOM", { timeout: 10000 })
  • 確定有顯示再動作
    除了設定 timeout,我們也可以善用 .should('be.visible')
    確定有顯示該元素再做後續動作

integration/xxx.spec.js

cy.get('#DOM', { timeout: 10000 }).should('be.visible')
.then(() => {
cy.get('#DOM').click();
});
  • 確定切換至指定頁面
    我們也可以多檢查一層 url 來確定有到指定頁面在做選取
    這裡要注意的是,假如你有在 cypress.json 設定 baseUrl 就可以省略 domain
    且 url 標示需使用跳脫字元
    例如: https://example.com/article/example
    => cy.url().should('match', /article\/example/)

integration/xxx.spec.js

cy.url().should('match', /xxxx/)
.then(() => {
cy.get('#DOM', { timeout: 10000 }).should('be.visible')
.then(() => {
cy.get('#DOM').click();
});
})
  • 確定 API response
    剛剛講到的都是等待元素是否有出現,另一種情形是要等 API 打完才做下一個動作
    這時候就不能使用 cy.wait() 傻傻的等了
    且這樣也有一個好處
    當錯誤發生,也可以確認 API 狀態,排除是不是 API 錯誤影響
    他的結構是這樣: cy.route('HTTP Verb', 'API').as('nickname')

integration/xxx.spec.js

cy.route('PUT','https://api/url/example/').as('submit');cy.wait('@submit').its('status').should('eq', 200)
.then(() => {
cy.get('#DOM', { timeout: 10000 }).should('be.visible')
.then(() => {
cy.get('#DOM').click();
});
})

6. iframe 要監聽有沒有 load 完

如果要對 iframe 內的輸入框等等的進行互動,建議都要先檢查 iframe 內部是否 render 完成
以免造成有時 Dom detached 導致測試失敗的問題
這很常出現在信用卡付款的地方,因為 Dom detached 而 type() 不上去

要檢查 iframe 有兩種分法,一種是使用 Cypress 的 plugin,一種是自己刻

support/commands.js

// 檢查 iframe 有沒有 load 好Cypress.Commands.add('iframeLoaded', { prevSubject: 'element' }, iframe => {
const contentWindow = iframe.prop('contentWindow');
return new Promise(resolve => {
if (contentWindow &&
contentWindow.document.readyState === 'complete') {
resolve(contentWindow);
} else {
iframe.on('load', () => {
resolve(contentWindow);
});
}
});
});// wrap iframeCypress.Commands.add('getIframeBody', iframe => {
cy.get(iframe).iframeLoaded().then(() => {
return cy
.get(iframe)
.its('0.contentDocument.body')
.should('not.be.empty')
.then(cy.wrap);
});
});

integration/xxx.spec.js

// 填入信用卡號cy.getIframeBody('#card-number > iframe')
.find('#cc-number')
.type('4242424242424242');

另外請注意,如果你有使用到 iframe 可能就有 CORS 的問題
記得在你的 cypress.json 加上 chromeWebSecurity 來避免此問題

{
"chromeWebSecurity": false,
}

7. 使用 i18n

如果你習慣偷懶,用 cy.contain() 某字串作為 selector
但專案有要使用 i18n 的話請注意!
commands.jsintegration/ 內的測試環境是不一樣的
commands.js 可以先在 index.js init i18n
但測試必須要 各別 init
且還要注意一點!
若在 local 起起來的環境預設語系是中文的話
Cypress 的瀏覽器預設是英文,因此他會取抓英文的翻譯,就對不到中文的值
最簡單的方式就是寫死語系為中文

但也有在 local 起起來是中文,在 Cypress 卻是英文的情形!
因每個人的系統設定而有所差別
所以這方面也要做調整!

plugins/index.js

module.exports = (on, config) => {  on('before:browser:launch', (browser, launchOptions) => {    if (browser.family === 'chromium' && browser.name !== 'electron'){
launchOptions.preferences.default.intl =
{ accept_languages: 'zh' };

return launchOptions;
}
);
};

support/index.js

import './commands';
import { init } from'../../../src/i18n';
init('zh');

support/commands.js

import { translate } from'../../../src/i18n';

integration/xxx.spec.js

import { init, translate } from'../../../src/i18n';describe('describe test case', function() {
beforeEach(() => {
init('zh');
})
})

8. 操作 local storage

有時因為測試需求
要初始化 local storage 或是塞值去避免不想要的產品流程等等
所以需要操作 local storage
在 Cypress 中,有提供 command 去清除 local storage
也就是: cy.clearLocalStorage()
但官方是沒有提供 set 的方法的,且 Cypress 的環境又無法直接跟 window 互動
在這裡推薦一個套件: cypress-localstorage-commands
他有很多方法: clear set get save restore remove
基本上所有的操作都涵蓋了,十分方便

npm i cypress-localstorage-commands

integration/xxx.spec.js

import "cypress-localstorage-commands"describe('describe test case', function() {
beforeEach(() => {
cy.setLocalStorage('key', '{"key": value}');
})
})

9. 辨識程式是否為 Cypress 操作

有時候會有一些需求會希望 Cypress 在運作時不要觸發某些事件
像是:使用數據
如果在自己的服務上有埋數據的話,若沒有擋掉 Cypress 會造成數據有不乾淨的髒資料
做法很簡單,只要利用 window.Cypress 物件去 判斷 Cypress 就可以了

if (window.Cypress) {
// we are running in Cypress
// so do something different here
} else {
// we are running in a regular ol' browser
}

不用安裝 Cypress 也能使用,所以如果你把 Cypress 安裝在某個資料夾
外層檔案也可以用 window.Cypress 判斷

10. 要點擊的元素被其他元素遮擋

如果發現,在點即時產生錯誤訊息是「要點擊的元素被其他元素遮擋」而無法點擊
首先先確認,是否真的有遮擋到

因為 Click 是點擊中間的點,但如果有懸浮的元素遮擋了原本要點擊的元素
可以考慮點擊其他位置
可以參考官方文件對於 cy.click() 參數的講解

https://docs.cypress.io/api/commands/click.html#Arguments
cy.get('img').click('topRight')

11. 設定 retries

在最新版的 Cypress 5.0.0 將 cypress-plugin-retries 整進 Cypress
可以在 cypress.json 設定,當錯誤發生要做幾次的「重新嘗試」
這我覺得是一個很棒的功能
因為很多 pipeline 測試不過,是因為網路不穩的問題
有了 retries 可以大幅降低測試出錯的機率

cypress.json

{
“retries”: {
// Configure retries for `cypress run`
// Default is 0
“runMode”: 1,
// Configure retries for `cypress open`
// Default is 0
“openMode”: 3
}
}

但我這裡遇到蠻弔詭的事
我升上了 5.0.0,但我的 retries 卻是沒有作用的
最後只能降版,改裝套件使用

npm install -D cypress-plugin-retries

support/index.js

require('cypress-plugin-retries')

cypress.json

{
"env":
{
"RETRIES": 2
}
}

Bitbucket pipeline

Bitbucket pipeline 和 GitHub Action 是差不多的服務
許多人拿他來做 CI/CD
今天我們就是要把測試整合上 pipeline 做 CI

首先先來設想一個情境
今天我要做一個測試來檢查註冊、登入功能是否正常
並把他上到 pipeline 上

所以我的 test case 就是:創建一個帳號,並檢查能否登入

但事情不是把程式碼丟上去這麼簡單而已
除了要編寫 yml 檔,也要設計一下如何讓我們在遠端測試,也能知道錯誤發生的點
不然如果在 pipeline 上測試跑不過,我們無法立即判斷問題
且有可以是因為網路連線問題等等的導致錯誤
再花時間確認問題會很浪費時間成本

因此我們會再搭配 Cypress 的另一個服務:Cypress DashBoard 來完成
只要在 npx cypress run 接上 --record --key xxxxxxxxxx 的參數
他就會將你跑的影片、問題截圖都放在上面,讓你快速定位問題

Cypress Dashboard

如果你有在用 pipeline 或是 Action 我認為 Dashboard 是非常好用的工具
很推薦使用
當然他是付費服務也有免費版,可以查看他的方案決定如何使用

測試全覽

https://docs.cypress.io/guides/dashboard/projects.html#Set-up-a-project-to-record

可以查看測試錯誤的過程影片與截圖

https://docs.cypress.io/guides/dashboard/runs.html#Run-details

透過 Dashboard 我們就不用像瞎子摸象一樣找問題了
十分清楚明瞭
那要如何使用 DashBoard 的服務呢?

https://docs.cypress.io/guides/dashboard/projects.html#Set-up-a-project-to-record
  1. 創建帳號與專案
    開啟 cypress GUI 畫面,切換到 Run 頁籤,點選 Connect to Dashboard

創建 Cypress Dashboard 帳號

現在就可以開始設定了

2. 設定 project ID

將 project ID 貼到 cypress.json

cypress.json

{
"projectId": "(你的 ID)",
}

3. record key
複製 record key,帶在 cypress run 後,跑完就會記錄在 Dashboard 了

npx cypress run --record --key (你的 Key)

那我們就來開始配置吧!

配置 cypress.json

首先你在 local 的 cypress.json 可能是長這樣的
寫死起起來的 url 以及 env 參數
讓 cypress 抓 env 的值來做登入

cypress.json

{
"projectId": "(你的 ID)",
"baseUrl": "http://localhost:3000/",
"env": {
"email": "test_e2e@test.com",
"pwd": "test1234"
},
"chromeWebSecurity": false,
"defaultCommandTimeout": 5000
}

但這樣會有一個問題就是,如果我都用 env 的值來 type
那要怎麼在每一次的測試都創建帳號呢?
也不可能每次都要想新帳號再 push
寫死不是個好方法,我們先拿掉 env

cypress.json

{
"projectId": "(你的 ID)",
"baseUrl": "http://localhost:3000/",
"chromeWebSecurity": false,
"defaultCommandTimeout": 5000
}

配置 yarn scripts

package.json

在外層先指定起 local 的 yarn scripts
假設我今天的專案是用 react

{
"scripts": {
"web": "react-native start"
}
}

e2e-cypress/package.json

先放著如何讓我每次測試時都能產生新帳號的問題,先來配置 yarn scripts
在 pipeline 上勢必得用 cypress run
因此我指定了 browser 並帶上 recordkey

{
"dependencies": {
"cypress": "4.7.0",
"cypress-localstorage-commands": "^1.2.1"
},
"scripts": {
"e2e:record": "npx cypress run --browser chrome --record --key xxxx-xxxxx-xxxx-xxxxxx"
}
}

指令準備好後就只剩配置我們的重頭戲 bitbucket-pipelines.yml

配置 bitbucket-pipelines.yml

Cypress 官方非常貼心有提供 各種 CI 的範例
官方示範的 pipeline Repo 在這
但他的設計主要是為純 HTML 的網頁寫的,使用的 docker image 也有點舊
可以參考 官方的 Repo 找到跟自己 Cypress 下載對應版本的 image

以下是我寫的版本,下面有詳細的說明:

image: cypress/browsers:node12.16.2-chrome81-ff75
options:
max-time: 30 // 跑的時間限制
pipelines:
default:
- step:
name: Install, build and tests
caches:
- node
script:
# 外層主程式的 yarn install
- yarn install --network-concurrency 1
# 將主程式起起來
- NODE_ENV=production yarn web &
- npx wait-on http://localhost:3000
- curl http://localhost:3000
- cd e2e-cypress
- yarn install --network-concurrency 1
- npx @bahmutov/print-env BITBUCKET
- yarn run e2e:record
-- --parallel --ci-build-id $BITBUCKET_BUILD_NUMBER
--env email=test$BITBUCKET_BUILD_NUMBER@test.com,
pwd=qwer1234
artifacts:
# store any generates images and videos as artifacts
- cypress/screenshots/**
- cypress/videos/**
definitions:
caches:
npm: $HOME/.npm
  1. 找到 image
    透過 這個網址,找到對應 Cypress 版本,把字串貼在 image: 之後
  2. 處理分層架構
    前面有講到因為 Cypress 檔案大又不常更新,所以將它丟到另一個資料夾處理
    因此,必須要 yarn install 兩次
    由外層起程式,內層起測試的架構
    在這裡不建議拆成兩個 Step
    貌似是 pipeline 的 Step 是獨立的環境,兩者無法相通
    外層起的程式,內層的測試訪問不到
  3. 處理 yarn install 的網路問題
    其實依照官網推薦,如果你是使用 yarn 建議是使用 yarn install --frozen-lockfile 以達到 npm ci 的效果
    但我發現有時候會因為網路問題,套件下載失敗….
    目前用過最好的方法就是帶 —-network-concurrency 1 參數
    讓他一個一個下載
    但缺點就是第一次下載或更新會載非常的久….
    所以 Cache 就很重要
  4. Cache 處理
    Bitbucket 的 Cache 不是很好處理
    按照文件 Cache Cypress 跟 yarn 我都沒有成功過….
    不過目前覺得直接 Cache node_modules 就蠻夠的
    大概是 9 分鐘和 12 秒的差距
    上面 Cache 的路徑雖然寫著 npm 但其實就是 Cache node_modules 的意思
    這邊也要特別注意一點,pipeline 不是跑過 download 就立即 Cache
    他是程序完全通過才會 Cache
    所以 pipeline 沒過,沒有 Cache 成功是正常情形,不是路徑問題喔
    還有一點要注意
    那就是 pipeline 上的 Cache 是要手動清的
    假設你更動了 package.json 他發現與先前 Cache 的內容不同
    他就會重新下載,但不會更新 Cache 的內容
    除非你手動清空,然後跑過 pipeline 讓他 Cache 新的 node_modules

我是猜測這邊會這樣處理是因為,每個 branch 都在跑
可能有些 branch 有更新,但大部分都是跟著 developpackage.json
因此如果每次跑就更新 Cache
反而會造成 Cache 太頻繁更新,要一直下載會更加不利
因此保持 Cache 是 develop 最新的狀態,我認為是最好的方式

5. 將程式運行移至背景執行
yarn web 後面加一個 & 就可以將這行指令移至背景執行
否則 pipeline 會一直卡在這個流程無法進行下一步

6. 確認編譯完成
因為 React、Vue、Agular 這類的前端框架需要編譯時間
若尚未編譯完成就直接訪問,會造成測試失敗
因此這裡使用了 wait-on 這個套件,搭配 curl 來確認
但因為 wait-on 只有在 bibucket 才會使用,因此不安裝在專案內
改用 npx 的方式載入

但其實光是用 wait-on 還是不夠
因此還是用 curl 訪問 local 看看,等到有回應在執行後續動作比較保險

7. 註冊測試帳號問題

由於我們預設的 case 是每一次都要註冊一個帳號,並測試登入
如果寫死在 cypress 裡,第一關的註冊就過不了了
因此我們把他從 cypress.json 拉出來
用 pipeline 的流水號方式來產生不同的帳號來測試註冊/登入

我們可以透過一個套件 @bahmutov/print-env
來取得 Bitbucket 上的資訊
因為他只用於 pipeline 上,我們就用 npx 的方式安裝他
他可以取的的資訊非常多元,Bitbucket 官方文件也有詳細的說明
可以參考參考

8. 加速測試時間

yarn e2e:record 後面,除了帶了要填入的帳號跟密碼
我還帶了一個參數: -- --parallel --ci-build-id $BITBUCKET_BUILD_NUMBER
這是為了讓測試可以同步執行,不需要一個接著一個,加速測試時間

https://docs.cypress.io/guides/guides/parallelization.html#Grouping-test-runs

可以參考一下 官方文件的解說

9. 語系處理

因為有使用 i18n 的緣故
我們強至網頁顯示中文,但 Cypress 瀏覽器是英文
所以我們可以在 yml 更改系統語系

export LANG=zh_TW.UTF-8

或是直接在 pipeline 的 setting 的環境變數設定

在幾個禮拜的努力下,我們的 Pipeline 終於上線
看到大家在使用自己寫的服務就好感動
透過 Cypress 也有發現一些平常不會發現的小問題
也降低改 A 壞 B 的風險!
寫自動化測試真的是一件很有意義的事
希望這篇文章可以幫助,正要接觸 Cypress 或是正在 Cypress 迷路的大家

拍個手讓我知道,這個文章對你們有幫助 ♥(´∀` )人

參考資料

  1. Cypress Doc
  2. [Cypress 2] 看官方文件學習 Command & Assertion
  3. [Cypress 3] 看官方文件學習 Variable and Aliases、Hooks、其他
  4. Cypress Continuous Integration
  5. cypress-example-kitchensink
  6. cypress-docker-images
  7. Variables in pipelines
  8. Validator for bitbucket-pipelines.yml

--

--

Kion

程式就是利用自動化與排程的特性解決問題 文章分類總覽: https://hackmd.io/@Kion/SyvyEks0L