研发效能 货拉拉云真机多机同步探索与实践

货拉拉质量星火 · 2024年12月09日 · 最后由 Loulan 回复于 2024年12月10日 · 1456 次阅读

作者介绍:胡嘉椿,来自货拉拉/技术中心/质量保障部,专注于移动测试效能方向。

一 背景

随着手机市场的多元化趋势的持续加剧,App 测试领域所承受的考验愈发严峻,测试人员需要覆盖的品牌、操作系统、系统版本数量呈现爆炸性增长,这无疑极大的增加了测试工作的复杂度和工作量。

  1. 随着 HarmonyOS Next 的快速发展,我们不仅要覆盖传统的 Android 和 iOS 平台,还要兼顾 HarmonyOS Next 系统的测试需求。当产品需求需要在多个端口(如 Android,iOS、鸿蒙、小程序)同步上线的时候,这一挑战变得尤为突出。
  2. 设备不足带来的等待时间也在一定程度上影响了测试效率。由于线下测试设备有限且分布在不同职场,每个测试人员通常只配了 1 台 Android 或 iOS 设备。为了覆盖 N 台手机的测试需求,往往需要线下临时租借设备,这一过程中产生的等待消耗,使得测试任务执行变得更加繁琐,增加了不少测试成本。
  3. 当前的功能测试范畴已不局限于某个端口的单独验证,而是要求每个执行步骤都要在不同端口间进行对比验证,以确保各端口表现的一致性。这一变化也带来了更多的测试工作量。 综上所述,如何在有限且紧迫的时间内,实现高效且全面的测试覆盖,已成为我们亟需攻克的核心难题。而针对已知的历史场景,我们可以通过编写自动化测试脚本来提升测试质量和效率。然而,对于新需求的测试交付提效,我们的突破口又在哪里?

二 提效方案探索

2.1 关于 1=N 的设想

泰勒在《科学管理原理》中提出的任务管理法强调了对工作的科学分析和分解,将复杂繁琐的任务划分为多个简单、标准化的子任务。这种任务分解的思路为并行处理提供了可能。通过合理的任务划分和分配,不同的任务或子任务可以由不同的工人或团队并行执行,从而提高整体的工作效率。
从泰勒管理学的任务拆解法中可提取关键信息:子任务、标准化,并行。

😄 我有个大胆的想法...
👏 让 1 = N ? 从 “串行测试” 到 “并行测试”

子任务:三端(Android、iOS、HarmonyOS)的功能测试、兼容性测试都可拆分为不同的子任务;
标准化:功能测试、兼容性测试的覆盖场景为准出条件;
并行:测试人员通过标准化流程并行执行多个子任务;

2.2 多机同步的使用诉求

我们也向一线业务测试团队征集了关于多机同步方案的一些想法,从可落地的角度了解业务测试团队在实际应用中的主要诉求,主要包括如下几个方面:

  • 支持多种群控模式:支持 Android 设备群控、iOS 设备群控、Android 与 iOS 跨平台群控三种模式,满足不同场景不同用户的个性化需求;
  • 操控精准度高,响应快:对于主控的每次的操控,从控响应要准确。用户在控制手机时,从体感上点击到视觉上的响应必须要快,不能超过 1s。
  • 操控相对简单、好用:主控要理解用户的操控,除业务上必要的操控外,不能有其它多余的步骤。从机可以独立去操作,增加灵活性。

2.3 业界方案

在移动端测试领域,面对测试量庞大的行业痛点,业界也有不少多机联动方案来提高测试效率,我们选取了具有代表性的一些大厂及测试服务提供商的实现方案做了一个简单调研。

SoloPi 通信层面是通过无线 adb 且不涉及 Web 画面传输,所以理论上联机设备不限量,精细化程度也做到了元素级别的同步。但是 SoloPi 是基于 Android 系统的产品,相较于基于云真机的 Testin 和泽众无法提供跨平台服务,且上手难度较高需要配置各种前置条件。
鉴于以上情况,为了更好地满足货拉拉在多机同步测试方面的需求,我们决定抛弃现有的开源方案,转而自研贴合货拉拉业务测试场景的多机同步工具。我们将充分借鉴 SoloPi 在元素级别同步方面的优势,同时克服其在跨平台兼容性和上手难度上的不足,通过深度定制和优化,期望能够打造出一款既高效又易用、能够全面满足货拉拉多机同步测试需求的解决方案。

