鸿蒙 hmdriver2 发布:开启鸿蒙 NEXT 自动化新时代

codematrixer · 2024年09月23日 · 最后由 沙漠丶 回复于 2024年10月08日 · 9346 次阅读
本帖已被设为精华帖!

前言

华为三折叠手机 Mate XT 发布在即,届时搭载着纯血鸿蒙系统 HarmonyOS NEXT 正式推送给用户使用。该系统不再兼容Android应用,这就意味着以前 Android 那套自动化测试框架都不能在该系统上运行。在此背景下,hmdriver2应运而生。

现在我郑重介绍一下hmdriver2:它是一款支持HarmonyOS NEXT系统的 UI 自动化框架,无侵入式,提供应用管理,UI 操作,元素定位等功能,轻量高效,上手简单,快速实现鸿蒙应用自动化测试需求。

经过一段时间的调研、编码、实践,就在今天,hmdriver2正式发布,代码已开源,欢迎提 PR 和 issue,动动你发财的手点点 Star ⭐️(你的 Star 是我迭代的动力)

https://github.com/codematrixer/hmdriver2

设计思想

  • 无侵入式
    • 无需提前在手机端安装 testRunner APP(类似 atx app)
  • 易上手
    • 在 PC 端编写 Python 脚本实现自动化
    • 对齐 Android uiautomator2的脚本编写姿势
  • 轻量高效
    • 摒弃复杂依赖(几乎 0 依赖),即插即用
    • 操作响应快,低延时

方案探索

在开发 hmdriver2 前,我对现有的鸿蒙自动化工具链进行了系列调研:

  • hdc:类似 android 系统的 adb 命令,提供设备信息查询,包管理,调试相关的命令
  • @ohos.UiTest:鸿蒙 sdk 的一部分,类似 android sdk 里的uiautomator,基于 Accessibility 服务,提供模拟 UI 操作的能力。但是需要用arkTS语言来写自动化 case,并且用例需要打包到被测 app 里面
  • hypium:鸿蒙官方的自动化框架,功能比较完善,可以基于 python 编写自动化 case

为啥不用 hypium?

分析鸿蒙官方自动化工具链,我们发现hypium已经具备了实现鸿蒙 Next 系统 UI 自动化能力,那直接用 hypium 就可行,为啥还要重复造轮子再搞一套?

深度体验 hypium 后发现,虽然它提供的能力已经比较全面了,但是还是有一些问题无法满足我的需求:

  1. 安装的依赖多,使用较繁杂,对用户不够友好,特别是小白用户
  2. 脚本执行效率较低,操作响应慢
  3. 未正式开源,无法共建,框架有 bug,等待官方修复时间确实太长。

于是决定自己写一套,说干就干!

1.0 版本

1.0 版本参考了 Android 端的 uiautomator2 自动化框架(这是一个很优秀的框架,不管是它的设计思路还是代码编写都值得借鉴),它的原理是在手机上运行了一个 http rpc 服务,将 android sdk 的uiautomator中的功能开放出来,然后再将这些 http 接口封装成 python 库。

类比到鸿蒙NEXT系统,前面说的@ohos.UiTest等价于 Android 系统的uiautomator,那只需要要手机端也实现一个 testRunner APP,这个 app 提供一个 socket 服务将鸿蒙系统@ohos.UiTest的 UI 操作的能力通过接口暴露出来,最终实现 python 调用。

思路有了,于是开始实现了,一顿操作猛于虎,几天过去了,demo 快亮相时,突然发现 github 上已经有人用相同的思路实现了,它的名字叫hmdriver,还是有人比我快啊(这也是我的框架为啥叫hmdriver2,这是后话)

这个版本需要单独开发一个 testRunner APP,然后将 app 安装到手机上,可以理解为侵入式,不够轻量,不符合即插即用原则;而且鸿蒙 Next 系统打包签名机制比较繁琐(类似于苹果的开发者证书那一套,天下苦苹果久矣),那时候还没有企业证书,我们的测试设备要想安装上 testRunner APP,需要提前将设备注册到开发者账号里,然后重新打包,每次有新设备都要重复出包,想想是不是很难接受。

基于此原因,有了让我继续探索下去的动力,是否有更好的方案?

2.0 版本(hmdriver2)

