
1. 项目概述为什么Appium依然是Android自动化测试的首选如果你是一名Android开发者或者测试工程师最近被“自动化测试”这个词刷屏了尤其是看到各种关于“AI自动化测试”、“Kiro自动化测试”的讨论可能会觉得传统的Appium是不是已经过时了。我干了快十年的移动端测试从最早的MonkeyRunner到后来的UIAutomator再到Appium可以很负责任地告诉你至少在可预见的未来Appium依然是Android端UI自动化测试的基石和首选工具。那些所谓的“AI自动化测试”其底层很多还是依赖Appium这样的框架来驱动设备、定位元素。简单来说Appium是一个开源的、跨平台的移动端自动化测试框架。它的核心魅力在于“一次编写到处运行”——你可以用同一套脚本比如用Python或Java写的去测试Android和iOS应用。它基于WebDriver协议这意味着如果你熟悉Selenium做Web自动化那么上手Appium会非常快。这次我们不谈空洞的理论直接通过一个完整的实战案例手把手带你从零搭建环境到写出第一个能稳定运行的自动化测试脚本并分享我这些年踩过的坑和积累的实战技巧。这个案例的目标很明确我们假设要测试一个Android计算器应用的核心运算功能。通过这个看似简单的例子你会掌握Appium环境搭建、元素定位、脚本编写、异常处理等一整套流程。无论你是刚入门的新手还是想系统梳理Appium知识的中级工程师这篇内容都能给你带来直接的、可复现的参考价值。2. 环境搭建与核心工具链解析环境搭建是劝退很多新手的第一个门槛。网上教程五花八门版本兼容性问题层出不穷。这里我结合最新的实践2024年给你梳理一条最清晰、最稳定的路径。我们主要使用Python语言因为它语法简洁生态丰富是自动化测试领域的主流选择。2.1 核心组件安装与版本协同Appium 2.x 的架构和1.x有较大不同它采用了插件化设计更轻量但也意味着安装步骤稍有变化。别被吓到跟着步骤走就行。第一步安装Node.js与npmAppium本身是一个Node.js应用所以首先需要安装Node.js环境。建议安装最新的LTS长期支持版本比如当前的Node.js 18.x或20.x。去Node.js官网下载安装包安装完成后在命令行输入node -v和npm -v检查是否安装成功。第二步安装Appium 2.x打开命令行Windows用CMD或PowerShellMac/Linux用Terminal执行以下命令进行全局安装npm install -g appiumnext安装完成后输入appium -v查看版本确认安装的是2.x版本。第三步安装Appium驱动Driver这是Appium 2.x最大的变化。Appium核心不再内置驱动你需要按需安装。对于Android我们必须安装uiautomator2驱动它是目前最稳定、功能最全的Android驱动。appium driver install uiautomator2安装时可能会提示你安装相关的辅助工具比如appium-uiautomator2-driver同意即可。第四步安装Python客户端库我们需要用Python来编写测试脚本因此要安装Appium的Python客户端库。注意Appium 2.x要求客户端的版本在2.0以上。同时由于它基于WebDriver协议我们还需要安装Selenium。pip install Appium-Python-Client2.0 pip install selenium4.0强烈建议使用虚拟环境如venv或conda来管理Python包避免项目间的依赖冲突。第五步配置Android开发环境SDK这是Android自动化测试的“地基”没有它一切无从谈起。下载Android Studio从官网下载并安装。安装过程中在“选择组件”页面务必勾选Android SDK和Android SDK Command-line Tools。配置环境变量安装完成后需要配置几个关键的环境变量ANDROID_HOME指向你的Android SDK安装路径。例如C:\Users\你的用户名\AppData\Local\Android\Sdk(Windows) 或/Users/你的用户名/Library/Android/sdk(Mac)。将SDK的platform-tools和tools(或cmdline-tools/latest/bin) 目录添加到系统的PATH变量中。platform-tools里包含至关重要的adb(Android Debug Bridge) 工具。验证安装打开新的命令行窗口输入adb version。如果能显示版本号说明SDK环境基本配置正确。注意很多教程会让人单独下载SDK但现在通过Android Studio安装和管理SDK是最省心、最不容易出错的方式。Android Studio本身也是强大的开发和调试工具一举两得。2.2 设备连接与基础检查环境搭好了我们得让脚本能找到我们的手机。连接真机用USB数据线连接手机和电脑。在手机上开启“开发者选项”通常是在“关于手机”里连续点击“版本号”7次。在开发者选项中开启USB调试和USB安装如果有的话。在电脑命令行输入adb devices。如果连接成功你会看到设备序列号后面跟着device字样。如果显示unauthorized需要在手机上弹出的授权对话框中点击“允许”。使用模拟器 如果你没有真机Android Studio自带的模拟器AVD是绝佳的替代品。在Android Studio中创建和启动一个模拟器然后同样使用adb devices命令查看模拟器会以一个类似emulator-5554的序列号出现。获取应用信息 我们需要知道被测应用的包名package和启动Activity名才能让Appium启动它。有两种方法如果你有APK文件使用aapt工具在SDK的build-tools目录下。命令较复杂更推荐第二种。通过已安装的应用在手机上打开被测应用比如计算器然后在命令行输入adb shell dumpsys window | findstr mCurrentFocusWindows用findstrMac/Linux用grep 输出结果类似mCurrentFocusWindow{... com.android.calculator2/com.android.calculator2.Calculator}。这里com.android.calculator2就是包名com.android.calculator2.Calculator就是启动Activity。至此我们的“武器库”就准备齐全了。接下来进入最核心的环节编写自动化脚本。3. 实战案例编写第一个Appium自动化测试脚本我们以测试Android原生计算器包名通常为com.android.calculator2为例实现一个简单的测试场景验证加法运算6 9 15是否正确。3.1 脚本骨架与Desired Capabilities配置首先创建一个Python文件比如test_calculator.py。所有Appium脚本的开始都是配置Desired Capabilities期望能力。这组键值对告诉Appium Server你要如何启动会话测试什么设备、什么应用、如何测试。from appium import webdriver from appium.options.android import UiAutomator2Options import time # 1. 定义Desired Capabilities capabilities { “platformName”: “Android”, # 平台固定为Android “platformVersion”: “13”, # 你的设备Android版本在手机设置中查看 “deviceName”: “your_device_or_emulator_name”, # 设备名adb devices查到的名称或自定义 “automationName”: “UiAutomator2”, # 自动化引擎必须和安装的驱动一致 “appPackage”: “com.android.calculator2”, # 被测App的包名 “appActivity”: “com.android.calculator2.Calculator”, # 被测App的启动Activity “noReset”: True, # 是否在会话开始前重置应用状态如不清除数据 “newCommandTimeout”: 600, # 新命令超时时间秒 } # 将字典转换为Appium 2.x推荐的Options对象 options UiAutomator2Options().load_capabilities(capabilities) # 2. 连接Appium Server # Appium Server默认运行在本地localhost的4723端口 driver webdriver.Remote(‘http://localhost:4723’, optionsoptions) # 等待App完全启动 time.sleep(2) # 3. 这里是我们的测试步骤接下来会填充 # ... # 4. 测试结束后关闭会话 driver.quit()关键配置解析platformVersion务必填写准确否则可能无法创建会话。deviceName对于真机可以填写adb devices列出的设备ID或者一个自定义的字符串如MyPhone。对于模拟器通常用Android Emulator也可以。automationName:UiAutomator2是必须的这是我们之前安装的驱动。noReset:True表示Appium不会在测试前清除应用数据适合连续测试。如果设为False每次测试都会像一个全新安装的应用。newCommandTimeout: 设置长一点比如600秒防止脚本因等待元素时间过长而被Server误认为超时断开。实操心得在启动Appium Server时我强烈建议使用命令行并打开日志。在终端输入appium --allow-cors启动。--allow-cors参数可以解决一些潜在的跨域问题。通过观察终端输出的日志当脚本执行时你可以看到详细的请求和响应信息这对于调试定位失败原因至关重要。3.2 元素定位自动化测试的“眼睛”脚本要操作界面上的按钮数字6、9加号等号首先必须找到它们。Appium提供了多种定位方式最常用的是通过“资源ID”resource-id和“文本”text。如何查看元素信息我们需要用到Appium Inspector。它是Appium官方提供的图形化元素查看工具相当于Selenium的浏览器开发者工具。启动Appium Server如上所述在终端运行appium。启动Appium Inspector从Appium官网下载桌面版Inspector。打开后其界面也需要你填写一组Desired Capabilities内容要和你的脚本基本一致特别是appPackage和appActivity。此外需要额外指定appium:serverAddress为localhostappium:serverPort为4723。连接并查看在Inspector中点击“Start Session”它会通过Appium Server启动你手机上的计算器应用并显示当前界面的UI层级树。点击屏幕上的元素右侧就会显示该元素的所有属性如resource-id,text,class,content-desc等。假设我们通过Inspector发现数字按钮6的resource-id是com.android.calculator2:id/digit_6加号按钮的resource-id是com.android.calculator2:id/op_add等号按钮的resource-id是com.android.calculator2:id/eq结果显示框的resource-id是com.android.calculator2:id/result那么我们的定位代码就可以这样写# 定位元素 digit_6 driver.find_element(byAppiumBy.ID, value“com.android.calculator2:id/digit_6”) op_add driver.find_element(byAppiumBy.ID, value“com.android.calculator2:id/op_add”) digit_9 driver.find_element(byAppiumBy.ID, value“com.android.calculator2:id/digit_9”) eq driver.find_element(byAppiumBy.ID, value“com.android.calculator2:id/eq”) result driver.find_element(byAppiumBy.ID, value“com.android.calculator2:id/result”) # 执行点击操作模拟计算 6 9 digit_6.click() op_add.click() digit_9.click() eq.click() # 获取结果并断言 actual_result result.text expected_result “15” assert actual_result expected_result, f“加法结果错误预期{expected_result}实际得到{actual_result}” print(“测试通过6 9 15”)元素定位策略优先级ID (resource-id)首选。通常由开发人员设置唯一性最强定位最稳定、最快。Accessibility ID (content-desc)次选。为无障碍功能设计也具备较好的唯一性。XPath强大但应慎用。当元素没有ID和Accessibility ID时使用。缺点是性能相对较差且对UI布局变化非常敏感容易导致脚本失效。尽量使用相对路径和非索引的表达式。Class Name / Text通用性定位但容易重复。例如所有数字按钮的class可能都是android.widget.Button用text定位“6”也可能在其他地方出现。避坑技巧很多新手会卡在元素定位上脚本总是报NoSuchElementException。除了检查定位表达式99%的问题出在时机上。UI元素可能还没加载出来你的脚本就去查找了。解决方案是使用“显式等待”。Appium Python客户端完美支持WebDriverWait。上面的代码可以优化为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, 10) # 最多等待10秒 digit_6 wait.until(EC.presence_of_element_located((AppiumBy.ID, “com.android.calculator2:id/digit_6”)))这行代码会每隔一段时间检查元素是否存在直到找到或超时。这比粗暴的time.sleep()更高效、更可靠。4. 脚本增强等待机制、异常处理与PageObject模式一个健壮的自动化脚本绝不能只有“理想路径”的代码。我们需要考虑网络延迟、页面加载慢、弹窗干扰、断言失败等各种情况。4.1 显式等待与智能等待策略time.sleep(seconds)是“硬等待”无论元素是否出现都强制暂停这是最低效的方式会让测试时间无谓延长。我们应该使用显式等待和隐式等待的组合。隐式等待driver.implicitly_wait(10)。这为整个WebDriver会话设置了一个全局的等待时间。当查找任何元素时如果元素没有立即出现WebDriver会轮询查找直到超时。它只对find_element方法有效。显式等待如上例所示针对某个特定条件如元素可点击、元素存在、文本出现等进行等待。更灵活更精确。最佳实践是设置一个较短的全局隐式等待如5秒然后在关键操作前使用显式等待。driver.implicitly_wait(5) # 全局隐式等待5秒 # 对于关键按钮使用显式等待确保其可点击 from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC wait WebDriverWait(driver, 10) calculate_button wait.until(EC.element_to_be_clickable((By.ID, “some_button_id”))) calculate_button.click()4.2 异常处理与截图记录测试难免会失败。失败时如果只有一个模糊的错误信息排查起来会很痛苦。最好的习惯是在断言失败或发生未捕获异常时自动截取当前屏幕截图。import os from datetime import datetime def take_screenshot(driver, test_name): “”“失败时截图”“” timestamp datetime.now().strftime(“%Y%m%d_%H%M%S”) screenshot_dir “./screenshots” if not os.path.exists(screenshot_dir): os.makedirs(screenshot_dir) filename f“{screenshot_dir}/{test_name}_FAIL_{timestamp}.png” driver.save_screenshot(filename) print(f“截图已保存至{filename}”) return filename # 在测试用例中使用 try: # ... 执行测试步骤 ... assert actual expected, “结果不匹配” except AssertionError as e: take_screenshot(driver, “test_addition”) raise e # 重新抛出异常让测试框架知道测试失败了 except Exception as e: take_screenshot(driver, “test_addition_unexpected”) raise e4.3 引入PageObject设计模式当测试用例越来越多如果所有元素定位和操作都散落在各个测试脚本里维护将是一场灾难。PageObjectPO模式是UI自动化测试中最重要的设计模式没有之一。它的核心思想是将一个页面或一个功能模块抽象成一个类页面的元素定位是类的属性页面的操作点击、输入、获取文本是类的方法。计算器页面的PageObject示例# pages/calculator_page.py from appium.webdriver.common.appiumby import AppiumBy from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC class CalculatorPage: def __init__(self, driver): self.driver driver self.wait WebDriverWait(driver, 10) # 元素定位器 (Locators) DIGIT_6 (AppiumBy.ID, “com.android.calculator2:id/digit_6”) DIGIT_9 (AppiumBy.ID, “com.android.calculator2:id/digit_9”) OP_ADD (AppiumBy.ID, “com.android.calculator2:id/op_add”) OP_EQUAL (AppiumBy.ID, “com.android.calculator2:id/eq”) RESULT (AppiumBy.ID, “com.android.calculator2:id/result”) # 页面操作方法 def click_digit_6(self): element self.wait.until(EC.element_to_be_clickable(self.DIGIT_6)) element.click() return self # 支持链式调用 def click_add(self): self.wait.until(EC.element_to_be_clickable(self.OP_ADD)).click() return self def click_equal(self): self.wait.until(EC.element_to_be_clickable(self.OP_EQUAL)).click() return self def get_result(self): result_element self.wait.until(EC.presence_of_element_located(self.RESULT)) return result_element.text # 一个完整的业务流方法 def perform_addition(self, a_locator, b_locator): “”“执行任意两个数字的加法”“” self.wait.until(EC.element_to_be_clickable(a_locator)).click() self.click_add() self.wait.until(EC.element_to_be_clickable(b_locator)).click() self.click_equal() return self.get_result()测试脚本使用PageObject# test_calculator.py import pytest from appium import webdriver from pages.calculator_page import CalculatorPage class TestCalculator: pytest.fixture(scope“class”) def driver(self): # ... 初始化driver的代码 ... yield driver driver.quit() def test_basic_addition(self, driver): calc_page CalculatorPage(driver) # 链式调用清晰表达操作流 calc_page.click_digit_6().click_add().click_digit_9().click_equal() assert calc_page.get_result() “15” def test_using_business_flow(self, driver): calc_page CalculatorPage(driver) result calc_page.perform_addition(calc_page.DIGIT_6, calc_page.DIGIT_9) assert result “15”使用PO模式后测试脚本变得非常简洁只关心业务逻辑做什么。而具体的“怎么做”元素定位、等待、点击被封装在PageObject类中。当UI发生变化时比如按钮ID改了你只需要去修改PageObject类中的一个常量所有用到这个按钮的测试用例都自动修复维护成本大大降低。5. 常见问题排查与性能优化实战录即使按照最佳实践编写脚本在实际运行中还是会遇到各种“坑”。这里我记录了几个最高频的问题和我的解决思路。5.1 会话创建失败Unable to create a new remote session这是最令人头疼的报错之一信息通常很模糊。检查Appium Server日志这是最重要的线索。看错误日志中是否有更具体的描述比如“无法安装apk”、“设备未找到”、“需要的Capability缺失”等。核对Capabilities确保platformVersion,deviceName,appPackage,appActivity完全正确。deviceName不一定要精确匹配但platformVersion必须准确。检查设备连接再次运行adb devices确认设备在线且状态是device而不是offline或unauthorized。尝试adb kill-server然后adb start-server重启adb服务。端口冲突确认4723端口没有被其他程序占用。可以尝试换一个端口启动Appiumappium -p 4724并在脚本中修改连接地址。驱动问题确认已正确安装uiautomator2驱动 (appium driver list查看)。有时需要更新驱动appium driver update uiautomator2。5.2 元素找不到NoSuchElementException时机问题这是最常见原因。务必用显式等待替代time.sleep()。上下文Context问题混合应用Hybrid App或WebView中需要切换上下文。使用driver.contexts获取所有上下文列表然后driver.switch_to.context(‘WEBVIEW_com.example’)切换到WebView上下文才能定位网页元素。定位表达式错误用Appium Inspector重新检查元素属性。注意原生应用和H5元素的定位方式可能不同。XPath表达式是否写得太绝对容易因UI微调而失效页面有多个相同元素find_element只返回第一个。如果需要操作第二个可以使用find_elements获取列表然后按索引选择或者使用更精确的XPath。键盘或弹窗遮挡操作输入框后键盘可能弹出遮挡了按钮。尝试在点击前先隐藏键盘driver.hide_keyboard()。5.3 脚本运行缓慢减少不必要的等待用显式等待替代固定的time.sleep。优化定位策略ID定位最快XPath最慢且不稳定。尽量避免使用包含//*或很长路径的XPath。批量操作对于连续的同类型操作如滑动列表查找某个项目可以考虑在底层使用adb shell命令或mobile:命令如mobile: scroll来替代多次UI交互速度会快很多但牺牲了一些可读性。关闭动画在手机开发者选项里将窗口动画缩放、过渡动画缩放、动画程序时长缩放全部设置为“关闭动画”。这能显著加快UI响应的感知速度。5.4 如何应对动态元素和Flaky Tests不稳定的测试Flaky Test是指有时成功有时失败的测试是自动化测试的顽疾。使用更稳定的定位器优先选择resource-id和content-desc它们通常由开发控制相对稳定。避免使用可能变化的文本或索引位置。重试机制对于非核心的、偶发性的失败如网络波动可以在测试框架层如pytest添加重试插件pytest-rerunfailures。独立测试用例确保每个测试用例都是独立的不依赖前一个用例的状态。在setUp方法中做好环境准备如重启App、进入首页在tearDown中做好清理。截图和日志如前所述失败时自动截图并保存详细的Appium Server日志和客户端日志为事后分析提供完整上下文。6. 超越基础高级特性与持续集成初探掌握了上面的内容你已经可以应对大部分常规的Android UI自动化测试需求了。但Appium的能力远不止于此。6.1 处理特殊控件与手势操作滚动与滑动使用driver.swipe(start_x, start_y, end_x, end_y, duration)或更推荐的W3C Actions API。from appium.webdriver.common.touch_action import TouchAction actions TouchAction(driver) actions.press(x500, y1500).wait(200).move_to(x500, y500).release().perform()长按TouchAction(driver).long_press(element).release().perform()多点触控使用MultiAction。处理Toast消息Toast是Android系统的短暂提示无法用普通UI定位。可以通过监听系统日志logcat来捕获。# 在点击操作后尝试获取logcat中最近的Toast消息 logcat_output driver.execute_script(‘mobile: getLogs’, {‘type’: ‘logcat’}) # 然后从输出中过滤包含‘Toast’或你应用包名的行文件上传Appium支持将文件从电脑推送到设备。driver.push_file(‘/sdcard/Download/test.png’, source_path‘/local/path/to/file.png’)。然后可以在应用内选择该文件。6.2 集成到持续集成CI流水线自动化测试只有集成到CI/CD流程中才能最大化其价值。核心思路是让CI服务器如Jenkins, GitLab CI, GitHub Actions能够执行你的Appium测试脚本。这需要解决几个问题环境准备CI机器上需要安装好所有依赖JDK, Node.js, Android SDK, Appium, Python环境及包。设备/模拟器使用云测平台最省事。如国内的WeTest、国外的BrowserStack、Sauce Labs它们提供了海量真机/模拟器直接通过远程URL连接即可。只需在Capabilities中指定对应的云平台配置。自建模拟器集群在CI服务器上通过Docker创建并管理Android模拟器镜像。技术复杂度高但可控性强成本低。脚本执行与报告使用pytest等测试框架组织用例并生成美观的测试报告如pytest-html,Allure。CI任务配置为安装依赖 - 启动Appium Server - 启动模拟器 - 运行测试 - 生成报告 - 归档报告和截图。一个简单的GitHub Actions工作流示例概念name: Android UI Tests on: [push] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkoutv2 - name: Set up JDK uses: actions/setup-javav2 with: { java-version: ‘11’ } - name: Set up Node.js uses: actions/setup-nodev2 with: { node-version: ‘18’ } - name: Install Appium run: npm install -g appiumnext - name: Install uiautomator2 driver run: appium driver install uiautomator2 - name: Set up Android SDK uses: android-actions/setup-androidv2 - name: Start Appium Server run: appium --log-level error - name: Run tests run: pytest test_suite.py --htmlreport.html - name: Upload test report uses: actions/upload-artifactv2 with: { name: test-report, path: report.html }这条路走通后每次代码提交都能自动进行回归测试快速发现版本迭代引入的Bug这才是自动化测试生产力的终极体现。从环境搭建的琐碎到元素定位的精准再到设计模式的优雅最后到工程化的集成Android Appium自动化测试是一个系统工程。它考验的不仅是编码能力更是对移动端生态、测试理念和工程实践的理解。我个人的体会是不要追求一开始就写出完美、庞大的测试套件。从一个核心场景比如我们今天的计算器加法开始把路径走通然后逐步扩展测试范围引入PageObject加入异常处理最后再考虑CI集成。每一步都稳扎稳打你的自动化测试体系就会像滚雪球一样越来越强大真正成为保障产品质量的可靠防线。最后再分享一个小技巧定期比如每两周回顾一下失败的测试用例和截图分析是脚本不稳定、环境问题还是真实的Bug这个过程能帮你持续优化测试策略和脚本健壮性。