vv、vt 埋点上报自动化

vv、vt 埋点自动化文档


文章目录


一、项目简介

该项目实现播放次数播放时长的数据上报进行自动化校验,解决发版手工校验播放器数据上报以及北极星埋点的痛点,保障数据的正常上报和落表。

二、环境搭建

Airtest官方参考文档

Airtest官方文档

AirtestIDE下载

AirtestIDE下载地址

Python环境安装

Python <= 3.9

  • pip3 install pytest-html
  • pip3 install pymysql
  • pip3 install pytest-assume

已把所有依赖项整理成了requirement.txt文件,详情可见项目(用pycharm打开可选择自动下载所有依赖)

Airtest安装

安卓环境

  • pip3 install airtest
    • 添加adb权限:cd {your_python_path}/site-packages/airtest/core/android/static/adb/mac, chmod+x adb
      • 无法执行时,尝试将本地的adb复制到以上路径下,替换原来的adb(比如:cp /Users/ogvqa/Android/sdk/platform-tools/adb . 然后chmod+x adb)
  • pip3 install pocoui

IOS 环境

iOS-Tagent 编译
  • git clone [email protected]:AirtestProject/iOS-Tagent.git
  • 使用Xcode打开 iOS-Tagent, 用数据线连接iPhone至Mac
  • 连接iPhone后,在iPhone设备上弹出的“是否信任Mac设备”,选择信任
  • 选择项目,在菜单栏 product -> Scheme -> WebDriverAgentRunner
  • 选择设备,在菜单栏 product -> Destination -> 选择你的真机

在这里插入图片描述

  • 使用苹果账号或苹果开发者账号,登录Xcode,并注册真机设备

    • 左侧导航栏,选择 WebDriverAgentRunner -> TARGETS -> WebDriverAgentRunner -> Signing & Capabilities -> Team
  • 选择 Team -> Add an Account -> 登录苹果账号(个人免费或开发者账号)

  • 选择 TARGETS -> WebDriverAgentRunner -> Build Settings -> Basic

  • 双击 Product Bundle Identifier值,填写一个属于自己独一无二的字串

  • 回到,上文提过的Signing & Capabilities界面,查看有无报错

  • 无报错,则继续;若有报错,查看 登陆开发者账号

  • 1.4 启动Test,在菜单栏 product -> Test。当你看到这样的日志的时候,代表 iOS-Tagent 已经启动成功了

  • 一点坑,xcode 12.x报错Building for iOS Simulator, but the linked and embedded framework ‘***‘ was built …解决方式,将Validate Workspace为Yes后,重新编译

  • 运行报错: wda.exceptions.WDAUnknownError: WDARequestError(status=110, value={‘error’: ‘unknown error’, ‘message’: ‘-[XCUIScreen screenshotDataForQuality:rect:error:]: unrecognized selector sent to instance 0x2832349c0’})

设置代理

  • 安装libimobiledevice
    • brew install libimobiledevice
  • 运行proxy
    • 终端输入 iproxy 8100 8100
  • -启动成功后,可以试着在浏览器访问 http://127.0.0.1:8100/status ,如果访问成功并且可以看到一些json格式的手机信息,即表示启动成功

三、框架介绍

Airtest是网易开源的一款UI自动化测试项目,基于python脚本的方式,用于web、windows程序、app自动化测试.

官方文档介绍:airtest官网

在这里插入图片描述
在这里插入图片描述

四、实现原理

4.1 框架流程图

在这里插入图片描述

4.2 初始化配置

在每个测试用例执行之前都会先进行初始化配置:

在这里插入图片描述

具体分为三步:

  1. 读取mock server配置,设置mock规则
  2. 清理mock server日志
  3. 手机日志清理并开启日志记录

4.2.1 读取mock server配置,设置mock规则

核心函数:buryingpoint_setmockrule_new()

该函数用于设置 Mock Rule,以在自动化测试中对每个case模拟指定的response或者request。以下是对函数的详细说明:

参数

  • mock_env (字符串): 表示要使用的 Mock Server 环境的名称。
  • tc_name (字符串): 表示当前测试用例的名称。
  • env (字符串): 表示测试环境的名称。
  • params (字典): 包含测试参数和配置信息的字典。
  • platform (字符串): 表示测试平台的名称。