我的目光再一次来到hypium身上,为啥它没有依赖 app 也能实现通过 python 调用@ohos.UiTest的能力,我的猜想是鸿蒙系统肯定给它开了后门(毕竟是自家的),于是我决定阅读下 hypium 的源码(它的代码确实多,包括 4 个基础库xdevicexdevice-devicetestxdevice-ohoshypium)这里就不详细介绍代码实现了,我找到了里面的关键实现,hypium是通过一系列 socket 调用和手机通信(端口是8012),这个 socket 服务是通过hdc命令启动uitest实现的

hdc shell uitest start-daemon singleness

同时hypium提供了一个动态链路库agent.so,这个是核心中的核心,所有的 UI 操作,录屏等功能都在这个动态库里实现,它可以在hdc uitest命令运行时被加载到内存中,然后其功能可以被uitest进程调用(IPC)

找到这个关键步骤后,我们可以把agent.so文件拿到,然后自己实现一套调用逻辑,这样就不用依赖 testRunner app 了

几个昼夜过去了,我整理出了详细的调用协议(详情请看 DEVELOP.md),然后快速开发出了 python 基础库,它的名字叫 hmdriver2

快速上手

  1. 配置鸿蒙HDC环境(等价于 android 端的 adb)
    1. 下载 Command Line Tools 并解压
    2. hdc文件在command-line-tools/sdk/HarmonyOS-NEXT-DB2/openharmony/toolchains目录下
    3. 配置环境变量,macOS 为例,在~/.bash_profile 或者 ~/.zshrc 文件中添加
export HM_SDK_HOME="/Users/develop/command-line-tools/sdk/HarmonyOS-NEXT-DB2"  //请以sdk实际安装目录为准
export PATH=$PATH:$HM_SDK_HOME/hms/toolchains:$HM_SDK_HOME/openharmony/toolchains
export HDC_SERVER_PORT=7035
  1. 电脑插上手机,开启 USB 调试,确保执行hdc list targets 可以看到设备序列号

  2. 安装hmdirver2 基础库

    pip3 install -U hmdriver2
    

    如果需要使用屏幕录屏 功能,则需要安装额外依赖opencv-python

    pip3 install -U "hmdriver2[opencv-python]"
    // 由于`opencv-python`比较大,因此没有写入到主依赖中,按需安装
    
  3. 接下来就可以愉快的进行脚本开发了 😊😊

    from hmdriver2.driver import Driver
    d = Driver("FMR0223C13000649")
    print(d.device_info)
    # ouput: DeviceInfo(productName='HUAWEI Mate 60 Pro', model='ALN-AL00', sdkVersion='12', sysVersion='ALN-AL00 5.0.0.60(SP12DEVC00E61R4P9log)', cpuAbi='arm64-v8a', wlanIp='172.31.125.111', displaySize=(1260, 2720), displayRotation=<DisplayRotation.ROTATION_0: 0>)
    d.start_app("com.kuaishou.hmapp", "EntryAbility")
    d(text="精选").click()
    ...
    

功能介绍

应用管理

  • 应用启动,停止
  • 应用安装,卸载
  • 应用数据清理
  • 获取应用列表,应用详情等

代码示例

from hmdriver2.driver import Driver

d = Driver("FMR0223C13000649")  # 替换成你的serial

d.install_app("/Users/develop/harmony_prj/demo.hap")

d.start_app("com.kuaishou.hmapp", "EntryAbility")

d.uninstall_app("com.kuaishou.hmapp")

d.stop_app("com.kuaishou.hmapp")

# 该方法表示清除App数据和缓存
d.clear_app("com.kuaishou.hmapp")

设备操作

  • 获取设备信息,分辨率,旋转状态等
  • 屏幕解锁,亮屏,息屏
  • Key Events
  • 文件操作
  • 屏幕截图
  • 手势操作(点击,滑动,输入,复杂手势)

代码示例

from hmdriver2.proto import DeviceInfo

info: DeviceInfo = d.device_info
# output:
# DeviceInfo(productName='HUAWEI Mate 60 Pro', model='ALN-AL00', sdkVersion='12', sysVersion='ALN-AL00 5.0.0.60(SP12DEVC00E61R4P9log)', cpuAbi='arm64-v8a', wlanIp='172.31.125.111', displaySize=(1260, 2720), displayRotation=<DisplayRotation.ROTATION_0: 0>)

# 获取设备旋转状态
rotation = d.display_rotation
# ouput: DisplayRotation.ROTATION_0

# KeyEvent
d.press_key(KeyCode.POWER)