三 多机同步核心解决方案

结合业界方案的调研与业务上对多机同步使用的诉求,再者,货拉拉在云真机建设的探索与成功实践(感兴趣的小伙伴可以参考我们之前发布的相关文章《货拉拉云真机平台的演进与实践》),我们决定基于货拉拉云真机来实践多机同步的测试。主要因为云真机有以下几个优势:

  • 云真机有丰富的设备池,可以满足业务线对不同机型兼容;
  • 较为完善的设备调度机制与自动化运维机制,可以高效管理设备资源和提供稳定的设备;
  • 跨平台的实现成本低,传输层面可以统一接口协议; 至于云真机的部署、通信、投屏等详细方案,感兴趣的同学可以查看之前的文章,这里是详细介绍多机同步的的实践,整体技术架构如图:

3.1 基于云真机的主从联动

EventBus:EventBus 是分配消息的一种实现机制,它拥有部署在 Agent 的手机建立的 websokect 长连接的管理及操控权限。消息则是通过 EventBus 的发送到 Agent 的端,最后由 Agent 注入到手机执行消费。

eventBus.emit('syncAppointControl', 
    {  
      udId: serial,   
      action: JSON.stringify(MITT_MESSAGE), 
    }) //操控消息
eventBus.emit('syncKeyevent', JSON.stringify(message)) //键盘事件

Message:为了降低维护成本并确保未来对 HarmonyOS 的顺利接入,我们在消息传递层面设计了一套统一的协议。这套协议的核心思想是将浏览器层面拦截到的用户操作转换为预定义的协议消息体,然后通过 EventBus 进行分发。不同接入的系统(如 Android、iOS、HarmonyOS)在接收到这些消息后,会根据各自平台的特性进行解析和定制化适配。

import {  
     ANDROID,  
     IPHONE, 
     MITT_MESSAGE,  
     TOUCH_U,  
     TOUCH_D,  
     TOUCH_M,  
     LONG_PRESS,  
     SWIPE,  
     TAP,  
     KEY_EVENT,  
     INPUT_EVENT,  
     SHELL_EVENT,  
     OPEN_APP,  
     OPEN_IOSAPP,  
     STOP_APP,  
     STOP_IOSAPP,  
     INSTALL,  
     UN_REINSTALL,  
     REINSTALL,  
     UNINSTALL,  
     UNLOCK
     } from '../Sync/ActionEvent'

3.2 基于 OCR 的精准控制

3.2.1 精准控制

实现精准控制,在《货拉拉 App 录制回放的探索与实践》我们有过相关 OCR 的介绍,这里我们不赘述原理,重点是决定复用相关的能力,采用 Paddleocr 来实现元素的精准控制,相对于基于系统 AccessibilityService 和 xcuitest 控制同步,基于 OCR 来实现精准控制有以下几个优点:

  • 跨平台兼容性:对于 OCR 的输入永远只是一张图片,可以忽略不同平台(Android、iOS、HarmonyOS)自身服务的差异化,甚至对于我们后续对小程序的兼容成本非常小。如果采用控件元素同步,需要对当前自动化框架进行特殊改造,实现成本非常大。

  • 响应快:对于 iOS 的 WebdriverAgent 框架,获取元素就非常耗时,导致响应速度也不能满足业务上的需求。然而 OCR 的飞速发展,响应速度大有可观,GPU 模式下基本都在 200ms 以内,比较简单的页面甚至可以在 100ms 左右返回结果,CPU 则是 500ms~1000ms 左右,而且响应速度极其不稳定,至于 GPU 为什么比 CPU 要快,简单一句话概括就是并行多线程批量处理与单线程处理的区别,也完美印证本次实践主题的思想。Paddleocr 的版本参数及运行硬件可供参考:

3.2.2 模型调优

Paddleocr 的能力和其提供官方通用模型,对于我们来说,虽然是开箱即用,但是还有一部分移动端的特殊场景检测效果并不理想,比如对于同行相近的文本、边界场景识别结果并不是我们想要的。


相近文本拼接与边界字符的引入

