利用 Cypress 和 Sentry 測試前端效能

在自動化測試前端效能時,可能會考慮的解法有:(1) Lighthouse CI Actionweb-vitals library + 丟到某個地方做資料儲存和 dashboard;另一個解法是 (2) 利用 Cypress + Sentry 來取得相關資訊並做呈現。

這篇文章會針對 Cypress + Sentry 這個解法做說明,並比較兩種解法。

流程

在做效能測試時,我們會希望能在統一的環境和機器規格下比較,也希望能定期或針對每個版本來做比較,最好在可能有潛在問題時通知開發者做改善。

如下流程所示圖,submit PR 後,在 CI/CD 的環境下 (在本文範例中是 GHA) trigger Cypress 的 E2E test script,跑測試並將資料送到 Sentry 或 New Relic 平台上。若資料超過設定的門檻,就發信作為 alert。

利用 Cypress 和 Sentry 測試前端效能

說明幾件事:

實作

利用 Cypress + Sentry 來做效能測試,要怎麼做呢?一步步拆解來看。

Step 1:由於要辨識跑測試的環境,因此在 GitHub 的 Secrets and variables 設定環境變數;如果想在本機測試記得要設定 .env 檔。這是為了當效能資料上傳到 Sentry 上時,要能辨識這是否為這樣的測試資訊,稍等會將這個資訊丟到 Sentry 上此 event 的 tag。

PERFORMANCE_TEST=cypress

Step 2:安裝 Cypress 和 Sentry,由於在這裡是用 react 建立專案,因此是安裝 @sentry/react,Sentry 有支援其他前後端各種語言,選擇適當的安裝說明即可看到相關的範例程式碼。

yarn add cypress @sentry/react

接著,檢視在 package.json 是否有 Cypress 的測試 script,記得設定檔要設定正確。

"scripts": {
  "cypress:open": "./node_modules/cypress/bin/cypress open",
  "cypress": "./node_modules/cypress/bin/cypress run"
}

加入測試檔案。

describe('performance test', () => {
  it('view this page', () => {
    cy.visit('http://localhost:3000/');
    cy.intercept('GET', '**/api=get-list', {
      "code": 200,
      "message": "success",
      "data": [{
          "photoUrl": "https://sample.com.tw?id=12345",
          "thumbnailUrl": "https://sample.com.tw/thumbnail?id=12345",
          "imageId": "12345",
          "name": "蛋糕 A"
        },
        {
          "photoUrl": "https://sample.com.tw?id=23456",
          "thumbnailUrl": "https://sample.com.tw/thumbnail?id=23456",
          "imageId": "23456",
          "name": "蛋糕 B"
        },
        {
          "photoUrl": "https://sample.com.tw?id=34567",
          "thumbnailUrl": "https://sample.com.tw/thumbnail?id=34567",
          "imageId": "34567",
          "name": "蛋糕 C"
        }
      ]
    });
  });
});

為了要讓瀏覽時送出效能測試資料,要初始化 Sentry、判斷是否在 GHA 環境來送出特定 tag,這樣之後在 Sentry 的 console 上才能順利撈到資料。

import * as Sentry from '@sentry/react';

Sentry.init({
    dsn: "https://test-sample-123456789.ingest.sentry.io/123456789",
    integrations: [
      new Sentry.BrowserTracing({
        tracePropagationTargets: ["localhost", /^https:\/\/test-sample\.web\.app/],
      }),
    ],
    tracesSampleRate: 1.0,
    replaysSessionSampleRate: 1.0,
    replaysOnErrorSampleRate: 1.0,
});

// 判斷是否在 GHA 環境來送出特定 tag
if (process.env['PERFORMANCE_TEST'] === 'cypress') {
  Sentry.setTag('test', 'peformance-test');
}

跑完測試後,稍等一下下即可看到測試結果。

在 Sentry 的 console 上,利用 tag 篩選 test:peformance-test 取得測試結果。

利用 Cypress 和 Sentry 測試前端效能