# 截图
d.screenshot(""./test.png")

手势操作包括单击,双击,滑动,输入,复杂手势

d.click(200, 300)
d.click(0.4, 0.6)
d.double_click(500, 1000)
d.swipe(0.5, 0.8, 0.5, 0.4, speed=2000)

d.gesture.start(x1, y1, interval=.5).move(x2, y2).pause(interval=1).move(x3, y3).action()

如下是一个复杂手势的效果展示

屏幕录屏

with d.screenrecord.start("test2.mp4"):
    # do somethings
    time.sleep(5)

通过上下文语法,在录屏结束时框架会自动调用stop 清理资源

控件操作

  • 控件查找(联合查找,模糊查找,相对查找)
  • 控件信息获取
  • 控件点击,长按,拖拽,缩放
  • 文本输入,清除
  • 获取控件树

控件查找支持这些by属性

  • id
  • key
  • text
  • type
  • description
  • clickable
  • longClickable
  • scrollable
  • enabled
  • focused
  • selected
  • checked
  • checkable
  • isBefore
  • isAfter

定位方式包括普通定位,模糊定位,相当定位

d(text="tab_recrod")

d(id="drag")

# 定位所有`type`为Button的元素,选中第0个
d(type="Button", index=0)

# 定位`type`为Button且`text`为tab_recrod的元素
d(type="Button", text="tab_recrod")

# 定位`text`为showToast的元素的前面一个元素
d(text="showToast", isAfter=True) 

# 定位`id`为drag的元素的后面一个元素
d(id="drag", isBefore=True)

定位到控件后就可以进行信息获取和控件操作了

from hmdriver2.proto import ComponentData

d(text="tab_recrod").info

# output:
{
    "id": "",
    "key": "",
    "type": "Button",
    "text": "tab_recrod",
    "description": "",
    "isSelected": False,
    "isChecked": False,
    "isEnabled": True,
    "isFocused": False,
    "isCheckable": False,
    "isClickable": True,
    "isLongClickable": False,
    "isScrollable": False,
    "bounds": {
        "left": 539,
        "top": 1282,
        "right": 832,
        "bottom": 1412
    },
    "boundsCenter": {
        "x": 685,
        "y": 1347
    }
}

d(text="tab_recrod").click()
d(type="Button", text="tab_recrod").click()

d(text="tab_recrod").click_if_exists()

d(text="tab_recrod").double_click()
d(text="tab_recrod").long_click()

# 控件拖拽
componentB: ComponentData = d(type="ListItem", index=1).find_component()
d(type="ListItem").drag_to(componentB)  # 将元素拖动到元素B上

# 控件缩放
d(text="tab_recrod").pinch_in(scale=0.5)
d(text="tab_recrod").pinch_out(scale=2)

Toast 获取

hmdriver2 还可以获取界面的 toast,用法如下

# 启动toast监控
d.toast_watcher.start()

# do something 比如触发toast的操作
d(text="xx").click()  

# 获取toast
toast = d.toast_watcher.get_toast()

# output: 'testMessage'

PS. 所有功能详细介绍可以查看 API 文档

API 文档

https://github.com/codematrixer/hmdriver2/tree/master#api-documents

后续计划

  • [TODO] 全场景弹窗处理
  • [TODO] 操作标记
  • [TODO] Inspector
共收到 13 条回复 时间 点赞

👍 ,希望有机会可以交流互助。自动化功能我也早实现了,公司项目加上平时自己没时间维护 所以没打算开源😹

👍 👍 👍 思路值得学习

哲豪 回复

好啊😃 多多交流

codematrixer 回复

留个交流方式呗 😆

哲豪 回复

微信号:runorunorun

TesterHome小助手 将本帖设为了精华贴 09月24日 09:57

非常赞,控件属性查看工具 Inspector 啥时候上呢

kkppa 回复

正在排期,业余时间开发,时间比较紧😀

点赞。
不了解鸿蒙测试,不过安装 apk 和 push jar 区别不大,都可以 init 自动化完成的。 主要看安装 app 的方式是不是限制了进程?

xiscoxu 回复

apk 的方式,在 android 上通过 init 就很难全部兼容,比如 ov,安装过程输入密码这些(虽然可以再搞个安装助手),还是麻烦啊。说回鸿蒙,我文档里写了一个重要的原因是鸿蒙的打包签名机制走了苹果的老路,需要开发者证书签名那一套,挺恶心的。

支持 webview 吗?如 flutter 之类的

马上试用

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