所以我们决定在官方的通用检测模型上进行调优,训练适合货拉拉移动端 UI 页面检测的模型。

  • 数据采集 我们选择了 PP-OCRV4_det(best_accuracy.pdparams)作为学习模型,为了确保模型能够学习到最真实、最具有代表性的数据特征。每当自动化测试过程中遇到失败的 Case,我们都会将其保存下来。这些失败的 Case 通常包含了各种复杂和多样的文本场景,如不同的字体、大小、颜色、背景以及排列方式等。由于自动化测试是持续进行的,因此我们可以实时地获取到最新的失败 Case,并将其添加到训练数据集中。
  • 数据标注 我们采用了半自动标注的方式,对已经筛选出来用于调优训练的数据进行了细致的特征划分。这一过程中,借助了官方提供的 PPOCRLabel 工具。然而,标注过程并非一帆风顺,特别是面对文本拼接问题时,由于官方通用模型倾向于将一整行内容作为一个整体进行标注,必须手动进行分割。这项繁琐而细致的工作耗费了笔者整整两天的时间。此外,数据标注并非一次性任务,而是一个持续的过程。随着模型的训练和测试,可能会发现新的标注需求或错误,需要及时进行调整和修正。因此,模型的优化工作是一个循环迭代的过程,不断优化和完善标注结果,以支持模型的持续改进和提升。
  • 数据增强 对于已标注的数据集,我们采用了一系列特殊的图像处理技术来丰富数据多样性,旨在显著提升模型的泛化能力,具体措施包括:
  • 缩放:通过调整图像尺寸,生成涵盖不同分辨率的图像变体。
  • 裁剪:从原图中截取局部区域,生成新的图像样本,增加数据的局部特征多样性。
  • 噪声添加:向图像随机引入噪声,增强模型对图像质量变化的健壮性。
  • 模糊处理:增加高斯模糊或运动模糊等效果,提升模型对模糊图像的识别能力。 数据集的增强处理,有效扩展了训练样本的覆盖范围,帮助模型更好地适应复杂多变的实际应用场景。 基于货拉拉 App 真实测试场景训练的模型识别效果:


解决文本拼接与边界字符问题

3.2.3 扩大点击热区

针对用户在实际操作过程中可能因点击到文本区域外而导致操作无法正确响应的问题,我们旨在通过扩大文本有效点击区域来提升用户体验。虽然官方提供的 det_db_unclip_ratio 参数可以调整 DB 模型输出框比例,但考虑到其对文本检测准确度产生负面影响,并不推荐直接修改该参数。更为优雅的解决方案是:在文本检测和识别流程完成后,对识别结果进行后处理,通过放大文本框的方式增加有效点击区域。为确保放大后的文本框之间不产生重叠,具体核心在于尝试使用最大的扩展像素值进行放大,并在检测到重叠时逐步减小扩展像素值,直至所有文本框均不再重叠或达到最大迭代次数。以下是该算法的实现步骤:

  1. 文本检测与识别:首先,利用模型进行文本检测,随后对检测到的文本区域进行 OCR 识别。
  2. 初始化扩展参数:设定一个初始的扩展像素值(可根据实际情况调整)和最大迭代次数。
  3. 放大文本框:对每个检测到的文本框,根据其位置和尺寸,按照初始扩展像素值进行放大。
  4. 检测重叠:检查放大后的文本框之间是否存在重叠。可以使用简单的矩形碰撞检测算法来实现。
  5. 调整扩展像素值:如果检测到重叠,则逐步减小扩展像素值,并重新进行放大操作。
  6. 迭代处理:重复步骤 4 和 5,直到所有文本框均不再重叠或达到最大迭代次数。
  7. 输出结果:将最终调整后的文本框作为有效点击区域输出,供后续的用户交互使用。 通过这种方法,我们可以在不牺牲文本检测准确度的情况下,有效扩大用户的文本有效点击区域,从而提升用户体验和防止 “手抖” 现象。
def is_overlapping(box1, box2):
    min_x1, min_y1 = min(pt[0] for pt in box1), min(pt[1] for pt in box1)
    max_x1, max_y1 = max(pt[0] for pt in box1), max(pt[1] for pt in box1)

    min_x2, min_y2 = min(pt[0] for pt in box2), min(pt[1] for pt in box2)
    max_x2, max_y2 = max(pt[0] for pt in box2), max(pt[1] for pt in box2)

    return not (min_x1 >= max_x2 or min_x2 >= max_x1 or min_y1 >= max_y2 or min_y2 >= max_y1)

