App 自动化测试实战:定位策略与稳定性三板斧

做 App 自动化最头疼的不是"能不能点中",而是"为什么这次又点不中"。这篇文章沉淀了 App 端自动化在元素定位执行稳定性上最实用的经验,全部来自真实项目的踩坑。

一、定位策略:优先级从稳定到脆弱

App 控件树比 Web 复杂得多(原生 + H5 + 自绘 UI 混合),定位策略直接决定脚本寿命。推荐按以下优先级选择:

优先级 策略 说明 脆弱性
1 resource-id / accessibility id 开发给控件起的稳定标识 低(需约定前缀规范)
2 content-desc(无障碍描述) 专为 AT 准备的描述
3 text 精确匹配 按钮/标签文案 中(多语言、动态文案会挂)
4 层级路径 + 索引 parent/child[2] 高(布局微调即失效)
5 XPath 全文档查询 万能但慢 高,且性能差

实践约定:和开发约定 resource-id 前缀(如 com.app:id/btn_send),UI 改版时前缀语义不变,脚本就能活下来。XPath 只用于"实在没得选"的兜底,且禁止写超过 5 层的路径。

二、隐式等待:告别固定 sleep

固定 time.sleep(5) 是稳定性的头号杀手:网络快时白白浪费时间,网络慢时照样超时。正确姿势是显式条件轮询

1
2
3
4
5
6
from appium.webdriver.wait import WebDriverWait

def wait_visible(driver, locator, timeout=10):
"""轮询等待元素可见,返回 WebElement;超时抛异常由上层决策"""
wait = WebDriverWait(driver, timeout, poll_frequency=0.5)
return wait.until(lambda d: _first_visible(d, locator))

要点:

  • 轮询间隔 0.5s 起,间隔太小会打满 UIAutomator 的 dump 开销,反而拖慢整体;
  • 等待条件分三级:存在 → 可见 → 可点击,点击前必须等到"可点击";
  • 超时后的动作要显式:截图 + 断言失败,而不是默默 continue。

三、异常恢复:让失败可重试、可定位

1. 崩溃/ANR 自愈

App 崩溃后 driver 的 session 还活着但控件树已经没了,直接重试会连环失败。恢复顺序:

1
2
3
4
5
6
7
def ensure_app_alive(driver, package, activity, max_relaunch=2):
for i in range(max_relaunch):
if driver.current_package == package:
return
driver.activate_app(f"{package}/{activity}")
wait_for_home(driver)
raise AppCrashError("连续重启仍崩溃")

2. 弹窗拦截器

自动化最怕"计划外弹窗"(权限请求、升级提示、广告)。写一个统一的弹窗拦截,在每次关键操作前扫描:

1
2
3
4
5
6
7
8
9
10
11
12
13
POPUP_RULES = [
(("确定", "允许", "忽略"), "click"), # 常见确认类弹窗
(("以后再说", "取消", "残忍拒绝"), "click"), # 升级/广告类
]

def dismiss_popups(driver):
for texts, action in POPUP_RULES:
for t in texts:
el = driver.find_element(AppiumBy.ACCESSIBILITY_ID, t)
if el.is_displayed():
el.click()
return True
return False

3. 断言分级

  • 硬断言:核心流程节点(消息发出、订单创建),失败即用例失败;
  • 软断言:UI 细节(文案、颜色),失败只记录不中断,汇总后看趋势。

四、小结

稳定性三板斧:定位走稳定标识、等待走条件轮询、失败走显式恢复。这三条做到位,App 自动化脚本的周通过率能从 60% 提到 95% 以上。剩下的 5% 基本是产品自身的 bug——那正是自动化测试存在的意义。