1. Uiautomator2项目概述
Uiautomator2是一个基于Python的Android自动化测试框架,它通过HTTP协议与运行在Android设备上的服务进行通信,实现了对Android设备的UI自动化操作。这个框架的核心价值在于将原生的UiAutomator API封装成更易用的Python接口,让测试人员能够用简洁的Python代码完成复杂的Android UI自动化任务。
作为一个在Android自动化测试领域深耕多年的工具,Uiautomator2解决了传统Android UI自动化测试中的几个痛点问题:
- 开发效率低:原生UiAutomator需要编写Java代码并打包成JAR运行,而Uiautomator2允许直接使用Python脚本
- 调试困难:提供了丰富的调试信息和可视化工具
- 稳定性问题:通过优化底层通信机制提高了测试稳定性
在实际项目中,Uiautomator2特别适合以下场景:
- 移动应用的功能回归测试
- 兼容性测试(不同设备/系统版本)
- 压力测试(长时间运行稳定性)
- 自动化数据采集(如爬取App内数据)
提示:Uiautomator2要求Android设备版本在4.4以上,Python版本3.8+。对于需要测试低版本Android系统的场景,可能需要考虑其他方案。
2. 核心架构与工作原理
2.1 整体架构设计
Uiautomator2采用客户端-服务端架构,分为两个主要部分:
设备端服务:
- 基于Android原生的UiAutomator框架实现
- 运行一个HTTP服务(默认端口9008)
- 提供各种Android自动化操作的RESTful接口
- 以JAR包形式运行在设备上(开源地址:github.com/openatx/android-uiautomator-server-jar)
Python客户端:
- 通过HTTP协议与设备端通信
- 封装各种UI操作为Python方法
- 提供简洁易用的API接口
# 典型的工作流程示例 import uiautomator2 as u2 # 连接设备 d = u2.connect() # 默认通过USB连接 # 操作设备 d(text="设置").click() # 点击"设置"文本元素2.2 通信机制解析
Uiautomator2底层使用JSON-RPC协议进行通信,这种设计带来了几个优势:
- 跨语言支持:理论上任何支持HTTP的语言都可以作为客户端
- 调试方便:可以直接用curl等工具手动发送请求
- 扩展性强:新增功能只需添加新的RPC接口
一个典型的请求/响应示例:
// 请求 { "jsonrpc": "2.0", "id": "1", "method": "click", "params": {"text": "设置"} } // 响应 { "jsonrpc": "2.0", "id": "1", "result": true }2.3 与原生UiAutomator的关系
Uiautomator2并不是完全重写的框架,而是在原生UiAutomator基础上做了以下改进:
- 接口封装:将Java API封装为更易用的Python接口
- 功能增强:添加了原生UiAutomator没有的功能,如XPath支持
- 稳定性优化:通过重试机制等提高自动化测试的稳定性
3. 环境搭建与基础使用
3.1 安装与配置
安装Uiautomator2非常简单,只需执行:
pip install uiautomator2为了获得完整的开发体验,建议同时安装元素检查工具:
pip install uiautodev uiautodev # 启动元素检查器设备端初始化:
python -m uiautomator2 init这个命令会自动在设备上安装必要的APK和服务。
常见问题:如果初始化失败,可以尝试手动安装atx-agent:
adb push atx-agent /data/local/tmp/ adb shell chmod 755 /data/local/tmp/atx-agent adb shell /data/local/tmp/atx-agent server -d
3.2 设备连接方式
Uiautomator2支持多种设备连接方式:
- USB连接(推荐):
d = u2.connect_usb('设备序列号') # 序列号通过adb devices获取- WiFi连接:
d = u2.connect('设备IP:端口') # 如'192.168.1.100:9008'- 通过ADB转发端口:
adb forward tcp:9008 tcp:9008然后可以使用:
d = u2.connect('127.0.0.1:9008')3.3 基础操作示例
下面是一个完整的测试脚本示例,演示了基本操作流程:
import uiautomator2 as u2 # 连接设备 d = u2.connect() # 解锁设备 d.unlock() # 启动应用 d.app_start("com.android.settings") # 操作UI元素 d(text="无线和网络").click() d(text="WLAN").click() # 获取元素属性 wifi_switch = d(resourceId="android:id/switch_widget") print("WiFi开关状态:", wifi_switch.info['checked']) # 截图保存 d.screenshot("wifi_setting.jpg")4. 元素定位与操作
4.1 元素定位策略
Uiautomator2提供了多种元素定位方式,各有适用场景:
- 文本定位:
d(text="设置") # 完全匹配 d(textContains="设") # 包含匹配 d(textMatches="设.*") # 正则匹配- 资源ID定位:
d(resourceId="com.android.settings:id/title")- 类名定位:
d(className="android.widget.TextView")- 描述文本定位:
d(description="设置按钮")- 组合定位:
d(text="设置", className="android.widget.TextView", resourceId="com.android.settings:id/title")4.2 XPath定位详解
Uiautomator2通过插件支持XPath定位,这是其相较于原生UiAutomator的一大优势:
# 基本XPath使用 d.xpath('//*[@text="设置"]').click() # 使用资源ID简化写法 d.xpath('@com.android.settings:id/title') # 等价于 //*[@resource-id="com.android.settings:id/title"] # 链式操作 setting = d.xpath('//*[@text="设置"]').wait(10) # 等待元素出现 if setting: setting.click()XPath定位特别适合处理复杂层级结构的UI,比如:
# 定位ListView中的特定项 d.xpath('//*[@resource-id="android:id/list"]/*[@class="android.widget.LinearLayout"][3]').click()4.3 元素操作API
定位到元素后,可以进行多种操作:
- 点击操作:
element.click() # 普通点击 element.long_click() # 长按 element.double_click() # 双击- 文本输入:
element.set_text("Hello") # 输入文本 element.clear_text() # 清空文本- 滑动操作:
element.swipe("left") # 向左滑动 element.swipe("up", steps=20) # 向上滑动,steps控制滑动速度- 属性获取:
print(element.info) # 获取元素所有属性 print(element.text) # 获取文本内容 print(element.bounds) # 获取元素坐标5. 高级功能与技巧
5.1 手势与滑动操作
Uiautomator2提供了丰富的手势操作API:
- 基本滑动:
d.swipe(100, 500, 100, 100) # 从(100,500)滑动到(100,100) d.swipe_ext("up") # 向上滑动整个屏幕- 元素滑动:
d(scrollable=True).scroll.to(text="底部元素") # 滚动直到指定元素可见- 复杂手势:
# 两点手势(用于缩放等操作) d.gesture((100,100), (200,200), (100,200), (200,100))5.2 多设备管理
对于需要同时控制多台设备的场景:
devices = u2.list_devices() # 获取所有连接设备 # 为每个设备创建连接 device_conns = [u2.connect(serial) for serial in devices] # 并行执行操作 for d in device_conns: d.app_start("com.android.settings")5.3 图像识别集成
虽然Uiautomator2主要基于UI层次结构操作,但可以集成OpenCV实现图像识别:
import cv2 # 截图并保存 d.screenshot("screen.png") # 使用OpenCV处理图像 img = cv2.imread("screen.png") gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 模板匹配等图像处理...5.4 性能优化技巧
- 减少截图频率:截图操作耗时较长,应尽量避免不必要的截图
- 合理设置等待时间:
d.implicitly_wait(5.0) # 设置全局等待超时为5秒- 使用缓存:对于不变的元素,可以缓存定位结果
- 关闭动画:执行测试前关闭系统动画可以提高速度
adb shell settings put global window_animation_scale 0 adb shell settings put global transition_animation_scale 0 adb shell settings put global animator_duration_scale 06. 常见问题与解决方案
6.1 元素找不到问题
这是最常见的问题,可能原因和解决方案:
- 界面未加载完成:
d(text="设置").wait(timeout=10) # 增加等待时间- 元素不在当前视图:
d(scrollable=True).scroll.to(text="目标元素") # 滚动查找- 动态ID问题:
# 使用相对定位 d(text="标题").right(className="android.widget.Button").click()6.2 稳定性问题处理
提高脚本稳定性的技巧:
- 增加重试机制:
def safe_click(element, max_retry=3): for _ in range(max_retry): try: element.click() return True except: time.sleep(1) return False- 处理弹窗干扰:
with d.watch_context() as ctx: ctx.when("确定").click() # 自动处理确定弹窗 # 执行主要测试逻辑- 异常捕获:
try: d(text="设置").click() except u2.UiObjectNotFoundError: print("设置按钮未找到")6.3 调试技巧
- 开启调试模式:
d.debug = True # 打印所有HTTP请求- 查看UI层次:
xml = d.dump_hierarchy() # 获取当前UI的XML结构 with open("hierarchy.xml", "w") as f: f.write(xml)- 使用元素检查工具:
uiautodev # 启动可视化检查器7. 实际项目应用案例
7.1 自动化测试框架集成
Uiautomator2可以轻松集成到现有测试框架中,例如与pytest结合:
import pytest import uiautomator2 as u2 @pytest.fixture(scope="module") def device(): d = u2.connect() yield d d.app_stop_all() def test_wifi_setting(device): device.app_start("com.android.settings") device(text="无线和网络").click() assert device(text="WLAN").exists7.2 数据采集示例
自动化采集应用内数据:
def collect_news(d): d.app_start("com.example.news") titles = [] while True: items = d(className="android.widget.ListView").child(className="android.widget.LinearLayout") for item in items: title = item.child(className="android.widget.TextView").text titles.append(title) if not d(scrollable=True).scroll.vert.forward(): break return titles7.3 兼容性测试方案
在多设备上运行相同的测试用例:
def test_on_multiple_devices(): devices = ["serial1", "serial2", "192.168.1.100:9008"] results = {} for serial in devices: d = u2.connect(serial) try: d.app_start("com.android.settings") assert d(text="设置").exists results[serial] = "PASS" except: results[serial] = "FAIL" return results8. 最佳实践与经验分享
8.1 脚本组织建议
- 页面对象模式:
class SettingsPage: def __init__(self, d): self.d = d def open_wifi(self): self.d(text="无线和网络").click() return WifiPage(self.d) class WifiPage: def __init__(self, d): self.d = d def toggle_wifi(self): self.d(text="WLAN").click()- 配置管理:
# config.py DEVICES = { "test_device1": "serial1", "test_device2": "192.168.1.100:9008" } # test_script.py from config import DEVICES d = u2.connect(DEVICES["test_device1"])8.2 性能测试技巧
进行长时间稳定性测试时:
import time def stress_test(d, hours=24): start = time.time() while time.time() - start < hours * 3600: d.app_start("com.example.app") # 执行一系列操作 d.app_stop("com.example.app") time.sleep(5)8.3 持续集成方案
将Uiautomator2测试集成到CI/CD流程中:
- 准备阶段:
# 安装依赖 pip install uiautomator2 pytest python -m uiautomator2 init- 测试阶段:
pytest tests/ --serial=设备序列号- 报告生成:
pytest --html=report.html9. 与其他工具的对比
9.1 与Appium比较
| 特性 | Uiautomator2 | Appium |
|---|---|---|
| 架构 | 直接基于UiAutomator | 多层抽象 |
| 性能 | 更高 | 相对较低 |
| 学习曲线 | 较低 | 较高 |
| 跨平台支持 | 仅Android | Android/iOS |
| 社区支持 | 较小 | 很大 |
| 适合场景 | 纯Android项目 | 跨平台项目 |
9.2 与原生UiAutomator比较
开发效率:
- 原生:需要Java开发,编译打包
- Uiautomator2:直接Python脚本运行
调试便利性:
- 原生:依赖Logcat
- Uiautomator2:实时交互式调试
功能扩展:
- 原生:功能受限于Android API
- Uiautomator2:可以方便地扩展Python功能
10. 未来发展与社区资源
10.1 学习资源推荐
官方文档:
- GitHub主页:github.com/openatx/uiautomator2
- 中文文档:github.com/openatx/uiautomator2/blob/master/README_CN.md
社区支持:
- QQ群:815453846
- Discord:https://discord.gg/PbJhnZJKDd
相关项目:
- weditor:增强版元素检查器
- atx-agent:设备端守护进程
10.2 常见开发路线
对于想要基于Uiautomator2进行二次开发的用户:
插件开发:
- 可以开发特定功能的插件
- 通过hook机制扩展功能
工具链整合:
- 与持续集成工具整合
- 开发可视化测试编排工具
云测试平台:
- 构建基于Uiautomator2的云测试服务
- 实现多设备并行测试
在实际使用Uiautomator2的过程中,我发现最影响效率的往往是元素定位的稳定性。经过多次实践,我总结出一个有效的方法:对于关键元素,最好使用相对定位结合多种属性,而不是依赖单一属性。例如,不要只用text定位,而是结合resourceId和className一起使用,这样可以大大提高脚本的稳定性。