ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Appium+Python Android自动化测试实战:从环境搭建到脚本优化

Appium+Python Android自动化测试实战:从环境搭建到脚本优化 1. 项目概述为什么选择AppiumPython做Android自动化如果你是一名移动端测试工程师、或者是一名想提升效率的Android开发者那么“自动化测试”这个词对你来说一定不陌生。手动一遍遍点击、滑动、输入不仅枯燥耗时更难以保证每次操作的一致性。而今天要聊的这套组合——基于Android的AppiumPython自动化脚本可以说是目前移动端UI自动化领域最流行、最易上手的方案之一。我过去几年在多个项目中用它来跑回归测试、兼容性测试和数据构造实实在在地把团队从重复劳动中解放了出来。简单来说这个项目就是用Python语言通过Appium这个开源工具来编写能够自动操作Android手机App的脚本。它能模拟人的所有操作启动应用、点击按钮、输入文本、滑动列表、验证页面元素等等。你可能会问市面上工具这么多为什么偏偏是它核心原因就三个跨平台、支持真机/模拟器、对主流编程语言友好。Appium遵循WebDriver协议这意味着你写好的脚本稍作调整就能用来测试iOS应用。它不依赖待测应用的源码无论是原生、混合还是Web应用都能搞定。而Python以其简洁的语法和丰富的生态极大地降低了自动化脚本的编写门槛让测试人员能更专注于业务逻辑而非语言细节。接下来我会从一个完整的实战项目角度带你从零开始拆解环境搭建、核心原理、脚本编写到高级优化的全流程。无论你是刚入门的新手还是想系统梳理的老手都能找到可实操的干货。2. 环境搭建与配置避坑指南万事开头难自动化测试的第一步——环境搭建就足以劝退不少人。网上教程五花八门版本兼容问题层出不穷。这里我结合最近的实际踩坑经验给你梳理一条最清晰、最稳妥的路径。2.1 核心三件套Java、Android SDK、Node.jsAppium服务器是基于Node.js运行的而它要驱动Android设备又离不开Android SDK。所以这三者是地基必须装对、配好。Java JDK建议安装JDK 8或JDK 11LTS长期支持版。很多环境问题源于JDK版本过高或过低。安装后务必配置JAVA_HOME系统环境变量指向你的JDK安装目录例如C:\Program Files\Java\jdk1.8.0_301并将%JAVA_HOME%\bin添加到Path变量中。在命令行输入java -version和javac -version验证。Android SDK现在谷歌官方推荐通过Android Studio来管理SDK这是最省心的方式。下载安装Android Studio后打开其SDK Manager。关键点来了除了默认勾选的你必须确保安装以下内容SDK Platforms至少选择一个你目标测试设备的Android版本API如Android 12.0 (API 31)。SDK Tools必须勾选Android SDK Command-line Tools、Android SDK Platform-Tools和Android SDK Build-Tools。Platform-Tools里的adbAndroid调试桥是我们与设备通信的核心工具。 安装完成后同样需要配置环境变量ANDROID_HOME指向SDK根目录例如C:\Users\YourName\AppData\Local\Android\Sdk并将%ANDROID_HOME%\platform-tools和%ANDROID_HOME%\tools或%ANDROID_HOME%\cmdline-tools\latest\bin取决于版本添加到Path中。命令行输入adb version验证。Node.js与npm去Node.js官网下载LTS版本安装即可。安装后自带npmNode包管理器。在命令行输入node -v和npm -v验证。注意所有环境变量配置后务必关闭当前命令行窗口重新打开一个新的否则可能不生效。这是新手最常忽略的一点。2.2 Appium服务器的两种安装姿势Appium服务器是连接你的Python脚本和Android设备的桥梁。你有两种主流选择方案A通过npm安装推荐给喜欢折腾和需要最新特性的用户在命令行执行npm install -g appium。这会安装最新的Appium服务器。安装后你可以通过appium -v查看版本直接输入appium启动服务。它的优势是版本新但可能需要额外处理驱动问题。方案B安装Appium Desktop推荐给绝大多数初学者和追求稳定的用户这是带图形界面的官方客户端从GitHub Release页面下载即可。它内置了Appium服务器和元素检查器Inspector开箱即用尤其适合脚本调试阶段定位元素。启动后点击“Start Server”按钮即可。这里有个巨坑无论哪种方式你很可能在启动时遇到类似[Appium] No plugins have been installed. Use the appium plugin command to...的警告。这在新版本中很常见它只是一个警告不影响基础功能除非你需要用到特定的插件。如果你看着烦可以通过安装默认插件来消除appium plugin install --sourcenpm appium-plugins。但对于新手完全可以忽略它。2.3 Python端环境与驱动配置服务器好了接下来是客户端你的脚本环境。安装Python去官网下载3.7以上版本安装时务必勾选“Add Python to PATH”。用python --version验证。安装Appium客户端库Appium通过WebDriver协议通信Python端我们需要安装Appium-Python-Client库。在命令行执行pip install Appium-Python-Client。这个库封装了所有与Appium服务器交互的指令。安装UI自动化框架可选但推荐单纯用Appium-Python-Client可以写脚本但结构可能松散。我强烈推荐结合pytest测试框架来组织用例它提供了固件、参数化、断言等强大功能。安装pip install pytest。驱动配置关键点Appium需要知道如何与不同设备打交道这靠驱动。对于Android就是uiautomator2驱动旧版是Espresso。好消息是Appium Server 1.22.0之后默认使用了uiautomator2你通常无需手动安装。但如果遇到设备连接或元素找不到的问题可以尝试手动安装或更新驱动通过Appium Desktop的配置或在命令行执行appium driver install uiautomator2。3. 核心原理与Desired Capabilities详解环境配好我们先不急着写代码。理解Appium如何工作以及如何正确配置一次会话的起点能让你在后续脚本编写和问题排查时游刃有余。3.1 Appium架构与通信流程你可以把Appium想象成一个翻译官和指挥官。你的Python脚本客户端用WebDriver协议一种基于HTTP的RESTful API向Appium服务器发送指令比如“点击这个ID为login的按钮”。Appium服务器接收指令并将其“翻译”成待测设备操作系统这里是Android能够理解的底层命令。对于Android它通过adb与设备建立连接并调用uiautomator2这样的测试框架来执行UI操作。设备上的测试框架如uiautomator2接收到命令后真正在设备上执行点击、滑动等操作并将执行结果成功/失败、获取到的元素属性等层层返回给Appium服务器最终传回给你的Python脚本。这个过程中Desired Capabilities期望能力是启动整个流程的“钥匙”。它是一组键值对在脚本初始化驱动webdriver.Remote时发送给Appium服务器告诉服务器“嗨我这次想测试什么样的应用在什么设备上用什么配置”。3.2 Desired Capabilities必备参数解析下面是一个最基础的Android Capabilities配置示例我们逐行拆解from appium import webdriver desired_caps { # 1. 平台与系统相关 platformName: Android, # 必填指定移动操作系统平台 platformVersion: 12, # 选填但强烈建议填设备安卓版本避免兼容问题 deviceName: Android Emulator, # 在Android上可任意取名但必填。用于日志标识非真实设备名。 # 2. 待测应用相关 appPackage: com.example.myapp, # 必填待测App的包名 appActivity: .MainActivity, # 必填待测App启动时的主Activity # 3. 自动化引擎相关 automationName: UiAutomator2, # 必填指定自动化驱动。Android上首选UiAutomator2。 # 4. 其他常用配置 noReset: True, # 选填。True: 会话间不重置应用数据如登录状态。False: 每次重置。 unicodeKeyboard: True, # 选填。True: 启用Unicode输入可输入中文等。 resetKeyboard: True, # 选填。True: 自动化结束后重置键盘到原始状态。常与上一项搭配。 newCommandTimeout: 600, # 选填。服务器等待新命令的超时时间秒默认60。复杂操作可调大。 } # 初始化驱动连接Appium服务器 driver webdriver.Remote(http://localhost:4723/wd/hub, desired_caps)如何获取appPackage和appActivity这是新手的第一道坎。有几种方法问开发最准确。使用adb命令手机打开目标App命令行输入adb shell dumpsys window | findstr mCurrentFocusWindows或adb shell dumpsys window | grep mCurrentFocusMac/Linux。输出会包含类似com.example.myapp/.MainActivity的信息。使用Appium Inspector在Appium Desktop中启动Inspector配置好Capabilities并连接后可以直接看到当前页面的包名和Activity。deviceName的误区在Android中这个字段并不用于识别具体哪台设备那是udid的工作它更像一个描述符。你可以填My Phone或Android Emulator。真正连接设备靠的是adb devices列出的设备序列号Appium会自动使用列表中的第一个设备除非你用udid指定。实操心得建议将Desired Capabilities单独写在一个配置文件如config.py或字典中管理。针对不同环境测试机、模拟器、不同App准备多套配置方便切换。另外对于模拟器deviceName填Android Emulator没问题对于通过USB连接的真机platformVersion务必填对否则可能无法创建会话。4. 元素定位策略与等待机制实战脚本的核心是“找到元素操作元素”。元素定位的准确性和稳定性直接决定了自动化脚本的成败。4.1 八大元素定位器深度使用Appium继承了Selenium的定位方式并扩展了移动端特有的定位器。按优先级和稳定性我推荐以下顺序id(resource-id)首选中的首选。Android中对应元素的resource-id属性。如果开发规范这是唯一且稳定的。driver.find_element(AppiumBy.ID, com.example.myapp:id/btn_login)accessibility_id(content-desc)次选。对应元素的contentDescription属性专为无障碍服务设计通常也具有唯一性。driver.find_element(AppiumBy.ACCESSIBILITY_ID, 登录按钮)xpath功能强大但慎用。当以上两种都没有时使用。可以遍历层级但性能较差且对UI变化极其敏感。# 绝对路径脆弱不推荐 driver.find_element(AppiumBy.XPATH, /hierarchy/android.widget.FrameLayout/.../android.widget.Button) # 相对路径属性稍好 driver.find_element(AppiumBy.XPATH, //android.widget.Button[text登录]) # 更灵活的用法 driver.find_element(AppiumBy.XPATH, //*[contains(text, 部分文字)])class_name通常用于定位同类元素的集合如所有TextView。all_textviews driver.find_elements(AppiumBy.CLASS_NAME, android.widget.TextView)android_uiautomator(UiAutomator2专属)Android平台的利器。可以使用Android原生的UiAutomator API进行定位非常灵活强大。# 通过文本定位 driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR, new UiSelector().text(登录)) # 通过组合条件定位 driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR, new UiSelector().resourceId(com.example.myapp:id/btn_login).className(android.widget.Button)) # 滚动查找元素处理长列表神器 driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR, new UiScrollable(new UiSelector().scrollable(true)).scrollIntoView(new UiSelector().text(列表底部的元素)))定位策略黄金法则优先使用id和accessibility_id。与开发团队沟通为关键测试元素添加稳定的resource-id这是提升脚本稳定性的最有效投资。xpath和android_uiautomator是强大的备选但编写时需要更多技巧。4.2 三种等待机制告别“元素找不到”的噩梦动态加载的页面元素还没出现你就去点击是脚本报错的主要原因。必须使用等待。强制等待time.sleep()不推荐在正式脚本中使用。死等固定时间效率低下且不可靠。仅用于临时调试。隐式等待driver.implicitly_wait(seconds)设置一个全局等待时间。在查找任何元素时如果元素没有立即出现WebDriver会轮询查找直到超时。只需设置一次。但它只对find_element方法有效对元素是否可点击、可见无效。driver.implicitly_wait(10) # 全局隐式等待10秒显式等待WebDriverWait推荐的核心方案。针对某个特定条件进行等待条件满足则继续超时则抛异常。更灵活、更精确。from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from appium.webdriver.common.appiumby import AppiumBy # 等待“登录按钮”出现并可点击 login_btn WebDriverWait(driver, 15).until( EC.element_to_be_clickable((AppiumBy.ID, com.example.myapp:id/btn_login)) ) login_btn.click()常用的条件EC有presence_of_element_located: 元素出现在DOM中不一定可见可点。visibility_of_element_located: 元素可见。element_to_be_clickable: 元素可见且可点击。点击操作前强烈建议使用此条件。text_to_be_present_in_element: 元素包含特定文本。最佳实践组合使用隐式等待和显式等待。设置一个较短的全局隐式等待如5秒作为兜底然后在关键操作如点击、输入前使用更长的、条件更严格的显式等待。这能在保证稳定性的同时避免在无关步骤上浪费过多时间。5. 常用API操作与脚本结构设计掌握了定位和等待我们就可以组合各种操作完成一个完整的业务流程了。5.1 基础操作API封装示例下面是一个模拟用户登录的脚本片段包含了最常见的操作from appium import webdriver from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from appium.webdriver.common.appiumby import AppiumBy import time # ... 初始化 driver配置 desired_caps ... def test_login(): try: # 1. 等待并点击“我的”Tab my_tab WebDriverWait(driver, 10).until( EC.element_to_be_clickable((AppiumBy.ACCESSIBILITY_ID, 我的)) ) my_tab.click() print(已进入‘我的’页面) # 2. 等待并点击“登录/注册”入口 login_entry WebDriverWait(driver, 10).until( EC.element_to_be_clickable((AppiumBy.ID, com.example.myapp:id/tv_login)) ) login_entry.click() # 3. 等待输入框出现并输入账号密码 # 注意先等待整个登录页面框架出现再找内部元素更稳定 WebDriverWait(driver, 10).until( EC.presence_of_element_located((AppiumBy.ID, com.example.myapp:id/login_layout)) ) account_input driver.find_element(AppiumBy.ID, com.example.myapp:id/et_account) account_input.clear() # 清空原有文本如果有 account_input.send_keys(testuserexample.com) # 输入文本 print(已输入账号) password_input driver.find_element(AppiumBy.ID, com.example.myapp:id/et_password) password_input.send_keys(password123) # 4. 点击登录按钮 login_btn driver.find_element(AppiumBy.ID, com.example.myapp:id/btn_login) login_btn.click() # 5. 验证登录成功例如等待用户昵称元素出现 WebDriverWait(driver, 15).until( EC.presence_of_element_located((AppiumBy.ID, com.example.myapp:id/tv_nickname)) ) nickname driver.find_element(AppiumBy.ID, com.example.myapp:id/tv_nickname).text print(f登录成功当前用户{nickname}) # 6. 滑动操作示例向下滑动查看页面 # get_window_size获取屏幕尺寸 size driver.get_window_size() start_x size[width] * 0.5 start_y size[height] * 0.8 end_x size[width] * 0.5 end_y size[height] * 0.2 driver.swipe(start_x, start_y, end_x, end_y, 500) # 滑动耗时500毫秒 except Exception as e: print(f登录流程执行失败{e}) # 可以在这里添加截图操作便于排查 driver.save_screenshot(login_error.png) raise # 执行测试函数 test_login()5.2 使用Pytest框架组织测试用例当用例越来越多时用函数散乱地写显然不行。pytest框架能帮你很好地组织。安装pytestpip install pytest创建测试文件文件名以test_开头如test_login.py。编写测试类和方法测试方法以test_开头。# test_login.py import pytest from appium import webdriver from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from appium.webdriver.common.appiumby import AppiumBy class TestLogin: classmethod def setup_class(cls): 整个测试类开始前执行一次用于初始化driver desired_caps {...} # 你的Capabilities cls.driver webdriver.Remote(http://localhost:4723/wd/hub, desired_caps) cls.driver.implicitly_wait(5) classmethod def teardown_class(cls): 整个测试类结束后执行一次用于退出driver if cls.driver: cls.driver.quit() def setup_method(self): 每个测试方法开始前执行这里可以做一些进入首页的操作 # 例如如果应用不在首页可以先启动主Activity self.driver.start_activity(com.example.myapp, .MainActivity) def test_login_success(self): 测试正常登录流程 # ... 登录操作断言 ... nickname self.driver.find_element(AppiumBy.ID, com.example.myapp:id/tv_nickname).text assert nickname 测试用户, f登录后昵称不符当前为{nickname} def test_login_failed_with_wrong_pwd(self): 测试密码错误登录失败 # ... 输入错误密码操作 ... error_msg self.driver.find_element(AppiumBy.ID, com.example.myapp:id/tv_error).text assert 密码错误 in error_msg运行测试在命令行进入脚本目录执行pytest test_login.py -v-v显示详细信息。pytest会自动发现并运行所有test_开头的方法并生成简洁的报告。使用Pytest的优势固件Fixturesetup_class/teardown_class,setup_method/teardown_method提供了灵活的生命周期管理。断言使用Python原生的assert语句失败时输出清晰信息。参数化可以用pytest.mark.parametrize轻松实现多组数据测试。丰富的插件生态生成HTML报告、并发执行、用例分组等。6. 高级技巧与常见问题排查实录脚本能跑起来只是第一步让它稳定、高效、易维护才是真正的挑战。这部分分享一些实战中积累的高级技巧和排错经验。6.1 封装Page Object模式PO这是UI自动化测试的经典设计模式核心思想是将页面对象和测试逻辑分离。每个页面对应一个类页面上的元素和操作作为这个类的方法。测试用例只关心业务流程不关心具体元素如何定位和操作。好处极大提高代码复用性、可读性和可维护性。当UI元素发生变化时通常只需要修改对应的Page类而不需要改动大量的测试用例。# base_page.py - 基础页面类封装通用操作 class BasePage: def __init__(self, driver): self.driver driver self.wait WebDriverWait(driver, 10) def find(self, by, locator): 查找元素自动加入显式等待 return self.wait.until(EC.presence_of_element_located((by, locator))) def click(self, by, locator): 点击元素 self.wait.until(EC.element_to_be_clickable((by, locator))).click() def input_text(self, by, locator, text): 输入文本 elem self.find(by, locator) elem.clear() elem.send_keys(text) # login_page.py - 登录页面类 from appium.webdriver.common.appiumby import AppiumBy from base_page import BasePage class LoginPage(BasePage): # 页面元素定位器 account_input (AppiumBy.ID, com.example.myapp:id/et_account) password_input (AppiumBy.ID, com.example.myapp:id/et_password) login_button (AppiumBy.ID, com.example.myapp:id/btn_login) error_msg (AppiumBy.ID, com.example.myapp:id/tv_error) def input_account(self, text): self.input_text(*self.account_input, text) # *用于解包元组 return self # 支持链式调用 def input_password(self, text): self.input_text(*self.password_input, text) return self def click_login(self): self.click(*self.login_button) def get_error_msg(self): return self.find(*self.error_msg).text # test_login_po.py - 使用PO模式的测试用例 class TestLoginWithPO: def setup_class(self): self.driver webdriver.Remote(...) self.login_page LoginPage(self.driver) def test_login_success(self): # 测试用例变得非常清晰就是业务流程描述 self.login_page.input_account(testmail.com)\ .input_password(correct_pwd)\ .click_login() # ... 断言 ...6.2 常见问题排查手册FAQ以下是我在实战中遇到的高频问题及解决方案希望能帮你快速排雷。问题现象可能原因排查步骤与解决方案SessionNotCreatedException1. Capabilities配置错误如appPackage/appActivity不对。2. 设备未连接或adb异常。3. 端口冲突默认4723被占用。4. Appium服务器或驱动版本不兼容。1. 检查appPackage和appActivity是否正确用adb命令或Inspector确认。2. 命令行执行adb devices确保设备已列出且状态为device。重启adbadb kill-server adb start-server。3. 换一个端口启动Appiumappium -p 4724。4. 检查Appium Server日志红字错误信息根据提示更新或重装驱动appium driver update uiautomator2。NoSuchElementException1. 元素定位符写错或元素不存在。2. 页面未加载完成元素尚未出现。3. 元素在WebView或Native以外的容器中。4. 屏幕外如需要滑动。1. 使用Appium Inspector重新定位元素核对定位符。2.增加显式等待确保元素出现、可见、可点击后再操作。3. 使用driver.contexts查看所有上下文切换到正确的WEBVIEW_上下文后再定位。4. 使用UiScrollable或swipe操作将元素滚动到屏幕内。元素可以找到但click()不生效1. 元素被遮挡如弹窗、蒙层。2. 元素实际不可点击clickablefalse。3. 坐标点击更稳定不得已时用。1. 检查是否有弹窗先关闭。用Inspector查看元素层级。2. 尝试使用driver.execute_script(mobile: clickGesture, {elementId: element.id})或touch_action。3.最后手段获取元素坐标后使用tapdriver.tap([(center_x, center_y)])。输入框无法输入中文或特殊字符未启用Unicode输入法。在Capabilities中设置unicodeKeyboard: True, resetKeyboard: True。脚本在真机上运行缓慢1. 动画效果影响。2. 等待策略不佳。3. 设备性能。1. 通过adb关闭动画adb shell settings put global window_animation_scale 0adb shell settings put global transition_animation_scale 0adb shell settings put global animator_duration_scale 0。2. 优化等待减少不必要的sleep多用显式等待。3. 关闭后台不必要的应用。如何获取Toast提示信息Toast是系统级控件普通定位方式找不到。使用driver.find_element(AppiumBy.XPATH, //*[contains(text, Toast内容)])。关键Toast出现时间短必须用显式等待且超时时间要短如3秒。处理权限弹窗如相机、存储权限系统弹窗元素可能不属于你的App。1.理想情况在Capabilities中设置autoGrantPermissions: true自动授予所有权限仅限Android。2. 手动处理定位弹窗元素通常是com.android.packageinstaller包下的按钮并点击。需要先获取当前所有上下文和窗口。6.3 性能优化与稳定性提升用例独立性每个测试用例应该可以独立运行不依赖其他用例的状态。善用setup和teardown来初始化和清理环境如回到首页、清除数据。截图与日志在关键步骤和断言失败时自动截图并配合print或日志模块输出详细执行日志这是线上排查问题的生命线。设备管理如果有多台设备可以使用udid参数在Capabilities中指定具体设备。考虑使用appium-device-farm或自己封装设备池管理脚本。并发执行对于大量用例可以使用pytest-xdist插件进行并发测试显著缩短执行时间。CI/CD集成将你的自动化脚本集成到Jenkins、GitLab CI等持续集成平台实现代码提交后自动触发回归测试。走到这里你已经掌握了从环境搭建到脚本编写再到设计模式和问题排查的完整知识链。自动化测试是一个需要不断实践和积累经验的领域最大的技巧往往来自于解决一个又一个具体的问题。我的建议是从一个你最熟悉的App、一个最简单的登录用例开始把它跑通、写稳然后逐步扩展。过程中遇到的每一个报错都是你深入理解这套工具的机会。当你看到脚本自动完成一长串复杂的业务流程并给出清晰的测试报告时那种成就感会让你觉得所有的折腾都是值得的。
返回列表