返回值

  • True (布尔值): 如果成功设置 Mock Rule,则返回 True。
  • False (布尔值): 如果设置 Mock Rule 失败或出现异常,则返回 False。

异常

  • 如果 mock_env 为空字符串,则会打印错误消息 “Mockserver未设置” 并返回 False。
  • 如果无法读取 Mock 数据文件,则会引发 FileNotFoundError 异常。
  • 如果重置 Mock 规则失败(状态码不为 200 或返回的数据不为 “ok”),则会打印错误消息并返回 False。
  • 如果测试用例的 Mock 规则未给出或未包含在参数中,则会打印错误消息并返回 False。
  • 如果设置 Mock 规则失败(状态码不为 200 或返回的数据不为 “ok”),则会打印错误消息并返回 False。
  • 如果在设置 Mock 规则过程中发生其他异常,则会打印异常消息并返回 False。

原理

该函数使用了 Python 的 requests 模块来发送 HTTP 请求,post请求地址如下:

在这里插入图片描述

通过控制两次post请求传参来改变 hasson平台上mock规则的状态(开关):

在这里插入图片描述

  1. 第一次请求会把当前mock_env 环境下的所有规则开关状态初始化为false;
  2. 第二次请求会把每个case需要的mock规则开关置为true;

代理规则配置json文件:

在这里插入图片描述

每个case具体配哪个mock规则在conf.py文件中配置:

在这里插入图片描述

4.2.2 清理mock server 日志

核心函数:buryingpoint_cleanup_new()

该函数用于清理 mock log 数据,以准备下一次的自动化测试。以下是对函数的详细说明:

参数

  • mock_env (字符串): 表示要清理的 Mock Server 环境的名称。
  • tc_name (字符串): 表示当前测试用例的名称。
  • test_env (字符串): 表示测试环境的名称。
  • params (字典): 包含测试参数和配置信息的字典。

返回值

  • True (布尔值): 如果成功清理 mock log,则返回 True。
  • False (布尔值): 如果清理 mock log 失败或出现异常,则返回 False。

异常

  • 如果无法访问 params 字典中指定的键或键不存在,则会引发 KeyError 异常。
  • 如果请求删除 mock log 的响应状态码不为 200,则会打印错误消息并返回 False。

原理

该函数实现清理hasson平台上指定env、host、path 下的日志数据,实现初始化,便于后续读取每个case的日志上报。

在这里插入图片描述

通过遍历conf.py文件的入参字典中的数据构建 args 列表,其中包含了需要清理的 Mock Log 的相关信息。
在这里插入图片描述

随后函数会调用 delete_mock_log 函数来发送http请求以删除 Mock Log。

在这里插入图片描述

4.2.3 手机日志清理并开启日志记录

核心函数:start_log_collection(cls)

原理

该函数实现手机日志的清理,开启一个子线程开启手机日志清理任务
在这里插入图片描述

随后创建一个AndroidLogThread对象log_thread,用于在后台收集并保存设备的日志信息。
在这里插入图片描述

以下为开启日志线程的具体实现(安卓端为例):

在这里插入图片描述

4.3 UI操作封装

埋点上报自动化在UI定位和操作上结合了airtest框架和poco框架

4.3.1 UI 控件定位

pages文件夹涵盖了目前case所需的所有UI控件名以及坐标位置
在这里插入图片描述
部分代码示例:
在这里插入图片描述

相关控件属性可以通过airtest IDE 提供的 poco-inspecter 抓取。

4.3.2 操作函数封装

相关的操作函数都经过封装放在了keywords文件夹

结合了poco框架和airtest框架提供的部分函数,以及一些shell指令实现。

部分代码示例:

在这里插入图片描述

4.3.3 整体case自动化实现

相关的case实现放在了testcase文件夹下,通过对封装好的操作函数进行拼接就可以实现各种case的UI操作
在这里插入图片描述

4.4 数据校验

核心函数:buryingpoint_player_assert()

该函数用于对api接口上报数据和es数据库中的数据的预期值和实际值进行断言。

