捨棄 create-react-app 之餘還架了個 astro blog 昭告天下:e2e 測試
今天介紹的 e2e 測試框架是 cypress,必須先說個人的使用體驗並不算太好,執行時有機率出現誤報(明明畫面已經渲染了預期內容,卻偵測不出來),要在腳本中引用第三方工具套件(比如 dayjs)的方法也不太直覺。但畢竟它還是省了不少手動操作的工,雖不完美但還是多少幫的上一點忙。
如果有使用 Playwright 或是其他 e2e 測試框架經驗的讀者,歡迎分享交流你的開發心得 (;´༎ຶД༎ຶ`)
設定流程
寫下這篇文章時 cypress 版本為 13.0.0,此代從「安裝」到「產生第一份 e2e 腳本」的流程大至如下:
- 透過終端安裝(
yarn add -D cypress
)後,執行npx cypress run
啟動 cypress 的設定儀表板。之後可以把啟動指令更新到 package.json 或 Makefile 中 - 參考官方的儀表板畫面點選左側的
E2E Testing
選項,此時 cypress 會偵測當下的專案是否有任何設定檔,沒有時會幫你自動建立好對應內容 - 設定完畢後,專案根目錄應該會出現一個
./cypress
資料夾(其中包含fixtures
與support
)以及cypress.config.ts
檔 - 選擇執行 e2e 測試後,儀表板會接著詢問你要在哪一個環境(Chrome/FireFox/Electron)跑測試。確認環境後,因專案內還沒有任何 e2e 腳本,儀表板會詢問你是否要建立一份新規格。確認建立後,專案中的
./cypress
中會多一個e2e
資料夾來讓你放測試腳本
參數設定
完成第一次設定流程後,專案根目錄應該會出現一個 cypress.config.ts
檔(沒有請直接手動新增一份)。
與 webpack 設定哲學一樣,個人習慣從最小可動內容開始,未來有更多需求再慢慢疊加上去:
import { defineConfig } from 'cypress';
export default defineConfig({
e2e: {
baseUrl: 'http://localhost:3000',
},
retries: 2,
video: false,
});
設定 baseUrl
後,在 e2e 測試腳本中可直接省略此段內容,即原本的 cy.visit('http://localhost:3000/product?id=xxxxxxx')
可以省略 domain、直接寫 cy.visit('/product?id=xxxxxxx')
即可。
考慮到有時 cypress 就是會有抓不到目標 DOM 的情況,在設定中給予兩次重試機會。錄影(video
)則根據實際需求選擇是否開啟。
而沒有在設定檔透過 Preprocessors API 傳入 webpack 設定則是因為 @cypress/webpack-preprocessor 的安裝說明列出依賴套件包含 babel
。考慮到整個專案除了 cypress 以外沒有人會用到它,最後決定放棄 alias 的便利性,畢竟在 cypress 腳本中需要大量引用其他模組的情境也比較少。
特殊處理:支援 dayjs
確認 13.0.0 版有效。請先在 ./cypress/support/commands.ts
追加以下內容:
/// <reference types="cypress" />
import dayjs from 'dayjs';
declare global {
namespace Cypress {
interface Cypress {
dayjs(date?: dayjs.ConfigType): dayjs.Dayjs;
}
}
}
Cypress.dayjs = dayjs;
接著可以透過 e2e 腳本確認是否設定成功:
describe('[dayjs]', () => {
it('should be able to use', () => {
const dayjsString = Cypress.dayjs().format('YYYY-MM-DD');
const today = new Date().toISOString().split('T')[0];
expect(dayjsString).to.equal(today);
});
});
腳本
cypress 的語法與 jest 類似,基本上測試都是以 describe
/ it
組成。以下即是一個簡單的「進入 ${baseUrl}/product?id=xxxxxxx
頁,點擊購買按鈕後,購物車中的對應產品數量應等於 1」:
describe('product shopping chart', () => {
it('should increase item amount accordingly', () => {
cy.visit('/product?id=xxxxxxx');
cy.get('.buy').click();
cy.get('.chart_sum .item-xxxxxxx').should('eq', '1');
});
});
.should()
中能使用的斷言(assertions)語法可參考 cypress 官方的整理結果。
個人經驗是寫腳本大部分的時間會花在透過 .get()
或 .contains()
來定位元件,尤其是在使用第三方 UI 套件的情況下(DOM 結構複雜)。這類路徑或是選取器複雜的腳本建議註解不要省,不然三天後大概就不知道是在選畫面上的什麼東西了。
於 Node.js 環境執行
上面透過 npx cypress open
會讓 cypress 開啟儀表板,再讓工程師與儀表板互動來開始測試。而當你只想等 cypress 跑完測試、看看結果就好的時候,則可以參考以下方式來跳過手動的部分:
/* Packages */
import cypress from 'cypress';
import webpack from 'webpack';
import WebpackDevServer from 'webpack-dev-server';
/* Data */
import webpackDevelopmentConfig, {
webpackDevServerConfig,
} from '@/config/webpack.config.development';
import config from '@/cypress.config';
/* Functions */
async function runCypressE2eTest() {
const compiler = webpack(webpackDevelopmentConfig);
const devServer = new WebpackDevServer(
{ ...webpackDevServerConfig, open: false },
compiler
);
// 不使用 try catch 捕捉錯誤,當 e2e 出錯時,直接暴露錯誤+停止流程
await devServer.start();
await cypress.run({ browser: 'chrome', config });
await devServer.stop();
}
/* Main */
runCypressE2eTest();
上述 ./script/e2e.ts
在做的事情是:啟動 webpack dev server 後,讓 cypress 在 chrome 環境跑過一遍 e2e 腳本。需要更多說明可參考官方文件。
然後在 Makefile 補上以下內容即可在終端輸入 make e2e
執行 cypress e2e 測試了:
.PHONY: e2e
e2e:
node -r esbuild-runner/register ./script/e2e.ts
恭喜,我們又減少了一些需要手動的工作 (゚ ω ゚)
總結
寫腳本的痛苦多半發生在尋找元件的時候,且在 cypress 中想執行「查詢元件是否存在」的腳本也不太直覺(寫法可參考個人過去文章)。如果有使用其他框架寫 e2e 測試腳本的朋友,歡迎留言分享你們在寫 e2e 測試時最大的困難通常是什麼 (›´ω`‹ )
感謝看到這裡的你。