资讯动态

Python+Appium移动端自动化测试实战:环境搭建、元素定位与稳定性优化

发布时间:2026/10/17 22:42:02 来源:尧图企业网站定制
1. 为什么我最终选择了 Appium 而不是其他自动化方案做移动端自动化测试这件事我前前后后折腾过好几套方案。最早用的是某款商业化的录制回放工具上手确实快但一旦遇到动态加载的页面就频繁失效维护成本高得离谱。后来也试过基于图像识别的方案靠截图比对来定位元素听起来很美好实际跑起来受分辨率、系统主题、弹窗干扰的影响极大稳定性完全没法保证。绕了一大圈之后我才把主力方案锁定在Python Appium这套组合上。Appium 的核心价值在于它走的是WebDriver 协议也就是说它本质上复用了 Selenium 那套成熟的元素定位和操作模型只不过把作用对象从浏览器换成了移动端 App。这意味着如果你之前写过 Web 自动化迁移成本非常低。它通过调用平台原生的自动化框架来驱动设备——Android 上走的是 UiAutomator2 或 EspressoiOS 上走的是 XCUITest——所以它不是一个模拟点击的外挂工具而是真正在系统层面驱动控件。这套方案适合什么人我总结下来是三类一是需要做回归测试的测试工程师每天要跑几十上百条用例二是需要做数据采集或流程自动化的开发者比如批量完成某些重复性的 App 内操作三是想把自己从重复劳动里解放出来的普通用户比如每天定时签到、批量处理消息这类场景。不管你属于哪一类只要涉及让程序代替人手去操作 AppAppium 都是绕不开的选项。不过我要先泼一盆冷水Appium 不是银弹。它的环境配置是出了名的劝退第一次搭环境卡个一两天是常态。而且它的执行速度比原生框架慢因为中间多了一层 HTTP 通信。所以如果你的需求是极高性能的压测那 Appium 不合适但如果你的需求是稳定、可维护、跨平台的自动化那它就是当前最平衡的选择。接下来的内容我会把环境搭建、元素定位、用例设计、稳定性优化这几个最容易踩坑的环节按我实际操作的顺序完整拆一遍。2. 环境搭建那些文档里不会告诉你的细节2.1 组件依赖的完整清单与安装顺序很多人搭 Appium 环境失败根本原因不是某一步做错了而是安装顺序错了。这套环境里各个组件之间有依赖关系顺序不对就会出现明明装了却找不到的诡异问题。我推荐的顺序是这样的JDKAndroid 自动化依赖 Java 环境建议 JDK 8 或 JDK 11这两个版本兼容性最好。装完后务必配置JAVA_HOME环境变量很多人只配了 PATH 忘了 JAVA_HOME后面启动时会报找不到 Java。Android SDK不需要装完整的 Android Studio用命令行工具包就够了。核心是platform-tools里面有 adb和build-tools。装完后配置ANDROID_HOME并把platform-tools和tools加进 PATH。Node.jsAppium Server 本身是 Node 写的所以需要 Node 环境。建议用 LTS 版本别追最新版。Appium Server通过 npm 全局安装命令是npm install -g appium。装完后用appium -v验证。Appium Driver这是新手最容易漏的一步。新版 Appium 把驱动拆成了独立包必须单独装。Android 用appium driver install uiautomator2iOS 用appium driver install xcuitest。Python 客户端库pip install Appium-Python-Client注意这个包名是大写开头的小写会装错。装完之后用appium driver list --installed确认驱动装上了用adb devices确认设备能被识别。这两条命令能跑通环境基本就没问题了。2.2 设备连接与开发者模式的隐藏开关真机调试比模拟器稳定得多我强烈建议用真机。连接步骤看似简单但有几个隐藏坑USB 调试必须在开发者选项里打开这个大家都知道。但很多人不知道的是部分机型还需要额外打开USB 安装和USB 调试安全设置这两个开关否则 adb 能识别设备但装不了应用。设备授权第一次连接时手机上会弹一个允许 USB 调试的对话框一定要勾选始终允许否则每次重连都要手动确认自动化脚本会卡死在这里。adb 版本冲突如果你电脑上装过其他会自带 adb 的软件比如某些手机助手它们的 adb 版本可能和 SDK 里的不一致导致设备识别异常。解决办法是统一用 SDK 里的 adb把其他路径从 PATH 里去掉。验证设备连接用adb devices正常应该输出设备序列号和device状态。如果显示unauthorized说明授权没通过重新插拔并确认弹窗即可如果显示offline通常是 adb 服务卡了执行adb kill-server再adb start-server一般能解决。2.3 获取 App 信息的三种方式与参数含义启动一个 App 需要几个关键参数这些参数决定了 Appium 会话的初始化行为。核心参数如下表参数名含义获取方式platformName平台名称Android 或 iOS固定值platformVersion系统版本号adb shell getprop ro.build.version.releasedeviceName设备名称adb devices输出的序列号appPackage应用包名adb shell dumpsys window找 mCurrentFocusappActivity启动页 Activity同上或问开发要automationName自动化引擎Android 填 UiAutomator2获取包名和 Activity 最实用的命令是先手动打开目标 App然后执行adb shell dumpsys window | grep mCurrentFocusWindows 上用 findstr输出里斜杠前面是包名后面是当前 Activity。注意这个 Activity 不一定是启动页如果 App 有闪屏页最好用adb shell dumpsys package 包名 | grep -A 1 android.intent.action.MAIN来找真正的入口 Activity。提示如果你拿不到源码可以用adb shell pm list packages列出所有包名再根据关键词筛选比一个个翻快得多。3. 元素定位自动化稳定性的命门所在3.1 定位方式的优先级与适用场景元素定位是自动化脚本里最容易出问题的地方脚本跑着跑着报NoSuchElementException十有八九是定位方式选得不好。Appium 支持多种定位策略我按稳定性和推荐度排个序Accessibility ID对应 Android 的content-desc和 iOS 的accessibility identifier。这是最稳定的方式因为它不随 UI 布局变化。但前提是开发给控件加了无障碍标签很多 App 没加所以可用性受限。ID对应 Android 的resource-id。稳定性次之但前提是 ID 唯一。列表项里的 ID 经常重复这时候就不能直接用。XPath最灵活但最脆弱。它能通过层级关系精确定位但一旦 UI 结构变动就失效而且解析速度慢。我一般只在其他方式都搞不定时才用。Class Name按控件类型定位通常会匹配到一堆元素需要配合索引使用稳定性差。UIAutomator 定位Android 专属支持一些高级语法比如通过文本和类名的组合定位性能比 XPath 好。我的实战原则是能用 Accessibility ID 就不用 ID能用 ID 就不用 XPath。XPath 是最后的兜底手段不是首选。3.2 用工具快速抓取元素属性手动猜元素属性效率太低一定要用工具。Appium Inspector 是官方提供的可视化工具能实时看到设备当前页面的元素树点哪个控件就显示哪个控件的所有属性。启动方式是先运行appium启动服务然后打开 Inspector填入和脚本里一样的 Desired Capabilities连接后就能看到页面结构。用 Inspector 有个技巧它默认会高亮显示当前选中元素在页面上的位置你可以借此确认自己选中的是不是想要的那个控件。另外Inspector 支持录制操作并生成代码虽然生成的代码质量一般但用来快速获取定位表达式非常方便。对于动态加载的页面Inspector 有个坑它抓取的是某一瞬间的快照如果页面还在加载抓到的可能是空节点。所以抓元素前一定要确保页面已经加载完成必要时手动刷新一下快照。3.3 动态元素与等待机制的正确用法移动端页面大量使用异步加载元素不是一打开就存在的。这时候如果直接去定位必然失败。很多人第一反应是加time.sleep()这是最糟糕的做法——固定等待要么不够导致失败要么过长拖慢整体速度。正确的做法是用显式等待。Appium 的 Python 客户端支持WebDriverWait配合expected_conditions使用from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from appium.webdriver.common.appiumby import AppiumBy wait WebDriverWait(driver, 15) element wait.until( EC.presence_of_element_located((AppiumBy.ID, com.example.app:id/btn_login)) )这段代码的含义是在 15 秒内不断轮询直到元素出现为止一旦出现立即返回不会多等一秒。这比sleep高效得多。对于需要点击的元素建议用element_to_be_clickable而不是presence_of_element_located因为元素存在不代表可点击可能被遮挡或还没渲染完。这个细节能避免大量偶发的点击失败。注意显式等待的超时时间不要设得太短移动端网络波动大我一般设 15 到 20 秒。设太短会在网络慢时误报失败设太长又会在真失败时浪费时间需要根据实际网络环境权衡。4. 用例设计从能跑到跑得稳的进阶思路4.1 页面对象模型的落地实践如果所有定位和操作都堆在一个脚本里用例一多就会变成一坨难以维护的代码。页面对象模型Page Object Model是解决这个问题的标准方案把每个页面封装成一个类页面上的元素定位和操作都作为类的方法测试用例只调用方法不直接碰定位表达式。这样做的价值在于当 UI 改版导致某个元素变了你只需要改对应页面类里的一处定位所有用到这个元素的用例自动生效不用满世界找。我举个简化的例子class LoginPage: def __init__(self, driver): self.driver driver self.username_input (AppiumBy.ID, com.example.app:id/username) self.password_input (AppiumBy.ID, com.example.app:id/password) self.login_btn (AppiumBy.ID, com.example.app:id/login) def login(self, username, password): self.driver.find_element(*self.username_input).send_keys(username) self.driver.find_element(*self.password_input).send_keys(password) self.driver.find_element(*self.login_btn).click()用例里就变成LoginPage(driver).login(user, pass)干净利落。这个模式前期多花点时间封装后期维护省下的时间是指数级的。4.2 测试数据与用例逻辑的分离另一个常见的坏习惯是把测试数据硬编码在用例里。比如登录用例里直接写死用户名密码一旦要测多组数据就得复制粘贴一堆用例。正确做法是把数据抽出来用参数化驱动。Python 里可以用pytest的pytest.mark.parametrize装饰器把数据以列表形式传入框架会自动为每组数据生成一个独立用例。数据源可以是列表、CSV、Excel 或数据库我一般用 YAML 文件因为可读性好非技术人员也能改。这样设计的好处是新增测试场景只需要加一行数据不用动代码逻辑而且用例失败时能精确定位是哪组数据出的问题排查效率高很多。4.3 断言策略别只看有没有报错很多新手写自动化用例只要脚本不抛异常就算通过这是极其危险的。自动化测试的核心价值在于验证结果是否符合预期而不是操作有没有执行成功。所以每个关键操作后都要有断言。移动端的断言比 Web 复杂因为很多状态变化不是通过 URL 体现的。常用的断言方式有元素存在性断言操作后某个预期元素出现了比如登录成功后首页的用户头像。文本内容断言某个控件的文本等于预期值比如下单后订单状态显示待付款。Toast 提示断言很多操作会弹 Toast可以通过定位 Toast 文本验证。页面 Activity 断言Android 上可以获取当前 Activity验证是否跳转到了正确页面。我踩过的一个坑是Toast 的显示时间很短用普通定位经常抓不到。解决办法是用WebDriverWait配合presence_of_element_located去等 Toast 出现或者用 UiAutomator 的wait_for_exists。另外Toast 的定位在不同 Android 版本上类名不一样需要做兼容处理。5. 稳定性优化让脚本从偶尔能跑到天天能跑5.1 常见失败原因的分类与对策自动化脚本跑一段时间后失败率会上升这是必然的。关键是要能快速定位失败原因。我把常见失败归为几类失败类型典型报错根本原因对策元素找不到NoSuchElementException页面未加载完/定位失效显式等待定期维护定位元素不可点击ElementNotInteractableException被遮挡/未渲染完等待可点击滚动到可见会话断开InvalidSessionIdException设备掉线/服务崩溃重连机制心跳检测超时TimeoutException网络慢/等待时间不足调整超时重试弹窗干扰定位到错误元素意外弹窗覆盖启动时统一处理弹窗其中弹窗干扰是最隐蔽的。App 启动时经常弹更新提示、权限申请、广告这些弹窗会挡住后续操作。我的做法是封装一个handle_popups方法在每次页面切换后调用检测并关闭常见弹窗。这个方法的逻辑是遍历一组已知的弹窗关闭按钮定位哪个存在就点哪个都不存在就跳过。5.2 失败重试与截图留证机制再稳的脚本也会有偶发失败这时候失败重试能救回大部分问题。我一般用装饰器封装重试逻辑捕获特定异常后重新执行重试次数设 2 到 3 次。但要注意不是所有失败都值得重试——如果是定位表达式本身错了重试一百次也没用所以要区分异常类型只对超时、元素未找到这类可能自愈的异常重试。比重试更重要的是失败截图。脚本失败时自动截一张图能极大加速排查。实现方式是在异常处理里调用driver.get_screenshot_as_file()文件名带上时间戳和用例名。配合日志一起看基本能还原失败现场。import time def run_with_retry(func, retries3): for i in range(retries): try: return func() except Exception as e: if i retries - 1: driver.get_screenshot_as_file(ffail_{int(time.time())}.png) raise time.sleep(2)这段代码的逻辑是执行失败后等 2 秒重试最后一次还失败就截图并抛出异常。截图文件名用时间戳保证唯一方便和日志对应。5.3 多设备并行与用例隔离当用例数量上来了串行执行会非常慢。这时候可以考虑多设备并行。Appium 支持同时启动多个服务实例每个实例绑定一台设备用不同的端口区分。Python 侧可以用多进程或线程池把用例分发到不同设备上跑。但并行有个前提用例之间必须相互隔离。如果用例 A 依赖用例 B 产生的数据并行时顺序就乱了。所以每个用例都要能独立运行自己准备数据、自己清理环境。我一般用setup和teardown方法前者负责启动 App 并登录后者负责退出登录并关闭 App保证每个用例从干净状态开始。另外并行时要注意设备性能差异。低端机执行慢如果任务分配不均会出现一台设备跑完了另一台还在跑的情况。简单的解决办法是按设备性能分组或者用动态任务队列谁空闲谁领任务。6. 我踩过的几个真实坑与解决过程6.1 输入中文乱码的排查链路有一次做搜索功能自动化输入英文没问题一输入中文就变成乱码或者干脆没输入进去。我一开始以为是编码问题试了各种encode、decode都没用。后来查资料才明白Appium 的send_keys在部分输入法下对中文支持不好尤其是当设备默认输入法是某些第三方输入法时。排查过程是这样的先确认脚本里的字符串编码没问题用print打印出来是正常中文然后换了一台设备发现有的设备正常有的不正常说明和输入法有关最后定位到问题——设备默认输入法拦截了send_keys的注入。解决办法有两个一是把设备默认输入法切换成系统自带的兼容性最好二是用adb shell input text命令直接注入但这种方式对特殊字符支持有限。我最终采用的是切换输入法方案在setup里用adb shell ime set命令把输入法固定成系统输入法问题彻底解决。6.2 滑动操作的坐标陷阱做列表滚动时我用swipe方法传入坐标滑动结果在某些分辨率不同的设备上滑动距离完全不对。原因是swipe用的是绝对坐标而不同设备屏幕尺寸不一样写死的坐标在小屏上可能滑过头在大屏上可能滑不动。正确的做法是用相对坐标基于屏幕宽高计算。先用driver.get_window_size()拿到屏幕尺寸然后按比例计算起点和终点size driver.get_window_size() start_x size[width] * 0.5 start_y size[height] * 0.8 end_y size[height] * 0.2 driver.swipe(start_x, start_y, start_x, end_y, 500)这样无论什么分辨率滑动都是从屏幕下方 80% 处滑到 20% 处距离相对固定。这个改动之后滑动操作在所有测试设备上都稳定了。6.3 应用状态重置的取舍Appium 有个noReset参数控制每次会话是否重置 App 状态。设成False会清空 App 数据相当于全新安装设成True则保留登录状态等数据。我一开始图省事设了True结果用例之间互相污染——前一个用例改了设置后一个用例就受影响。后来改成False又发现每次都要重新登录慢得要命。最终的方案是折中大部分用例用noResetTrue保持登录但在用例内部通过退出登录再登录的方式保证状态干净。只有涉及首次启动引导、权限申请这类必须全新状态的用例才单独用noResetFalse。这样既保证了速度又保证了隔离性。7. 关于这套方案后续还能怎么玩跑通基础自动化之后我实际用下来觉得还有几个方向值得深挖。一是接入持续集成把脚本挂到定时任务上每天凌晨自动跑一遍回归第二天早上看报告能第一时间发现开发引入的问题。二是结合数据驱动做兼容性测试同一套用例在多台不同品牌、不同系统版本的设备上跑能快速发现兼容性问题。三是把常用操作封装成工具库比如自动登录、自动清理缓存、自动截图对比这些在多个项目里都能复用。我个人在实际操作中的体会是Appium 这套东西入门门槛确实高但一旦跨过去后面就是纯粹的工程问题——怎么组织代码、怎么提高稳定性、怎么优化执行速度。它不像某些录制工具那样看起来很美用起来很坑而是前期投入大、后期回报也大的类型。如果你正准备入这个坑我的建议是先把环境搭稳再花时间研究元素定位和等待机制这两块吃透了后面就顺了。至于那些花里胡哨的高级特性等基础用例能稳定跑起来再说别一上来就追求大而全。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