前端UI自動化測試(TypeScript+Jest+Puppeteer)

最近幾個月來,筆者一直在探索前端UI自動化測試的場景和方案。最初的時候,面對衆多的技術選型,顯得有些茫然,而團隊此前也沒有太多關於這方面的經驗,只能一步一步摸索總結,當然期間也踩過不少坑,最終形成了一套相對穩定的測試方案,未來還將繼續擴展和完善。

項目地址:jest-puppeteer-testing

在這篇文章中,筆者想和大家分享一下自己對於UI自動化測試的思考和經驗。

爲什麼要進行UI自動化測試

業務的更新迭代頻繁,傳統測試大部分都還是手工、肉眼的模式來進行,無法滿足產品敏捷開發、快速迭代的需求。而UI自動化能讓全功能的迴歸變得簡單,釋放純手工測試的人力資源,並且迴歸測試能夠覆蓋到所有的邏輯場景,這對測試的效率,以及整個開發流程的效率都是很大的提升,並且能夠規避很多人的主觀和客觀因素導致的漏測或者疏忽。

其他測試方式的侷限性:

單元測試(Unit Testing)

事實上,單元測試確實能夠幫助我們發現大部分的問題,但是在複雜的前端交互中,單純的單元測試並不能真實地反映用戶操作的路徑,而單元測試一般的場景是測試一系列的功能集合。

快照測試(Snapshot Testing)

DOM結構並不能完全反映頁面的視覺效果,DOM結構不變並不完全等於樣式不變。此外,大多數工具都是React專用,非React應用基本不支持。

筆者想說:

很多人認爲,UI總是頻繁的變動,導致測試用例維護成本高,性價比低,因此UI自動化測試比較適合場景穩定的業務。其實不是,這裏的UI不僅僅指的是視覺,更多的是業務邏輯。UI可以多變,但業務邏輯一定是趨於穩定的,尤其是核心業務,想一想用戶得多辛苦才能適應這種業務邏輯頻繁變更的產品啊。

關於技術選型

TypeScript + Jest + Puppeteer

事實上,對於UI自動化測試來說,許多框架之間並沒有太多差別,也從來不是影響整套測試用例是否健壯的關鍵性因素。相比之下,如何提高測試用例穩定性及全面性纔是讓UI自動化測試方案落地的重要細節。

開發實踐

項目搭建

大家可以參考jest-puppeteer-testing,這裏不再累述。

核心文件

// setup/expect-image-snapshot.ts
// 讓jest支持保存/比對屏幕截圖
import { configureToMatchImageSnapshot } from 'jest-image-snapshot';

expect.extend({
  toMatchImageSnapshot: configureToMatchImageSnapshot({
    customSnapshotsDir: '__image_snapshots__',
  }),
});
// setup/enhance-puppeteer.ts
// 增強puppeteer功能,如:攔截請求並使用mock數據
import { onRequestInterceptor } from '../utils/request';

jest.setTimeout(30000);

beforeAll(async () => {
  page.on('request', onRequestInterceptor); // 攔截請求,使用代理數據
  await page.setRequestInterception(true);
});
// utils/request.ts
// mock數據的核心文件
// 這裏只攔截xhr或fetch請求,當然你也可以自行擴展
import { URL } from 'url';
import { Request } from 'puppeteer';
import mocks from '../mocks';

// 設置請求攔截器的數據,用於同一請求返回不同結果,生效一次後自動銷燬
export const interceptors: { [api: string]: any } = {};
export const setRequestInterceptor = (api: string, value: any) => {
  interceptors[api] = value;
};

export const onRequestInterceptor = (request: Request) => {
  const resourceType = request.resourceType();
  if (resourceType === 'xhr' || resourceType === 'fetch') {
    const location = new URL(request.url());
    const mockKey = location.pathname;
    if (mockKey && mocks.hasOwnProperty(mockKey)) {
      const mock = mocks[mockKey];
      let response: any;
      if (typeof mock === 'function') {
        response = mock({ location, request, interceptor: interceptors[mockKey] });
        delete interceptors[mockKey]; // 生效一次後自動銷燬
      } else {
        response = mock;
      }
      if (response) {
        if (response.body != null && typeof response.body === 'object') {
          response.body = JSON.stringify(response.body);
        }
        request.respond(response);
      }
    } else {
      request.continue();
    }
  } else {
    request.continue();
  }
};

其他文件

  • shared.d.ts定義數據類型
  • cases目錄下存放測試用例
  • mocks目錄下存放mock數據
  • utils目錄下存放工具方法

補充說明:關於mock的類型定義,可以在shared.d.ts中找到,當然你也可以在這裏增加其他類型定義

經驗總結

測試地址的選擇(本地/線上)

  • 本地服務器:請求響應快,測試結果穩定,但無法排除由線上環境差異或代碼打包過程中引發的問題
  • 線上服務器:能夠反映網站真實的展示,無需額外啓動服務器,任何時候都可以測試,但受網絡因素影響,可能導致測試結果不穩定

儘量抹平不確定因素帶來的影響

如維持數據請求的結果穩定,日期時間穩定,保證頁面渲染的一致性。假如由於數據返回或時間的不確定性,導致每次頁面渲染不一樣,那這樣測試也失去了意義。

儘量明確保存屏幕截圖的時機

如訪問一個頁面後截圖,由於網絡因素的原因,圖片資源並不是每次都加載完成,從而導致截圖前後不一樣。

......(暫時寫這麼多,有空再更)

總結

事實上,這套UI自動化測試方案更像是端到端測試(E2E Testing),即模擬一個用戶將程序作爲一個完全的黑盒,打開應用程序模擬輸入,檢查功能以及界面是否正確,配合屏幕截圖可以直觀感受到用戶進行某些交互產生的具像化視覺效果。

項目地址:jest-puppeteer-testing
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章