天天看点

Web性能测试——页面弹出窗口中内容加载时间的计算

作者:软件测试开发技术栈
Web性能测试——页面弹出窗口中内容加载时间的计算

为什么Web性能很重要

研究表明,更好的核心网页指标可以提高用户互动度和业务指标。例如:

  • 研究表明,当网站达到核心网页指标阈值时,用户放弃加载网页的可能性会降低 24%。
  • Largest Contentful Paint (LCP) 每减少 100 毫秒,Farfetch 的网站转化率就会提高 1.3%。
  • 将 Cumulative Layout Shift (CLS) 降低 0.2 后,Yahoo! JAPAN 的每次访问带来的网页浏览量提高了 15%,访问时长延长了 13%,跳出率下降了 1.72 个百分点。
  • Netzwelt 提升核心网页指标后,广告收入增加了 18%,网页浏览量增加了 27%。
  • 将 CLS 从 1.65 降低至 0,使 redBus 域名在全球的排名大幅提升。

核心 Web 指标

每项核心 Web 指标代表用户体验的一个不同方面,能够进行实际测量,并且反映出以用户为中心的关键结果的真实体验。核心 Web 指标的构成指标会随着时间的推移而发展 。当前针对 2020 年的指标构成侧重于用户体验的三个方面——加载性能、交互性和视觉稳定性——并包括以下指标(及各指标相应的阈值):

Web性能测试——页面弹出窗口中内容加载时间的计算
  • Largest Contentful Paint (LCP) :最大内容绘制,测量加载性能。为了提供良好的用户体验,LCP 应在页面首次开始加载后的2.5 秒内发生。
  • First Input Delay (FID) :首次输入延迟,测量交互性。为了提供良好的用户体验,页面的 FID 应为100 毫秒或更短。
  • Cumulative Layout Shift (CLS) :累积布局偏移,测量视觉稳定性。为了提供良好的用户体验,页面的 CLS 应保持在 0.1. 或更少。
本文将主要讨论复杂页面的加载性能—— Largest Contentful Paint (LCP)。

什么是最大内容绘制 (LCP)?

以下示例展示了一些热门网站上出现最大内容绘制的时间点:

Web性能测试——页面弹出窗口中内容加载时间的计算
Web性能测试——页面弹出窗口中内容加载时间的计算

在上方的两个时间轴中,最大元素随内容加载而变化。在第一个示例中,新内容被添加进 DOM,并因此使最大元素发生了改变。在第二个示例中,由于布局的改变,先前的最大内容从可视区域中被移除。

虽然延迟加载的内容通常比页面上已有的内容更大,但实际情况并非一定如此。接下来的两个示例显示了在页面完全加载之前出现的最大内容绘制。

Web性能测试——页面弹出窗口中内容加载时间的计算
Web性能测试——页面弹出窗口中内容加载时间的计算

在第一个示例中,Instagram 标志加载得相对较早,即使其他内容随后陆续显示,但标志始终是最大元素。在 Google 搜索结果页面示例中,最大元素是一段文本,这段文本在所有图像或标志完成加载之前就显示了出来。由于所有单个图像都小于这段文字,因此这段文字在整个加载过程中始终是最大元素。

哪些元素在考量范围内?

根据当前最大内容绘制 API中的规定,最大内容绘制考量的元素类型为:

  • <img>元素
  • 内嵌在<svg>元素内的<image>元素
  • <video>元素(使用封面图像)
  • 通过url()函数(而非使用CSS 渐变)加载的带有背景图像的元素
  • 包含文本节点或其他行内级文本元素子元素的块级元素。