def expand_box(box, expand_pixels):
    min_x = min(point[0] for point in box) - expand_pixels
    max_x = max(point[0] for point in box) + expand_pixels
    min_y = min(point[1] for point in box) - expand_pixels
    max_y = max(point[1] for point in box) + expand_pixels

    return [[min_x, min_y], [max_x, min_y], [max_x, max_y], [min_x, max_y]]

def adjust_boxes(ocr_results, max_expand_pixels, min_expand_pixels=0, step=1):
    original_boxes = [result['bounding_box'] for result in ocr_results]
    current_expand = [max_expand_pixels] * len(original_boxes)

    def adjust_single_box(idx):
        nonlocal current_expand, original_boxes
        expanded_boxes = [expand_box(box, current_expand[idx]) for idx, box in enumerate(original_boxes)]

        for j in range(len(expanded_boxes)):
            if idx != j and is_overlapping(expanded_boxes[idx], expanded_boxes[j]):
                if current_expand[idx] > min_expand_pixels:
                    current_expand[idx] = max(current_expand[idx] - step, min_expand_pixels)
                    return adjust_single_box(idx)
                elif current_expand[j] > min_expand_pixels:
                    current_expand[j] = max(current_expand[j] - step, min_expand_pixels)
                    return adjust_single_box(j)
        return expanded_boxes, False

    iteration = 0
    while True:
        no_collision = True
        for i in range(len(original_boxes)):
            expanded_boxes, collision_detected = adjust_single_box(i)
            if collision_detected:
                no_collision = False
                break

        if no_collision:
            break

        iteration += 1
        if iteration > 1000:  
            break

    return expanded_boxes

def zoom_in(ocr_results):
    max_expand_pixels = 10 
    return adjust_boxes(ocr_results, max_expand_pixels, min_expand_pixels=1, step=0.5)

调整前效果与扩大后效果如下图所示:
文本区域周围最大偏移 10 个像素,如果出现相邻文本框,动态调整间距,直至矩阵框不交割。

3.3 控制流量开销

多机同步过程中,若流量开销过大,可能导致区域网络拥堵(目前工区网络的使用已经达到极限)和实际体验的卡顿。解决方案通常涉及优化传输数据及同步策略:比如压缩传输数据,减少不必要的通信。

  • 压缩画面:压缩视频流的分辨率和比特率可以显著减少数据大小。分辨率决定了视频的清晰度,而比特率则决定了视频的质量。
  • 减少不必要的通信:然后一味的降低比特率,会严重影响视频的质量,我们可以关闭过渡动画来降低比特率对视频质量带来的影响:

多机同步中会关闭过渡动画,目前机房大部分设备基本控制在(静置)10kb~(高度渲染)500kb 左右每秒的带宽消耗。

PD2130:/ $ settings put global window_animation_scale 0
PD2130:/ $ settings put global transition_animation_scale 0
PD2130:/ $ settings put global animator_duration_scale 0

3.4 减少弹窗影响

在自动化测试过程中,突然出现的弹窗可能会影响测试结果,同样在多机同步测试过程中,如果突然出现弹窗,对用户来讲就意味着增加的额外的操作成本。对于弹窗,我们主要分为两大类:系统弹窗和 App 弹窗。

3.4.1 系统弹窗

主要系统的升级提醒,对于华为、荣耀等品牌的手机,我们可以通过禁用系统应用的方式来屏蔽更新

adb shell pm disable-user com.huawei.android.hwouc
adb shell pm disable-user com.hihonor.ouc
adb shell pm disable-user com.meizu.flyme.update
adb shell pm disable-user com.bbk.updater

但对于 iPhone,普遍的方法是通过安装某个过期 Beta 更新描述文件来屏蔽正式版本的更新,但是此方法对于新加入的手机需要经常换描述文件,且每次配置比较繁琐。我们采用在局域网内屏蔽掉苹果的更新域名地址,目前可以说是一劳永逸的方法,以下两个域名屏蔽后,验证下来只会屏蔽掉系统更新服务,不会影响到其它服务。

