Android Uiautomator2 Python Wrapper与Selenium Grid集成:分布式测试方案
1. 分布式测试的痛点与解决方案
1.1 移动自动化测试的挑战
在移动应用测试中,单一设备测试面临三大核心痛点:
- 设备碎片化:Android设备型号、系统版本、分辨率组合超过1000种
- 测试效率低下:单设备执行1000个用例需8小时,多设备并行可缩短至1小时
- 资源利用率不足:开发机闲置时测试资源浪费
1.2 集成架构概览
2. 环境准备与核心依赖
2.1 软件版本要求
| 组件 | 最低版本 | 推荐版本 |
|---|---|---|
| Python | 3.6 | 3.9+ |
| Uiautomator2 | 2.0.0 | 2.16.0+ |
| Selenium | 3.141.0 | 4.4.3+ |
| Selenium Grid | 3.141.0 | 4.4.3+ |
| Adb | 1.0.41 | 1.0.41+ |
2.2 安装核心库
# 克隆项目仓库
git clone https://gitcode.com/gh_mirrors/ui/uiautomator2.git
cd uiautomator2
# 安装依赖
pip install -e .
pip install selenium==4.4.3
3. Selenium Grid节点配置
3.1 自定义Uiautomator2远程驱动
from appium import webdriver
from uiautomator2 import Device
class Uiautomator2RemoteDriver:
def __init__(self, command_executor, desired_capabilities):
self.command_executor = command_executor
self.desired_capabilities = desired_capabilities
self.device = self._init_device()
def _init_device(self):
# 从capabilities获取设备序列号
serial = self.desired_capabilities.get('udid')
if not serial:
raise ValueError("设备序列号(udid)必须在capabilities中指定")
# 初始化Uiautomator2设备连接
d = Device(serial)
d.wait_ready(timeout=30)
return d
def find_element(self, by, value):
# 实现元素查找逻辑
if by == 'xpath':
return self.device.xpath(value).get()
raise NotImplementedError(f"查找方式 {by} 暂未实现")
def click(self, x, y):
self.device.click(x, y)
def swipe(self, fx, fy, tx, ty, duration=0.5):
self.device.swipe(fx, fy, tx, ty, duration)
3.2 Selenium Grid节点配置文件
创建uiautomator2-node-config.json:
{
"capabilities": [
{
"browserName": "Android",
"platformName": "Android",
"automationName": "Uiautomator2",
"maxInstances": 5,
"udid": "emulator-5554",
"deviceName": "Pixel_6_Pro_API_30"
},
{
"browserName": "Android",
"platformName": "Android",
"automationName": "Uiautomator2",
"maxInstances": 5,
"udid": "R58M95xxxx",
"deviceName": "Galaxy_S21"
}
],
"proxy": "org.openqa.grid.selenium.proxy.DefaultRemoteProxy",
"maxSession": 10,
"port": 5555,
"register": true,
"registerCycle": 5000,
"hubHost": "192.168.1.100",
"hubPort": 4444
}
4. 启动与管理Selenium Grid集群
4.1 启动Hub节点
# 下载Selenium Server
wget https://repo1.maven.org/maven2/org/openqa/selenium/selenium-server/4.4.3/selenium-server-4.4.3.jar
# 启动Hub
java -jar selenium-server-4.4.3.jar hub --port 4444
4.2 启动Uiautomator2节点
# 启动节点(确保已安装Java 8+)
java -jar selenium-server-4.4.3.jar node \
--config uiautomator2-node-config.json \
--selenium-manager true
4.3 验证集群状态
访问Grid控制台:http://localhost:4444/ui/index.html,应显示所有注册的Android设备节点。
5. 核心API封装与使用
5.1 分布式测试客户端实现
from selenium import webdriver
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
class Uiautomator2GridClient:
def __init__(self, hub_url):
self.hub_url = hub_url
self.driver = None
def connect(self, device_name=None, platform_version=None):
# 配置Desired Capabilities
caps = DesiredCapabilities.ANDROID.copy()
caps['automationName'] = 'Uiautomator2'
caps['platformName'] = 'Android'
if device_name:
caps['deviceName'] = device_name
if platform_version:
caps['platformVersion'] = platform_version
# 连接到Selenium Grid
self.driver = webdriver.Remote(
command_executor=self.hub_url,
desired_capabilities=caps
)
return self.driver
def run_test_case(self, test_func):
try:
test_func(self.driver)
return True, "测试通过"
except Exception as e:
return False, str(e)
finally:
if self.driver:
self.driver.quit()
5.2 元素定位与操作示例
def test_wechat_login(driver):
# 使用XPath定位元素(Uiautomator2核心功能)
driver.find_element_by_xpath('//*[@text="登录"]').click()
# 输入账号密码(使用Uiautomator2的send_keys方法)
driver.find_element_by_xpath('//*[@resource-id="com.tencent.mm:id/et_username"]').send_keys("test_account")
driver.find_element_by_xpath('//*[@resource-id="com.tencent.mm:id/et_password"]').send_keys("test_password")
# 点击登录按钮(使用Uiautomator2的click方法)
driver.find_element_by_xpath('//*[@text="登录"]').click()
# 验证登录成功
assert driver.find_element_by_xpath('//*[@text="微信"]').exists()
6. 测试任务分发与并行执行
6.1 动态设备分配策略
from concurrent.futures import ThreadPoolExecutor
def distribute_tests(hub_url, test_cases, max_workers=5):
client = Uiautomator2GridClient(hub_url)
# 使用线程池并行执行测试
with ThreadPoolExecutor(max_workers=max_workers) as executor:
# 提交所有测试任务
futures = [
executor.submit(client.run_test_case, test_case)
for test_case in test_cases
]
# 获取执行结果
results = []
for future in futures:
results.append(future.result())
return results
# 测试用例列表
test_suite = [
test_wechat_login,
test_wechat_send_message,
test_wechat_pay
]
# 执行分布式测试
results = distribute_tests("http://192.168.1.100:4444/wd/hub", test_suite)
6.2 负载均衡与任务优先级
7. 结果收集与报告生成
7.1 集成HTML测试报告
from uiautomator2.ext.htmlreport import HTMLReport
def run_with_report(driver, test_func, report_dir="distributed_report"):
# 初始化报告生成器
report = HTMLReport(driver, report_dir)
report.patch_click() # 自动记录点击操作
try:
# 执行测试并生成报告
test_func(driver)
report._addtosteps({"action": "测试完成", "status": "success"})
except Exception as e:
report._addtosteps({"action": f"测试失败: {str(e)}", "status": "error"})
raise
finally:
report._flush() # 生成HTML报告
7.2 分布式日志聚合
import logging
from logging.handlers import RotatingFileHandler
def setup_logging(log_file="distributed_test.log", max_bytes=10*1024*1024, backup_count=5):
# 配置日志聚合
logger = logging.getLogger("uiautomator2_grid")
logger.setLevel(logging.DEBUG)
# 文件处理器
file_handler = RotatingFileHandler(
log_file, maxBytes=max_bytes, backupCount=backup_count
)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
# 控制台处理器
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
# 添加处理器
logger.addHandler(file_handler)
logger.addHandler(console_handler)
return logger
8. 高级优化与最佳实践
8.1 设备状态监控
def monitor_device_health(driver):
"""监控设备CPU、内存和网络状态"""
device_info = driver.device.info()
cpu_usage = driver.device.shell("top -n 1 | grep uiautomator").output
memory_usage = driver.device.shell("dumpsys meminfo uiautomator").output
return {
"device": device_info,
"cpu": cpu_usage,
"memory": memory_usage,
"timestamp": datetime.now().isoformat()
}
8.2 异常处理与重试机制
def retry_on_failure(max_retries=3, delay=2):
def decorator(test_func):
def wrapper(driver):
for attempt in range(max_retries):
try:
return test_func(driver)
except Exception as e:
if attempt == max_retries - 1:
raise
logger.warning(f"尝试 {attempt+1} 失败,重试中...")
time.sleep(delay)
return wrapper
return decorator
# 应用重试装饰器
@retry_on_failure(max_retries=3)
def test_unstable_feature(driver):
# 可能不稳定的测试操作
driver.find_element_by_xpath('//*[@text="不稳定元素"]').click()
9. 常见问题与解决方案
9.1 连接超时问题
| 问题场景 | 解决方案 |
|---|---|
| Uiautomator2 Server启动失败 | 1. 检查设备是否授权 2. 执行 uiautomator2 init重置服务3. 检查设备存储空间 |
| Selenium节点注册失败 | 1. 验证Hub地址和端口可达性 2. 检查防火墙设置 3. 确保节点配置文件正确 |
| 设备响应缓慢 | 1. 降低单个节点并发数 2. 增加设备资源(CPU/内存) 3. 优化测试用例,减少不必要操作 |
9.2 元素定位失败
-
解决方案1:使用Uiautomator2的隐式等待
driver.implicitly_wait(10) # 设置10秒隐式等待 -
解决方案2:结合XPath和坐标定位
# 先尝试XPath定位,失败则使用坐标 try: driver.find_element_by_xpath('//*[@text="按钮"]').click() except: driver.click(500, 1000) # 直接坐标点击
10. 性能优化与扩展方向
10.1 测试效率优化策略
- 用例分层:将测试分为单元、集成、UI层,减少UI层测试数量
- 元素缓存:复用已定位元素,减少重复查找开销
- 并行粒度:按功能模块拆分测试套件,实现细粒度并行
10.2 未来扩展方向
- 设备农场集成:对接AWS Device Farm、Firebase Test Lab等云设备平台
- AI测试生成:使用机器学习分析UI变化,自动生成测试用例
- 实时监控面板:构建设备状态和测试进度的实时可视化平台
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