如何确定一个元素的大小?

  • 报告给最大内容绘制的元素大小通常是用户在可视区域内可见的大小。如果有元素延伸到可视区域之外,或者任何元素被剪裁或包含不可见的溢出,则这些部分不计入元素大小。
  • 对于在原始尺寸之上经过调整的图像元素,报告给指标的元素大小为可见尺寸或原始尺寸,以尺寸较小者为准。例如,远小于其原始尺寸的缩小图像将仅报告图像的显示尺寸,而拉伸或放大成更大尺寸的图像将仅报告图像的原始尺寸。
  • 对于文本元素,指标仅考量其文本节点的大小(包含所有文本节点的最小矩形)。
  • 对于所有元素,通过 CSS 设置的任何边距、填充或边框都不在考量范围内。

如何计算复杂页面的加载性能?

本文主要讨论复杂页面的加载性能度量,相对于简单的页面加载性能,可以直接使用Google Performance,如下图。

Web性能测试——页面弹出窗口中内容加载时间的计算

对于特殊页面的交互是无法通过使用Performance获取相关指标数据,如下图,获取 PageOffice弹窗插件中的文本内容加载时间。

Web性能测试——页面弹出窗口中内容加载时间的计算

本文主要讨论这种复杂场景加载性能的计算方式。

复杂页面的加载性能计算

计算的指标同样参考Largest Contentful Paint (LCP) 指标,具体计算原理为:

将视频进行逐帧分析,首先计算最大元素出现的第一帧,同时计算出“开始加载的页面标识元素”所出现的最后一帧,然后计算出帧数差,最后使用帧差数除以视频帧率,得出加载时间。

具体实现逻辑

步骤一: 将采用将视频进行逐帧分析,计算最大元素出现的第一帧,再通过“开始加载的页面标识元素”所出现的最后一帧,如下图,折线图为最大元素,最大元素出现的第一帧就是我们要找出的帧;图中分析按钮为“开始加载的页面标识元素”,当点击按钮后,按钮消失前的最后一帧就是我们要找出的帧。

Web性能测试——页面弹出窗口中内容加载时间的计算

步骤二:计算出帧数差,“开始加载的页面标识元素”出现的最后一帧的帧数 与 最大元素出现的第一帧的帧数之差,这就是页面加载过程的总帧数。

Web性能测试——页面弹出窗口中内容加载时间的计算

步骤三:帧数差除以视频帧率(每一秒包含的帧数),最终计算出加载时间。

Python实现伪代码

# -*- coding: cp936 -*-

import cv2, os, time
from config.deployment_config import END_IMAGE_PATH, IMAGE_SAVE_PATH, IMAGE_SAVE_AS_PATH, \
    START_IMAGE_PATH
from lib.ui_image_identification.core.image_identification import ImageIdentification
from lib.ui_image_identification.uicv import imread