點進去這個 event 後,可以看到更多的細節,像是網路連線、資源載入的狀況、瀏覽器解析與渲染頁面 (不過這裡只會看到 javascript 執行時間來告訴我們是不是 long task) 還有 web-vitals 像是 FCP、LCP 和 FID 等。

利用 Cypress 和 Sentry 測試前端效能

如果對於瀏覽器輸入網址並送出後,到底發生什麼事情這個議題有興趣,可參考這裡

另外,若測試結果低於預期,則通知相關人等來做改善。關於及時通知怎麼做,可參考這裡

客製化測試流程

除了觀察頁面載入的狀況外,還可以自行定義要測試的流程,例如:圖片上傳。這是由於除了 Sentry 提供預設的 pageload 外,我們可能還需要追蹤更多的行為、事件或指標,那就需要一些客製化的設定來達成。

在這裡可以寫一個關於上傳圖片的 script,在整個過程中根據我們想要追蹤的部分做些設定來記錄其細節,這個就是 Sentry 的自訂追蹤功能。

來看怎麼設定追蹤客製化的 transaction,並在一個 transaction 裡面切分多個 span 來分段記錄細節。

在設定客製化的 transaction 上,要用 Sentry.startTransaction開始,並用 finish 結尾,它們兩者之間的部份即為追蹤範圍。

在這段範圍內,再利用 startChildfinish 切分成多個追蹤區段。例如:使用者想要上傳圖檔,在一連串的操作當中,可分為點擊上傳按鈕、發出 HTTP request 傳送檔案、收到檔案顯示回應訊息。

範例程式碼如下,這一連串的操作行為可當成一個 transaction,然後再切為兩個 span click-submit-buttonupload-file-request

import * as Sentry from '@sentry/react';

const transaction = Sentry.startTransaction({ name: 'upload-photo-transaction' });
const handleSubmit = (data) => mutate(data);

const { data, status, mutate } = useMutation({
  //...
  mutationFn: async (data: FileBase64) => {
    spanClickSubmitButton.finish();
    const spanUploadFile= transaction.startChild({ op: 'upload-file-request' });

    return axios.post(`/upload-record`).finally(() => {
      spanUploadFile.finish();
      transaction.finish();
    });
  }
});

當使用者點擊「上傳檔案」按鈕時,開始記錄 click-submit-button 這個 span,並在發出 HTTP request 前結束;發出 HTTP request 時開始記錄 upload-file-request 這個 span,然後在得到回應時結束。

<Button onClick={() => {
    const spanClickSubmitButton = transaction.startChild({ op: 'click-submit-button' });
    handleSubmit(data)
}>
  上傳檔案
</Button>

完成操作後,到 Sentry 的 console 查看,會看到這樣的資料。

客製化追蹤 transaction 和 span 的好處是可以追蹤我們想要的東西,例如在上面的例子,我們特別想知道使用者上傳檔案到底花了多少時間,那麼經由 upload-file-request 這個 span 就能計算出來,在後續要分析資料時也能很方便的被檢索出來。

產生 rendering performance 最大原因是 long task,藉由觀察 timeline 可知是否有這個問題。

在效能分析上,如果想要分析 loading performance,Sentry 提供 web vitals 的功能已經很足夠了;但若想改善 rendering performance 或特定 UI 的行為,使用 Sentry 的自訂追蹤功能會是很好的解法。

總結

若是在評估要用 (1) Lighthouse CI Action 或 web-vitals library + 丟到某個地方做資料儲存和 dashboard 或 (2) Cypress + Sentry 來測試效能,我的想法是,(1) 與 (2) 都能做到 page load 的效能檢測與提供 web vitals 資訊,唯一不同處在於 (2) 可提供自行定義測試流程,以及除了 web vitals 之外更多資訊…看實際情況來做出適合的選擇吧!


Sentry Cypress End-to-End Testing GitHub Actions Lighthouse Loading Performance Rendering Performance Synthetic Monitoring active monitoring Web Vitals Memori 轉譯效能 加載效能 效能監控 效能調校 CI/CD Core Web Vitals cypress.io lighthouse-cli 端對端測試 自動化測試 前端效能 系列文