参数

  • cls:类对象
  • mock_env:模拟环境信息
  • tc_name:测试用例名称
  • env:环境信息
  • buvid:Buvid信息
  • check_point_index:检查点索引
  • params:检查点参数
  • timestamp:时间戳信息
  • log_path:日志文件路径
  • ignore_play:是否忽略播放
  • miniplayer_play_time:最小播放器播放时间
  • paused_time:暂停时间

返回值

  • result:断言结果(True/False)
  • api_result_list:API结果列表
  • es_result_list:ESP结果列表

原理

函数会检索conf.py文件下的配置信息,获得对应case的预期值字典(包含api和es的校验预期值)

在这里插入图片描述

随后函数会获取api接口实际上报的值和es库中的值,和预期值进行比对。

自动化当前校验的点(新播放器)

vv:

  • aid、cid、epid、sid= 实际值
  • epid_status:EP 付费状态
  • type :4 (视频类型为pgc)
  • sub_type: 1番剧 2电影 3纪录片 4国创 5电视剧
  • auto_play :(0:非自动播放上报,1:动态自动播放上报,2:天马feed流inline 自动播放,3:相关推荐自动播放)
  • mobi_app:实际测试系统(安卓传android、iPhone传iPhone)
  • device:实际测试终端(安卓传空、iPhone传phone)
  • mid 、buvid: 实际值
  • spmid:pgc.pgc-video-detail.0.0
  • from_spmid:(进入页面的spmid)
  • playlist_type + playlist_id:播单、离线缓存的场景特有字段

vt:

  • played_time = 播放器实际播放耗时
  • paused_time= 暂停时间
  • total_time = played_time+paused_time
  • actual_played_time = 计算倍速后的真实耗时
  • last_play_progress_time = 结束播放时的播放进度
  • max_play_progress_time = 此次播放中最大播放进度
  • miniplayer_play_time = 小窗播放时长
  • auto_play:(0:非自动播放上报,1:动态自动播放上报,2:天马feed流inline 自动播放,3:相关推荐自动播放)
  • quality:上报应为当前播放的清晰度,非用户清晰度

vv包含的所有字端vt均需要校验

当前会校验上报的数量和接口的数据

4.4.1 API接口实际值获取

核心函数: get_actual_api_counts_and_values()

该函数用于获取实际的API计数和值

参数

mock_env:模拟环境信息
check_point:检查点信息

返回值

actual_api_counts:实际API计数字典
actual_api_values:实际API值字典

原理

该函数会检索conf.py文件,并获取到对应case 的mock_env, host, path作为入参
在这里插入图片描述

然后去调用get_mock_log_info函数去通过post请求获取hasson平台上的mock_log日志

在这里插入图片描述

最后对返回的json数据进行整理,过滤,获得实际的上报的值

在这里插入图片描述

4.4.2 ES数据实际值获取

核心函数: get_es_data()

该函数用于从Elasticsearch获取ESP数据。它接受日志ID、Buvid、起始时间戳等参数作为输入。

参数

  • log_id:日志ID
  • buvid:Buvid信息
  • gte:开始时间戳
  • expected_size:期望的结果集大小
  • event_id=“”:事件ID(默认为空字符串)
  • platform=“”:平台信息(默认为空字符串)
  • tc_name=“”:测试用例名称(默认为空字符串)
  • *kw:conf.py文件中es的期望key值(元组)

返回值

  • data:包含ESP数据的字典
  • actual_size:实际的结果集大小
  • message:获取到的Elasticsearch日志消息

原理

定义不同场景下可能的事件ID列表
在这里插入图片描述

构建Elasticsearch查询的URL、请求头和排序方式,以及根据输入参数构建查询字符串,包括环境、日志ID、Buvid、事件ID等

在这里插入图片描述

获取当前时间戳 lte 并构建Elasticsearch查询的查询体,发送POST请求到Elasticsearch,获取返回结果。

在这里插入图片描述

解析返回结果,提取命中的文档列表和文档数量。

遍历每个命中的文档:

  1. 解析日志内容,并对一些特定情况下的字段进行处理。
  2. 将解析结果添加到ESP数据存储字典 data 中。

在这里插入图片描述

4.4.3 如何查询ElasticSerarch埋点相关日志

  • 步骤1:登录kibana kibanna
  • 步骤2: 筛选索引 billions-datacenter.buryingpoint.buryingpoint-@*