class FrameTimeDiff:

    def __init__(self, mp4):
        # 视频路径,直接把脚本和视频放在同一个目录下最好,也可以指定对应的视频路径
        self.cap = cv2.VideoCapture(mp4)
        (major_ver, minor_ver, subminor_ver) = (cv2.__version__).split('.')

        if int(major_ver) < 3:
            self.fps = self.cap.get(cv2.cv.CV_CAP_PROP_FPS)
        else:
            self.fps = self.cap.get(cv2.CAP_PROP_FPS)


    def extract_picture_frame(self):
    		"""
				提取视频中的图片帧
				"""
        i = 0
        while True:
            ret, frame = self.cap.read()  # ret:True或者False,代表有没有读取到图片;frame:表示截取到一帧的图片
            if not ret:
                break
            # 展示图片
            cv2.imshow('capture', frame)
            image_path = f".\image\\{i}.png"
            # 保存图片
            cv2.imwrite(image_path, frame)
            i = i + 1
            if cv2.waitKey(1) & 0xFF == ord('q'):
                break

    @staticmethod
    def merge_number(num_lst):
        """
				合并临近数,方便计算“开始加载的页面标识元素”出现的最后一帧的帧数 与 最大元素出现的第一帧的帧数
        eg: [1, 3,4,5,6, 9,10] -> [[1], [3, 4, 5, 6], [9, 10]]
        """
        num_lst_tmp = [int(n) for n in num_lst]
        sort_lst = sorted(num_lst_tmp)  # ascending
        len_lst = len(sort_lst)
        i = 0
        split_lst = []

        tmp_lst = [sort_lst[i]]
        while True:
            if i + 1 == len_lst:
                break
            next_n = sort_lst[i + 1]
            if sort_lst[i] + 2 == next_n:
                tmp_lst.append(next_n)
            else:
                split_lst.append(tmp_lst)
                tmp_lst = [next_n]
            i += 1
        split_lst.append(tmp_lst)
        return split_lst

    @staticmethod
    def extract_key_frame(target_pos, _filter_image_list=None, image_save_path=IMAGE_SAVE_PATH):
    		图片帧检测标识图,如最大元素截图
        __list = []
        png_list = {
            int(os.path.splitext(yml)[0]): os.path.join(image_save_path, yml)
            for root, dirs, files in os.walk(image_save_path)
            for yml in files
            if os.path.splitext(yml)[-1] == ".png"
        }
        if _filter_image_list is None:
            _filter_image_list = []
        for index in sorted(png_list):
            if int(index % 2) == 0:
                file_path = png_list[index]
                print(file_path)
                # 图像识别定位
                if index not in _filter_image_list:
                		# 在当前图片帧中检测是否存在标识图片
                    last_scroll_location, last_scroll_match_result, last_scroll_best_match_detail = \
                        ImageIdentification(
                            target_pos,
                            threshold=0.99,
                            resolution=(1920, 1080)
                        ).match_in(imread(file_path))

                    if last_scroll_location is not None:
                        print(file_path, last_scroll_location)
                        image_path = os.path.join(IMAGE_SAVE_AS_PATH, f"{index}.png")
                        frame = imread(file_path)
 												# 保存图片
                        cv2.imwrite(image_path, frame) 
                        __list.append(index)
                        _filter_image_list.append(index)

        return __list, _filter_image_list


    def analysis_more_picture_frame(self):
				"""
				计算多次重复页面操作的加载时间
				"""
        find_1_list, filter_image_list = FrameTimeDiff.extract_key_frame(target_pos=START_IMAGE_PATH)

        find_2_list, filter_image_list = FrameTimeDiff.extract_key_frame(
            _filter_image_list=filter_image_list,
            target_pos=END_IMAGE_PATH
        )
				# “开始加载的页面标识元素”出现的帧
        merge_list_1 = FrameTimeDiff.merge_number(find_1_list)
				# 最大元素出现的帧
        merge_list_2 = FrameTimeDiff.merge_number(find_2_list)
        sectionalization = len(merge_list_1)
        time_difference_list = []
        for i in range(sectionalization):

            start_frame_index = merge_list_1[i][-1]
            end_frame_index = merge_list_2[i][0]

            time_difference = (end_frame_index - start_frame_index) / self.fps
            time_difference_list.append({"time_difference": time_difference, "start_frame_index": start_frame_index,
             "end_frame_index": end_frame_index})

        return time_difference_list


if __name__ == '__main__':
    frameTimeDiff = FrameTimeDiff("20230703-164844.mp4")
    # 提取视频中的图片帧
    frameTimeDiff.extract_picture_frame()
    # 释放对象和销毁窗口
    frameTimeDiff.cap.release()
    cv2.destroyAllWindows()
		# 计算页面操作的加载时间
    time_diff = frameTimeDiff.analysis_more_picture_frame()
    print(time_diff)           

考虑到伪代码易读性,未添加多进程处理逻辑。上述 ImageIdentification 方法是封装的图像识别方法,封装了kaze、brisk、sift、surf等图像识别算法。

拓展

通过 Selenium 实现页面的操作自动化执行,通过自动录屏,获取操作过程的视频。最后通过上述 LCP指标的计算方式,是可以实现复杂页面加载时间测试的自动化测试的,有兴趣的可以尝试一下。