Cypress测试掘金的首页渲染时间

1,678 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第8天,点击查看活动详情

首屏渲染时间到底是什么

先来看几个web.dev上面有关页面性能的几个重要测量指标:

名称(英)名称(中)含义
First Contentful Paint(FCP)首次内容绘制页面从开始加载到内容的任何部分在屏幕上完成渲染的时间
Largest Contentful Paint(LCP)最大内容绘制页面从开始加载到最大文本块或图像元素在屏幕上完成渲染的时间
First input delay(FID)首次输入延迟从用户第一次与网站交互(例如点击按钮)直到浏览器实际能够对交互做出响应所经过的时间
Time to Interactive(TTI)可交互时间页面从开始加载到视觉上完成渲染并初始化脚本,能够快速可靠地响应用户输入所需的时间

这里我们更多关注的是FCPLCP,结合一张图可以更好的理解:

image.png

对这个页面的渲染过程来说,FCP是页面上首次出现文本和图标的时间点,虚线框选的部分是当前页面的最大元素,这里是图片,因此图片渲染出来的时间点就是LCP的时间。此外,大家可能也听过FMP这个指标,也即First Meaningful Paint(首次有效绘制),但这个指标并没有一个统一的标准,每个网站上也不一样,更多是作为辅助指标。

那么应该采取哪个指标来衡量首屏渲染时间,web.dev上的结论是:

Based on discussions in the W3C Web Performance Working Group and research done at Google, we've found that a more accurate way to measure when the main content of a page is loaded is to look at when the largest element was rendered.

显然是倾向于LCP的,这也更符合用户的体验。那下面就是如何测的问题了,准确说是如何优雅的测。这里我介绍一种使用自动化测试框架Cypress的方法,至于原因,一来是最近在接触这方面的内容,再者确实很方便。至于安装,按照官网来就行,感兴趣的话也可以参考我近期「一文,教你搭建前端自动化测试环境」文章中关于Cypress的部分。

回到测试的思路,这里借助DOM元素记录页面加载前后的时间差,严格来说这个计算出的时间是小于LCP的,但肯定大于FCP,也更接近用户的真实体验。将这个值记为fs,也即First Paint

测试用例编写思路

首先实现核心逻辑,也即利用测试页面加载前后的时间戳之差计算首屏渲染时间,这里用到了两个Cypress API

  • beforeEach():在每个测试用例运行之前运行一次
  • afterEach():在每个测试用例运行之后运行一次

我们在测试用例中会访问页面,所以利用beforeEach()很容易实现页面加载前,问题在于访问页面后应该什么时候记录时间戳,考虑到页面加载过程对用户而言最直观的就是DOM元素的变化,因此这里选取了页面上的一个按钮是否渲染出来作为页面加载完的依据。代码如下:

describe("测试首屏加载时间", () => {
  let startTime;
  const url = "https://juejin.cn";
  beforeEach(() => { 
    startTime = +new Date();
  });
  afterEach(() => {
    let fp = +new Date() - startTime;
    console.log(fp);
  });
  it("加载掘金页面", () => {
    cy.visit(`${url}`);
    cy.get('[d="M3.69678 3.69531L9.00008 8.9986L14.3034 14.3019"]').click(); //消除弹窗
    cy.get(
      ".sticky-block > .sidebar-block > .block-body > a > .app-link > .content-box > .headline"
    ).should("contain", "下载稀土掘金APP");
  });
});

这里cy.get()中的选择器是直接使用Cypress选取的,所以可能不太优雅,选取的原因是在没登录时加载掘金首页会出现闪念笔记的弹窗,但选择器这里目前不是我们关心的,先看测试用例运行的结果:

image.png

可以看到测试用例通过🍻🍻🍻,而且在控制台打印出了渲染时间24558ms,根据电脑和网络情况可能会有所不同

下面,来完善代码

  • 实现多次测试取均值
    • 使用fpArr数组保存每次记录的时间戳差值->使用lodash_.mean方法求数组均值

      Cypress内置lodash,使用const { _ } = Cypress引入就能直接使用

  • 不是将结果打印到控制台,而是记录下来
    • 使用cy.exec()执行node 命令,将数据写入到指定路径下的fs.json文件

代码及结果

const { _ } = Cypress;

describe("测试首屏加载时间", () => {

  const TEST_TIMES = 5;
  let startTime;
  let fpArr = [];
  const url = "https://juejin.cn";
  
  beforeEach(() => {
    startTime = +new Date();
  });
  
  afterEach(() => {
    const fp = +new Date() - startTime;
    // 将数据记录到fpArr
    fpArr.push(fp);
    // 将fpArr写入本地文件
    cy.exec(
      `echo ${JSON.stringify({
        fpArr,
        fpAvg: _.floor(_.mean(fpArr), 2),
        pageUrl: url,
      })} >tests/e2e/fixtures/fp.json` // 项目根目录下,也即包含默认cypress.config.js配置文件的目录
    );
  });
  for (let i = 0; i < TEST_TIMES; i++) {
    // 定义页面加载完成的标志:断言dom结构
    it("加载掘金页面", () => {
      cy.visit(`${url}`);
      cy.get('[d="M3.69678 3.69531L9.00008 8.9986L14.3034 14.3019"]').click();
      cy.get(
        ".sticky-block > .sidebar-block > .block-body > a > .app-link > .content-box > .headline"
      ).should("contain", "下载稀土掘金APP");
    });
  }
});


运行Cypress后可以看到测试通过,并且在tests/e2e/fixtures目录下生成了fs.json文件,其中包括5次测试记录的时间、均值以及测试页面的地址🍻🍻🍻

image.png

image.png

总结

  • 借助DOM元素和时间戳计算页面加载前后的时间差作为首屏渲染时间
  • 使用cy.exec()命令将数据写入json文件