Skip to main content

UI Automation Framework for Harmony Next

Project description

hmdriver2

github actions pypi version python downloads

写这个项目前github上已有个hmdirver,但它是侵入式(需要提前在手机端安装一个testRunner app)。另外鸿蒙官方提供的hypium自动化框架,使用较为复杂,依赖繁杂。于是决定重写一套。

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

arch

Key idea

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

Feature

  • 支持应用管理
    • 应用启动,停止
    • 应用安装,卸载
    • 应用数据清理
    • 获取应用列表,应用详情等
  • 支持设备操作
    • 获取设备信息,分辨率,旋转状态等
    • 屏幕解锁,亮屏,息屏
    • Key Events
    • 文件操作
    • 屏幕截图
    • 屏幕录屏
    • 手势操作(点击,滑动,输入,复杂手势)
  • 支持控件操作
    • 控件查找(联合查找,模糊查找,相对查找,xpath查找)
    • 控件信息获取
    • 控件点击,长按,拖拽,缩放
    • 文本输入,清除
    • 获取控件树
  • 支持Toast获取
  • UI Inspector
  • [TODO] 全场景弹窗处理
  • [TODO] 操作标记

QUICK START

  1. 配置鸿蒙HDC环境
    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`比较大,因此没有写入到主依赖中
  1. 接下来就可以愉快的进行脚本开发了 😊😊
from hmdriver2.driver import Driver

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

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()
d.swipe(0.5, 0.8, 0.5, 0.4)
...

UI inspector

UI 控件树可视化工具,查看控件树层级,获取控件详情。

ui-viewer

详细介绍请看 ui-viewer


API Documents

初始化Driver

from hmdriver2.driver import Driver

d = Driver("FMR0223C13000649")

参数serial 通过hdc list targets 命令获取

初始化driver后,下面所有的操作都是调用dirver实现

App管理

安装App

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

卸载App

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

传入的参数是package_name,可通过hdc命令获取hdc shell bm dump -a

启动App

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

传入的两个参数分别是package_name, page_name,可以通过hdc命令获取hdc shell aa dump -l

停止App

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

清除App数据

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

该方法表示清除App数据和缓存

获取App详情

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

输出的数据结构是Dict, 内容如下

{
    "appId": "com.kuaishou.hmapp_BIS88rItfUAk+V9Y4WZp2HgIZ/JeOgvEBkwgB/YyrKiwrWhje9Xn2F6Q7WKFVM22RdIR4vFsG14A7ombgQmIIxU=",
    "appIdentifier": "5765880207853819885",
    "applicationInfo": {
        ...
        "bundleName": "com.kuaishou.hmapp",
        "codePath": "/data/app/el1/bundle/public/com.kuaishou.hmapp",
        "compileSdkType": "HarmonyOS",
        "compileSdkVersion": "4.1.0.73",
        "cpuAbi": "arm64-v8a",
        "deviceId": "PHONE-001",
				...
        "vendor": "快手",
        "versionCode": 999999,
        "versionName": "12.2.40"
    },
    "compatibleVersion": 40100011,
    "cpuAbi": "",
    "hapModuleInfos": [
        ...
    ],
    "reqPermissions": [
        "ohos.permission.ACCELEROMETER",
        "ohos.permission.GET_NETWORK_INFO",
        "ohos.permission.GET_WIFI_INFO",
        "ohos.permission.INTERNET",
        ...
    ],
		...
    "vendor": "快手",
    "versionCode": 999999,
    "versionName": "12.2.40"
}

设备操作

获取设备信息

from hmdriver2.proto import DeviceInfo

info: DeviceInfo = d.device_info

输入内容如下

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>)

然后就可以获取你想要的值, 比如

info.productName
info.model
info.wlanIp
info.sdkVersion
info.sysVersion
info.cpuAbi
info.displaySize
info.displayRotation

获取设备分辨率

w, h = d.display_size

# outout: (1260, 2720)

获取设备旋转状态

from hmdriver2.proto import DisplayRotation

rotation = d.display_rotation
# ouput: DisplayRotation.ROTATION_0

设备旋转状态包括:

ROTATION_0 = 0    # 未旋转
ROTATION_90 = 1  # 顺时针旋转90度
ROTATION_180 = 2  # 顺时针旋转180度
ROTATION_270 = 3  # 顺时针旋转270度

设置设备旋转

from hmdriver2.proto import DisplayRotation

# 旋转180度
d.set_display_rotation(DisplayRotation.ROTATION_180)

Home

d.go_home()

返回

d.go_back()

亮屏

d.screen_on()

息屏

d.screen_off()

屏幕解锁

d.unlock()

Key Events

from hmdriver2.proto import KeyCode

d.press_key(KeyCode.POWER)

详细的Key code请参考 harmony key code

执行 HDC 命令

data = d.shell("ls -l /data/local/tmp")

print(data.output)

这个方法等价于执行 hdc shell ls -l /data/local/tmp

Notes: HDC详细的命令解释参考:awesome-hdc

打开URL (schema)

d.open_url("http://www.baidu.com")

d.open_url("kwai://myprofile")

文件操作

# 将手机端文件下载到本地电脑
d.pull_file(rpath, lpath)

# 将本地电脑文件推送到手机端
d.push_file(lpath, rpath)

参数rpath表示手机端文件路径,lpath表示本地电脑文件路径

屏幕截图

d.screenshot(path)

参数path表示截图保存在本地电脑的文件路径

屏幕录屏

方式一

# 开启录屏
d.screenrecord.start("test.mp4")

# do somethings
time.sleep(5)

# 结束录屏
d.screenrecord.stop()

上述方式如果录屏过程中,脚本出现异常时,stop无法被调用,导致资源泄漏,需要加上try catch

【推荐】方式二 ⭐️⭐️⭐️⭐️⭐️

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

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

Notes: 使用屏幕录屏需要依赖opencv-python

pip3 install -U "hmdriver[opencv-python]"

Device Touch

单击

d.click(x, y)

# eg.
d.click(200, 300)
d.click(0.4, 0.6)

参数x, y表示点击的坐标,可以为绝对坐标值,也可以为相当坐标(屏幕百分比)

双击

d.double_click(x, y)

# eg.
d.double_click(500, 1000)
d.double_click(0.5, 0.4)

长按

d.long_click(x, y)

# eg.
d.long_click(500, 1000)
d.long_click(0.5, 0.4)

滑动

d.swipe(x1, y1, x2, y2, spped)

# eg.
d.swipe(600, 2600, 600, 1200, speed=2000)  # 上滑
d.swipe(0.5, 0.8, 0.5, 0.4, speed=2000)
  • x1, y1表示滑动的起始点,x2, y2表示滑动的终点
  • speed为滑动速率, 范围:200~40000, 不在范围内设为默认值为2000, 单位: 像素点/秒

滑动 ext

d.swipe_ext("up")  # 向上滑动,"left", "right", "up", "down"
d.swipe_ext("right", scale=0.8)  # 向右滑动,滑动距离为屏幕宽度的80%
d.swipe_ext("up", box=(0.2, 0.2, 0.8, 0.8))  # 在屏幕 (0.2, 0.2) -> (0.8, 0.8) 这个区域上滑

# 使用枚举作为参数
from hmdriver2.proto import SwipeDirection
d.swipe_ext(SwipeDirection.DOWN)  # 向下滑动
  • direction表示滑动方向,可以为up, down, left, right, 也可以为SwipeDirection的枚举值
  • scale表示滑动距离百分比,范围:0.1~1.0, 默认值为0.8
  • box表示滑动区域,格式为(x1, y1, x2, y2), 表示滑动区域的左上角和右下角的坐标,可以为绝对坐标值,也可以为相当坐标(屏幕百分比)

Notes: swipe_extswipe的区别在于swipe_ext可以指定滑动区域,并且可以指定滑动方向,更简洁灵活

输入

d.input_text(text)

# eg.
d.input_text("adbcdfg")

参数x, y表示输入的位置,text表示输入的文本

复杂手势

复杂手势就是手指按下start,移动move,暂停pause的集合,最后运行action

g = d.gesture

g.start(x1, y1, interval=0.5)
g.move(x2, y2)
g.pause(interval=1)
g.move(x3, y3)
g.action()

也支持链式调用(推荐)

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

参数x, y表示坐标位置,可以为绝对坐标值,也可以为相当坐标(屏幕百分比),interval表示手势持续的时间,单位秒。

如果只有start手势,则等价于点击:

d.gesture.start(x, y).action() # click

# 等价于
d.click(x, y)

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

Watch the gif

控件操作

常规选择器

控件查找支持这些by属性

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

Notes: 获取控件属性值可以配合 UI inspector 工具查看

普通定位

d(text="tab_recrod")

d(id="drag")

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

Notes:当同一界面有多个属性相同的元素时,index属性非常实用

模糊定位TODO

组合定位

指定多个by属性进行元素定位

# 定位`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)

