资讯动态

Appium移动自动化测试实战:跨平台架构、环境搭建与元素定位

发布时间:2026/9/29 7:33:40 来源:尧图企业网站定制
1. 为什么我选择Appium以及它到底能解决什么问题很多刚接触自动化测试的朋友第一个问题往往是市面上框架那么多我凭什么选Appium。这个问题我在带新人时被问过不下几十次。先说结论如果你的目标是实现一套脚本同时覆盖Android和iOS而且团队里有人会写Java或Python那Appium就是最稳妥的长期选择。Appium本质上是一个基于HTTP协议的WebDriver协议扩展。它做的事情很纯粹让你用操作网页元素的方式去操作手机App里的控件。你写一条点击某个按钮的指令Appium负责把这个指令翻译成各个平台能听懂的命令再通过各平台自己的自动化引擎去执行。Android上走的是UIAutomator2或者EspressoiOS上走的是XCUITest。这样的架构带来一个非常实际的好处测试脚本里大部分代码是跨平台复用的。我在实际项目里Android和iOS的用例代码复用率能达到六成以上区别基本只集中在元素定位的表达式上。对于创业公司或者中小型团队这种一套技术栈打两边的优势是很值钱的因为你不需要养两支测试开发团队。另外Appium对语言的包容性也很友好。Java、Python、Ruby、JavaScript、C#都能写。我个人推荐用Python或者Java。如果你是测试团队里负责做框架的人Java搭配TestNG在工程化方面更顺如果你是个人学习或者脚本量不大Python的简洁性会让你写起来很舒服。还有一个很容易被忽略但很重要的点Appium不需要改造你的App。它走的是黑盒路径通过系统的无障碍服务或者测试代理去驱动界面不需要开发团队在App里嵌入任何SDK。这一点在很多公司里是刚需因为很多业务团队不愿意为了测试去改动正式包的代码。当然Appium也不是万能的。它最大的短板是慢。因为是黑盒驱动每个操作都要经过脚本-服务器-设备这条链路跑大规模用例集的时候整体耗时比单元测试和接口测试长得多。所以它最适合的场景是核心业务流程的回归测试、多机型兼容性验证、以及上线前的冒烟测试。如果是每次提交代码都要跑的快速验证Appium并不合适那种场景应该交给单元测试和接口自动化。接下来我把从零搭建Appium环境、写出第一条脚本、到能落地到实际项目里的完整路径拆开讲。每一个环节我都会标注哪些是文档里不会写但你必须知道的坑。2. 环境搭建版本兼容性比你想的更容易出问题环境搭建是Appium入门的第一道坎也是劝退率最高的环节。大多数人的失败不是某个工具装不上而是各个组件之间的版本相互不兼容报错信息又看不懂最后只能放弃。2.1 你需要准备哪些基础组件一套可用的Appium环境至少包含以下这些东西Java JDKAndroid SDK工具链依赖它建议JDK 8或JDK 11太新版反而容易出问题Android SDK包含adb和uiautomator2等平台工具Node.js环境Appium Server本身就是个Node应用Appium Server2.x版本现在用npm全局安装Appium Inspector用于定位元素相当于浏览器里的F12开发者工具一个模拟器或者真机客户端测试库Python是appium-python-clientJava是java-client我给团队建环境的时候习惯画一个非常直白的链路图方便新人理解你的测试代码 → 调起Appium客户端库 → 通过HTTP请求发给Appium Server → Appium Server在设备上驱动引擎执行操作 → 操作结果原路返回。有了这个认知你遇到问题时就能判断出问题出在哪一层。请求发不出去多半是Server没起或者端口被占用Server起来了但操作失败多半是设备连接或者元素定位出了问题。2.2 环境变量配置的细节Android SDK配好后有四个环境变量必须确认无误ANDROID_HOME指向SDK根目录PATH里要包含platform-tools和tools这两个子目录。这是最基础的要求。实际操作中我见过很多人在这一步卡住明明配了ANDROID_HOMEadb也能用但Appium就是提示找不到Android SDK。这种情况下八成是因为没有配JAVA_HOME或者JAVA_HOME指向了JRE而不是JDK。Appium的Android驱动在初始化时需要通过Java环境去调一些底层工具JRE不完整就会失败。还有一个很容易被忽略的点环境变量改完之后终端窗口一定要重新打开。Windows下配置完环境变量后已经打开的CMD或PowerShell窗口不会自动刷新很多新人改了配置后继续用旧窗口执行命令报错半天找不到原因。这个看起来特别傻的问题在实操中出现的频率高得惊人。2.3 Appium 2.x的变化目前新环境我建议直接装Appium 2.x因为2.x把驱动做了插件化管理。安装方式很简单npm install -g appium装完之后需要单独安装各个平台的驱动appium driver install uiautomator2 appium driver install xcuitest这里有个好处2.x版本把驱动和主程序解耦了驱动有更新时你只需要单独升级驱动不需要动主程序。1.x时代那种为了修一个bug要重新装整个Appium的情况不会再出现了。安装驱动的时候国内网络环境可能会遇到下载慢或者超时的问题。如果你用的是npm默认源可以先把registry切到国内镜像站下载速度会快很多。2.4 验证环境是否OK装完之后不要急着写脚本先跑一条最简单的命令验证appium doctor这个命令会逐项检查Java、Node、Android SDK、adb等依赖是否安装正确。如果检查项里有红色的Error就先解决掉再往下走不要带着隐患去写脚本否则后面排查起来会非常痛苦。另外Appium Server启动后默认监听4723端口。启动时加上--address 127.0.0.1 --port 4723固定好地址和端口避免端口冲突。3. 第一条自动化脚本从启动App到点击登录按钮环境搭好之后最快建立信心的方式就是跑通一条最简单的脚本。我习惯用一个打开App并点击登录按钮的用例来带新人入门虽然简单但已经覆盖了Appium脚本最核心的几个环节。3.1 创建会话的DesiredCapabilitiesAppium启动时需要先建立一个会话告诉Server你要测什么设备、什么App。这个信息通过一个叫DesiredCapabilities的字典传过去。Python版本的基础配置是这个样子from appium import webdriver desired_caps { platformName: Android, platformVersion: 12.0, deviceName: emulator-5554, appPackage: com.example.app, appActivity: .MainActivity, noReset: True, automationName: UiAutomator2 } driver webdriver.Remote(http://127.0.0.1:4723/wd/hub, desired_caps)这几个配置项里appPackage和appActivity是最容易出错的地方。appPackage是App的包名appActivity是你想直接打开的页面组件名。很多人不知道这两个值怎么拿这里分享一个最简单的办法先在模拟器里手动打开目标App然后执行命令adb shell dumpsys window | grep mCurrentFocus这条命令会输出当前前台窗口的信息里面就带着包名和Activity名。比如输出mCurrentFocusWindow{xxx com.example.app/.MainActivity}那com.example.app就是appPackage.MainActivity就是appActivity。noReset这个参数我建议第一次跑的时候设为True意思是每次启动App时保留原来的数据不清空。如果你不设这个参数Appium每次都会帮你卸载App导致登录状态丢失经常需要重新输账号密码很烦人。3.2 元素定位的常用方式App要能自动操作核心前提是能看到界面上的控件。Appium支持多种定位方式我日常使用频率从高到低排序是id、text文本、content-desc描述、XPath路径。id定位优先级最高因为它最稳定。用Appium Inspector在屏幕上点一下按钮工具会展示这个控件所有可用的属性信息你把resource-id复制出来放进脚本就行find_element(By.ID, com.example.app:id/btn_login).click()如果App开发时没有给控件写id那就退而求其次用text定位。text属性的好处是直观但风险是界面文案一改脚本就挂了find_element(By.XPATH, //android.widget.TextView[text登 录]).click()注意text里中文前后带了空格这在真机上很常见。定位不到时把实际文本原样复制过来宁可多一个空格也不能少一个。content-desc是给无障碍功能用的描述属性在Android原生控件里用得挺多但WebView里的元素通常没有这个属性。3.3 等待机制必须一开始就养成习惯新人最容易犯的一个错误是定位元素时完全不考虑加载时间。App界面不是静态的网络请求、页面渲染都要时间如果脚本在元素还没出现时就急着点击必然报错。Appium提供了三种等待方式我建议只记最实用的显式等待from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC element WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.ID, com.example.app:id/btn_login)) )这段代码的意思是最多等10秒每0.5秒检查一次页面直到目标元素出现才继续往下走。超过了10秒还没出现就抛异常。我为什么强调一开始就要养成习惯因为很多人写脚本时嫌麻烦直接time.sleep(3)糊弄过去。sleep是固定等待网络慢的时候3秒不够就会偶发失败网络快的时候3秒又太长拖慢整体执行时间。显式等待是按需等待元素出现了就立刻执行下一步这才是可靠且高效的做法。4. 实操实录一个完整的登录测试用例纸上谈兵到此为止下面展示一个完整的用例从设计到落地的全过程。我用一个模拟器上的天气App来举例目标场景是启动App点击我的Tab点击登录入口输入账号密码点击登录断言跳转成功。4.1 用例设计的思路在写代码之前先把操作步骤拆解成人能看懂的行为链这也是测试设计的一部分。我习惯在编写脚本前先手工把这条路径完整走一遍同时打开Appium Inspector观察每一步的界面变化。这样做的好处是能在写代码前就确认每个元素的存在性和属性值避免脚本写一半才发现元素定位不到。拆解行为链的时候我建议以用户能感知的操作为单位。比如打开App是一个行为但等首页加载完成就不算因为这是系统行为不是用户操作。行为链里每一步都应该是一个明确的输入-反馈闭环这对后续编写和维护用例很有帮助。4.2 完整代码看一个完整的Python实现import time from appium import webdriver from appium.webdriver.common.appiumby import AppiumBy from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC desired_caps { platformName: Android, platformVersion: 12.0, deviceName: emulator-5554, appPackage: com.example.weather, appActivity: .MainActivity, noReset: True, automationName: UiAutomator2, unicodeKeyboard: True, resetKeyboard: True } driver webdriver.Remote(http://127.0.0.1:4723/wd/hub, desired_caps) wait WebDriverWait(driver, 10) try: # 1. 点击底部Tab中的我的 mine_tab wait.until( EC.presence_of_element_located((AppiumBy.ID, com.example.weather:id/tab_mine)) ) mine_tab.click() # 2. 点击登录入口 login_entry wait.until( EC.presence_of_element_located((AppiumBy.ID, com.example.weather:id/login_entry)) ) login_entry.click() # 3. 输入账号和密码 username_input wait.until( EC.presence_of_element_located((AppiumBy.ID, com.example.weather:id/phone_input)) ) username_input.send_keys(13800138000) password_input wait.until( EC.presence_of_element_located((AppiumBy.ID, com.example.weather:id/pwd_input)) ) password_input.send_keys(test123456) # 4. 点击登录按钮 login_btn wait.until( EC.element_to_be_clickable((AppiumBy.ID, com.example.weather:id/login_submit)) ) login_btn.click() # 5. 断言登录成功等待个人中心页面出现 success_mark wait.until( EC.presence_of_element_located((AppiumBy.ID, com.example.weather:id/user_avatar)) ) assert success_mark is not None, 登录成功标志未出现用例失败 print(登录用例执行通过) finally: driver.quit()这段脚本看起来简单但每个步骤背后都有值得细说的点。比如unicodeKeyboard和resetKeyboard这两个配置是为了解决send_keys输入中文或特殊字符的问题。不启用这两个参数Appium在部分真机上用adb输入文本时会丢字符或者无法输入。开启了这两个配置后Appium会调起自己的输入法来保证输入可靠性。再比如登录按钮的定位用了element_to_be_clickable而不是presence_of_element_located。前者不仅检查元素存在还检查它是否可点击。很多App的登录按钮在账号密码为空时是置灰不可点的状态如果只检查存在性就点很大概率点击不生效。4.3 执行结果排查第一次跑这个脚本大概率不会一次通过。常见的失败情形和原因我整理在后面的章节里。这里先提醒一个很多人忽略的点driver.quit()一定要写在finally里。为什么因为脚本一旦在中间步骤抛异常如果不清理会话Appium Server会一直保持那个连接设备上会残留一个自动化Session跑到第五次的时候你就只能重启Server了。执行过程中如果想看每一步的实际界面变化可以在关键节点调用driver.get_screenshot_as_file(screenshot.png)保存截图。这种方法在排查问题是效率很高比看日志直观多了。5. 从模拟器到真机多设备适配的关键细节模拟器上手快但最终项目落地必须考虑真机兼容性。这里面的坑模拟器上演示时一个都碰不到。5.1 真机调试前的准备真机调试需要先打开开发者选项里的USB调试开关。不同品牌手机的开启方式不同通用做法是进入设置找到关于手机连续点击版本号7次系统会提示您已进入开发者模式。然后在开发者选项里打开USB调试。用数据线连接电脑后第一次连接手机会弹出一个授权对话框需要在手机上确认允许。如果之前点了拒绝需要拔掉数据线重新连接或者进入开发者选项里撤销USB调试授权后重试。这个授权弹窗很容易被忽略很多人连接后adb devices看不到设备多半就是没确认授权。连接后执行adb devices看到类似xxxxxxx device的输出就说明设备正常接入。如果显示的是unauthorized那就是授权还没通过如果显示offline大概率是adb版本和手机系统不兼容可以试试升级platform-tools。5.2 一个脚本跑多个设备的配置多设备并行的思路很简单每个设备分配一个独立的adb端口脚本里deviceName和系统版本使用对应的值。更优雅的做法是把设备信息抽出来放到配置列表里devices [ {platformName: Android, platformVersion: 12.0, deviceName: emulator-5554}, {platformName: Android, platformVersion: 13.0, deviceName: R58M42KHFPL} ]跑测试时遍历这个列表在每个设备上执行一遍用例。如果你用的是JavaTestNG可以直接用TestNG的DataProvider来驱动多设备执行。Python也有自己的方式比如通过pytest的parametrize来参数化设备列表。真机执行的一个巨大坑是文案差异。同一个App在小米和华为的Rom上部分控件的text属性可能不完全一致。比如底部Tab栏的我的有的机型上显示我的有的机型上可能因为字体渲染导致文本定位失败。我的建议是能用id定位的绝不用text定位id是所有目标机型的公共交集时稳定性才有保障。另外真机的屏幕分辨率和尺寸差异很大。如果用例里用到坐标点击或滑动坐标参数在不同机型的表现完全不同。这个问题没有银弹只能通过建一个设备型号-分辨率的映射表在为每个机型执行时动态计算坐标比例。好在大多数场景我们都能用元素定位绕开坐标只有极少数App的控件连无障碍属性都不支持才需要走坐标方案。6. Inspect元素定位实战用好Inspector少走一半弯路Appium Inspector是官方提供的界面分析工具很多新手觉得它只是个看控件属性的小工具但实际上它的能力远不止这些。熟练使用Inspector定位元素的效率能翻倍。6.1 Inspector的安装与启动Appium 2.x时代Inspector的启动方式变了。它是Appium Server的一个扩展插件安装命令是appium plugin install --sourcenpm appium-inspector启动Appium时加上插件参数appium --use-pluginsappium-inspector然后打开Appium Inspector的桌面客户端或者直接访问Server提供的Inspector页面填入和脚本里完全一致的DesiredCapabilities点击Start Session就能看到手机屏幕的实时镜像和控件树。这里有一个很关键的技巧Inspector里填入的配置务必与脚本保持一致尤其是appPackage、appActivity和automationName。如果配置不一致Inspector能看到的元素在脚本里却定位不到这种情况我已经见过很多人踩坑了。6.2 定位策略的选择经验Inspector界面上左上角是设备屏幕截图点击任意控件右侧会展示该控件的完整属性列表。看到属性的第一时间不要着急复制resource-id先在脑子里过一遍这个控件的稳定性。具体的判断标准有三个。第一resource-id是否唯一。有的App在列表型页面上同一种控件会重复出现比如商品列表里的多个加入购物车按钮如果不带index定位脚本永远点的是第一个。第二属性值是否包含动态变化的内容。有些App会动态生成id后缀核对时需要特别留意。第三XPath写出来尽量短而具体不要从根节点开始一路写到目标节点。界面结构一变长XPath立刻就废了。Inspector还有一个功能很多人没用上可以实时验证XPath表达式。你在界面底部输入XPath点击搜索它会立刻高亮匹配到的元素。利用这个功能可以快速校验自己写的定位表达式是否有效不用一遍遍改脚本重跑。6.3 页面滑动和WebView的特殊处理App里的列表页经常需要滑动才能看到目标元素。Appium里提供了driver.swipe()方法但坐标参数需要自己算。我一般这样处理先用driver.get_window_size()获取屏幕宽高然后按比例算出起始点和终点的坐标值。WebView场景则更特殊。如果App内嵌了H5页面那么原生的id和text定位都失效了因为WebView里是HTML元素不是原生控件。处理这种场景有两种主流方案一种是切换上下文到WebView再用类似Selenium的方式用CSS选择器或XPath定位HTML元素另一种是让开发在H5页面里暴露原生webview调试开关。前者是通用解法但需要App在调试模式下才能连接到WebView线上包的Chrome调试默认是关闭的。我在实际项目中碰到WebView元素时第一反应是找开发沟通商量能否在关键流程页面补充原生容器或者content-desc属性。如果开发配合度不高才会上WebView上下文切换的方案毕竟那套方案依赖的环境条件更多。7. 自动化测试框架封装别把用例写成一次性脚本很多团队做Appium几十条脚本写在一个文件里跑起来也能工作但越到后期维护成本越高。一个录制的脚本一旦到了下一个版本按钮的位置变了、文案改了就会整个崩溃。这时候把代码按页面对象整理成框架的价值就体现出来了。7.1 Page Object模式的思想Page Object模式的核心理念是每一个页面对应一个类这个类封装了该页面操作所需的全部元素和业务方法。测试用例只关注业务步骤不关心具体的元素定位细节。用登录页举例。新建login_page.py把登录页上的输入框、按钮、报错提示都封装成对象再把登录这个动作封装成方法class LoginPage: def __init__(self, driver): self.driver driver def input_username(self, username): element self.driver.find_element(AppiumBy.ID, com.example.weather:id/phone_input) element.clear() element.send_keys(username) def input_password(self, password): element self.driver.find_element(AppiumBy.ID, com.example.weather:id/pwd_input) element.clear() element.send_keys(password) def click_login_btn(self): element self.driver.find_element(AppiumBy.ID, com.example.weather:id/login_submit) element.click()测试用例就变得非常干净login_page LoginPage(driver) login_page.input_username(13800138000) login_page.input_password(test123456) login_page.click_login_btn()这样做的好处是登录页元素只要发生变化只需要改LoginPage这个类里的定位表达式所有调用它的用例都不需要动。如果页面上有10条用例涉及登录你只改1个文件而不是改10个文件。7.2 数据驱动的价值实际项目中同一个流程往往需要跑多组测试数据。比如登录功能至少要覆盖正确的账号密码、错误的密码、不存在的账号、空账号。每写一遍重复代码就是浪费生命。数据驱动可以理解为把操作步骤和测试数据拆开。测试数据放在一个独立的文件里可以是Excel、CSV或JSON测试代码读取这些数据后执行用例。一个数据驱动用例的好处显而易见新增一条测试数据只需要在数据文件里加一行完全不碰代码。对于新入行的测试同学来说这条实践可以大幅降低维护成本和犯错概率。7.3 框架分层带来的安全感分层的另外一个好处是出了问题能快速定位。界面上一个按钮点击不生效如果你的日志里能看到是LoginPage.click_login_btn失败了而不是一大段定位异常堆栈排查效率完全不一样。在实际的框架设计里我还会加封装一层基础的支持库。比如封装日志记录、截图工具、重试机制、测试报告生成等。这些通用能力越早沉淀后续所有用例都能受益前期的一次性投入很值。8. Appium常见问题排查和避坑技巧最后这部分是硬通货。下面这些问题是我这些年在真实项目中遇到的、文档里很难找到完整解法的坑按频率从高到低整理出来。8.1 常见报错速查报错信息常见原因解决方法Could not find a connected Android device设备未连接、adb未授权执行adb devices确认设备状态重新点击手机授权弹窗SessionNotCreatedExceptionDesiredCapabilities配置错误检查appPackage和appActivity是否正确automationName是否设置Activity used to start app doesnt existappActivity填错用dumpsys window命令重新获取注意完整路径An element could not be located元素定位表达式失效用Inspector重新确认属性优先使用id定位sendKeys failed due to unicode输入中文字符出错添加unicodeKeyboard和resetKeyboard配置The server has active sessions上次异常退出遗留会话在Appium Server上关闭所有会话或者重启ServerElement is not clickable元素被遮挡或不可点击检查是否有点击前弹窗弹出改用element_to_be_clickable8.2 定位不到元素的排查思路定位不到元素是最恼人的问题我整理了一套自己的排查顺序先确认当前页面就是你以为的那个页面。这个看起来废话但真的发生过很多次脚本A点完登录后跳转失败了脚本B还在旧的页面上找元素当然找不到。再检查页面有没有弹窗遮挡。Android系统弹窗、App自家弹窗比如更新提示、隐私协议授权都是Top 3的元素找不到元凶。解决办法是执行点击操作前先写一个关闭弹窗的公共函数尝试查找这些弹窗元素存在就关掉。最后检查元素是否在一个if容器里或者界面是否在加载中。如果是页面还在加载数据还没有渲染出来此时元素树里就没有目标节点。显式等待能覆盖一部分问题但如果等待条件本身写错了——比如等到一个本身就隐藏的元素——那还是会失败。8.3 UI层面之外的心法最后一个比任何技术点都重要的建议学会评估该不该自动化。Appium脚本有一个显著的特点脆。界面上任何一个像素级的调整都可能导致脚本失败。所以我在团队里推行自动化测试时始终会划定边界。核心稳定的主流程、跨端的兼容性验证、以及回归比较频繁的模块这几个方向用Appium最划算。而一次性的活动页面、频繁改版的功能列表、以及验证价值低于维护成本的边缘功能不如老老实实手工测。边界清晰的自动化才是有意义的自动化。很多团队的Appium项目最后变成做完了不用了不是技术本身不行而是没想清楚哪些用例值得自动化。Auto测试的价值不是代替手工测试而是把手工测试员从重复劳动中解放出来让他们把精力投入到探索性测试上。这才是自动化测试真正的意义。我在实际应用中的体会是Appium这套工具链用得好完全可以成为测试团队的重要基础设施。但前提是要有耐心把基础设施一步步夯实环境、定位、框架、边界每一步都踩踏实。遇到问题不要慌按着设备层-Server层-元素层的顺序排查大部分问题都能在一刻钟内解决。

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

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

免费获取报价 →
↑