在这里插入图片描述

  • 步骤3: 添加筛选条件,如 事件id,buvid,fields.env 等等

在这里插入图片描述

4.4.4 如何获取ElasticSerarch埋点相关日志

billions-proxy 支持通过Restful api的方式查询ops-log/uat-log中的数据

uat-log使用姿势

http需要包含的header

  • Appid: 部门.项目.应用
  • Appkey: key
  • Content-Type: application/json

统一访问地址:172.22.33.113:81/{location}

Appid: billions Appkey: proxy

相应的请求方法对应location为:

  • v7/<index_name>/_search

  • v7/<index_name>/_count

请求body必须传入!!!

在这里插入图片描述

日志查询接口:

curl --location --request POST 'http://172.22.33.113:81/v7/billions-datacenter.buryingpoint.buryingpoint-@*/_search' \
--header 'content-type: application/json' \
--header 'Appid: billions' \
--header 'Appkey: proxy' \
--data-raw '{"size": 2, "sort": {"@timestamp": {"order": "asc"}}, "query": {"bool": {"filter": [{"query_string": {"query": "fields.env:\"prod\" AND \"logId=000032\" AND \"buvid=ZB485FB68E7385DB42BA887F688058916AF5\""}}, {"range": {"@timestamp": {"gte": 1647513614961, "lte": 1647513645514}}}]}}}'
(gte:开始测试的时间,lte:当前的时间)

4.5 日志计算逻辑

核心函数:get_play_time_v2()

该函数用于从手机日志文件中获取ijk播放器状态和事件来计算播放器以及小窗的播放时间、暂停时间、倍速播放时间。

4.5.1 播放器日志计算

原理

逐行读取日志文件,并从每行中提取ijk播放器相关信息,将其存储在 player_record 列表和 small_player_record 列表中。

在这里插入图片描述

接着遍历 player_record 列表,根据时间戳和播放器状态计算播放时间和暂停时间。

在这里插入图片描述

处理特定事件,例如播放速度更改或在播放过程中切换项目。

在这里插入图片描述

计算完播放时间和暂停时间后,将结果存储在 time_list 列表中。

4.5.2 小窗日志计算

该函数还会计算小窗口播放器(miniplayer)的持续时间(如果适用)。

在这里插入图片描述

原理

过滤手机日志中获取小窗相关的事件以及里面的mCTime去计算小窗的持续时间,开始时间

过滤出来的日志如下:

在这里插入图片描述

4.6 Android 包下载及安装自动化

已支持从fawkes平台上自动下载包以及包的安装和卸载

原理

在fawkes平台上构建好指定的包
在这里插入图片描述

通过对指定接口发送get请求获取到自己当下构建好的包,从接口返回中拿到下载的url

在这里插入图片描述

然后把这个链接下载到本地项目中,再通过调用shell指令去安装指定的apk

注意:

此处注意把params、cookies、headers 改成自己实际的值,否则无法请求到自己构建的包
在这里插入图片描述

4.7 企业微信机器人推送

已支持企业微信机器人,后续接入测试报告的推送。

五、使用&脚本示例

5.1 概要

本项目是在UAT环境下基于Hasson和数据平台进行校验,UI脚本与数据校验相分离

5.2 环境准备

双端通过Hasson连接至uat环境
在这里插入图片描述

5.3 依赖下载及配置参数

在conftest.py文件下面设置设备的参数:

在这里插入图片描述主函数可以运行对应的测试套件:

在这里插入图片描述

5.4 测试用例编写步骤

在这里插入图片描述

示例:

在这里插入图片描述

相关conf.py 配置如下:

在这里插入图片描述

5.4 注意事项

在这里插入图片描述

六、常见问题