控件查找

结合上面讲的控件选择器,就可以进行元素的查找

d(text="tab_recrod").exists()
d(type="Button", text="tab_recrod").exists()
d(text="tab_recrod", isAfter=True).exists()

# 返回 True or False

d(text="tab_recrod").find_component()
# 当没找到返回None

控件信息

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").id
d(text="tab_recrod").key
d(text="tab_recrod").type
d(text="tab_recrod").text
d(text="tab_recrod").description
d(text="tab_recrod").isSelected
d(text="tab_recrod").isChecked
d(text="tab_recrod").isEnabled
d(text="tab_recrod").isFocused
d(text="tab_recrod").isCheckable
d(text="tab_recrod").isClickable
d(text="tab_recrod").isLongClickable
d(text="tab_recrod").isScrollable
d(text="tab_recrod").bounds
d(text="tab_recrod").boundsCenter

控件数量

d(type="Button").count   # 输出当前页面`type`为Button的元素数量

# 也可以这样写
len(d(type="Button"))

控件点击

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

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

以上两个方法有一定的区别

  • click 如果元素没找到,会报错ElementNotFoundError
  • click_if_exists 即使元素没有找到,也不会报错,相当于跳过

控件双击

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

控件长按

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

控件拖拽

from hmdriver2.proto import ComponentData

