Appium 移动端自动化测试(四):高级元素定位技巧
本篇深入讲解移动端动态元素定位、平台特有的高级定位方法,以及处理复杂 UI 层级结构的技巧。
一、动态元素定位
1.1 常见动态元素场景
移动端动态元素常见模式:
- 动态资源 ID:
com.example:id/text_1738901234 - 动态文本:列表项文本随数据变化
- RecyclerView/CollectionView:元素动态加载和回收
- 广告/弹窗:随机出现的覆盖层
1.2 应对策略
策略一:使用 Accessibility ID
# 稳定性最高的定位方式,不会因版本更新而变化
driver.find_element(AppiumBy.ACCESSIBILITY_ID, "登录按钮")
建议:推动开发团队为关键元素添加
content-desc(Android)或accessibilityIdentifier(iOS)。
策略二:XPath 模糊匹配
# Android:ID 包含关键词
driver.find_element(AppiumBy.XPATH,
"//*[contains(@resource-id, 'login_btn')]"
)
# 文本包含关键词
driver.find_element(AppiumBy.XPATH,
"//*[contains(@text, '登录')]"
)
# Class Name + 属性组合
driver.find_element(AppiumBy.XPATH,
"//android.widget.Button[contains(@text, '提交')]"
)
# iOS:name 包含关键词
driver.find_element(AppiumBy.XPATH,
"//*[contains(@name, 'Login')]"
)
策略三:Android UIAutomator 滚动查找
# 在可滚动容器中查找元素(非常适合列表)
driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR,
'new UiScrollable(new UiSelector().scrollable(true))'
'.scrollIntoView(new UiSelector().text("目标文本"))'
)
# 滚动到指定位置的元素
driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR,
'new UiScrollable(new UiSelector().scrollable(true))'
'.scrollIntoView(new UiSelector().resourceId("com.example:id/item_50"))'
)
# 最大滚动次数
driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR,
'new UiScrollable(new UiSelector().scrollable(true))'
'.setMaxSearchSwipes(10)'
'.scrollIntoView(new UiSelector().text("加载更多"))'
)
# 水平滚动(横向列表)
driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR,
'new UiScrollable(new UiSelector().scrollable(true))'
'.setAsHorizontalList()'
'.scrollIntoView(new UiSelector().text("更多"))'
)
策略四:通过兄弟/父子关系定位
# 找到文本为"用户名"的 TextView,然后定位旁边的输入框
driver.find_element(AppiumBy.XPATH,
"//*[@text='用户名']/following-sibling::android.widget.EditText"
)
# 通过父元素定位
driver.find_element(AppiumBy.XPATH,
"//*[@resource-id='com.example:id/form']//android.widget.EditText[1]"
)
# 通过祖父元素定位
driver.find_element(AppiumBy.XPATH,
"//*[@resource-id='com.example:id/card']/*[@class='android.widget.LinearLayout']//android.widget.Button"
)
二、Android 高级定位
2.1 UIAutomator 复杂选择器
# 实例选择(第 N 个匹配元素)
driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR,
'new UiSelector().className("android.widget.EditText").instance(0)'
)
# 属性组合
driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR,
'new UiSelector()'
'.className("android.widget.Button")'
'.text("登录")'
'.enabled(true)'
'.clickable(true)'
)
# 正则匹配
driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR,
'new UiSelector().textMatches(".*登录.*")'
)
# 从子元素向上查找
driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR,
'new UiSelector().className("android.widget.EditText")'
'.fromParent(new UiSelector().text("密码"))'
)
# 检查属性
driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR,
'new UiSelector().checkable(true).checked(true)'
)
2.2 Android DataMatcher
# 使用 Espresso DataMatcher 定位(需要 Espresso 驱动)
driver.find_element(AppiumBy.ANDROID_DATA_MATCHER,
'{"name": "hasEntry", "args": ["title", "Expected Title"]}'
)
2.3 Android ViewMatcher
# 使用 View Matcher
driver.find_element(AppiumBy.ANDROID_VIEW_MATCHER,
'withText("Login")'
)
三、iOS 高级定位
3.1 iOS Predicate 高级用法
# 基本谓词
driver.find_element(AppiumBy.IOS_PREDICATE,
"label == 'Login'"
)
# 多条件
driver.find_element(AppiumBy.IOS_PREDICATE,
"type == 'XCUIElementTypeButton' AND label == 'Login'"
)
# 模糊匹配
driver.find_element(AppiumBy.IOS_PREDICATE,
"label CONTAINS '登录'"
)
# 正则匹配
driver.find_element(AppiumBy.IOS_PREDICATE,
"label MATCHES '.*登录.*'"
)
# 值为空
driver.find_element(AppiumBy.IOS_PREDICATE,
"type == 'XCUIElementTypeTextField' AND value == ''"
)
# 可见性
driver.find_element(AppiumBy.IOS_PREDICATE,
"type == 'XCUIElementTypeButton' AND visible == true"
)
3.2 iOS Class Chain 高级用法
# 基本查找
driver.find_element(AppiumBy.IOS_CLASS_CHAIN,
"**/XCUIElementTypeButton[`label == 'Login'`]"
)
# 层级查找
driver.find_element(AppiumBy.IOS_CLASS_CHAIN,
"**/XCUIElementTypeTable/**/XCUIElementTypeCell[`name == 'Settings'`]"
)
# 指定索引
driver.find_element(AppiumBy.IOS_CLASS_CHAIN,
"**/XCUIElementTypeButton[`label == 'OK'`][1]"
)
# 组合条件
driver.find_element(AppiumBy.IOS_CLASS_CHAIN,
"**/XCUIElementTypeTextField[`value == ''` AND visible == 1`]"
)
# 查找所有匹配元素
elements = driver.find_elements(AppiumBy.IOS_CLASS_CHAIN,
"**/XCUIElementTypeCell"
)
3.3 iOS 深层链接查找
# 使用 Descendant(查找任意层级的后代)
driver.find_element(AppiumBy.IOS_CLASS_CHAIN,
"XCUIElementTypeWindow/XCUIElementTypeOther/**/XCUIElementTypeButton[`label == 'Submit'`]"
)
四、WebView 元素定位
4.1 WebView 调试模式
Android:在 WebView 初始化时开启调试模式:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
WebView.setWebContentsDebuggingEnabled(true);
}
iOS:在 scheme 中配置:
<key>NSAllowsArbitraryLoads</key>
<true/>
4.2 WebView 上下文切换
# 获取所有上下文
contexts = driver.contexts
# 示例: ['NATIVE_APP', 'WEBVIEW_com.example.app']
# 切换到 WebView
webview_context = [ctx for ctx in contexts if "WEBVIEW" in ctx][0]
driver.switch_to.context(webview_context)
# 在 WebView 中使用 Web 定位方式
driver.find_element(AppiumBy.CSS_SELECTOR, "#username").send_keys("test")
driver.find_element(AppiumBy.CSS_SELECTOR, ".submit-btn").click()
driver.find_element(AppiumBy.XPATH, "//button[text()='Submit']").click()
# 切回原生
driver.switch_to.context("NATIVE_APP")
4.3 WebView 调试技巧
# 获取 WebView 控制台日志
logs = driver.get_log("webdriver")
# 执行 JavaScript
result = driver.execute_script("return document.title")
五、列表元素定位
5.1 RecyclerView 定位
# 获取所有列表项
items = driver.find_elements(
AppiumBy.ID, "com.example:id/item_container"
)
# 点击指定文本的列表项
driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR,
'new UiSelector().text("目标文本")'
).click()
# 滚动到指定项并点击
driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR,
'new UiScrollable(new UiSelector().resourceId("com.example:id/recycler_view"))'
'.scrollIntoView(new UiSelector().text("目标项"))'
).click()
5.2 通用列表定位封装
def find_list_item_by_text(driver, text, max_scrolls=5):
"""通过滚动查找列表中的文本项"""
from appium.webdriver.common.touch_action import TouchAction
for _ in range(max_scrolls):
try:
item = driver.find_element(
AppiumBy.ANDROID_UIAUTOMATOR,
f'new UiSelector().text("{text}")'
)
if item.is_displayed():
return item
except:
pass
# 向上滑动
size = driver.get_window_size()
TouchAction(driver) \
.press(x=size["width"]//2, y=int(size["height"]*0.8)) \
.wait(ms=300) \
.move_to(x=size["width"]//2, y=int(size["height"]*0.2)) \
.release() \
.perform()
raise Exception(f"未找到列表项: {text}")
六、定位策略决策树
需要跨平台?
├── 是 → 使用 Accessibility ID
└── 否 → 仅 Android?
├── 是 → 有固定 ID?
│ ├── 是 → AppiumBy.ID
│ └── 否 → 需要滚动查找?
│ ├── 是 → ANDROID_UIAUTOMATOR + UiScrollable
│ └── 否 → XPath / UIAutomator
└── 仅 iOS → iOS Predicate / Class Chain
七、总结
| 场景 | Android 推荐方法 | iOS 推荐方法 |
|---|---|---|
| 跨平台 | ACCESSIBILITY_ID |
ACCESSIBILITY_ID |
| 固定 ID | AppiumBy.ID |
AppiumBy.ID |
| 滚动查找 | ANDROID_UIAUTOMATOR + UiScrollable |
手势滑动 + 查找 |
| 动态文本 | XPath contains |
Predicate CONTAINS |
| 列表项 | UIAutomator / XPath | Class Chain |
| WebView | CSS_SELECTOR / XPath |
CSS_SELECTOR / XPath |
下一篇:等待机制与脚本优化,提升移动端测试的稳定性。

浙公网安备 33010602011771号