6.1 E airtest.core.error.DeviceConnectionError: ‘device not ready’

  • 确保设备正确连接:请确保您的设备已连接到计算机,并且在执行测试之前已完成设备驱动程序的安装和设置。可以通过运行 adb devices 命令来验证设备是否正确连接。

  • 重启设备和计算机:有时候,重新启动设备和计算机可以解决连接问题。尝试断开设备,并重新启动设备和计算机,然后再次进行测试。

  • 检查设备调试模式和USB调试选项:确保设备已启用调试模式,并且 USB 调试选项已启用。这些选项通常在设备的开发者选项中找到。启用调试模式后,重新连接设备并尝试运行测试。

  • 检查设备连接方式:根据您的测试环境,可能需要使用正确的连接方式。例如,如果您正在使用真实的 Android 设备,则需要使用 Android:/// 进行连接。如果您正在使用模拟器,则可能需要使用相应的模拟器连接方式。

  • 更新相关软件:确保您使用的相关软件(如 Airtest、ADB)是最新版本,并且与您当前的测试环境兼容。有时候,更新软件可以解决一些已知的问题或错误。

6.2 adb server version(40) doesn’t match this client(41)

这个错误提示表明你的ADB(Android Debug Bridge)客户端版本与ADB服务器版本不兼容。ADB客户端和服务器之间需要保持相同的版本以确保它们可以正常通信。

要解决这个问题,你可以尝试以下几种方法:

  • 更新ADB客户端:如果你的ADB客户端版本较旧,请尝试更新到与ADB服务器版本匹配的最新版本。你可以从Android SDK Manager中下载最新的ADB版本,或者从Android官方网站上下载ADB二进制文件并手动替换掉旧版本。

  • 降级ADB服务器:如果你的ADB客户端是最新版本,但服务器版本过高,可能需要将ADB服务器降级到与客户端兼容的版本。你可以在Android SDK目录下找到ADB二进制文件,并替换为与你的ADB客户端版本匹配的较旧版本。

  • 检查环境变量和路径设置

参考如下:

把pycharm中的adb版本改成和终端下载的版本一致即可

把这个路径 /venv/lib/python3.9/site-packages/airtest/core/android/static/adb/Mac/adb 下的adb 换成终端下载好的adb就行

注意:

记得在环境变量加入终端的path,否则运行不成功!!!
在这里插入图片描述

6.3 获取不到es数据

  • 有时候es那边可能在做什么改动,偶尔会出现拿不到es数据的情况
  • 如果是发生错误了,请检查一下无线网是否连接的是正确的
    在这里插入图片描述

获取es的接口对网段做了一些限制

6.4 PocoService连跑后不断重启,闪屏

表现如下:

在这里插入图片描述

主要原因

每条case都会实例化多个poco对象,若手机后台自动清理了poco,框架自带的监控进程会不断尝试重启,影响自动化运行

解决方法

修改框架里面的Androiduiautomation类变成了单例模式实现,全局只实例化一个对象(目前的做法):

# 实现单例模式(线程安全的)
def singleton(cls):
    instances = {}
    lock = threading.Lock()  # 定义一个全局锁对象

    def getinstance(*args, **kwargs):
        with lock:  # 加锁
            if cls not in instances:
                instances[cls] = cls(*args, **kwargs)
            return instances[cls]

    return getinstance

