安卓UI自动化测试:uiautomator2与weditor 0.6.4高效组合实战

📅 2026/6/21 15:36:51
安卓UI自动化测试:uiautomator2与weditor 0.6.4高效组合实战
1. 项目概述为什么是uiautomator2weditor如果你还在用Appium做安卓UI自动化测试感觉像是在开一辆需要频繁保养的老爷车那今天这个组合——Python的uiautomator2加上weditor 0.6.4——可能就是你的“新能源超跑”。我并不是说Appium不好它在跨平台、多语言支持上依然是王者。但对于专注安卓、追求极致执行速度和开发效率的团队或个人来说Appium那套基于WebDriver协议的架构有时候显得有点“重”了。每次执行都要启动一个服务通信开销也不小在需要快速迭代、高频执行的场景下体验并不丝滑。uiautomator2则走了另一条路。它直接利用了安卓系统自带的UI Automator测试框架通过Python库在PC端与手机上的atx-agent守护进程通信实现了对手机UI的直接操控。这就好比从“远程遥控”变成了“本地直连”指令传输的路径更短速度自然快得多。实测下来同样的点击、滑动操作uiautomator2的响应速度通常比Appium快30%以上稳定性也更好因为它绕过了WebDriver协议可能带来的一些兼容性层。那weditor又是什么你可以把它理解为uiautomator2的“专属UI侦查兵”。早期我们用uiautomator2写脚本定位元素要么靠adb shell uiautomator dump拉取XML再分析要么凭经验写选择器效率很低还容易出错。weditor的出现彻底改变了这一点。它提供了一个可视化的Web界面连接手机后能实时显示手机屏幕并且像Chrome开发者工具一样让你可以点击查看任意UI元素的详细属性resource-id, text, class, bounds等并直接生成可用的Python定位代码。weditor 0.6.4是目前一个比较稳定且功能完善的版本修复了不少早期版本的bug比如对悬浮窗、动态列表的支持更好。所以这个组合的核心价值在于“原生速度” “可视化效率”。它特别适合以下场景专注安卓应用的测试开发工程师需要编写大量稳定、快速的UI自动化用例。爬虫开发者需要对一些安卓App进行自动化操作来获取数据。个人开发者或小团队希望用最轻量、学习成本最低的方式实现自动化快速验证想法。从Appium迁移过来的老手受够了Appium的某些慢速或不稳定想寻求一个更纯粹的安卓解决方案。接下来我会带你从零开始搞定环境搭建、核心操作并附上我踩过无数坑才总结出来的编码避坑指南让你能真正“告别”那些繁琐和低效。2. 环境搭建与核心工具解析工欲善其事必先利其器。这一部分我们详细拆解uiautomator2和weditor的安装、初始化过程并解释每个步骤背后的原理确保你的基础环境坚如磐石。2.1 Python环境与uiautomator2安装首先你需要一个Python环境。我个人强烈推荐使用Python 3.7到3.9之间的版本。Python 3.10及以上版本在某些依赖库的兼容性上偶尔会出点小问题而3.6又太老。如果你电脑上已经有Anaconda用它创建一个干净的虚拟环境是再好不过的选择能有效避免包冲突。# 创建并激活一个名为u2的虚拟环境以conda为例 conda create -n u2 python3.8 conda activate u2安装uiautomator2非常简单一条pip命令搞定pip install uiautomator2这条命令不仅仅安装了uiautomator2这个Python库它后续在初始化手机时还会自动帮你处理很多事情。这里有个关键点uiautomator2库本身不包含驱动手机的核心能力那个能力在手机端。所以安装完Python库只是完成了第一步。2.2 手机端初始化atx-agent的秘密安装好库之后你需要用一行命令初始化你的安卓手机或模拟器python -m uiautomator2 init这行命令是魔法开始的地方。它会做以下几件重要的事检查ADB连接首先确保你的电脑通过ADB连接到了手机adb devices能看到设备。你需要提前安装好Android SDK Platform-Tools并配置好环境变量。推送atx-agent将一个小型的守护进程程序atx-agent推送到手机的/data/local/tmp/目录下。这个agent是用Go写的非常轻量它是PC端Python库和手机UI交互的桥梁。启动并安装服务在手机上运行atx-agent并让它监听7912端口默认。同时它还会自动在手机上安装两个辅助APKcom.github.uiautomator负责提供UI Automator v2的测试服务和com.github.uiautomator.test一个测试应用。这两个APK是实际执行点击、滑动等操作的“手”。注意事项手机需要开启USB调试并且最好在弹出“允许USB调试吗”的对话框时勾选“始终允许”。如果初始化失败最常见的原因是网络问题需要从GitHub下载atx-agent或者手机系统特别是某些深度定制的国产ROM禁止安装来自未知来源的应用。对于后者你需要手动在手机设置中开启“USB安装”或“通过USB安装应用”的权限。模拟器对于Android模拟器如Android Studio自带的AVD或夜神、雷电等初始化过程完全一样。确保模拟器已启动并通过ADB连接即可。初始化成功后你的手机就变成了一个等待Python指令的“智能终端”。你可以通过python -m uiautomator2 doctor命令来检查环境是否全部就绪。2.3 weditor 0.6.4的安装与启动weditor是一个独立的工具也需要通过pip安装。指定版本0.6.4是为了确保稳定性。pip install weditor0.6.4安装完成后启动它python -m weditor执行后它会自动在你的默认浏览器中打开一个本地网页地址通常是http://localhost:17310。这就是weditor的操作界面。weditor界面解析连接区顶部需要输入设备的ADB序列号adb devices列出的那个或者直接输入127.0.0.1:7912如果只有一台设备且atx-agent运行正常。点击“Connect”即可连接。实时投屏区连接成功后手机屏幕会实时显示在这里。你可以刷新Refresh来获取最新画面。元素属性区点击投屏上的任意元素右侧会显示该元素的所有可定位属性这是你编写脚本时最重要的信息来源。代码生成区在元素属性区下方weditor可以直接生成用于定位该元素的Python代码片段支持多种定位方式可以直接复制粘贴。实操心得 有时候weditor连接不上提示“无法获取截图”或“HTTP错误”。别慌按以下步骤排查确保python -m uiautomator2 init成功执行手机端的atx-agent正在运行。可以尝试在命令行用adb shell ps | grep atx查看进程。检查防火墙是否阻止了本地端口17310, 7912的通信。尝试重启weditor或者换一个浏览器Chrome/Firefox。如果使用模拟器确保是标准的ADB连接某些模拟器的多开器可能需要特殊的连接参数。3. 核心API与自动化脚本编写实战环境准备好后我们进入实战环节。uiautomator2的API设计非常直观学过一点Python就能快速上手。我们通过一个模拟打开“设置”应用并操作的过程来讲解。3.1 连接设备与基础操作首先在Python脚本中引入库并连接设备。import uiautomator2 as u2 # 连接设备方式一通过ADB序列号推荐多设备时精准 d u2.connect(你的设备序列号) # 例如 emulator-5554 # 连接设备方式二通过无线网络初始化后atx-agent会开启无线端口 # d u2.connect(手机IP:7912) # 例如 192.168.1.100:7912 # 连接设备方式三自动连接当前唯一的设备 # d u2.connect() # 当电脑只连接一台设备时使用连接成功后我们就可以像操作一个对象一样操作手机。# 1. 获取当前应用信息 print(d.info) # 打印屏幕分辨率、当前包名等信息 # 2. 屏幕操作 d.screen_on() # 点亮屏幕 d.screen_off() # 关闭屏幕 d.press(home) # 按下Home键其他还有back, power等 # 3. 启动应用以系统设置为例 d.app_start(com.android.settings) # 通过包名启动 # 或者通过Activity名启动更精确 # d.app_start(com.android.settings, .Settings) # 4. 停止应用 d.app_stop(com.android.settings) d.app_clear(com.android.settings) # 停止并清除数据 # 5. 点击与滑动使用绝对坐标不推荐除非万不得已 d.click(500, 1000) # 点击坐标(500, 1000) d.swipe(500, 1500, 500, 500, 0.5) # 从(500,1500)滑动到(500,500)耗时0.5秒但绝对坐标是自动化测试的“天敌”屏幕分辨率一变脚本就失效了。所以我们必须学会通过元素定位来操作。3.2 元素定位weditor的用武之地这是uiautomator2最核心的部分。我们不再关心坐标而是关心“按钮”、“文本框”这些UI元素本身。weditor在这里扮演了“侦察兵”的角色。操作流程在weditor中连接你的手机并打开你要测试的应用比如“设置”。点击weditor界面上的“刷新”按钮获取当前屏幕截图。用鼠标点击截图上的目标元素比如“WLAN”设置项。查看右侧面板你会看到类似如下的属性resource-id: android:id/title text: WLAN class: android.widget.TextView package: com.android.settings ... bounds: [42, 456][1038, 552]在“Code”区域weditor已经生成了定位代码比如d(text“WLAN”)。现在我们把这些定位器用到脚本里。uiautomator2支持多种定位方式可以组合使用# 通过文本定位最常用 d(textWLAN).click() # 通过resource-id定位最稳定如果开发给了id的话 d(resourceIdandroid:id/title).click() # 通过类名定位 d(classNameandroid.widget.TextView).click() # 通过描述content-desc定位常用于无障碍或ImageView d(description搜索).click() # 组合定位提高精确度 d(textWLAN, classNameandroid.widget.TextView).click() d(resourceIdcom.android.settings:id/search_bar, textContains搜索).click() # 父子节点、兄弟节点定位处理复杂层级 # 例如先定位到一个父容器再在里面找子元素 parent d(classNameandroid.widget.ListView) parent.child(text蓝牙).click()获取元素信息与等待 元素不是随时都存在的所以等待是必须的。# 等待元素出现默认最多等20秒 element d(textWLAN).wait(timeout10.0) # 等待10秒返回元素对象 if element: element.click() else: print(元素未找到) # 判断元素是否存在 if d(textWLAN).exists: d(textWLAN).click() # 获取元素的属性 element d(textWLAN).get_text() print(f元素文本是{element}) bounds d(textWLAN).bounds() # 获取元素的坐标范围 center_x (bounds[0] bounds[2]) / 2 center_y (bounds[1] bounds[3]) / 23.3 复杂交互输入、滑动与手势除了点击自动化测试离不开输入文本和复杂滑动。# 1. 输入文本 - 必须先定位到输入框EditText d(resourceIdcom.android.settings:id/search).set_text(蓝牙) # 直接设置文本会先清空 d(resourceIdcom.android.settings:id/search).send_keys(蓝牙) # 模拟键盘输入不清空 # 2. 滑动列表 - 这是处理滚动列表的关键 # scroll.vert.forward() 垂直向前滑动手指向上内容向下 # scroll.vert.backward() 垂直向后滑动手指向下内容向上 # scroll.horiz.forward() 水平向前滑动手指向左内容向右 # scroll.horiz.backward() 水平向后滑动手指向右内容向左 # 在某个元素如ListView上滑动 list_view d(classNameandroid.widget.ListView) list_view.scroll.vert.forward() # 向下滚动一屏 list_view.scroll.vert.backward(steps10) # 向上滚动steps控制滚动速度/幅度 # 在整个屏幕上滑动更常用 d.swipe(500, 1500, 500, 500, 0.5) # 从下往上滑动 d.swipe(500, 500, 500, 1500, 0.5) # 从上往下滑动 # 3. 更精确的滑动使用元素作为参考点 # 从元素A滑动到元素B el_a d(text通知) el_b d(text显示) el_a.drag_to(el_b, duration0.5) # 4. 手势操作多点触控需要坐标 d.touch.down(100, 200).move(100, 300).up() # 模拟长按拖动3.4 实战案例自动化配置WLAN让我们编写一个完整的迷你脚本实现打开设置 - 进入WLAN - 打开WLAN开关 - 选择指定网络并连接假设已知密码。import uiautomator2 as u2 import time d u2.connect() # 连接设备 try: # 1. 启动设置 d.app_start(com.android.settings) time.sleep(2) # 等待应用启动 # 2. 点击进入WLAN设置 if d(text网络和互联网).exists: d(text网络和互联网).click() time.sleep(1) d(textWLAN).click() else: # 有些系统设置布局不同直接找WLAN d(textWLAN).wait(timeout5).click() time.sleep(1.5) # 3. 打开WLAN开关通常是一个Switch组件 # 先判断当前状态避免重复操作 wlan_switch d(resourceIdcom.android.settings:id/switch_widget) if wlan_switch.exists: # 获取开关状态可能需要根据实际属性判断这里假设通过checked属性 # 更通用的方法是如果开关是“ON”文本或者其兄弟节点有“开”字 # 这里我们简单使用点击来切换 wlan_switch.click() print(已切换WLAN开关状态) time.sleep(3) # 等待扫描网络 # 4. 在列表中找到目标网络并点击这里以网络SSID为“MyHomeWiFi”为例 target_ssid “MyHomeWiFi” # 方法不断向下滑动直到找到目标网络或滑动到底部 max_swipes 10 for i in range(max_swipes): if d(texttarget_ssid).exists: d(texttarget_ssid).click() print(f找到并点击网络: {target_ssid}) break else: # 向下滑动列表 d.swipe(500, 1200, 500, 400, 0.3) time.sleep(1) # 等待滑动后UI稳定 else: print(f未找到网络: {target_ssid}) # 可以在这里加入截图保存方便排查 d.screenshot(f“./not_found_{int(time.time())}.png”) # 5. 在密码输入页面输入密码并连接 # 等待密码输入框出现 password_field d(resourceIdcom.android.settings:id/password).wait(timeout5) if password_field: password_field.set_text(“MyPassword123”) # 点击“连接”按钮注意按钮文本可能因系统/语言而异 d(text“连接”).click() # 或 d(text“Connect”).click() print(“密码已输入尝试连接”) time.sleep(5) # 等待连接过程 # 6. 验证连接成功可选 # 可以检查是否出现“已连接”或IP地址等信息 if d(textContains“已连接”).exists or d(textContains“Connected”).exists: print(“WLAN连接成功”) else: print(“WLAN连接状态未知请手动检查。”) except Exception as e: print(f“自动化过程出现异常: {e}”) # 发生异常时截图 d.screenshot(f“./error_{int(time.time())}.png”) finally: # 脚本结束可以按需停止应用或返回主页 # d.press(“home”) pass这个案例涵盖了启动应用、条件判断、元素等待、列表滑动、文本输入和异常处理等核心操作是一个比较完整的流程。4. 编码避坑指南与高级技巧用了一段时间后你会发现uiautomator2虽然强大但也有一些“坑”。下面是我总结的常见问题和解决方案以及一些提升脚本健壮性和效率的高级技巧。4.1 元素定位的“玄学”与稳定性提升坑1元素属性动态变化有些App的resource-id或者text是动态生成的每次打开都不一样。比如一个新闻列表的每一项id可能都包含时间戳。应对策略使用相对稳定的属性优先使用class、固定的resource-id如果有一部分是固定的或description。使用模糊匹配textContains(“新闻”)、textMatches(“正则表达式”)、resourceIdMatches(“.*button.*”)。使用XPath虽然uiautomator2原生定位器很强但复杂层级下XPath更直观。d.xpath(‘//android.widget.TextView[text“登录”]’)。使用兄弟/父子定位如果目标元素不好定位但它旁边有一个特征明显的兄弟元素可以先用兄弟定位到那个元素再通过兄弟元素.sibling()或父元素.child()来定位目标。坑2元素加载慢或偶尔找不到这是UI自动化最常见的问题。应对策略显式等待是王道摒弃time.sleep()多用wait()方法。d(text“确定”).wait(timeout10).click()。设置全局隐式等待d.implicitly_wait(10.0)但这只对d(selector).操作有效对d(selector).exists无效。重试机制对于关键操作可以封装一个带重试的点击函数。def click_with_retry(selector, max_retries3): for i in range(max_retries): if selector.exists: selector.click() return True else: print(f“第{i1}次重试等待元素...”) time.sleep(1) print(f“元素{selector.info}在{max_retries}次重试后仍未找到”) return False click_with_retry(d(text“稍后再说”))坑3悬浮窗、权限弹窗遮挡自动化过程中突然弹出的系统权限弹窗或应用内悬浮广告会打断流程。应对策略在关键步骤前预判比如在启动App后立即加入一个处理常见弹窗的逻辑。使用watcher监视器这是uiautomator2的一个神器可以注册一个“监视器”当特定元素出现时自动触发操作。# 注册一个监视器当出现“允许”按钮时自动点击它 d.watcher(“ALLOW_PERMISSION”).when(text“允许”).click(text“允许”) # 启动监视器默认不启动 d.watcher.start() # 在需要的时候手动运行一次监视器检查 d.watcher.run() # 注意监视器会消耗一定资源脚本结束后可以 d.watcher.remove(“ALLOW_PERMISSION”) 移除4.2 性能优化与脚本健壮性技巧1减少不必要的截图和日志weditor在连接时会持续截图这会影响脚本执行速度。在稳定的脚本中可以关闭weditor或断开连接。在生产环境运行脚本时确保没有开启weditor的浏览器页面。技巧2使用Session管理应用状态对于需要反复测试同一个App的场景使用session可以避免每次都用app_start冷启动速度更快。# 启动应用并获取session sess d.app_start(“com.example.app”, stopTrue) # stopTrue表示启动前先关闭旧实例 # 后续使用sess来进行操作上下文保持在这个应用内 sess(text“登录”).click() # 测试结束后 sess.close()技巧3善用adb shell命令uiautomator2可以直接执行adb命令有些操作用命令更直接。# 获取当前活动Activity current_activity d.shell(“dumpsys window windows | grep -E ‘mCurrentFocus|mFocusedApp’”).output print(current_activity) # 模拟物理按键 d.shell(“input keyevent 3”) # 3是HOME键的keycode d.shell(“input keyevent 4”) # 4是BACK键 # 清除应用数据 d.shell(“pm clear com.example.app”)技巧4结构化你的测试代码不要把所有代码写在一个巨大的文件里。使用Page Object模式将每个页面的元素定位和操作封装成类让测试用例更清晰维护更方便。# page/settings_page.py class SettingsPage: def __init__(self, d): self.d d def enter_wlan(self): self.d(text“网络和互联网”).click() self.d(text“WLAN”).wait(timeout5).click() return WlanPage(self.d) # 返回下一个页面的对象 # page/wlan_page.py class WlanPage: def __init__(self, d): self.d d self.switch d(resourceId“com.android.settings:id/switch_widget”) def toggle_wlan(self, enableTrue): current_state self._get_switch_state() # 假设有一个方法获取状态 if (enable and not current_state) or (not enable and current_state): self.switch.click() def connect_to_network(self, ssid, password): # ... 连接网络的逻辑 pass # test_case.py from page.settings_page import SettingsPage def test_connect_wifi(): d u2.connect() settings SettingsPage(d) wlan_page settings.enter_wlan() wlan_page.toggle_wlan(True) wlan_page.connect_to_network(“MyWiFi”, “password”) assert d(text“已连接”).exists4.3 常见错误与排查清单当你遇到脚本报错或行为不符合预期时可以按照这个清单来排查问题现象可能原因排查步骤与解决方案HTTPError: 500 Internal Server Erroratx-agent服务异常或未启动。1. 重启手机USB调试。2. 命令行执行adb shell /data/local/tmp/atx-agent server -d重启服务。3. 重新python -m uiautomator2 init。UiObjectNotFoundError元素定位不到。1.用weditor实时查看确认元素属性是否和脚本一致。2. 检查是否在正确的页面Activity。3. 元素是否被遮挡弹窗加入等待或弹窗处理。4. 属性是否是动态的改用模糊匹配或XPath。脚本执行速度慢1. 使用了大量time.sleep。2. weditor连接着在持续截图。3. 网络连接不稳定无线连接时。1. 用wait()代替固定sleep。2. 关闭weditor浏览器页面。3. 尝试使用USB连接。点击/输入无效1. 点击坐标落在了元素不可交互区域。2. 输入框未获取焦点。1. 尝试用元素.click()代替坐标点击。2. 点击输入框后再set_text。3. 尝试使用元素.long_click()或元素.double_click()。列表滑动不到底scroll.forward()一次滑动的距离是固定的。1. 改用d.swipe()并调整滑动起点、终点和持续时间。2. 使用while循环直到找到目标元素或达到最大滑动次数。在多台设备上运行不稳定设备分辨率、系统版本差异。1.绝对禁止使用硬编码坐标。2. 使用相对定位如d(className“ListView”).child(text“xxx”)。3. 针对不同设备可以准备不同的元素定位策略如备用text。5. 与CI/CD集成及更多可能性将uiautomator2脚本集成到持续集成/持续部署CI/CD流水线中可以实现自动化测试的常态化运行。这里以Jenkins为例给出一个最简单的思路。基本步骤准备Slave节点在Jenkins的Slave节点或Master节点上配置好Python环境、uiautomator2、以及连接好的安卓设备可以是实体手机长期连接也可以是稳定的模拟器。编写启动脚本创建一个shell脚本用于启动模拟器如果需要、运行你的Python测试套件可以使用pytest或unittest组织用例。配置Jenkins Job创建一个自由风格的软件项目。在“构建”环节选择“Execute shell”调用你的启动脚本。在“构建后操作”中添加归档测试报告如pytest生成的html报告和日志的步骤。处理测试结果使用pytest插件如pytest-html生成美观的测试报告并在Jenkins中展示。测试失败时可以自动保存当时的手机截图和日志方便排查。更高级的用法图像识别对于游戏或某些无法通过属性定位的控件可以结合OpenCV等图像识别库进行辅助定位。uiautomator2本身也提供简单的d.image.click(“button.png”)功能但精度和性能需要评估。并行测试如果你有多台设备可以使用pytest-xdist等插件实现测试用例的并行分发大幅缩短测试总时间。性能监控在自动化操作过程中可以穿插使用adb shell dumpsys gfxinfo或adb shell top等命令收集应用的帧率、内存、CPU占用等性能数据。从我个人的使用经验来看从Appium切换到uiautomator2weditor最直接的感受就是“快”和“稳”。少了中间层的开销指令执行更干脆weditor的可视化定位也让脚本编写效率提升了不止一个档次。当然它目前主要专注于安卓平台如果你的项目需要覆盖iOSAppium依然是更合适的选择。但对于安卓原生应用的自动化无论是功能测试、数据采集还是简单的重复操作这个组合都值得你花时间深入掌握。最后一个小建议把常用的操作和避坑逻辑封装成你自己的工具函数库下次新项目开始时你会感谢现在勤于总结的自己。