搭建自動化 Web 頁面性能檢測系統 —— 實現篇

来源:https://www.cnblogs.com/dtux/archive/2023/06/20/17493445.html
-Advertisement-
Play Games

>我們是[袋鼠雲數棧 UED 團隊](http://ued.dtstack.cn/),致力於打造優秀的一站式數據中台產品。我們始終保持工匠精神,探索前端道路,為社區積累並傳播經驗價值。。 >本文作者:琉易 [liuxianyu.cn](https://link.juejin.cn/?target=h ...


我們是袋鼠雲數棧 UED 團隊,致力於打造優秀的一站式數據中台產品。我們始終保持工匠精神,探索前端道路,為社區積累並傳播經驗價值。。

本文作者:琉易 liuxianyu.cn

前段時間分享了《搭建自動化 Web 頁面性能檢測系統 —— 設計篇》,我們提到了性能檢測的一些名詞和自研性能檢測系統的原因,也簡單介紹了一下系統的設計。在這裡我們書接上篇記錄下是如何實現一個性能檢測系統的。

開始前歡迎大家 Star:https://github.com/DTStack/yice-performance

先看下性能檢測系統 —— 易測的實現效果:

file

一、技術選型

服務端框架選擇的是 Nestjs,Web 頁面選擇的是 Vite + React。由於所在團隊當前的研發環境已經全面接入自研的 devops 系統,易測收錄的頁面也是對接 devops 進行檢測的。

1.1、整體架構設計

易測的檢測服務基於 Lighthouse + Puppeteer 實現。下圖是易測的一個整體架構圖:

file

1.2、實現方案

易測的檢測流程是:根據子產品的版本獲取到待檢測的地址、對應的登錄地址、用戶名和密碼,然後通過 Puppeteer 先跳轉到對應的登錄頁面,接著由 Puppeteer 輸入用戶名、密碼、驗證碼,待登錄完成後跳轉至待檢測的頁面,再進行頁面性能檢測。如果登錄後還在登錄頁,表示登錄失敗,則獲取錯誤提示並拋出到日誌。為了檢測方便,檢測的均為開發環境且將登錄的驗證碼校驗關閉。

以下是易測的檢測流程圖:

file

二、Lighthouse

易測通過 Node 模塊引入 Lighthouse,不需要登錄的頁面檢測可以直接使用 Lighthouse,基礎用法:

const lighthouse = require('lighthouse');
const runResult = await lighthouse(url, lhOptions, lhConfig);

2.1、options

lhOptions 的主要參數有:

{
  port: PORT, // chrome 運行的埠
  logLevel: 'error',
  output: 'html', // 以 html 文件的方式輸出報告
  onlyCategories: ['performance'], // 僅採集 performance 數據
  disableStorageReset: true, // 禁止在運行前清除瀏覽器緩存和其他存儲 API
}

2.2、config

lhConfig 的主要參數有:

{
  extends: 'lighthouse:default', // 繼承預設配置
  settings: {
    onlyCategories: ['performance'],
    // onlyAudits: ['first-contentful-paint'],
    formFactor: 'desktop',
    throttling: {
      rttMs: 0, // 網路延遲,單位 ms
      throughputKbps: 10 * 1024,
      cpuSlowdownMultiplier: 1,
      requestLatencyMs: 0, // 0 means unset
      downloadThroughputKbps: 0,
      uploadThroughputKbps: 0,
    },
    screenEmulation: {
      mobile: false,
      width: 1440,
      height: 960,
      deviceScaleFactor: 1,
      disabled: false,
    },
    skipAudits: ['uses-http2'], // 跳過的檢查
    emulatedUserAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4695.0 Safari/537.36 Chrome-Lighthouse',
  },
}

settings 屬於 Lighthouse 的運行時配置,主要是用來模擬網路和設備的信息,以及使用到哪些審查器。如果檢測的頁面有 web 端和 h5 端之分,也是在 settings 進行配置。

檢測結果會有總分、各小項的耗時、瀑布圖、改進建議等,如下:

file

三、Puppeteer

需要登錄後才能訪問的頁面涉及到登錄、點擊等操作,我們需要藉助 Puppeteer 來模擬點擊。基礎用法:

const puppeteer = require('puppeteer');

const browser = await puppeteer.launch(puppeteerConfig);
const page = await browser.newPage();

3.1、puppeteerConfig

{
  args: ['--no-sandbox', '--disable-setuid-sandbox', `--remote-debugging-port=${PORT}`],
  headless: true, // 是否使用無頭瀏覽器
  defaultViewport: { width: 1440, height: 960 }, // 指定打開頁面的寬高
  slowMo: 15, // 使 Puppeteer 操作減速,可以觀察到 Puppeteer 的操作
}

headless 為 false 時方便本地調試,通過調整 slowMo 的大小可以觀察到 Puppeteer 的模擬操作。

四、開始檢測

4.1、主方法

const taskRun = async (task: ITask, successCallback, failCallback, completeCallback) => {
  const { taskId, start, url, loginUrl } = task;
  try {
    // 依據是否包含 devops 來判斷是否需要登錄
    const needLogin = url.includes('devops') || loginUrl;
    console.log(
      `\ntaskId: ${taskId}, 本次檢測${needLogin ? '' : '不'}需要登錄,檢測地址:`,
      url
    );

    // 需要登錄與否會決定使用哪個方法
    const runResult = needLogin ? await withLogin(task) : await withOutLogin(task);

    // 保存檢測結果的報告文件,便於預覽
    const urlStr = url.replace(/http(s?):\/\//g, '').replace(/\/|#/g, '');
    const fileName = `${moment().format('YYYY-MM-DD')}-${taskId}-${urlStr}`;
    const filePath = `./static/${fileName}.html`;
    const reportPath = `/report/${fileName}.html`;
    fs.writeFileSync(filePath, runResult?.report);

    // 整理性能數據
    const audits = runResult?.lhr?.audits || {};
    const auditRefs =
      runResult?.lhr?.categories?.performance?.auditRefs?.filter((item) => item.weight) || [];
    const { score = 0 } = runResult?.lhr?.categories?.performance || {};

    const performance = [];
    for (const auditRef of auditRefs) {
      const { weight, acronym } = auditRef;
      const { score, numericValue } = audits[auditRef.id] || {};
      if (numericValue === undefined) {
        throw new Error(
          `檢測結果出現問題,沒有單項檢測時長,${JSON.stringify(audits[auditRef.id])}`
        );
      }
      performance.push({
        weight,
        name: acronym,
        score: Math.floor(score * 100),
        duration: Math.round(numericValue * 100) / 100,
      });
    }
    const duration = Number((new Date().getTime() - start).toFixed(2));

    // 彙總檢測結果
    const result = {
      score: Math.floor(score * 100),
      duration,
      reportPath,
      performance,
    };

    // 拋出結果
    await successCallback(taskId, result);

    console.log(`taskId: ${taskId}, 本次檢測耗時:${duration}ms`);
    return result;
  } catch (error) {
    // 錯誤處理
    const failReason = error.toString().substring(0, 10240);
    const duration = Number((new Date().getTime() - start).toFixed(2));
    await failCallback(task, failReason, duration);
    console.error(`taskId: ${taskId}, taskRun error`, `taskRun error, ${failReason}`);
    throw error;
  } finally {
    completeCallback();
  }
};

4.2、不需要登錄

const withOutLogin = async (runInfo: ITask) => {
  const { taskId, url } = runInfo;
  let chrome, runResult;
  try {
    console.log(`taskId: ${taskId}, 開始檢測`);

    // 通過 API 控制 Node 端的 chrome 打開標簽頁,藉助 Lighthouse 檢測頁面
    chrome = await chromeLauncher.launch(chromeLauncherOptions);
    runResult = await lighthouse(url, getLhOptions(chrome.port), lhConfig);
    
    console.log(`taskId: ${taskId}, 檢測完成,開始整理數據`);
  } catch (error) {
    console.error(`taskId: ${taskId}, 檢測失敗`, `檢測失敗,${error?.toString()}`);
    throw error;
  } finally {
    await chrome.kill();
  }

  return runResult;
};

4.3、需要登錄

const withLogin = async (runInfo: ITask) => {
  const { taskId, url } = runInfo;
  
  // 創建 puppeteer 無頭瀏覽器
  const browser = await puppeteer.launch(getPuppeteerConfig(PORT));
  const page = await browser.newPage();

  let runResult;
  try {
    // 登錄
    await toLogin(page, runInfo);
    // 選擇租戶
    await changeTenant(page, taskId);

    console.log(`taskId: ${taskId}, 準備工作完成,開始檢測`);

    // 開始檢測
    runResult = await lighthouse(url, getLhOptions(PORT), lhConfig);
    
    console.log(`taskId: ${taskId}, 檢測完成,開始整理數據`);
  } catch (error) {
    console.error(`taskId: ${taskId}, 檢測出錯`, `${error?.toString()}`);
    throw error;
  } finally {
    // 檢測結束關閉標簽頁、無頭瀏覽器
    await page.close();
    await browser.close();
  }

  return runResult;
};

4.4、模擬登錄

所在團隊的子產品均需要登錄後才能訪問,且每次檢測打開的都是類似無痕瀏覽器的標簽頁,不存在登錄信息的緩存,所以每次檢測這些頁面前需要完成登錄操作:

const toLogin = async (page, runInfo: ITask) => {
  const { taskId, loginUrl, username, password } = runInfo;
  try {
    await page.goto(loginUrl);
    // 等待指定的選擇器匹配元素出現在頁面中
    await page.waitForSelector('#username', { visible: true });

    // 用戶名、密碼、驗證碼
    const usernameInput = await page.$('#username');
    await usernameInput.type(username);
    const passwordInput = await page.$('#password');
    await passwordInput.type(password);
    const codeInput = await page.$('.c-login__container__form__code__input');
    await codeInput.type('bz4x');

    // 登錄按鈕
    await page.click('.c-login__container__form__btn');
    // await page.waitForNavigation();
    await sleep(Number(process.env.RESPONSE_SLEEP || 0) * 2);

    const currentUrl = await page.url();
    // 依據是否包含 login 來判斷是否需要登錄,若跳轉之後仍在登錄頁,說明登錄出錯
    if (currentUrl.includes('login')) {
      throw new Error(`taskId: ${taskId}, 登錄失敗,仍在登錄頁面`);
    } else {
      console.log(`taskId: ${taskId}, 登錄成功`);
    }
  } catch (error) {
    console.error(`taskId: ${taskId}, 登錄出錯`, error?.toString());
    throw error;
  }
};

4.5、得分落庫

等待所有的檢測步驟都完成後,在 successCallback 方法中處理檢測數據,此時可根據不同的性能指標計算得出最終得分和小項得分,統一落庫。

五、自動檢測

除了可以在頁面手動觸發檢測,易測主要使用的是自動檢測。自動檢測的目的是方便統計所有子產品的性能趨勢,便於分析各版本間的性能變化,以及子產品間的性能優劣,最終得出優化方向。

5.1、任務主動調度

易測試運行階段,由於使用的是開發環境進行檢測,所以將自動檢測時間設置為工作時間的間隙,減少影響檢測結果的干擾因素,後續正式部署後,也將調低檢測的頻率。

file

自動檢測可以主動進行任務的調度,也可以手動觸發任務,藉助 @nestjs/schedule 實現定時任務:

import { Cron } from '@nestjs/schedule';

export class TaskRunService {
  // 每分鐘執行一次 https://docs.nestjs.com/techniques/task-scheduling#declarative-cron-jobs
  @Cron('0 * * * * *')
  async handleCron() {
    // 檢測版本的 cron 符合當前時間運行的則創建任務
    process.env.NODE_ENV === 'production' && this.checkCronForCurrentDate();
  }
}

5.2、失敗告警

檢測失敗會有釘釘通知,點擊可快速跳轉至易測內查看具體原因。

file

file

5.3、性能趨勢圖

由下方的趨勢圖簡單分析後,可以得出子產品版本間的性能變化。

file

六、對接內部系統

6.1、對接 Jenkins

所在團隊的子產品在版本間做了一些腳手架的封裝升級,對接 Jenkins 就可以採集到各個版本間構建時長和構建後的文件大小等信息的變化,有助於性能相關數據的彙總、腳手架的分析改進。
在 Jenkins 的構建回調里,處理後可以拿到構建時長和構建後的文件大小等信息,由 Jenkins 調用易測提供的介面,按分支處理好版本後將數據落庫,在易測中展示出來。

七、結尾

如果你也準備搭建一個自己團隊的檢測系統,可以參考下易測的設計思路,希望這兩篇文章對你的工作有所助力。
完成上述工作後,接下來需要考慮的有易測功能的許可權控制、數據分析、如何根據業務場景進行檢測等方面。畢竟 Lighthouse 檢測的一般是單個頁面,而業務場景一般是工作流程的編排即流程的整體操作。

最後,歡迎大家不吝 Star:https://github.com/DTStack/yice-performance


最後

歡迎關註【袋鼠雲數棧UED團隊】~
袋鼠雲數棧UED團隊持續為廣大開發者分享技術成果,相繼參與開源了歡迎star


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • ##[Ooonly新人貼]記錄工作中遇到的問題,話不多說先上乾貨 問題:類似K線與藍牙接收部門模塊,要求由原來的接收串口中斷改為DMA接收。據說要用到空閑中斷與DMA中斷,但是經模擬發現DMA每完成傳輸一個數據(比如1BYTE)就會進入空閑中斷(k線發現這種情況),考慮到這樣進入中斷的頻率和以前串口 ...
  • 更改緩衝區(Change Buffer)是一種特殊的數據結構,用於緩存不在緩衝池中的二級索引(secondary index)頁的更改。可能來自於 INSERT、UPDATE 或 DELETE 操作(數據操作語言,DML)的緩衝更改,會在後續通過其他讀操作將這些頁載入到緩衝池時被合併。 ...
  • 摘要:華為宣佈實現了自主創新的MetaERP研發,並且完成了對舊ERP系統的全面替換,這其中,就採用了華為雲GaussDB資料庫特有的全密態技術,對ERP系統中的絕密數據進行加密保護,從而保障了數據的安全。 ERP系統在幫助企業優化業務流程、實現數字化管理方面有重要作用,可以說企業所有的業務流轉都需 ...
  • 摘要:HyG圖計算引擎採用CSR格式來存儲圖的拓撲信息,CSR格式可以將稀疏矩陣的存儲空間壓縮,進而大大降低圖的存儲開銷,同時具備訪問效率高、格式易轉化等優點。 本文分享自華為雲社區《CSR格式如何更新? GES圖計算引擎HyG揭秘之數據更新》,作者: π 。 HyG圖計算引擎採用CSR格式來存儲圖 ...
  • MVCC併發版本控制 本文大部分來自《MySQL是怎樣運行的》,這裡只是簡單總結,用於各位回憶和複習。 版本鏈 對於使用 InnoDB 存儲引擎的表來說,它的聚簇索引記錄中都包含兩個必要的隱藏列(不知道的快去看《MySQL是怎樣運行的》) trx_id :每次一個事務對某條聚簇索引記錄進行改動時,都 ...
  • 聲明式UI其實並不是近幾年的新技術,但是近幾年聲明式UI框架非常的火熱。單說移動端,跨平臺方案有:RN、Flutter。iOS原生有:SwiftUI。android原生有:compose。可以看到聲明式UI是以後的前端發展趨勢。而狀態管理是聲明式UI框架的重要組成部分。 ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 前端實現文件預覽功能 需求:實現一個線上預覽pdf、excel、word、圖片等文件的功能。 介紹:支持pdf、xlsx、docx、jpg、png、jpeg。 以下使用Vue3代碼實現所有功能,建議以下的預覽文件標簽可以在外層包裹一層彈窗 ...
  • 一.簡介 在做web ui自動化時,遇到操作視頻的時候有時比較讓人頭疼,定位時會發現只有一個<video>標簽,用selenium來實現的話比較麻煩,使用js後我們只需定位到video標簽,然後通過js 中處理video的相關屬性和方法就可實現,我們繼續往下看。 二.實例用法 1.獲取視頻的總時長( ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...