@singleton
class AndroidUiautomationPoco(Poco):
    """
    Poco Android implementation for testing **Android native apps**.

    Args:
        device (:py:obj:`Device`): :py:obj:`airtest.core.device.Device` instance provided by ``airtest``. leave the 
         parameter default and the default device will be chosen. more details refer to ``airtest doc``
        using_proxy (:py:obj:`bool`): whether use adb forward to connect the Android device or not
        force_restart (:py:obj:`bool`): whether always restart the poco-service-demo running on Android device or not
        options: see :py:class:`poco.pocofw.Poco`

    Examples:
        The simplest way to initialize AndroidUiautomationPoco instance and no matter your device network status::

            from poco.drivers.android.uiautomation import AndroidUiautomationPoco

            poco = AndroidUiautomationPoco()
            poco('android:id/title').click()
            ...

    """

    def __init__(self, device=None, using_proxy=True, force_restart=False, use_airtest_input=False, **options):
        # 加这个参数为了不在最新的pocounit方案中每步都截图
        self.screenshot_each_action = True
        if options.get('screenshot_each_action') is False:
            self.screenshot_each_action = False

        self.device = device or current_device()
        if not self.device:
            self.device = connect_device("Android:///")

        self.adb_client = self.device.adb
        if using_proxy:
            self.device_ip = self.adb_client.host or "127.0.0.1"
        else:
            self.device_ip = self.device.get_ip_address()

        # save current top activity (@nullable)
        try:
            current_top_activity_package = self.device.get_top_activity_name()
        except AirtestError as e:
            # 在一些极端情况下,可能获取不到top activity的信息
            print(e)
            current_top_activity_package = None
        if current_top_activity_package is not None:
            current_top_activity_package = current_top_activity_package.split('/')[0]

        # install ime
        self.ime = YosemiteIme(self.adb_client)

        # install
        self._instrument_proc = None
        self._install_service()

        # forward
        if using_proxy:
            p0, _ = self.adb_client.setup_forward("tcp:10080")
            p1, _ = self.adb_client.setup_forward("tcp:10081")
        else:
            p0 = 10080
            p1 = 10081

        # start
        ready = self._start_instrument(p0, force_restart=force_restart)
        if not ready:
            # 之前启动失败就卸载重装,现在改为尝试kill进程或卸载uiautomator
            self._kill_uiautomator()
            ready = self._start_instrument(p0)

            if current_top_activity_package is not None:
                current_top_activity2 = self.device.get_top_activity_name()
                if current_top_activity2 is None or current_top_activity_package not in current_top_activity2:
                    self.device.start_app(current_top_activity_package, activity=True)

            if not ready:
                raise RuntimeError("unable to launch AndroidUiautomationPoco")
        if ready:
            # 首次启动成功后,在后台线程里监控这个进程的状态,保持让它不退出
            self._keep_running_thread = KeepRunningInstrumentationThread(self, p0)
            self._keep_running_thread.start()

        endpoint = "http://{}:{}".format(self.device_ip, p1)
        agent = AndroidPocoAgent(endpoint, self.ime, use_airtest_input)
        Poco.__init__(self, agent, **options)

或者参考一下下面的文章,修改一下手机的设置项:

poco无限重启解决办法

6.5 total_time,pause_time 计算不准确导致fail

由于跟播放时长和暂停时长的计算逻辑是根据ijk播放器的状态去计算,并未结合ijk播放器的事件去计算,受网络原因影响或者手机性能影响会造成计算值与实际值不准确的情况(自动化时间算法和开发那边的逻辑不一致),可能有类似于缓冲态这种事件,会导致计算不准确。

6.6 连跑出现httpconnection错误,连接池无法新建连接(连接池爆了)

原因

  1. http保持长连接导致短时间内大量连接存在,最终服务器拒绝访问;
  2. 访问次数频繁,被禁止访问

解决办法

  • 更改连接池最大数量
  • 修改keep-alive = false
  • 把代码中所有的request都关闭连接,让连接池回收(目前的做法)

6.7 连跑出现logcat的日志缓冲区给刷爆了,错误:logcat: Unexpected EOF!

解决办法

  1. 在开发者选项中,可以通过显示/允许增加环形缓冲区的大小,进行相关的缓冲区的大小设置(这样只能改一部分的缓冲区)
  2. getprop | grep “persist.logd.size” 查看缓冲区大小,然后手动设置
  3. 每次case开始都清除所有的缓冲区(目前的做法)
    在这里插入图片描述

6.8 连跑偶现出现error报错,openAI 获取屏幕截图失败

原因

airtest框架问题,长时间运行代码可能某些case截取屏幕就会失败,所以建议某些case最好不使用airetest框架,直接用adb shell来控制手机的操作

6.9 hasson平台mock_log日志查询不到请求头信息

原因

直接去请求get 平台的信息,平台做了一些过滤,只会给出接口上报的请求体,响应头和响应体信息。

解决

若后续需要增加的字段是在请求头中的,比如 buvid , 需要修改请求的参数,并把这个字段做处理增加到校验列表里(已在代码中实现)

6.10 连跑的时候由于记录了上一次播放位置导致提早播放结束(视频时长太短了)

解决

已新增mock规则,每个case结束 history接口上报的progress = -1 ,即不记录历史记录

在这里插入图片描述

后续只需要在conf.py中 在对应的case 配置一下这个mock规则即可。
在这里插入图片描述

七、代码仓库

八、jenkins配置

猜你喜欢

转载自blog.csdn.net/qq_43575801/article/details/131520046
vv
今日推荐