mesu.apple.com
appldnld.apple.com 

3.4.2 App 应用弹窗

主要是 App 向系统的权限申请弹窗,一般是位置定位、网络申请权限、SD 卡申请权限等。Android 各种系统弹窗的碎片化主要是由于 Android 的开放性和多样性,导致不同设备制造商、不同系统版本以及不同定制版本的 Android 系统对弹窗的设计、实现和展示方式存在显著差异。最简单粗暴的方式就是如何屏蔽掉这些,而不是通过脚本去自动化处理,因为随着系统及 ROM 的升级,自动化脚本也需要随之维护


不同品牌、系统的权限弹窗

我们通过运行在手机内拥有 DeviceOwner 权限的天宫管家来控制 App 的权限申请弹窗。关于天宫管家 App,我们之前的文章也有相关的引用,这里我们再简单介绍一下实现方法。

  • 配置相关权限:

    <?xml version="1.0" encoding="utf-8"?>
    <device-admin xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-policies>
        <force-lock />
        <expire-password />
        <encrypted-storage />
        <disable-camera />
        <limit-password />
        <watch-login />
        <reset-password />
        <wipe-data />
    </uses-policies>
    </device-admin>
    
  • 实现相关服务

public class DevAdminReceiver extends DeviceAdminReceiver {
    @Override
    public void onDisabled(Context context, Intent intent) {
        super.onDisabled(context, intent);
    }

    @Override
    public void onNetworkLogsAvailable(Context context, Intent intent, long batchToken, int networkLogsCount) {
        super.onNetworkLogsAvailable(context, intent, batchToken, networkLogsCount);
    }

    @Override
    public void onPasswordChanged(Context context, Intent intent, UserHandle userHandle) {

    }

    @Override
    public void onSystemUpdatePending(Context context, Intent intent, long receivedTime) {
        super.onSystemUpdatePending(context, intent, receivedTime);
    }

    @Override
    public void onUserAdded(Context context, Intent intent, @NonNull UserHandle newUser) {
        super.onUserAdded(context, intent, newUser);
    }
}
  • 注册系统服务
ComponentName adminComponentName = new ComponentName(context, DevAdminReceiver.class);
DevicePolicyManager devicePolicyManager = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);

四 实践效果演示

4.1 效果演示

Android 端 5 台同步下单测试演示,可点击链接查看

Android+iOS 同步下单测试演示,可点击链接查看

4.2 研发体系中提效场景

  1. 测试场景应用:适用于多种测试场景,包括新需求的功能验证、兼容性测试以及跨平台测试等,显著提升了测试覆盖率和执行效率。
  2. 研发场景下的性能与兼容性优化:研发同学在开发阶段,通过验证不同设备下 App 功能,进行兼容性适配。
  3. 高效 UI 验收解决方案:帮助 UI 同学轻松进行跨平台 UI 一致性测试,确保 UI 设计在不同设备上的完美呈现。
  4. 加速产品验收流程:无需外借设备,产品同学可同步验证 Android 和 iOS 平台上的功能实现与交互效果,加快产品验收测试流程。

五 未来规划

“道阻且长,行则将至,行而不辍,未来可期”。——《荀子·修身》
面对移动端测试的繁琐性、平台多样性以及覆盖设备广泛的复杂挑战,本次探索是我们从单线程的传统测试模式向多线程、多设备同步测试的一次重要实践与大胆尝试。正如《荀子·修身》所言:“道阻且长,行则将至,行而不辍,未来可期”。我们坚信,通过持续的技术创新与实践努力,依然可以不断提升测试效率与质量。

  1. 迭代式模型优化:模型的优化是一个循序渐进的过程,核心在于不断调整和完善,以此作为提升用户体验的关键策略。
  2. 增加 Icon 识别功能:尽管当前基于文本检测和相对坐标定位的解决方案已能应对多数测试场景,但仍存在控件覆盖不全的问题。通过增加 Icon 识别能力进一步提升用户体验。
  3. HarmonyOS 与小程序的多机同步拓展:将多机同步测试能力拓展至 HarmonyOS NEXT,想象一次测试便能同时验证四端设备的便捷与高效。
共收到 1 条回复 时间 点赞

Hi 大佬,效果演示 demo 链接失效了,可否更新下,谢谢。

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册