# hmdriver2
**Repository Path**: ksllh/hmdriver2
## Basic Information
- **Project Name**: hmdriver2
- **Description**: No description available
- **Primary Language**: Unknown
- **License**: MIT
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 1
- **Forks**: 0
- **Created**: 2025-06-19
- **Last Updated**: 2025-07-24
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# hmdriver2
[](https://github.com/codematrixer/hmdriver2/actions)
[](https://pypi.python.org/pypi/hmdriver2)

[](https://pepy.tech/project/hmdriver2)
> 写这个项目前github上已有个叫`hmdriver`的项目,但它是侵入式(需要提前在手机端安装一个testRunner app);另外鸿蒙官方提供的hypium自动化框架,使用较为复杂,依赖繁杂。于是决定重写一套。
**hmdriver2** 是一款支持`HarmonyOS NEXT`系统的UI自动化框架,**无侵入式**,提供应用管理,UI操作,元素定位等功能,轻量高效,上手简单,快速实现鸿蒙应用自动化测试需求。

*微信交流群(永久生效)*
# Key idea
- **无侵入式**
- 无需提前在手机端安装testRunner APP(类似atx app)
- **易上手**
- 在PC端编写Python脚本实现自动化
- 对齐Android端 [uiautomator2](https://github.com/openatx/uiautomator2) 的脚本编写姿势
- **轻量高效**
- 摒弃复杂依赖(几乎0依赖),即插即用
- 操作响应快,低延时
# Feature
- 支持应用管理
- 应用启动,停止
- 应用安装,卸载
- 应用数据清理
- 获取应用列表,应用详情等
- 支持设备操作
- 获取设备信息,分辨率,旋转状态等
- 屏幕解锁,亮屏,息屏
- Key Events
- 文件操作
- 屏幕截图
- 屏幕录屏
- 手势操作(点击,滑动,输入,复杂手势)
- 支持控件操作
- 控件查找(联合查找,模糊查找,相对查找,xpath查找)
- 控件信息获取
- 控件点击,长按,拖拽,缩放
- 文本输入,清除
- 获取控件树
- 支持Toast获取
- UI Inspector
- [TODO] 全场景弹窗处理
- [TODO] 操作标记
# QUICK START
1. 配置鸿蒙`HDC`环境
1. 下载 [Command Line Tools](https://developer.huawei.com/consumer/cn/download/) 并解压
2. `hdc`文件在`command-line-tools/sdk/default/openharmony/toolchains`目录下
3. 配置环境变量,macOS为例,在~/.bash_profile 或者 ~/.zshrc文件中添加
```bash
export HM_SDK_HOME="/Users/develop/command-line-tools/sdk/default" //请以sdk实际安装目录为准
export PATH=$PATH:$HM_SDK_HOME/hms/toolchains:$HM_SDK_HOME/openharmony/toolchains
export HDC_SERVER_PORT=7035
```
2. 电脑插上手机,开启USB调试,确保执行`hdc list targets` 可以看到设备序列号
3. 安装`hmdirver2` 基础库
```bash
pip3 install -U hmdriver2
```
如果需要使用[屏幕录屏](#屏幕录屏) 功能,则需要安装额外依赖`opencv-python`
(由于`opencv-python`比较大,因此没有写入到主依赖中,按需安装)
```bash
pip3 install -U "hmdriver2[opencv-python]"
```
4. 接下来就可以愉快的进行脚本开发了 😊😊
```python
from hmdriver2.driver import Driver
d = Driver()
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=)
d.start_app("com.kuaishou.hmapp")
d(text="精选").click()
d.swipe(0.5, 0.8, 0.5, 0.4)
...
```
# UI inspector
UI 控件树可视化工具,查看控件树层级,获取控件详情。

详细介绍请看 [ui-viewer](https://github.com/codematrixer/ui-viewer)
# Environment
如何需要连接远端的HDC Server来实现操作远端设备执行自动化,运行脚本前需要设置环境变量
```bash
export HDC_SERVER_HOST=127.0.0.1 # Replace with the remote host
export HDC_SERVER_PORT=8710
```
PS 如果需要移除环境变量,执行以下命令
```bash
unset HDC_SERVER_HOST
unset HDC_SERVER_PORT
```
---
# API Documents
- [API Documents](#api-documents)
- [初始化Driver](#初始化driver)
- [App管理](#app管理)
- [安装App](#安装app)
- [卸载App](#卸载app)
- [启动App](#启动app)
- [停止App](#停止app)
- [清除App数据](#清除app数据)
- [获取App详情](#获取app详情)
- [获取App main ability](#获取App-main-ability)
- [设备操作](#设备操作)
- [获取设备信息](#获取设备信息)
- [获取设备分辨率](#获取设备分辨率)
- [获取设备旋转状态](#获取设备旋转状态)
- [设置设备旋转](#设置设备旋转)
- [Home](#home)
- [返回](#返回)
- [亮屏](#亮屏)
- [息屏](#息屏)
- [屏幕解锁](#屏幕解锁)
- [Key Events](#key-events)
- [执行 HDC 命令](#执行-hdc-命令)
- [打开URL (schema)](#打开url-schema)
- [文件操作](#文件操作)
- [屏幕截图](#屏幕截图)
- [屏幕录屏](#屏幕录屏)
- [Device Touch](#device-touch)
- [单击](#单击)
- [双击](#双击)
- [长按](#长按)
- [滑动](#滑动)
- [滑动 ext](#滑动-ext)
- [输入](#输入)
- [复杂手势](#复杂手势)
- [控件操作](#控件操作)
- [常规选择器](#常规选择器)
- [控件查找](#控件查找)
- [控件信息](#控件信息)
- [控件数量](#控件数量)
- [控件点击](#控件点击)
- [控件双击](#控件双击)
- [控件长按](#控件长按)
- [控件拖拽](#控件拖拽)
- [控件缩放](#控件缩放)
- [控件输入](#控件输入)
- [文本清除](#文本清除)
- [XPath选择器](#xpath选择器)
- [xpath控件是否存在](#xpath控件是否存在)
- [xpath控件点击](#xpath控件点击)
- [xpath控件双击](#xpath控件双击)
- [xpath控件长按](#xpath控件长按)
- [xpath控件输入](#xpath控件输入)
- [获取控件树](#获取控件树)
- [获取Toast](#获取toast)
## 初始化Driver
```python
from hmdriver2.driver import Driver
d = Driver()
# d = Driver("FMR0223C13000649")
```
参数`serial` 通过`hdc list targets` 命令获取;如果不传serial,则默认读取`hdc list targets`的第一个设备
初始化driver后,下面所有的操作都是调用dirver实现
## App管理
### 安装App
```python
d.install_app("/Users/develop/harmony_prj/demo.hap")
```
### 卸载App
```python
d.uninstall_app("com.kuaishou.hmapp")
```
传入的参数是`package_name`,可通过hdc命令获取`hdc shell bm dump -a`
### 启动App
```python
d.start_app("com.kuaishou.hmapp")
d.start_app("com.kuaishou.hmapp", "EntryAbility")
```
`package_name`, `page_name`分别为包名和ability name,可以通过hdc命令获取:`hdc shell aa dump -l`
不传`page_name`时,默认会使用main ability作为`page_name`
### 停止App
```python
d.stop_app("com.kuaishou.hmapp")
```
### 清除App数据
```python
d.clear_app("com.kuaishou.hmapp")
```
该方法表示清除App数据和缓存
### 获取App详情
```python
d.get_app_info("com.kuaishou.hmapp")
```
输出的数据结构是Dict, 内容如下
```bash
{
"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"
}
```
### 获取App main ability
```python
d.get_app_main_ability("com.kuaishou.hmapp")
```
输出的数据结构是Dict, 内容如下
```
{
"name": "EntryAbility",
"moduleName": "kwai",
"moduleMainAbility": "EntryAbility",
"mainModule": "kwai",
"isLauncherAbility": true,
"score": 2
}
```
## 设备操作
### 获取设备信息
```python
from hmdriver2.proto import DeviceInfo
info: DeviceInfo = d.device_info
```
输入内容如下
```bash
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=)
```
然后就可以获取你想要的值, 比如
```python
info.productName
info.model
info.wlanIp
info.sdkVersion
info.sysVersion
info.cpuAbi
info.displaySize
info.displayRotation
```
### 获取设备分辨率
```python
w, h = d.display_size
# outout: (1260, 2720)
```
### 获取设备旋转状态
```python
from hmdriver2.proto import DisplayRotation
rotation = d.display_rotation
# ouput: DisplayRotation.ROTATION_0
```
设备旋转状态包括:
```python
ROTATION_0 = 0 # 未旋转
ROTATION_90 = 1 # 顺时针旋转90度
ROTATION_180 = 2 # 顺时针旋转180度
ROTATION_270 = 3 # 顺时针旋转270度
```
### 设置设备旋转
```python
from hmdriver2.proto import DisplayRotation
# 旋转180度
d.set_display_rotation(DisplayRotation.ROTATION_180)
```
### Home
```python
d.go_home()
```
### 返回
```python
d.go_back()
```
### 亮屏
```python
d.screen_on()
```
### 息屏
```python
d.screen_off()
```
### 屏幕解锁
```python
d.unlock()
```
### Key Events
```python
from hmdriver2.proto import KeyCode
d.press_key(KeyCode.POWER)
```
详细的Key code请参考 [harmony key code](https://github.com/codematrixer/hmdriver2/blob/4d7bceaded947bd63d737de180064679ad4c77b8/hmdriver2/proto.py#L133)
### 执行 HDC 命令
```python
data = d.shell("ls -l /data/local/tmp")
print(data.output)
```
这个方法等价于执行 `hdc shell ls -l /data/local/tmp`
Notes: `HDC`详细的命令解释参考:[awesome-hdc](https://github.com/codematrixer/awesome-hdc)
### 打开URL (schema)
```python
d.open_url("http://www.baidu.com")
d.open_url("kwai://myprofile")
```
### 文件操作
```python
# 将手机端文件下载到本地电脑
d.pull_file(rpath, lpath)
# 将本地电脑文件推送到手机端
d.push_file(lpath, rpath)
```
参数`rpath`表示手机端文件路径,`lpath`表示本地电脑文件路径
### 屏幕截图
```python
d.screenshot(path)
```
参数`path`表示截图保存在本地电脑的文件路径
### 屏幕录屏
方式一
```python
# 开启录屏
d.screenrecord.start("test.mp4")
# do somethings
time.sleep(5)
# 结束录屏
d.screenrecord.stop()
```
上述方式如果录屏过程中,脚本出现异常时,`stop`无法被调用,导致资源泄漏,需要加上try catch
【推荐】方式二 ⭐️⭐️⭐️⭐️⭐️
```python
with d.screenrecord.start("test2.mp4"):
# do somethings
time.sleep(5)
```
通过上下文语法,在录屏结束时框架会自动调用`stop` 清理资源
Notes: 使用屏幕录屏需要依赖`opencv-python`
```bash
pip3 install -U "hmdriver[opencv-python]"
```
### Device Touch
#### 单击
```python
d.click(x, y)
# eg.
d.click(200, 300)
d.click(0.4, 0.6)
```
参数`x`, `y`表示点击的坐标,可以为绝对坐标值,也可以为相当坐标(屏幕百分比)
#### 双击
```python
d.double_click(x, y)
# eg.
d.double_click(500, 1000)
d.double_click(0.5, 0.4)
```
#### 长按
```python
d.long_click(x, y)
# eg.
d.long_click(500, 1000)
d.long_click(0.5, 0.4)
```
#### 滑动
```python
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
```python
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_ext`和`swipe`的区别在于swipe_ext可以指定滑动区域,并且可以指定滑动方向,更简洁灵活
#### 复杂手势
复杂手势就是手指按下`start`,移动`move`,暂停`pause`的集合,最后运行`action`
```python
g = d.gesture
g.start(x1, y1, interval=0.5)
g.move(x2, y2)
g.pause(interval=1)
g.move(x3, y3)
g.action()
```
也支持链式调用(推荐)
```python
d.gesture.start(x1, y1, interval=.5).move(x2, y2).pause(interval=1).move(x3, y3).action()
```
参数`x`, `y`表示坐标位置,可以为绝对坐标值,也可以为相当坐标(屏幕百分比),`interval`表示手势持续的时间,单位秒。
如果只有start手势,则等价于点击:
```python
d.gesture.start(x, y).action() # click
# 等价于
d.click(x, y)
```
*如下是一个复杂手势的效果展示*

#### 输入
```python
d.input_text(text)
# eg.
d.input_text("adbcdfg")
```
参数`x`, `y`表示输入的位置,`text`表示输入的文本
## 控件操作
### 常规选择器
控件查找支持这些`by`属性
- `id`
- `key`
- `text`
- `type`
- `description`
- `clickable`
- `longClickable`
- `scrollable`
- `enabled`
- `focused`
- `selected`
- `checked`
- `checkable`
- `isBefore`
- `isAfter`
Notes: 获取控件属性值可以配合 [UI inspector](https://github.com/codematrixer/ui-viewer) 工具查看
**普通定位**
```python
d(text="tab_recrod")
d(id="drag")
# 定位所有`type`为Button的元素,选中第0个
d(type="Button", index=0)
```
Notes:当同一界面有多个属性相同的元素时,`index`属性非常实用
**模糊定位TODO**
**组合定位**
指定多个`by`属性进行元素定位
```python
# 定位`type`为Button且`text`为tab_recrod的元素
d(type="Button", text="tab_recrod")
```
**相对定位**
```python
# 定位`text`为showToast的元素的前面一个元素
d(text="showToast", isAfter=True)
# 定位`id`为drag的元素的后面一个元素
d(id="drag", isBefore=True)
```
#### 控件查找
结合上面讲的控件选择器,就可以进行元素的查找
```python
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
```
#### 控件信息
```python
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
}
}
```
也可以单独调用对应的属性
```python
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
```
#### 控件数量
```python
d(type="Button").count # 输出当前页面`type`为Button的元素数量
# 也可以这样写
len(d(type="Button"))
```
#### 控件点击
```python
d(text="tab_recrod").click()
d(type="Button", text="tab_recrod").click()
d(text="tab_recrod").click_if_exists()
```
以上两个方法有一定的区别
- `click` 如果元素没找到,会报错`ElementNotFoundError`
- `click_if_exists` 即使元素没有找到,也不会报错,相当于跳过
#### 控件双击
```python
d(text="tab_recrod").double_click()
d(type="Button", text="tab_recrod").double_click()
```
#### 控件长按
```python
d(text="tab_recrod").long_click()
d(type="Button", text="tab_recrod").long_click()
```
#### 控件拖拽
```python
from hmdriver2.proto import ComponentData
componentB: ComponentData = d(type="ListItem", index=1).find_component()
# 将元素拖动到元素B上
d(type="ListItem").drag_to(componentB)
```
`drag_to`的参数`component`为`ComponentData`类型
#### 控件缩放
```python
# 将元素按指定的比例进行捏合缩小1倍
d(text="tab_recrod").pinch_in(scale=0.5)
# 将元素按指定的比例进行捏合放大2倍
d(text="tab_recrod").pinch_out(scale=2)
```
其中`scale`参数为放大和缩小比例
#### 控件输入
```python
d(text="tab_recrod").input_text("abc")
```
#### 文本清除
```python
d(text="tab_recrod").clear_text()
```
### XPath选择器
xpath选择器基于标准的xpath规范,也可以使用`//*[@属性="属性值"]`的样式(xpath lite)
```python
d.xpath('//root[1]/Row[1]/Column[1]/Row[1]/Button[3]')
d.xpath('//*[@text="showDialog"]')
```
控件xpath路径获取可以配合 [UI inspector](https://github.com/codematrixer/ui-viewer) 工具查看
#### xpath控件是否存在
```python
d.xpath('//*[@text="showDialog"]').exists() # 返回True/False
d.xpath('//root[1]/Row[1]/Column[1]/Row[1]/Button[3]').exists()
```
#### xpath控件点击
```python
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控件双击
```python
d.xpath('//*[@text="showDialog"]').double_click()
```
#### xpath控件长按
```python
d.xpath('//*[@text="showDialog"]').long_click()
```
#### xpath控件输入
```python
d.xpath('//*[@text="showDialog"]').input_text("adb")
```
## 获取控件树
```python
d.dump_hierarchy()
```
输出控件树格式参考 [hierarchy.json](/docs/hierarchy.json)
## 获取Toast
```python
# 启动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](/docs/DEVELOP.md)
# 拓展阅读
[hmdriver2 发布:开启鸿蒙 NEXT 自动化新时代](https://testerhome.com/topics/40667)
# Contributors
[Contributors](https://github.com/codematrixer/hmdriver2/graphs/contributors)
# Reference
- https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/ut-V5
- https://github.com/codematrixer/awesome-hdc
- https://github.com/openatx/uiautomator2
- https://github.com/mrx1203/hmdriver