Python+Appium APP自动化测试全攻略:从环境搭建到实战脚本
做了几年测试越来越多人问我同一个问题手工点手机点到怀疑人生想学APP自动化测试到底从哪里下手市面上教程确实不少但不是讲得太散就是环境搭到一半就卡住最后不了了之。这篇文章我直接以Python Appium这套目前最主流的组合为主线把APP自动化测试从环境搭建、第一个脚本、元素定位到常见问题排查的完整过程讲清楚重点解决“知道怎么用”和“知道为什么这么用”这两件事适合想转自动化方向的测试工程师、刚入行想找突破点的初级测试同学以及想独立搭一套APP自动化测试框架的开发者参考。学习APP自动化绕不开一个核心矛盾想学的工具太多但能跑通一条完整用例的太少。所以这篇文章只围绕一条主线——用Python写脚本通过Appium操控Android设备上的APP完成“启动APP—查找元素—操作—断言—出报告”的闭环。只要搞定了这条路后面再扩展到iOS、小程序、多设备并行都只是同一个套路的不同变体。1. 为什么偏偏是Python Appium这套组合1.1 Python不是唯一选择但确实是最省心的选择讲APP自动化测试语言选择真的非常多Java、Kotlin、JavaScript、Python都有人用。我自己也接触过不少用Java写Appium脚本的团队之所以最终建议测试团队统一用Python核心原因倒不是Python性能多好而是它语法表达足够直接出活快排错成本低。你在写APP自动化脚本时百分之八十的时间不是在挑战性能极限而是在和元素定位、等待策略、异常场景打交道Python这种“读代码基本等于读逻辑”的语言恰好能把这部分心智负担降到最低。另外Python在测试生态里的积累是目前所有语言里最全的pytest、allure-pytest、requests、Appium-Python-Client这些库组合起来覆盖了脚本编写、用例管理、断言、报告生成的全流程。你不需要自己造轮子基本都是拿来就用。更关键的是社区资料足够多遇到问题搜一下基本都能找到答案这对于新手来说比纸面上的性能优势重要得多。1.2 Appium为什么能成为事实标准移动端自动化框架还有别的选择比如Airtest、Macaca、Selendroid这些但目前行业覆盖面最广、跨端能力最强的还是Appium。原因不难理解它的设计思路直接继承了Selenium WebDriver那一套通过HTTP协议把客户端的操作指令发给Appium Server再由Server调用手机端的自动化驱动去执行。这个架构带来的最大好处是“一次学透别处复用”——你会写Selenium的Web自动化上手Appium基本是无缝迁移Desired Capabilities、元素定位、WebDriverWait这些概念几乎完全一致。还有一点容易被忽略Appium是跨平台的同一套API既能跑Android也能跑iOS。虽然iOS自动化依然要求你用Mac Xcode但至少你的Python脚本逻辑不用推倒重来。这意味着测试团队的知识资产不会被绑定在单一平台上以后公司业务出iOS版本你不至于从零再学一套框架。提示Appium 2.x之后驱动Driver和Server拆分了Android自动化需要单独安装uiautomator2驱动iOS需要安装xcuitest驱动。很多老教程还停留在1.x写法如果你照着配置发现连不上设备先检查是不是驱动没装。1.3 学APP自动化前你最好具备这些基础我不喜欢把门槛讲得太高但至少有三样东西建议先有个底第一是Python基础语法至少要能看懂类和函数、循环和判断、import和异常处理不至于连代码文件怎么组织都一头雾水第二是pytest的基本玩法会写test_开头的用例函数会跑pytest命令知道怎么用断言第三是adb命令至少会adb devices、adb shell、adb install这三板斧手机连接、装应用的问题排查都靠它。如果你现在连Python还没装好先别碰自动化。把Python环境配通能跑通一个最简单的pytest用例再回来读这篇文章进度会快很多。很多人学APP自动化失败根因不是Appium难而是基础的前置环境一塌糊涂后面所有问题都变成环境问题。2. 环境搭建与工具选型2.1 需要哪些软件分别干什么用环境准备这块我直接给你列一张清单不想让你在“到底该装多少个软件”上纠结了。软件作用装不装Python 3.8运行测试脚本必须JDK 8或11Android SDK的依赖Appium Server也要用必须Android SDK Platform-Tools提供adb等基础命令必须Android SDK Build-Tools编译和构建Android应用时的辅助工具建议Appium ServerDesktop版或命令行版转发客户端脚本指令到手机必须Appium-Python-ClientPython脚本里操作手机的核心库必须UiAutomator2驱动Appium 2.x的Android自动化驱动必须Android模拟器或真机测试执行的目标设备必须Appium Inspector或UI Automator Viewer查看页面元素结构、定位元素强烈建议这套清单看着不少但实际装起来按顺序走半小时内基本能搞定。最容易踩坑的反而是版本匹配问题Appium Server版本、UiAutomator2驱动版本、手机Android版本这三者之间如果出现兼容性问题表现千奇百怪从session创建失败到元素找不到都有可能。2.2 环境变量的配置细节Windows用户装完JDK和Android SDK之后需要配置系统环境变量。我自己的习惯是这样的JAVA_HOME指向JDK安装目录ANDROID_HOME指向Android SDK目录然后在Path里追加%JAVA_HOME%\bin和%ANDROID_HOME%\platform-tools。配置完后打开命令行分别输入java -version和adb version能正常输出版本号说明配置成功。macOS/Linux用户则是在.zshrc或.bashrc里设置export原理一样。每次配完环境变量记住一件事重新打开命令行终端再验证否则新配置不生效你会在“为什么明明配了还是报错”这个问题上白白花掉半小时。我遇到过不少同事Android SDK不是通过官方方式安装的而是通过Android Studio自带的SDK路径这时候ANDROID_HOME要指向Android Studio SDK的实际目录不是安装目录本身。如果adb devices能识别设备但Appium启动时报找不到Android SDK十有八九是ANDROID_HOME指错了位置。2.3 Appium 2.x和UiAutomator2驱动安装的实操要点现在网上的教程五花八门有的是老版Appium 1.x的图形界面有的是命令行方式。我建议直接用Appium 2.x 命令行驱动管理的方式理由很简单驱动拆分后更新和维护变得更可控不必为一次环境问题把整个Server升级一遍。第一步安装Appium Server。如果你用Desktop版下载图形安装包直接装就行。如果你习惯命令行可以用npm安装npm install -g appium安装完成后appium -v看版本。第二步安装UiAutomator2驱动。Appium 2.x下执行appium driver install uiautomator2。这步如果网络慢可能需要几分钟装完用appium driver list确认状态为已安装。第三步启动Appium Server。命令行方式直接在终端执行appium默认监听4723端口看到“Appium REST http interface listener started on 0.0.0.0:4723”就是启动成功。这一步我提醒一句appium启动之后那个终端不要关闭脚本执行时它还承担着指令转发的工作关了就全断了。3. 从零到一写通第一个自动化脚本3.1 Desired Capabilities参数到底是什么意思Appium脚本开头的Desired Capabilities你可以理解成“告诉Server我要什么环境、操作哪台设备、启动哪个APP”的一组描述信息。常用参数各有分工我给个对照说明参数含义示例值platformName平台类型AndroidplatformVersion手机系统版本13deviceName设备名称真机是序列号emulator-5554或设备adb序列号appPackageAPP的包名com.example.demoappActivity启动的Activitycom.example.demo.MainActivityautomationName自动化驱动名称UiAutomator2noReset是否不重置应用数据trueunicodeKeyboard是否支持中文输入trueappAPP安装包路径不装已安装的包可以直接给路径/path/to/app.apk这里新手最容易卡住的点是appPackage和appActivity不知道去哪查。如果手机或模拟器上已经装了目标APP你可以在命令行执行adb shell dumpsys window | grep mCurrentFocus然后在手机上打开目标APP再执行一次上面命令就能看到当前焦点窗口的包名和Activity名这个信息再填进Desired Capabilities。注意appPackage不小心填错Appium会报“Activity used to start app doesnt exist or cannot be launched”这类问题头几次遇到会怀疑人生实际就是包名不匹配。3.2 最小可跑的Appium脚本下面这个脚本是我日常写APP自动化的起点模板每一行都有明确作用。你先把它跑通再逐步加内容会比一上来就写几十行强太多。import time from appium import webdriver from appium.webdriver.common.appiumby import AppiumBy desired_caps { platformName: Android, platformVersion: 13, deviceName: emulator-5554, appPackage: com.example.demo, appActivity: .MainActivity, automationName: UiAutomator2, noReset: True, unicodeKeyboard: True, resetKeyboard: True } driver webdriver.Remote(http://localhost:4723, desired_caps) time.sleep(3) print(driver.page_source) driver.quit()这个脚本的作用很朴素启动Appium Server指定的APP然后打印当前页面的DOM树结构。为什么第一步要打印page_source因为你在不知道页面长什么样的情况下根本没法继续写定位逻辑page_source能直接告诉你当前页面有哪些控件、资源id是什么、文本内容是什么比肉眼盯着模拟器猜要靠谱得多。webdriver.Remote的url指向Appium Server的地址这个地址要确保端口和启动Appium时一致否则会一直卡在连接阶段直到超时。3.3 从启动APP到完成一次点击输入跑通上面的脚本之后接下来的目标就很明确启动APP输入文字点击按钮断言结果。下面这段就是完整的操作示例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: 13, deviceName: emulator-5554, appPackage: com.example.demo, appActivity: .MainActivity, automationName: UiAutomator2, noReset: True, unicodeKeyboard: True, resetKeyboard: True } driver webdriver.Remote(http://localhost:4723, desired_caps) # 等待输入框出现并输入内容 input_box WebDriverWait(driver, 10).until( EC.presence_of_element_located((AppiumBy.ID, com.example.demo:id/editText)) ) input_box.send_keys(自动化测试入门) # 点击搜索按钮 search_btn driver.find_element(AppiumBy.ID, com.example.demo:id/search_button) search_btn.click() # 等待搜索结果出现 result WebDriverWait(driver, 10).until( EC.presence_of_element_located((AppiumBy.ID, com.example.demo:id/search_result)) ) assert 自动化测试入门 in result.text这段脚本里最值得学习的是WebDriverWait的用法。为什么不用sleep因为sleep是固定等待写3秒就是3秒页面2秒加载完你会白等1秒页面5秒加载完你又会报错效率和稳定性都差。WebDriverWait是条件等待最多等10秒只要元素一出现就立刻继续执行这才是工业级脚本该有的写法。还有一个细节中文输入。很多用户真机测试时中文输不进去是因为Appium默认的输入方式不走系统输入法你在capabilities里加上unicodeKeyboard: True和resetKeyboard: True脚本会自动切到Appium自带的输入法完成文字输入用例结束后再切回系统输入法。4. 元素定位与实际操作细节4.1 定位工具该怎么看写APP自动化脚本最耗时间的一件事就是定位元素。你肉眼能看到的按钮在代码里到底叫什么名字需要靠工具去查看。现在最常用的方式是Appium Inspector连上设备后它会展示当前APP页面的控件树鼠标点一下某个控件右侧就会显示它的resource-id、class、content-desc、text、bounds等属性。如果不用Appium Inspector也可以直接用UI Automator Viewer它在Android SDK的tools目录下连接设备后点击Device Screenshot就能拿到当前页面的UI层级。这种方式轻量但操作原子化程度低看单个元素时不如Appium Inspector方便。定位之前先明白一个底层逻辑Appium在Android端是通过UiAutomator2去解析页面控件树的所谓“定位”本质上是在这棵控件树上通过某些属性找到唯一节点。因此元素属性的稳定性直接决定了脚本的稳定性这一点App端和Web端几乎没有区别。4.2 优先用什么定位方式为什么以我个人的习惯APP元素定位优先级是这样的首先看resource-id。这个属性在Android原生控件里通常带有包名前缀稳定性极高样式调整不影响它。比如com.example.demo:id/btn_login这种肯定是首选。其次看content-desc。它的定位语义接近Web里的alt属性对于某些只有图片没有文字的控件非常关键。然后再考虑xpath。xpath灵活但性能相对差页面结构一变就容易挂。如果要用尽量利用resource-id或text来限定尽量避免//*[class\android.widget.TextView\]这种裸路径。最后才考虑用text文本定位。对于列表项、选项文字这类元素text定位确实直观但中文文案一旦改了脚本就得跟着维护。我还经常在真机上遇到一种情况UI层级里有多个控件拥有相同的resource-id但父容器不同这时用find_elements返回列表按下标选择比如driver.find_elements(AppiumBy.ID, \xxx\)[0]判断哪个是你要的。定位方式优点缺点建议使用场景resource-id稳定、直观、性能好部分自定义控件没有id原生控件首选content-desc适合无文字图标前端开发不一定填图标按钮xpath灵活多变慢、脆无id无desc时的兜底text符合肉眼习惯文案修改易挂列表项、设置项class简单同类型元素太多无法区分极少用4.3 等待策略三层递进别再乱用sleepElement定位不到很多新手第一时间怀疑是选择器写错了其实有一半的case是页面还没加载到那个元素你就去取了。等待策略是最容易忽略却又影响最大的环节。第一层是隐式等待。driver.implicitly_wait(10)设定之后Appium在每次查找元素时会自动轮询一段时间。它的缺点是全局生效一旦设了所有找元素操作都会等待至少到元素出现某些你希望立即失败的断言也会被拖慢。第二层是显式等待。WebDriverWait配合expected_conditions一起用效果是只为某个特定元素等待条件满足就立即执行下一步。这是我最推荐的主流方式精准且高效。第三层才是强制等待。time.sleep(3)只有在极特殊场景才用比如启动APP后某种动画必须播完才能点或者某个弹窗需要固定时间才出现。在主要流程里滥用sleep脚本运行时间会被拉长几倍。我在上面的示例脚本里用的就是显式等待。判断标准如果是“元素出现在页面上我才能操作”用presence_of_element_located如果要“元素可见且可点击”用element_to_be_clickable如果是等某个文字消失用invisibility_of_element_located。想明白这个你就不会去猜该等多久了。5. 进阶玩法弹窗、WebView、坐标点击、断言报告5.1 权限弹窗与APP内弹窗的处理套路真机或模拟器上跑自动化最烦的就是一堆系统弹窗。第一次启动APP时弹“允许摄像头”“允许获取位置”这些系统弹窗的控件不在APP的DOM树里而在系统UI层级里直接在元素树上找经常会扑空。处理方式有两种。一种是一劳永逸型用adb命令提前授予权限比如adb shell pm grant com.example.demo android.permission.CAMERA这样APP启动时就不会弹权限框。另一种是流程处理型在定位APP元素之前先判断当前界面有没有系统弹窗如果有就通过driver.find_element(AppiumBy.ID, \android:id/button1\)这类系统级资源id去点击确定。APP内部的弹窗比如新手引导、活动弹窗核心思路是记录下弹窗关闭按钮的定位方式在启动流程里先执行“如果弹窗存在就关掉”的逻辑然后再进入核心业务操作。我自己写框架时会把每个APP的弹窗关闭逻辑封装成一个公共方法比如close_popup_if_exists()这样每个用例启动时先调用一次自动化脚本的健壮性会有质的提升。5.2 WebView混合页面怎么操作现在的APP大量嵌套了H5页面登录页、活动页、商品详情页经常是WebView渲染的。如果脚本在WebView页面上用原生定位方式find_element会直接报找不到元素。原因很简单WebView内的元素不在原生控件树里而在浏览器内核的DOM树里。解决方法分两步。第一步切换上下文先driver.contexts拿到当前所有上下文会看到类似NATIVE_APP和WEBVIEW_com.example.demo这样的值然后driver.switch_to.context(\WEBVIEW_com.example.demo\)切换过去。第二步之后的操作就可以像Selenium一样用css selector、xpath这些Web定位方式去找元素了。这个环节有个高频坑WebView调试功能没开。Android系统里WebView需要开启调试模式App端代码里要执行WebView.setWebContentsDebuggingEnabled(true)否则Appium连接不上WebView。如果你的APK是别人打好的测试包得先和开发确认一下这个开关是否已经打开否则你会在switch context这一步卡很久。5.3 坐标点击能不用就尽量不用的兜底方案有些页面是Canvas绘制的或者游戏类APP整屏就是一个大控件所有UI都不是原生节点资源id、xpath全失效。这种时候只能坐标点击。Appium里用driver.tap([(x, y)], durationNone)实现坐标点击x和y是屏幕像素坐标值。这里有个硬伤不同机型分辨率不一样硬编码的坐标换台设备就废了。所以更工程化的做法是动态计算比例比如先driver.get_window_size()拿到宽度和高度再用宽高乘以百分比来得到坐标比如x width * 0.5, y height * 0.8。我自己的原则是先和开发沟通有没有办法暴露可访问性属性实在不行才用坐标方案。坐标点击是最后选择它能跑通但维护成本很高而且必须在脚本里写清楚坐标的来源和适用设备范围不然三个月后你自己都看不懂当初这个坐标是怎么算出来的。5.4 pytest Allure让脚本变成一套规范化的测试资产单个脚本能跑通只是开始你还需要一套用例管理框架来组织这些脚本。pytest是Python自动化测试事实标准通过命名规则test_*.py自动发现用例通过assert断言结果通过fixture实现初始化和清理。一个标准的APP自动化测试用例结构可以长这样import pytest from appium import webdriver pytest.fixture(scopeclass) def driver(): desired_caps { platformName: Android, platformVersion: 13, deviceName: emulator-5554, appPackage: com.example.demo, appActivity: .MainActivity, automationName: UiAutomator2, noReset: True, unicodeKeyboard: True, resetKeyboard: True } driver webdriver.Remote(http://localhost:4723, desired_caps) yield driver driver.quit() class TestLogin: def test_login_success(self, driver): username driver.find_element(AppiumBy.ID, com.example.demo:id/username) username.send_keys(test_user) password driver.find_element(AppiumBy.ID, com.example.demo:id/password) password.send_keys(123456) login_btn driver.find_element(AppiumBy.ID, com.example.demo:id/login_button) login_btn.click() assert driver.find_element(AppiumBy.ID, com.example.demo:id/home_page).is_displayed()fixture里建driver是一次性的类别里多条用例能复用。配合Allure报告你可以把每个用例的截图、日志、网络请求都挂到报告里测试失败时一张截图比什么日志都直观。命令大概是pytest test_case.py --alluredir./allure-results然后allure serve ./allure-results起一个本地web服务看报告。这套流程跑起来后你的测试就不再是一堆零散脚本而是一份能发给团队其他人看的规范化测试资产。6. 常见问题与排查技巧实录学APP自动化测试最大的障碍往往不是学不会而是遇到了问题不知道从哪查起。这一节我把带团队时最常碰到的几个问题和排查思路整理成一张速查表希望帮你少走我当年走过的弯路。问题典型原因排查与解决思路session创建失败appPackage/appActivity写错、设备未识别、驱动未装先adb devices确认设备状态再确认包名和Activity准确最后检查appium driver list是否包含uiautomator2定位不到元素等待不够、控件在WebView、弹窗遮挡、动态id打印page_source看当前界面检查上下文是否需要切换用显式等待替代sleep动态id用xpath的相对路径中文输入不进去未启用unicodeKeyboardcapabilities加unicodeKeyboard和resetKeyboard两个参数adb devices看不到设备驱动未装、手机未授权、USB模式不对换数据线、重新开启USB调试、手机上允许USB调试授权必要时重启adb服务Appium Server连接超时端口被占用、Server没启动换端口启动Server脚本里的url同步改检查终端里的Server日志页面加载慢导致脚本失败网络慢、图片资源多把固定sleep换成WebDriverWait给核心元素设置合理的超时时间WebView上的元素找不到没有切换上下文、WebView调试未开启driver.contexts查看上下文确认开发开启WebView调试开关6.1 学会看日志所有问题排查的第一步很多新手一报错就慌了盯着终端最后一行反复看然后到处问人。我的建议很直接先往上翻日志看你报错之前Server和Appium做了什么。Session创建失败日志里会写Failed to create session后面通常会跟着具体原因比如Activity used to start app doesnt exist、Could not find a connected Android device这类明确信息。定位不到元素时日志里没有一长串的NoSuchElementException也会保留你最后一次查找的定位策略。对照page_source看看是否存在这个属性基本能定位是哪一边出了问题。学会自己分析日志之后你会发现大多数问题根本不需要问别人日志已经把答案写得很清楚了。6.2 版本兼容问题Appium 2.x与老脚本的差别2024年之后再用Appium 1.x的教程配置环境会碰到一个隐蔽的大坑Desired Capabilities里的automationName如果写Old形式或者不写而驱动没装对Server会报各种奇怪的错。正确的做法是Appium 2.x下Android明确使用automationName: \UiAutomator2\并且提前安装uiautomator2驱动。另外旧的定位方式用driver.find_element_by_id这种写法在Appium Python Client的新版本里已经废弃统一改成driver.find_element(AppiumBy.ID, \xxx\)这种形式。用老代码跑新版Client直接会报AttributeError。如果你在GitHub上抄了代码跑不通大概率不是你的逻辑错了而是API版本变了去查一下官方文档更新一下写法就好。6.3 真机与模拟器我在实践中更推荐先真机调试模拟器启动速度快、环境可控适合刚开始学的时候跑流程。但做实际项目尤其是企业级的APP自动化我建议优先用真机。原因有两点一是模拟器和真机的UI渲染、启动速度有明显差异模拟器上能跑通的等待策略换到真机可能超时二是很多APP在模拟器上压根不显示某些页面模块比如扫码、蓝牙、通讯录相关功能必须真机才能触发。真机调试前记得先打开开发者选项和USB调试连接后手机会弹出是否允许USB调试的授权框一定要点允许否则adb devices里一直显示unauthorized很多人卡在这一步浑然不知。6.4 把自动化用例跑起来只是开始写到这里我相信你可以照着上面的内容从零搭好环境、写通第一个脚本、定位元素、处理常见问题最终跑出一条完整的UI自动化用例了。但从一条用例到一套稳定的测试体系中间还有一段路要走比如把用例接入持续集成、失败自动截图、日志收集、多设备并发。这些都是在日常工作里随着项目需要逐步沉淀的。我个人的建议是先不要贪多求快把手头一条重复度最高的手工用例用自动化跑通让它稳定跑一周再逐步扩展。自动化测试的核心价值从来不是“写了多少条脚本”而是“帮你节省了多少回归时间、拦截了多少线上问题”。跑得稳的用例比跑得快的用例值钱得多。