尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Appium元素定位实战:UI Automator Viewer控件属性与脚本落地

Appium元素定位实战:UI Automator Viewer控件属性与脚本落地 写Appium脚本的人十有八九都经历过这种时刻一个findElement写下去跑起来要么报NoSuchElementException要么定位到一堆相似控件导致点击错位。回头一看问题几乎都出在没把界面上的控件属性摸透。做Appium自动化测试元素定位是绕不过去的基础功而定位的第一步是看清目标页面上每一个控件到底暴露了哪些可用的属性。这个需求UI Automator Viewer就是最直接的入口。UI Automator Viewer 是 Android SDK 自带的可视化界面查看工具平时我们都叫它 uiautomatorviewer。它能抓取当前界面的 UI 层级结构把每个控件的resource-id、class、text、content-desc、bounds等一系列属性清清楚楚列出来。对做 Appium 测试的人来说它就是定位元素时用来“摸底”的侦察兵。这篇内容围绕 Appium 元素定位这个核心场景把 UI Automator Viewer 的启动、使用、属性解读、脚本落地以及避坑技巧讲透适合刚接触 Appium、正被findElement折磨的新手也适合想系统整理定位工具使用思路的测试开发。1. Appium 元素定位场景下UI Automator Viewer 为什么值得先学1.1 元素定位决定脚本稳定性的底层逻辑Appium 作为跨平台的移动端自动化框架底层原理是通过 WebDriver 协议把命令发送到手机端再由手机端的驱动框架Android 上是 UIAutomator2 或 EspressoiOS 上是 XCUITest去执行查找、点击、输入等操作。也就是说你在脚本里写的driver.findElement(By.id(xxx)),本质上是在向设备要一个控件。设备能不能准确回答你“这个控件在哪”取决于你给出的属性是否唯一、是否稳定。实际项目里界面控件的属性来源就是 App 的布局文件在运行时的真实状态。一个控件可能有几十个属性但真正能被 Appium 用来定位的只有少数几个id对应 resource-id、class、text、content-desc、xpath基于层级关系。问题在于这些属性的值长什么样、层级结构深不深、动态控件会不会变化光靠肉眼看模拟器是看不出来的。UI Automator Viewer 做的就是把这个“运行时状态”可视化截一张图把每个控件的属性挂上去你点哪它显示哪整个页面的层级树也一并展开。理解了这层关系你就知道为什么社区里讲 Appium 基础时总是绕不开这个工具它不是用来看热闹的而是用来回答“我的定位表达式到底依托在什么属性上”这个关键问题的。没有它你写定位表达式只能靠猜猜就难免踩坑。1.2 几大元素定位工具对比为什么先拿它下手目前 Android 端常见的元素定位工具有三款UI Automator Viewer、Appium Inspector、UIAutomator2 自带的uiautomator2模块配合 Python 使用时能直接 dump 层级。我见过不少新人一上来就装 Appium Inspector折腾配置半天没跑通回头连 Appium 基础环境哪里有问题都说不清。Appium Inspector 确实功能更强但它的依赖环节更多——需要 Appium Server 正常启动、需要 Desired Capabilities 配置正确、需要设备连接稳定任何一个环节报错新人就会卡住反而干扰对元素属性本身的理解。UI Automator Viewer 是 Android SDK 自带的独立小工具不需要额外配置服务双击就能跑抓到的属性结构是 UIAutomator 框架直接输出的标准格式和 Appium 在 Android 端拿到的层级完全一致。这就意味着你在 UI Automator Viewer 里看到的resource-id和bounds放到 Appium 脚本里能一一对应。把它作为学习元素定位的起点工具本身不制造额外问题你只需要专注理解属性本身。等你彻底掌握了属性怎么读、xpath 怎么写再切换到 Appium Inspector 处理动态页面、WebView 混合页面这些进阶场景会顺畅得多。1.3 UI Automator Viewer 的工作原理简述从原理层面讲UI Automator Viewer 依赖 Android 系统的辅助功能机制Accessibility来读取当前窗口的控件树。在 Android 系统中每个可见界面都有一个 View Hierarchy系统会通过 AccessibilityService 把控件树信息暴露给外部工具。UI Automator Viewer 连接设备后会向设备发送 dump 指令拿到一个 XML 格式的层级文件同时截取一张当前界面的 PNG 图片然后把两者关联起来展示。这就是为什么你点击截图上的某个区域时左侧的属性面板会同步显示对应的节点属性和它在 XML 层级中的路径。它本质上是把 uiautomator 的 dump 结果做了一层可视化封装。理解了这一点你就知道为什么这个工具一定要连接一台已开启调试模式的设备或模拟器也就能理解为什么它有时候抓不到动态弹窗——因为弹窗可能属于另一个 Window 层级需要切换到对应窗口才能看到。2. 把环境收拾利索启动 UI Automator Viewer 之前要做的准备2.1 三个前置条件逐一核对工欲善其事必先利其器。UI Automator Viewer 本身是 SDK 的一个小工具但它要正常工作依赖三个基本条件JDK、Android SDK 平台工具、可调试的设备或模拟器。JDK 是 UIAutomator 工具链的运行环境没装 JDK 或者版本过旧双击uiautomatorviewer.bat常常会闪退或报UnsupportedClassVersionError。建议统一使用 JDK 8 或 JDK 11这两个版本与 Appium 生态的兼容性最稳定。Android SDK 平台工具提供adb命令UI Automator Viewer 连接设备依赖的就是 adb。设备方面真机需要开启“开发者选项”和“USB 调试”模拟器则直接使用默认调试端口。有个容易被忽略的细节如果你同时开了多个 Android 设备比如一个模拟器加一个真机UI Automator Viewer 默认连的是 adb 列表里的第一个设备容易造成混淆。所以实操前建议先执行adb devices确认当前只有目标设备在线。如果存在多个设备要么断开多余的要么用adb -s 设备序列号 shell uiautomator dump这种指定设备的方式先验证再打开工具。2.2 三种启动方式与选择建议UI Automator Viewer 的启动方式有几种每个人习惯不同但效果一样。第一种命令行启动。在 Android SDK 的tools目录下Windows 系统运行uiautomatorviewer.batmacOS 或 Linux 运行uiautomatorviewer。这是最原始、最通用的方式。# 进入 SDK 的 tools 目录后执行 ./uiautomatorviewer第二种通过 Android Studio 的 SDK Manager 找到安装路径然后直接在文件管理器中定位到tools目录双击启动。这种方式适合不熟悉命令行的同学。第三种如果你用的是较新的 SDK 版本tools目录可能已经不在默认 PATH 里可以通过Android Studio 的菜单Tools-SDK Manager查看到 SDK 路径再手动进入。这里有个坑新版 Android SDK 默认不安装tools目录下的部分工具UI Automator Viewer 有时需要你单独勾选安装。如果你打开 SDK 目录发现里面根本没有uiautomatorviewer不用慌打开 SDK Manager勾选Android SDK Tools进行安装即可。我用得最多的是第一种顺手记一条 shell 别名下次直接敲uiautomatorviewer就能起来效率高很多。启动后工具界面会出现两个面板左侧是屏幕截图区域右侧是控件属性面板顶部还有文件操作按钮。2.3 连接设备后的第一次界面抓取设备连接好、工具启动后点一下工具栏里最显眼的Device Screenshot按钮一个手机图标工具会执行一次界面抓取。如果运气好你会立刻在左侧看到手机当前屏幕的截图右侧出现Hierarchy Viewer树形结构和控件属性。但如果你第一次点击就遇到报错比如Error taking device screenshot: Could not get screenshot大概率是以下两种情况一是设备屏幕处于熄屏状态UI Automator 无法截取二是设备上有锁屏密码导致窗口层级无法读取。解决办法很简单先把设备唤醒并停留在目标页面再重新点击抓取。注意抓取前一定要让设备停留在你实际要测试的页面上且在抓取过程中不要操作手机。UI Automator Viewer 抓的是静态快照如果页面元素在持续变化抓到的层级可能与真实状态存在偏差定位时容易误判。3. 读懂 UI Automator Viewer 面板属性是定位表达式的基石3.1 一张截图背后的树状层级第一次看到 UI Automator Viewer 右侧的层级树时很多新人会懵这一层套一层的节点到底在看什么其实可以把它理解成 HTML 的 DOM 树。Android 的每个界面布局都是一棵树根节点是FrameLayout往下依次是各种LinearLayout、RelativeLayout、RecyclerView等容器最终挂载按钮、输入框、文本这些真正的控件节点。UI Automator Viewer 的左侧是渲染后的视觉界面右侧是这棵树的层级结构。当你点击左侧截图上的任意元素时右侧树会自动选中对应节点同时下方属性面板会显示该节点的全部属性。反过来你在右侧树中点击一个节点左侧截图上对应的控件区域也会高亮。实际操作中有个技巧不必每次都去点树节点直接在左侧截图点目标控件效率更高。但如果目标控件非常小或者多个控件重叠这时再到右侧树里手动选择配合高亮区域确认当前选中的是不是你要的控件。搞清了层级树你才能理解 xpath 定位里的绝对路径和相对路径也才能知道为什么建议优先用相对路径——因为界面上方一旦多了一个TextView绝对路径里的索引全部会变脚本就得跟着改。3.2 核心属性逐个拆解id、class、text、content-desc、boundsUI Automator Viewer 展示出来的属性很多但真正高频用于 Appium 定位的就那几个。这里逐一说透。resource-id是最常用的属性在 Appium 脚本里对应By.id()。它长这样com.example.app:id/username_input。前面的一长串是 App 的包名冒号后面是开发者在布局文件里定义的 ID。要注意不是所有控件都会有resource-id很多图片控件或自定义控件没有 ID导致你没法用By.id()定位只能另想办法。class对应控件类型取值是 Android 完整的类名比如android.widget.EditText、android.widget.Button、android.widget.TextView。在 Appium 里对应By.className()。但class的粒度通常太粗一个页面上十个TextView很正常直接用className基本没法唯一定位。text属性就是控件上显示的文字在 Appium 里用By.androidUIAutomator(text(\登录\))或 xpath 的text登录来定位。文本属性的优势是直观缺点是太容易变了——App 切语言、后端返回文字调整脚本里写的文本断言和定位就全崩了。content-desc是内容描述属性主要服务于无障碍功能。对于纯图标按钮没有文本开发通常会设置content-desc。这也是为什么很多 App 的“返回”箭头按钮在 UI Automator Viewer 里能看到描述为“返回”或“Navigate up”。bounds是控件在屏幕上的坐标范围形如[60,240][360,400]表示控件的左上角和右下角坐标。它决定了元素的位置Appium 的tap和swipe坐标操作就得依据它。但 bounds 属于硬坐标一旦屏幕分辨率或设备尺寸变化坐标就失效所以它适合辅助确认元素不适合作为主要定位依据。3.3 组合属性写出稳定的定位表达式看完属性拆解你可能会问每个属性都有弱点那到底该用什么定位我的习惯是遵循一个优先级resource-id优先其次content-desc再次文本组合最后才用 xpath 层级。如果resource-id在当前界面唯一直接By.id()完事性能最好代码也最干净。如果 ID 重复比如 RecyclerView 列表项里的多个相同控件就用 xpath 配合文本//android.widget.TextView[resource-idcom.example:id/title and text用户名]。注意Android 的 xpath 语法是小写标签名属性和值的引号要正确这在写动态表达式时是特别容易踩的坑。判断表达式是否足够可靠有一个小技巧打开终端手动执行一次uiautomator dump然后在 XML 里搜索你预写的定位表达式看看命中的节点是不是唯一。例如adb shell uiautomator dump /sdcard/ui.xml adb pull /sdcard/ui.xml然后在你本地用文本编辑器打开ui.xml把 xpath 表达式放进去验算。这种方式虽然原始但对建立定位直觉非常有帮助。4. 实战走一遍用 UI Automator Viewer 定位登录页元素并落地到 Appium 脚本4.1 场景设定搭建一个典型的登录页面纸上谈兵到这里该结束了。下面我用一个很常见的登录页场景把 UI Automator Viewer 定位到 Appium 脚本的完整链路走一遍。假设被测应用包名为com.example.demo登录页包含以下元素顶部一个 Logo 图片、下方一个“欢迎登录”标题、一个手机号输入框、一个验证码输入框、一个“获取验证码”按钮以及底部一个“登录”按钮。启动模拟器打开 App 进入登录页。先提醒一句抓取前务必要等页面动画完全结束。很多 App 的页面切换和控件加载有延迟如果动画还在执行就截图UI 层级里可能只有部分控件让你误以为元素不存在。等界面稳定后点击 UI Automator Viewer 的抓取按钮得到当前页面快照。4.2 逐步定位三个关键控件先在左侧截图点击手机号输入框。右侧属性面板显示的关键属性如下模拟值resource-id: com.example.demo:id/phone_number class: android.widget.EditText text: 请输入手机号 content-desc: (null) bounds: [120,620][960,780]注意这里的text是hint提示语而不是真实输入内容。Appium 的sendKeys操作不会改变它的定位策略但如果你用文本定位要清楚定位的是初始提示文本一旦用户输入了内容text 就会变成输入值可能导致定位失效。所以这里最稳妥的方式是直接用By.id()。再点击“登录”按钮属性面板显示resource-id: com.example.demo:id/login_btn class: android.widget.Button text: 登录 content-desc: (null) bounds: [120,1050][960,1200]这里resource-id和text同时存在用哪一个都行但我依然建议用 ID。因为如果产品后续把按钮文字改成“立即登录”文本定位就得跟着改用 ID 就不用管显示文字怎么变。“获取验证码”按钮通常是一个小按钮可能在验证码输入框右侧。如果它在布局里没有resource-id属性面板可能只有class和text。这种情况就得考虑用By.xpath(//android.widget.Button[text获取验证码])。通过 UI Automator Viewer 先确认页面上没有第二个相同文本的按钮然后才可以放心使用。4.3 把属性转成可执行的 Appium 脚本定位属性确认后写脚本就是水到渠成的事。下面给 Java 和 Python 两个版本的示例。Java 是很多 Appium 老项目的选择Python 则更多用在测试脚本和工具链里。Java 版本的核心片段driver.findElement(By.id(com.example.demo:id/phone_number)).sendKeys(13800138000); driver.findElement(By.id(com.example.demo:id/code_input)).sendKeys(123456); driver.findElement(By.xpath(//android.widget.Button[text获取验证码])).click(); driver.findElement(By.id(com.example.demo:id/login_btn)).click();Python 版本的核心片段driver.find_element(By.ID, com.example.demo:id/phone_number).send_keys(13800138000) driver.find_element(By.ID, com.example.demo:id/code_input).send_keys(123456) driver.find_element(By.XPATH, //android.widget.Button[text获取验证码]).click() driver.find_element(By.ID, com.example.demo:id/login_btn).click()有些同学封装的 Page Object 模式里会把定位器单独抽出来class LoginPage: phone_input (By.ID, com.example.demo:id/phone_number) code_input (By.ID, com.example.demo:id/code_input) code_btn (By.XPATH, //android.widget.Button[text获取验证码]) login_btn (By.ID, com.example.demo:id/login_btn)这样后续维护定位器时只需要改一处。脚本写完不代表万事大吉。建议先用 Appium Desktop 或 Appium Inspector 连接设备执行一次相同的定位表达式确认元素能高亮再放进完整的测试流程里跑。这样可以快速区分是定位表达式的问题还是测试流程逻辑的问题。4.4 动态等待的配合别让元素没加载完就 to 定位定位表达式写得再好如果脚本在执行findElement时元素还没有渲染出来一样会失败。很多新人在 UI Automator Viewer 里看到元素存在代码里却报找不到问题就出在时序上。UI Automator Viewer 截图时页面已经渲染完成但脚本运行时页面可能才刚开始加载控件。所以落地脚本时必须配合显式等待。WebDriverWait 是 Web 自动化里常用的方案Appium 里同样适用。下面是一个典型用法from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC wait WebDriverWait(driver, 10) phone_input wait.until(EC.presence_of_element_located((By.ID, com.example.demo:id/phone_number))) phone_input.send_keys(13800138000)等待策略的核心是“等元素可用”而不是“等固定时间”。time.sleep(5)这种硬等待虽然简单但会造成不必要的耗时而且网络慢的时候照样不稳属于新手阶段应该尽早戒掉的习惯。5. UI Automator Viewer 高频问题与排查技巧实录5.1 常见报错速查表UI Automator Viewer 本身是一个轻量工具但使用过程中依然会出现各种报错。我把这些年带新人时遇到的高频问题整理成一个速查表现象常见原因解决方案点击截图按钮后提示Error taking device screenshot设备锁屏、页面动画未结束、adb 连接不稳定唤醒设备并解锁等待页面稳定后重试工具启动后闪退JDK 版本不兼容或未正确配置 PATH安装 JDK 8/11重设 JAVA_HOME检查是否已加载抓取到的层级里没有目标控件App 使用 WebView、Flutter 或自定义渲染引擎换用 WebView 调试或 Flutter 专属定位方式截图显示正常层级树是空的设备上的某些 App 禁止辅助功能截取确认是系统级弹窗还是应用内页面关闭屏幕叠加层点击截图无响应工具卡住界面尚未刷新关闭工具后重新打开再次抓取属性面板 mike 是 null当前控件确实没有该属性不能使用该属性定位考虑 xpath 组合或父节点5.2 关于动态内容和 WebView 的踩坑记录UI Automator Viewer 最大的局限之一是它只能看到原生控件。现在很多 App 的核心页面都是 WebView 渲染甚至整个应用都是 React Native、Flutter 这类跨端框架写的。在这种情况下UI Automator Viewer 抓到的层级要么只有整个 WebView 容器要么干脆空白根本看不到页面内的按钮和输入框。遇到这类页面就不要硬生生拿着 UI Automator Viewer 去试了需要切换到对应的调试协议WebView 页面用 Chrome DevTools 的chrome://inspect来查看 DOMFlutter 页面用 Flutter Inspector。这不是说 UI Automator Viewer 没有用而是你要建立一种判断力在什么场景下用什么工具。原生页面优先 UI Automator Viewer混合页面配合多工具协作。另一个踩坑点是动态弹窗。有些 App 的登录页会先在首页弹一个广告弹窗或隐私协议弹窗。UI Automator Viewer 抓到的很可能只是弹窗层而不是你真正要操作的登录页。这时候需要在抓取前先手动把弹窗关闭让目标页面处于最顶层这个操作听起来简单但在脚本自动化时经常被人忽略导致定位总是对不上。5.3 从 UI Automator Viewer 平滑过渡到 Appium Inspector如果你已经能熟练使用 UI Automator Viewer 抓取和分析原生页面元素下一步建议切换到 Appium Inspector。这是个更现代化的工具界面和操作习惯有延续性但功能强很多内置了多种定位策略的即时验证能直接筛选元素、生成定位代码片段还能看到通过率更详细的控件树。切换的过程也很平滑。Appium Inspector 连接设备后右侧的层级树和属性面板布局跟 UI Automator Viewer 的思路基本一致只是额外加了搜索框和表达式验证功能。你可以把 UI Automator Viewer 里验证过的属性直接放到 Appium Inspector 里测试定位表达式是否高亮如果高亮区域正确且唯一就可以复制到脚本里。这种验证方式比写完脚本再跑更高效也算是从“入门工具”过渡到“生产工具”的一条捷径。我个人在实际操作中的一个体会是UI Automator Viewer 虽然看起来简单但它逼着你把元素的“身份”问题想清楚这个控件在整棵布局树里是什么位置、暴露了哪些稳定的属性、哪些属性会随用户操作变化。这些积累恰恰是后面写出稳定、可维护脚本的基础。工具会迭代Appium 的 API 会升级但“先看清元素属性再决定定位策略”的思路什么时候都不过时。
返回列表