Android自动化测试进阶:UIAutomator2与ADB命令的深度结合实践
1. 项目概述为什么需要结合UIAutomator2与ADB在Android应用质量保障这条路上我踩过的坑可能比很多人走过的路还多。从早期的手工点点点到后来接触各种自动化框架我一直在寻找一个既稳定、高效又能深入系统底层、应对复杂场景的“终极”方案。最终我的工具箱里沉淀下来的核心组合就是UIAutomator2和ADB命令。这听起来可能像老生常谈但真正能把这两者“完美结合”起来发挥出112威力的实践并不多见。很多人把UIAutomator2仅仅当作一个元素定位和操作的工具把ADB看作安装应用、截图的辅助命令。这种认知太浅了。UIAutomator2提供了强大的UI遍历和控件交互能力但它本质上运行在设备内部与测试脚本通常跑在PC上的通信存在延迟和稳定性挑战。而ADB作为Android调试桥是你与设备系统直接对话的“上帝模式”命令行工具。它的强大在于其“底层性”和“直接性”可以绕过UI层直接操作文件、进程、设置甚至模拟硬件事件。所谓“完美结合”其核心思想是让UIAutomator2专注于它擅长的“业务逻辑”模拟如点击、输入、滑动、断言而让ADB命令去处理那些UIAutomator2不擅长、不稳定或根本无法完成的“环境控制”与“系统级操作”。例如在测试开始前用ADB清理应用数据、设置特定网络状态在测试过程中用ADB监控CPU/内存在UI操作失败时用ADB截图并拉取到本地分析甚至用ADB模拟各种极端中断如来电、低电量弹窗来检验应用的健壮性。这个指南适合所有层次的Android测试开发者和质量工程师。如果你是新手可以把它看作一份从零搭建健壮自动化体系的路线图如果你已有经验这里面的深度结合技巧和避坑实录或许能帮你解决那些困扰已久的“玄学”问题。我们的目标不是简单地罗列命令或API而是构建一个以UIAutomator2为“躯干”ADB命令为“四肢”和“感官”的、高度协同的自动化测试生命体。2. 环境搭建与工具选型背后的逻辑工欲善其事必先利其器。但“利器”的选择背后是无数个深夜调试换来的教训。我不会给你一个简单的pip install列表就完事而是告诉你为什么选这些以及如何配置才能避免90%的初期环境问题。2.1 Python与UIAutomator2为什么是Python首先明确我们讨论的UIAutomator2特指其Python客户端库uiautomator2它通过HTTP协议与安装在设备上的atx-agent服务通信从而驱动UIAutomator。选择Python生态原因有三一是语法简洁开发效率高适合快速构建和迭代测试用例二是社区庞大拥有丰富的第三方库如pytest用于测试组织allure-pytest用于报告生成能轻松构建完整的测试工程三是与CI/CD工具如Jenkins集成非常方便。安装不是简单一句pip install uiautomator2。我强烈建议使用虚拟环境并与特定版本的adb绑定。# 1. 创建并激活虚拟环境避免包冲突 python -m venv venv_android_test source venv_android_test/bin/activate # Linux/Mac # venv_android_test\Scripts\activate # Windows # 2. 安装核心库指定版本以确保稳定性 pip install uiautomator22.16.24 # 选择一个经过广泛验证的稳定版本 pip install weditor0.6.4 # UI元素定位的利器必装 pip install pytest7.4.0 # 测试框架 pip install allure-pytest2.13.2 # 美观的测试报告注意不要盲目追求最新版本。uiautomator2的某些新版本可能存在兼容性问题。2.16.x系列在长期实践中被证明是相当稳定的。2.2 ADB工具链不仅仅是“adb”一个命令很多人以为ADB就是一个adb.exe文件。大错特错。完整的Android SDK Platform-Tools才是关键它包含了adb,fastboot,sqlite3等一系列工具。我推荐直接从Google官方下载独立的Platform-Tools包而不是通过Android Studio安装这样更干净版本控制也更直接。下载访问 Android开发者网站 下载对应操作系统的包。配置环境变量将解压后的目录路径例如D:\platform-tools添加到系统的PATH环境变量中。验证打开终端输入adb version应能正确显示版本号。这里有一个关键技巧在自动化脚本中最好使用ADB命令的绝对路径或者将platform-tools目录置于脚本可访问的位置。这能避免因系统环境变量配置不同导致的“adb不是内部或外部命令”错误尤其是在CI服务器上。2.3 设备连接与初始化稳定性的第一步连接设备是第一步也是问题最多的一步。除了常见的adb devices你需要一套组合拳来确保连接稳定。# 查看连接设备 adb devices -l # 输出示例device usb:3-4.4 product:raphael model:Mi_9T device:raphael transport_id:5 # 注意 -l 参数会显示更详细的设备信息对于多设备时指定设备非常有用。如果设备未授权需要手动在设备上点击“允许USB调试”。对于自动化环境这很麻烦。可以通过预先安装设备证书或使用一些厂商提供的工具解决但对于普通测试手动授权一次即可。初始化UIAutomator2环境到设备 这是UIAutomator2 Python库特有的步骤很多人会忘记导致后续连接失败。import uiautomator2 as u2 # 方法1通过设备序列号连接推荐尤其多设备时 device_serial 你的设备序列号 # 从 adb devices 获取 d u2.connect(device_serial) # 方法2通过无线连接调试用 # 首先确保设备与电脑在同一网络并用adb tcpip开启无线调试端口 # adb tcpip 5555 # 然后连接 # d u2.connect(192.168.1.100:5555) # 连接成功后自动初始化环境首次运行会安装atx-agent等 d.healthcheck()healthcheck()方法会检查设备端服务状态如果缺失会自动安装必要的组件atx-agent,com.github.uiautomator等。务必确保设备已解锁屏幕并处于主界面否则安装可能失败。3. UIAutomator2核心能力深度解析与定位策略UIAutomator2的核心价值在于其对Android UI层卓越的操控能力。但仅仅会d(text“确定”).click()是远远不够的。3.1 元素定位从“能用”到“健壮”定位是自动化的基石。不稳定的定位是脚本脆弱的首要原因。资源ID定位最优先选择。但要注意有些应用的资源ID是动态生成的如包含时间戳。d(resourceIdcom.example.app:id/btn_login).click()文本定位非常直观但受语言、文本变化影响大。d(text登录).click() # 使用模糊匹配应对微小变化 d(textContains录).click()描述定位content-desc对于无障碍支持好的应用是利器。d(description搜索按钮).click()组合定位与层级定位这是应对复杂UI、提高定位精度的关键。# 组合条件同时满足多个属性 d(classNameandroid.widget.Button, text提交, resourceIdcom.example:id/btn).click() # 相对定位通过兄弟节点或父子关系 # 例如定位“价格”文本旁边的输入框 price_text d(text价格) edit_box price_text.sibling(classNameandroid.widget.EditText) edit_box.set_text(100) # 使用XPath谨慎使用性能较差但某些复杂场景无可替代 d.xpath(//android.widget.TextView[text用户名]/../android.widget.EditText).set_text(test)实操心得永远不要依赖单一的定位策略。我通常会采用“主定位 备用定位 智能等待”的策略。优先使用稳定的resourceId如果没有则使用text或description并结合child,sibling等方法缩小范围。同时所有定位操作都必须包裹在显式等待中。3.2 等待机制告别“No matching node found”脚本报“找不到元素”十有八九是等待问题。UIAutomator2提供了几种等待方式隐式等待为整个会话设置一个全局的查找元素超时时间。d.implicitly_wait(10.0) # 设置隐式等待10秒这很方便但不够灵活对于加载特别慢的页面可能不够对于本应快速失败的情况又显得拖沓。显式等待这是我最推荐也是实际项目中使用最多的方式。它针对特定条件进行等待更精确。# 等待一个元素出现 d(text加载中...).wait(timeout15.0) # 等待最多15秒直到“加载中”文本出现或消失等待其消失 # 更常见的用法是等待目标元素出现 submit_btn d(resourceIdsubmit).wait(timeout10.0) if submit_btn: submit_btn.click() else: raise Exception(提交按钮未在10秒内出现)轮询检查用于更复杂的条件比如等待某个元素消失或者等待页面处于某种稳定状态。# 等待“加载中”提示消失 d(text加载中...).wait_gone(timeout30.0) # 自定义轮询条件 def is_page_loaded(): return d(text首页).exists and not d(text加载中).exists start_time time.time() while time.time() - start_time 30: if is_page_loaded(): break time.sleep(1) else: raise TimeoutError(页面加载超时)避坑指南在wait和wait_gone的使用上有一个常见的误解。d(ele).wait()是等待这个元素出现而d(ele).wait_gone()是等待这个元素消失。在等待一个加载动画消失时一定要用wait_gone而不是用wait去等一个不存在的元素。3.3 高级交互手势、截图与断言除了点击和输入现代App的测试离不开复杂手势和结果验证。手势操作# 滑动 d.swipe(sx, sy, ex, ey, duration0.5) # 从(sx,sy)滑到(ex,ey)持续0.5秒 # 更常用的便捷方法 d.swipe_ext(up, scale0.8) # 向上滑动滑动距离为屏幕高度的80% d.swipe_ext(left) # 向左滑动 # 拖拽 d.drag(sx, sy, ex, ey, duration0.5) # 双指操作如缩放 d.two_finger_swipe(sx1, sy1, sx2, sy2, ex1, ey1, ex2, ey2) # 更简单的捏合/张开 d.pinch_in(percent50) # 双指向内捏合缩小50% d.pinch_out() # 双指向外张开放大截图与图像识别UIAutomator2可以方便地截图结合weditor可以进行图像对比或简单的OCR需求。# 截图并保存到本地 d.screenshot(homepage.png) # 获取元素的截图 ele d(text用户头像) image ele.screenshot() # 返回PIL.Image对象 image.save(avatar.png)断言与验证测试的核心是验证。不要只做操作不做检查。# 检查元素是否存在 assert d(text登录成功).exists() # 检查元素特定属性 ele d(resourceIdstatus) assert ele.get_text() 已完成 assert ele.info[enabled] True # 检查Toast消息需要开启辅助功能或使用ADB监听logcat后面会讲4. ADB命令的赋能突破UI自动化的天花板如果说UIAutomator2是“演员”按照剧本在UI界面上表演那么ADB就是“导演”和“场务”负责搭建舞台、控制灯光音效、甚至处理突发状况。下面这些场景是纯UI自动化难以搞定而ADB可以优雅解决的。4.1 测试环境准备与清理每次测试开始前一个干净、一致的环境至关重要。# 1. 清理应用数据让应用回到初始状态比在应用内点“清除数据”更彻底 adb shell pm clear com.example.app # 2. 强制停止应用确保完全重启 adb shell am force-stop com.example.app # 3. 授予应用运行时权限避免弹窗打断测试 adb shell pm grant com.example.app android.permission.ACCESS_FINE_LOCATION adb shell pm grant com.example.app android.permission.WRITE_EXTERNAL_STORAGE # 4. 设置系统设置如关闭动画让测试更快更稳定 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 0 # 5. 模拟网络条件测试弱网、断网场景 # 开启飞行模式 adb shell svc wifi disable adb shell svc data disable # 更精细的控制可以使用adb shell cmd network或adb shell settings put需要root在Python脚本中我们可以这样集成import subprocess def reset_app_env(package_name): 重置应用环境 subprocess.run(fadb shell pm clear {package_name}, shellTrue, checkTrue) subprocess.run(fadb shell am force-stop {package_name}, shellTrue, checkTrue) # 授予权限... print(f应用 {package_name} 环境已重置。) def disable_animations(): 关闭系统动画 subprocess.run(adb shell settings put global window_animation_scale 0, shellTrue) # ... 其他动画设置 print(系统动画已关闭。)4.2 性能监控与数据采集自动化测试不仅要验证功能还要关注性能。ADB可以实时获取丰富的系统数据。# 1. 监控CPU占用 adb shell top -n 1 -d 1 -s cpu | grep com.example.app # 2. 监控内存占用PSS是关键指标 adb shell dumpsys meminfo com.example.app | grep -E TOTAL|PSS # 3. 监控帧率适用于游戏或高流畅度要求的App adb shell dumpsys gfxinfo com.example.app framestats # 4. 监控网络流量 adb shell cat /proc/net/xt_qtaguid/stats | grep app_uid # 5. 获取电池信息 adb shell dumpsys battery我们可以写一个Python函数来周期性地采集这些数据并和测试步骤关联起来生成带性能数据的测试报告。import subprocess import time import csv def monitor_performance(package_name, duration60, interval2): 监控应用性能 start_time time.time() data [] app_uid get_app_uid(package_name) # 需要先写一个函数通过adb shell dumpsys package获取UID while time.time() - start_time duration: timestamp time.time() # 获取CPU cpu_output subprocess.check_output(fadb shell top -n 1 -d 1 -s cpu | grep {package_name}, shellTrue, textTrue) cpu parse_cpu_from_top(cpu_output) # 解析函数 # 获取内存 mem_output subprocess.check_output(fadb shell dumpsys meminfo {package_name} | grep TOTAL, shellTrue, textTrue) mem_pss parse_pss_from_meminfo(mem_output) # 获取当前测试步骤需要与你的测试框架结合 current_step get_current_test_step() data.append([timestamp, current_step, cpu, mem_pss]) time.sleep(interval) # 保存到CSV with open(performance_log.csv, w, newline) as f: writer csv.writer(f) writer.writerow([Timestamp, Step, CPU%, Memory_PSS(KB)]) writer.writerows(data) print(性能监控数据已保存。)4.3 模拟中断与异常场景这是体现测试深度的关键。你的应用接电话时、电量不足时、弹出系统对话框时表现如何# 1. 模拟来电需要root或特定系统权限 adb shell am start -a android.intent.action.CALL -d tel:10086 # 2. 模拟低电量警告通过修改电池状态 adb shell dumpsys battery set level 15 # 设置电量为15% adb shell dumpsys battery set status 3 # 设置状态为“正在放电” # 观察应用是否有低电量模式或保存提示 # 3. 模拟屏幕旋转 adb shell content insert --uri content://settings/system --bind name:s:user_rotation --bind value:i:1 # 旋转90度 # 或者使用传感器模拟 adb shell settings put system accelerometer_rotation 0 # 关闭自动旋转 adb shell settings put system user_rotation 1 # 强制旋转 # 4. 模拟返回键、Home键、菜单键 adb shell input keyevent KEYCODE_BACK adb shell input keyevent KEYCODE_HOME adb shell input keyevent KEYCODE_MENU # 5. 模拟按下电源键锁屏 adb shell input keyevent KEYCODE_POWER在测试脚本中可以在关键操作前后插入这些中断验证应用的恢复能力。def test_purchase_with_interruption(): 测试购买流程中被来电中断 d(text立即购买).click() d(text确认支付).wait(timeout5) # 模拟来电中断 subprocess.run(adb shell am start -a android.intent.action.CALL -d tel:13800138000, shellTrue) time.sleep(3) # 等待来电界面弹出 # 挂断电话 subprocess.run(adb shell input keyevent KEYCODE_ENDCALL, shellTrue) time.sleep(2) # 检查是否回到支付页面状态是否正确 assert d(text确认支付).exists(), 支付流程被中断后未正确恢复 # 继续完成支付...4.4 日志抓取与分析定位问题的“黑匣子”当UI自动化失败时控制台的错误信息往往不够。设备端的日志Logcat是定位问题的金矿。# 1. 抓取指定应用和级别的日志 adb logcat -v time -s MyAppTag:D *:S # 只抓打上MyAppTag的Debug及以上级别日志 # 2. 抓取崩溃日志Fatal, Error级别 adb logcat -v time *:E # 3. 抓取特定进程的日志通过PID adb logcat -v time --pidpid_of_your_app # 4. 实时输出并保存到文件最常用 adb logcat -v time app_test.log # 在Python中可以用subprocess.Popen来启动并实时处理日志更高级的用法是在Python脚本中启动一个后台进程实时监听日志并过滤出关键错误或崩溃信息一旦发现立即截图保存现场极大提升问题排查效率。import subprocess import threading import re def monitor_logcat(keywords[FATAL, ERROR, Exception, Crash], package_namecom.example.app): 实时监控logcat捕获关键错误 def _read_log(proc): for line in iter(proc.stdout.readline, b): line_str line.decode(utf-8, errorsignore).strip() for kw in keywords: if kw in line_str and package_name in line_str: print(f[CRITICAL LOG] {line_str}) # 发现关键错误立即截图 timestamp time.strftime(%Y%m%d_%H%M%S) subprocess.run(fadb shell screencap -p /sdcard/crash_{timestamp}.png, shellTrue) subprocess.run(fadb pull /sdcard/crash_{timestamp}.png ., shellTrue) break # 启动logcat进程 process subprocess.Popen([adb, logcat, -v, time], stdoutsubprocess.PIPE, stderrsubprocess.PIPE) # 在新线程中读取 thread threading.Thread(target_read_log, args(process,)) thread.daemon True thread.start() return process # 在测试开始前启动监控 logcat_proc monitor_logcat() # ... 执行测试用例 ... # 测试结束后终止监控 logcat_proc.terminate()5. 结合实战构建一个健壮的自动化测试用例让我们用一个完整的例子将UIAutomator2和ADB命令融合起来测试一个简单的“登录-查看个人资料”场景。这个例子将涵盖环境准备、UI操作、中断处理、性能检查和日志收集。import uiautomator2 as u2 import subprocess import time import pytest # 配置 APP_PACKAGE com.example.myapp APP_MAIN_ACTIVITY .MainActivity DEVICE_SERIAL emulator-5554 # 替换为你的设备 pytest.fixture(scopefunction) def device(): 初始化设备连接每个测试用例执行前后进行环境清理 d u2.connect(DEVICE_SERIAL) d.healthcheck() # 用例开始前重置环境 subprocess.run(fadb -s {DEVICE_SERIAL} shell pm clear {APP_PACKAGE}, shellTrue, checkFalse) subprocess.run(fadb -s {DEVICE_SERIAL} shell am force-stop {APP_PACKAGE}, shellTrue, checkFalse) # 授予必要权限 subprocess.run(fadb -s {DEVICE_SERIAL} shell pm grant {APP_PACKAGE} android.permission.ACCESS_FINE_LOCATION, shellTrue) # 关闭动画 subprocess.run(fadb -s {DEVICE_SERIAL} shell settings put global window_animation_scale 0, shellTrue) yield d # 将设备对象提供给测试用例 # 用例结束后清理恢复动画可选 subprocess.run(fadb -s {DEVICE_SERIAL} shell settings put global window_animation_scale 1, shellTrue) print(f测试结束环境已清理。) def test_login_and_profile_with_interruption(device): 测试登录并查看个人资料期间模拟低电量中断 # 启动应用 device.app_start(APP_PACKAGE, APP_MAIN_ACTIVITY) time.sleep(2) # 等待应用启动 # 步骤1登录 device(resourceIdcom.example.myapp:id/et_username).set_text(testuser) device(resourceIdcom.example.myapp:id/et_password).set_text(password123) device(resourceIdcom.example.myapp:id/btn_login).click() # 等待登录成功跳转到首页 device(text首页).wait(timeout10) # **关键结合点1使用ADB模拟低电量中断** print(模拟低电量中断...) subprocess.run(fadb -s {DEVICE_SERIAL} shell dumpsys battery set level 10, shellTrue) subprocess.run(fadb -s {DEVICE_SERIAL} shell dumpsys battery set status 3, shellTrue) time.sleep(3) # 等待系统可能弹出的低电量提示 # 检查应用是否有正确处理例如是否弹出节省流量提示 # 这里假设我们的应用会弹出一个Toast提示“电量不足请注意” # 我们可以尝试通过ADB logcat抓取这个ToastToast本质是系统通知 # 更可靠的方式是在应用内设计一个可检测的元素 # 此处仅作为示例我们继续操作 # 恢复电池状态避免影响后续测试 subprocess.run(fadb -s {DEVICE_SERIAL} shell dumpsys battery reset, shellTrue) # 步骤2进入个人资料页 device(text我的).click() device(text个人资料).wait(timeout5).click() # **关键结合点2在加载资料时监控性能** print(开始监控个人资料页加载性能...) # 启动一个后台性能监控线程简化版 start_monitor_time time.time() # 这里可以调用前面章节写的monitor_performance函数监控加载过程的CPU/内存 # 等待资料页加载完成 device(text昵称).wait(timeout8) load_time time.time() - start_monitor_time print(f个人资料页加载耗时: {load_time:.2f}秒) assert load_time 5.0, 个人资料页加载时间过长 # 验证资料内容 nickname device(resourceIdcom.example.myapp:id/tv_nickname).get_text() assert nickname 测试用户, f昵称显示错误期望‘测试用户’实际‘{nickname}’ # **关键结合点3操作完成后拉取应用特定日志进行分析** log_filename fprofile_test_{int(time.time())}.log # 抓取测试期间我们应用相关的日志 subprocess.run(fadb -s {DEVICE_SERIAL} logcat -d -v time --pid$(adb shell pidof -s {APP_PACKAGE}) {log_filename}, shellTrue) print(f应用日志已保存至: {log_filename}) # 最后截图保存最终状态 device.screenshot(ftest_result_profile.png)这个用例展示了如何将两者无缝衔接ADB用于搭建舞台清理数据、授予权限、关闭动画。UIAutomator2执行主流程完成登录、导航、断言等核心UI操作。ADB制造“剧情冲突”模拟低电量中断测试应用鲁棒性。ADB进行“现场监测”监控性能、抓取日志提供测试深度。两者协同收尾截图保存证据恢复环境。6. 常见问题排查与实战技巧实录即使按照最佳实践在实际项目中你还是会遇到各种“妖孽”问题。下面是我总结的一些高频问题及解决方案。6.1 UIAutomator2连接失败或操作无响应现象u2.connect()超时或执行click()后毫无反应。排查步骤检查基础连接adb devices -l确认设备在线且状态为device而不是unauthorized或offline。检查设备端服务adb shell ps | grep atx查看atx-agent进程是否存在。如果不存在尝试adb shell /data/local/tmp/atx-agent server --stop然后重新d.healthcheck()。检查端口占用UIAutomator2默认使用7912端口。adb shell netstat -tlnp | grep 7912查看是否被占用。可以尝试重启设备或更换端口通过u2.connect(设备序列号:端口)。重启ADB服务有时ADB守护进程会卡住。adb kill-server然后adb start-server。关闭设备开发者选项中的“监控ADB安装应用”这个选项有时会干扰自动化安装过程。实操心得在CI/CD环境中我习惯在脚本开头加入一个强力的初始化函数包含杀进程、重装atx-agent等操作确保每次运行环境都是干净的。6.2 元素定位不到或定位错误现象No matching node found或定位到了错误的元素。排查步骤实时查看UI树使用weditor。在代码中import weditor; weditor.start()会自动打开浏览器显示当前设备界面和元素层级。这是最强大的调试工具没有之一。检查上下文是否在正确的Activity或WebView中使用d.current_app()和d.info查看当前包名和活动。对于H5页面可能需要d.webview相关方法切换上下文。检查等待时间是否页面还没加载完增加wait的超时时间或使用更智能的等待条件如等待某个特征元素出现。检查元素属性是否动态变化特别是resourceId和text。用weditor多次刷新观察属性值是否改变。使用更精确的定位器尝试组合多个属性或使用child,sibling进行相对定位。避坑技巧对于列表中的元素避免使用索引如d(classNameListView).child(classNameandroid.widget.TextView)[3]因为列表顺序可能变化。应该使用文本或其他稳定属性来定位列表项。6.3 ADB命令执行慢或无输出现象执行adb shell命令卡住或需要很长时间才有返回。排查步骤检查USB连接和线缆劣质USB线或接口松动会导致传输不稳定。尝试更换USB口或线缆。设备负载过高如果设备CPU占用率100%ADB响应会变慢。检查是否有其他耗电应用在后台运行。命令本身耗时像dumpsys meminfo或dumpsys gfxinfo这类命令本身执行就需要时间。在脚本中为其设置合理的超时。使用-s指定设备在多设备环境下必须用adb -s serial shell ...明确指定设备。优化建议对于需要频繁执行的ADB命令如获取CPU可以考虑在设备端写一个简单的shell脚本一次性收集所有数据然后通过ADB一次拉取减少通信次数。6.4 处理系统弹窗和权限对话框问题运行时突然弹出的系统弹窗如“是否允许访问位置”会打断自动化流程。解决方案预先授权在测试开始前使用adb shell pm grant授予所有需要的权限。这是最推荐的方法一劳永逸。自动点击如果无法预先授权如某些厂商定制权限可以用UIAutomator2检测并点击。但这类弹窗的控件属性可能不标准。# 尝试点击“允许”按钮可能需要多种定位方式组合 allow_btn d(textMatches(?i)allow|始终允许|允许) # 不区分大小写匹配 if allow_btn.exists(timeout3): allow_btn.click()使用ADB模拟按键对于简单的“确定/取消”对话框可以尝试adb shell input keyevent KEYCODE_ENTER或KEYCODE_DPAD_RIGHT KEYCODE_ENTER来操作焦点。终极方案对于特别顽固的厂商定制UI可以考虑使用图像识别如OpenCV来定位并点击弹窗按钮但这会引入额外的复杂性和维护成本。6.5 在CI/CD流水线中集成将这套结合方案集成到Jenkins、GitLab CI等平台才能真正发挥价值。环境准备CI节点必须安装好Android SDK Platform-Tools和Python环境含所需库。建议使用Docker镜像固化环境。设备管理使用云真机平台如STF、OpenSTF、各大云测平台提供的设备农场或本地连接的常开设备。确保CI脚本能通过序列号稳定连接设备。脚本稳定性CI环境下的脚本必须比本地更健壮。要加入大量的错误恢复逻辑如连接重试、失败重启应用、异常截图和日志保存。测试报告使用pytestallure-pytest生成丰富的测试报告并集成性能监控数据、截图和日志附件。一个简单的Jenkins Pipeline示例pipeline { agent any stages { stage(Checkout) { steps { git ... } } stage(Setup Environment) { steps { sh python -m pip install -r requirements.txt sh adb start-server // 连接云真机或本地设备 } } stage(Run Tests) { steps { sh pytest tests/ --alluredir./allure-results } post { always { // 无论成功失败都收集结果 allure includeProperties: false, jdk: , results: [[path: allure-results]] // 将测试过程中的截图和日志归档 archiveArtifacts artifacts: *.png,*.log,*.csv, fingerprint: true } } } } }这套“UIAutomator2 ADB”的组合拳经过多个大型项目的锤炼已经被证明是Android自动化测试中兼顾效率、深度和稳定性的优选方案。它要求测试开发者不仅会写UI脚本还要理解Android系统的基本运作。当你能够熟练地在UI操作和系统命令间自由切换时你会发现那些曾经令人头疼的“随机失败”和“无法自动化”的场景都逐渐变得清晰和可控。真正的自动化是让机器像一位经验丰富、既懂业务又懂系统的测试专家一样去工作。