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

Feature

  • 支持鸿蒙Next系统的UI自动化测试
  • 无侵入式,无需在手机安装基于ArkTS的testRunner APP
  • 稳定高效,直接和鸿蒙底层uitest服务交互
  • 轻量,上手简单,即插即用
  • 支持应用管理
    • 应用启动,停止
    • 应用安装,卸载
    • 应用数据清理
    • 获取应用列表,应用详情等
  • 支持设备操作
    • 获取设备信息,分辨率,旋转状态等
    • 屏幕解锁,亮屏,息屏
    • Key Events
    • 文件操作
    • 屏幕截图
    • 屏幕录屏
    • 手势操作(点击,滑动,输入,复杂手势)
  • 支持控件操作
    • 控件查找(联合查找,模糊查找,相对查找)
    • 控件信息获取
    • 控件点击,长按,拖拽,缩放
    • 文本输入,清除
    • 获取控件树
  • 支持Toast获取
  • [TODO] 全场景弹窗处理
  • [TODO] 操作标记
  • [TODO] Inspector

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 hmdriver

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

pip3 install -U "hmdriver[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>)

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.samples.test.uitest")

输出的数据结构是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度

备注:目前旋转状态只能查看,不支持设置

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, 单位: 像素点/秒

输入

d.input_text(x, y, text)

# eg.
d.input_text(0.3, 0.5, "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

普通定位

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

获取控件树

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

Refer to

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.2.5.tar.gz (88.9 kB view details)

Uploaded Source

Built Distribution

hmdriver2-1.2.5-py3-none-any.whl (86.3 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for hmdriver2-1.2.5.tar.gz
Algorithm Hash digest
SHA256 7488601b32c0985909607e10b3a4f1775fe4e2bf54fb6992f684954172964ade
MD5 037b3e976ee9822914c441d81509437f
BLAKE2b-256 db2c7c6f839251f8c076aea3b4700ff4fde4d796ed08ff4f3c62e57d2cbf50e6

See more details on using hashes here.

File details

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

File metadata

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

File hashes

Hashes for hmdriver2-1.2.5-py3-none-any.whl
Algorithm Hash digest
SHA256 d1a74f959cbdb4a7f317a8b54d8f73056b53755baa68c6970b9f8e3b5abaf46c
MD5 6a1e245577dfa568dc695f65081929e4
BLAKE2b-256 a25973c825c54dfd888653c05c0feb6065716bdac15dd94503302836d54b248c

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