componentB: ComponentData = d(type="ListItem", index=1).find_component()

# 将元素拖动到元素B上
d(type="ListItem").drag_to(componentB)

drag_to的参数componentComponentData类型

控件缩放

# 将元素按指定的比例进行捏合缩小1倍
d(text="tab_recrod").pinch_in(scale=0.5)

# 将元素按指定的比例进行捏合放大2倍
d(text="tab_recrod").pinch_out(scale=2)

其中scale参数为放大和缩小比例

控件输入

d(text="tab_recrod").input_text("abc")

文本清除

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

XPath选择器

xpath选择器基于标准的xpath规范,也可以使用//*[@属性="属性值"]的样式(xpath lite)

d.xpath('//root[1]/Row[1]/Column[1]/Row[1]/Button[3]')
d.xpath('//*[@text="showDialog"]')

控件xpath路径获取可以配合 UI inspector 工具查看

xpath控件是否存在

d.xpath('//*[@text="showDialog"]').exists()   # 返回True/False
d.xpath('//root[1]/Row[1]/Column[1]/Row[1]/Button[3]').exists()

xpath控件点击

d.xpath('//*[@text="showDialog"]').click()
d.xpath('//root[1]/Row[1]/Column[1]/Row[1]/Button[3]').click_if_exists()

以上两个方法有一定的区别

  • click 如果元素没找到,会报错XmlElementNotFoundError
  • click_if_exists 即使元素没有找到,也不会报错,相当于跳过

xpath控件双击

d.xpath('//*[@text="showDialog"]').double_click()

xpath控件长按

d.xpath('//*[@text="showDialog"]').long_click()

xpath控件输入

d.xpath('//*[@text="showDialog"]').input_text("adb")

获取控件树

d.dump_hierarchy()

输出控件树格式参考 hierarchy.json

获取Toast

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

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

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

# output: 'testMessage'

鸿蒙Uitest协议

See DEVELOP.md

拓展阅读

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

Contributors

Contributors

Reference

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

hmdriver2-1.3.1.tar.gz (153.0 kB view details)

Uploaded Source

Built Distribution

hmdriver2-1.3.1-py3-none-any.whl (149.5 kB view details)

Uploaded Python 3

File details

Details for the file hmdriver2-1.3.1.tar.gz.

File metadata

  • Download URL: hmdriver2-1.3.1.tar.gz
  • Upload date:
  • Size: 153.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.1.1 CPython/3.12.7

File hashes

Hashes for hmdriver2-1.3.1.tar.gz
Algorithm Hash digest
SHA256 0bbfe0c04d9e1de3b535db382739eedd2b0f3afb2b5bdd51952f7029040bd277
MD5 903d9e702feb487cd3b504a81360ab3a
BLAKE2b-256 c81a2f9f47a9170eca81d0517b5ffdea8461651789e0e2d6d89ce9bf72a405f9

See more details on using hashes here.

File details

Details for the file hmdriver2-1.3.1-py3-none-any.whl.

File metadata

  • Download URL: hmdriver2-1.3.1-py3-none-any.whl
  • Upload date:
  • Size: 149.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.1.1 CPython/3.12.7

File hashes

Hashes for hmdriver2-1.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 397f72a4987478f5113b3d86e0137589e753f9f90a8233cdc24355d943fb0a6b
MD5 6d7f387f821bc2ae5a4087ebf9695997
BLAKE2b-256 d689501d304d0d863881dbc7efa33ec98f44698a5f18b5277f4faa57b2b51acf

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page