拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Python+Appium实战:搭建企业级App自动化回归框架

先问一个绝大多数测试同学都经历过的问题App 已经迭代了好几个版本业务回归还靠手工一遍一遍点加一个新需求要先花半天理清楚会影响哪些旧页面等到晚上发版群里的测试负责人开始催“核心链路再回归一次”。这种场景如果每周重复一次不是测试能力问题而是工程方法问题。Python Appium 的价值就是给这种重复工作搭建一条可复用、可继续生长的自动化回归体系。2026 年再谈 Appium它显然不是“最年轻”“最智能”的方案但它依然是企业级 APP 自动化测试绕不开的基础设施。原因并不复杂Appium 支持 Android 与 iOS兼容真实设备和模拟器底层通过 WebDriver 协议与设备控件树交互这个思路已经被大量企业测试框架验证过。本文选择“网易严选”作为被测业务载体不是因为它特殊而是它具备电商类 App 最典型的关键链路启动、搜索、商品详情、加入购物车、购物车核对、个人中心。只要能把这个闭环跑通把脚本组织成可维护的框架迁移到团队自己的业务 App 上就会顺畅很多。这篇文章会从一个相对真实的项目视角展开而不是只贴命令。读完你会得到三样东西第一搞懂 Python Appium 的底层运行原理知道报错时该从哪里查第二完整搭出一套企业级可用的 APP UI 自动化项目结构包括 Page Object、pytest、测试报告第三拿到一份“3 天落地计划”用于快速验证自己业务的第一个自动化用例。1. 这篇文章真正要解决的问题很多人对 APP UI 自动化的第一反应是“录制回放”。录制回放看起来省事也确实适合 Demo但进入企业级项目后问题会出现页面改版后脚本大面积失效、用例之间依赖登录态、执行不稳定、报告可读性差。真正让自动化在团队内产生价值的不是录制工具而是“谁在维护代码”和“用例怎么设计”。Python Appium 解决的核心问题可以拆成四个层面。第一跨平台与兼容层面。Appium 的底层设计是同一套 WebDriver 思路Android 走 UiAutomator2iOS 走 XCUITest。对测试团队来说业务脚本里的 Page 层可以抽象出来只把设备能力层做区分。即使公司只有 Android 测试需求这种分层也能让以后接 iOS 时少返工。第二脚本成本与表达层面。Python 语言本身容易阅读理解pytest 对小规模用例到大规模测试体系的支持都够用。Java 也能写 Appium但 Python 的迭代效率更适合测试团队从 0 到 1 搭建时快速试错。这也是很多测试开发岗位把 Python 作为默认语言的原因。第三工程协同层面。企业级自动化不是一个人写脚本而是多人维护用例。如果脚本没有分层所有定位符散落在测试函数里每次改版都要全局搜索替换这种维护成本最终会让用例库变成坑。Appium 体系配合 Page Object 模式可以把“页面元素”和“业务动作”封装成对象测试函数只描述“用户要做什么”。第四可持续运行层面。Appium 启动 session、连接设备的机制是标准化的可以接入本地模拟器、真机群控设备也能接入云测平台。让测试用例在固定环境里定时执行配合 HTML 报告和失败截图才是企业级自动化的完整闭环。这里也要给出一个明确判断Python Appium 最适合的业务场景是“核心链路回归”和“兼容性冒烟测试”不适合做所有功能的全量验证。UI 自动化脚本对页面结构和业务文案很敏感如果团队想要的是接口级、数据层的稳定保障应该优先做 API 自动化而不是让 UI 自动化承担一切。2. Python Appium 自动化测试的核心原理很多新手容易把 Appium 理解成“一个能控制手机的桌面软件”这个比喻大方向没错但容易忽略它的工程本质。Appium 由三部分协作测试脚本、Appium Server、移动设备上的自动化引擎。测试脚本运行在自己电脑上通过 HTTP 请求告诉 Appium Server 要做什么Appium Server 把命令转换成对应平台能识别的指令Android 端的 UiAutomator2 拿到指令后直接在设备上执行点击、输入、滑动等动作。可以用一个容易理解的类比Appium Server 像遥控器的信号转发基站测试脚本是遥控器按钮手机里的 UiAutomator2 是接收器。真正完成“点击”的不是 Appium Server而是设备端的自动化引擎。所以命令行里看到UiAutomator2相关日志是设备端已经开始工作的信号。这套机制里有一个容易被忽略的关键概念Session中文一般叫“会话”。每一次测试启动Appium 都会创建一个 Session相当于给“被测 App”建立一条专属连接。Session 创建时脚本要传一批 Desired Capabilities也就是自动化启动的“配置参数”。Appium Server 根据这些参数决定启动哪个平台的驱动、打开哪个 App、进入哪个页面。核心参数一般包含下列几项Capability作用示例platformName设备平台Android / iOSappium:deviceName设备名或 idemulator-5554appium:appPackage被测 App 包名实际要测的包名appium:appActivity被测 App 启动 Activity具体入口appium:automationName自动化引擎UiAutomator2appium:noReset是否不重置应用数据true / falseappium:newCommandTimeout命令超时时间180Session 建立后测试脚本的每一个 API 调用比如element.click()、element.send_keys()实际上都会走一遍“客户端 - Appium Server - 设备端 - 控件树”的往返。这也是 UI 自动化天生比接口自动化慢的本质原因。慢不是问题问题是脚本不能过度依赖固定 sleep否则执行一次要白白多等几十秒。在元素定位上Appium 支持多种方式。常见的有id、class name、xpath、accessibility id以及 Android 平台特有的 UiAutomator 表达式。实际项目中UI 组件越稳定脚本越稳定。resource-id 通常比 text 稳定但国内 App 改版频率高text 和 content-desc 可能会有中文场景的限制所以更推荐的做法是把定位策略封装成一层。关于等待常见的错误是不加等待或乱加固定时间。Appium 底层处理控件查找时如果元素还没出现在界面上findElement 会立刻返回找不到。企业级脚本必须使用显式等待也就是“轮询控件直到超时或成功”而不是写死time.sleep(5)。这既能减少误报也能提高运行速度。3. 企业级 APP 自动化测试环境搭建环境搭建是很多学习者第一天最容易卡住的地方。经常出现的状态是教程里的截图环境从安装 Android Studio 开始最后跑起来时又冒出几十个报错。为了避免这种混乱环境准备应该遵循“先装运行时再装工具最后验证设备链路”的顺序。3.1 需要准备的工具清单以 Android 方向为例建议准备以下工具工具用途安装优先级Python 3.10编写自动化脚本必须Appium Server提供自动化服务端必须Android SDK Platform Toolsadb、uiautomator 支持必须Java JDKAndroid 工具链依赖按系统需要Appium Inspector页面控件树查看器强烈建议Android 模拟器或真机被测设备必须Node.js 也需要安装因为 Appium 2.x 通过 npm 分发和启动。安装完 Node.js 后Appium Server 可以直接从 npm 安装。下面是完整的安装命令示例。# 安装 Appium 服务器 npm install -g appium # 查看 Appium 版本 appium --version # 安装 Android 平台驱动 appium driver install uiautomator2 # 查看已安装的驱动 appium driver listPython 侧主要安装 Appium 的 Python Client以及后面要用到的 pytest 和 pytest-html 报告库。python -m pip install --upgrade pip python -m pip install Appium-Python-Client pytest pytest-html如果下载慢可以临时切换国内镜像源这里以清华源为例python -m pip install Appium-Python-Client pytest pytest-html -i https://pypi.tuna.tsinghua.edu.cn/simple3.2 环境变量配置Android SDK 配置是否正确是很多新手跑不起来 adb 的常见原因。无论是 Windows 还是 macOS核心都是让命令行能识别adb。Android Studio 安装后SDK 默认路径通常是Windows 系统检查C:\Users\你的用户名\AppData\Local\Android\SdkmacOS 系统检查~/Library/Android/sdk。# macOS / Linux 临时配置写入 ~/.zshrc 或 ~/.bashrc 更稳妥 export ANDROID_HOME$HOME/Library/Android/sdk export PATH$PATH:$ANDROID_HOME/platform-tools export PATH$PATH:$ANDROID_HOME/emulatorWindows 上可以直接在环境变量界面新增ANDROID_HOME把值设为 SDK 路径并把platform-tools目录追加到Path。验证环境是否就绪的关键命令# 查看 Android 设备列表 adb devices # 查看 Appium 是否安装成功 appium --version如果adb devices能看到设备并且状态是device说明设备链路已经打通。如果状态是offline或unauthorized先检查设备是否解锁是否点击了允许 USB 调试的弹窗。状态正常后再启动 Appium Server。# 默认 4723 端口启动 appium看到类似Appium REST http interface listener started的日志并且端口号是 4723表示服务已经准备好接受请求。此时不要急着写用例先用 Appium Inspector 连接设备查看网易严选真实的页面控件结构。3.3 获取被测 App 的包名与启动页面每个 Android App 都有包名和启动 Activity。如果使用内置的真实包名会不准确因为不同版本开发者可能调整入口页面。稳妥的方式是用 adb 现查。在设备上打开网易严选 App然后在命令行执行adb shell dumpsys window | grep mCurrentFocus输出结果中斜杠前面是包名斜杠后面是当前 Activity。比如输出格式类似mCurrentFocusWindow{xxx com.example.shop/com.example.shop.activity.MainActivity}这里com.example.shop只是示例。实际操作时要把它替换成你从 dumpsys 命令里看到的真实包名和 Activity。不要把网上复制来的包名直接写死在代码里尤其是企业内测版本签名环境不同会直接影响启动。4. 被测应用分析与用例设计网易严选这个业务对象本质上是一个标准电商 App。电商 App 的 UI 自动化用例设计有一条通用思路先识别核心转化链路再控制用例数量然后逐层拆分动作。4.1 核心业务链路从用户视角看严选的日常高频路径可以抽象为首页 - 搜索栏 - 搜索结果 - 商品详情 - 加入购物车 - 购物车页 - 结账入口这条链路覆盖了搜索、列表、详情、购物袋、结算页等核心场景是任何一次版本迭代都不应该回归出问题的路径。对于 UI 自动化来说第一次落地不建议做一长串完整的登录支付流程因为支付环节涉及真实资金与环境依赖稳定性很差。企业实践里一般把自动化边界控制在“提交订单前”真正的支付验证交给沙箱环境或配置了测试支付的专用设备完成。从这条链路可以拆出下面的测试用例表用例编号用例名称核心操作预期结果TC-001App 启动与首页冒烟启动 App等待首页加载底部导航出现“首页”“购物车”“我的”TC-002首页搜索商品点击搜索入口输入关键词提交搜索搜索列表展示相关商品TC-003搜索列表进入商品详情点击搜索结果列表中的第一个商品页面展示商品详情信息TC-004商品详情加入购物车点击加入购物袋/购物车按钮出现加入成功提示TC-005购物车核对从首页切换到购物车 Tab购物车内存在刚加入的商品TC-006我的页面边界点击底部“我的” Tab展示个人中心入口或登录提示第一次做自动化建议把 TC-001 到 TC-005 定义为 P0 级用例。TC-006 可以放到下一期因为它往往与登录态强相关。4.2 页面对象模式设计企业级 Appium 项目很少把元素定位直接写在测试函数中。更合理的做法是引入 Page Object 模式每个页面对应一个类页面里的元素定位和操作方法都封装在类里测试函数只表达业务动作。比如首页有“搜索入口”和“点击搜索并输入关键词”两个行为那么 HomePage 类就负责封装这些行为。ProductPage 负责商品详情页的操作。这样设计之后如果商品详情页的“加入购物袋”按钮文案改了只需要修改 ProductPage不需要一个用例一个用例地改维护成本会低很多。项目结构可以这样设计app_demo/ ├── pages/ │ ├── __init__.py │ ├── base_page.py │ ├── home_page.py │ └── product_page.py ├── tests/ │ ├── __init__.py │ ├── conftest.py │ └── test_yanxuan_flow.py ├── report/ └── requirements.txt这个结构看起来很简单但已经具备向大型项目演进的基础。后面可以继续增加 config、data、utils、logger 等模块但骨架职责是清晰的。4.3 定位符的工程管理国内 App 的页面结构有两个特点第一Android 原生控件与自绘控件混用第二不同版本之间 resource-id 很可能不一致。所以定位符不应该裸写在测试用例里建议统一放在 config 文件或页面类顶部。定位符的稳定性优先级可以参考定位方式稳定性说明resource-id高尽量多用content-desc中高无障碍语义稳定时可用text中文案改版会挂xpath 绝对路径低不要用于主流程UIAutomator 表达式中高适合复合条件5. 完整代码实现从启动到加购全流程下面代码的项目根目录假设为app_demo所有模块从根目录调用。代码中的定位符属于演示通用写法正式接入时请先在 Appium Inspector 中查看当前版本网易严选的真实控件属性再替换成自己的值。5.1 设备与 App 启动配置conftest.py 是 pytest 的全局固定装置文件负责创建和释放 Appium Session。# 文件路径app_demo/tests/conftest.py import os import sys from pathlib import Path import pytest from appium import webdriver # 把项目根目录加入 sys.path便于 tests 中的用例 import pages ROOT Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) pytest.fixture(scopesession) def driver(): 创建 Appium 连接测试结束时关闭驱动。 caps { platformName: Android, appium:automationName: UiAutomator2, # 真机或模拟器设备 id使用 adb devices 查看 appium:deviceName: os.getenv(ANDROID_DEVICE, emulator-5554), # 请使用 adb shell dumpsys window | grep mCurrentFocus 查到的真实包名 appium:appPackage: os.getenv(APP_PACKAGE, com.example.shop), appium:appActivity: os.getenv(APP_ACTIVITY, .activity.MainActivity), # 输入中文时需要开启这两个配置 appium:unicodeKeyboard: True, appium:resetKeyboard: True, # 每次启动是否保留 App 之前的数据 appium:noReset: False, appium:newCommandTimeout: 180, } # Appium Server 默认在 4723 端口监听 driver webdriver.Remote( http://127.0.0.1:4723/wd/hub, caps ) driver.implicitly_wait(10) yield driver driver.quit()这段代码里有几个细节需要注意。scopesession表示整个测试会话只创建一个 Appium 连接所有用例共享同一个 driver这样执行速度更快。但如果用例之间相互影响登录态可能需要改成 function 级别并在每个用例前通过appium:noReset: True配合“回到首页”的动作来复位状态。unicodeKeyboard和resetKeyboard这两个配置在输入中文关键字时非常关键。如果不开启很多模拟器上send_keys会丢失字符或无法输入中文。5.2 基础页面类基础页面类封装通用等待和点击行为后续所有页面类都继承它。# 文件路径app_demo/pages/base_page.py from appium.webdriver.common.appiumby import AppiumBy from selenium.webdriver.support.ui import WebDriverWait class BasePage: def __init__(self, driver): self.driver driver def find_android(self, ui_selector, timeout10): 按 UiSelector 表达式查找元素并做显式等待。 locator (AppiumBy.ANDROID_UIAUTOMATOR, fnew UiSelector().{ui_selector}) return WebDriverWait(self.driver, timeout).until( lambda d: d.find_element(*locator) ) def find_by_text(self, text, timeout10): 按 TextView 文案定位。 return self.find_android(ftext({text}), timeout) def click_by_text(self, text, timeout10): 点击可见文本。 self.find_by_text(text, timeout).click() def click_android(self, ui_selector, timeout10): 点击 UiSelector 表达式匹配的元素。 self.find_android(ui_selector, timeout).click() def is_text_visible(self, text, timeout5): 判断文本是否在预期时间内出现。 try: self.find_by_text(text, timeout) return True except Exception: return False这里使用WebDriverWait做显式等待如果元素在 10 秒内出现就立即返回如果一直不出现等到超时后抛出异常。相比time.sleep这种方式既能保证稳定的运行速度又能减少偶发失败。5.3 首页与商品页首页类负责搜索动作商品列表页负责打开第一个搜索结果商品详情页负责加入购物车并跳转。# 文件路径app_demo/pages/home_page.py from time import sleep from appium.webdriver.common.appiumby import AppiumBy from pages.base_page import BasePage class HomePage(BasePage): def search(self, keyword): 从首页进入搜索页并输入关键词执行搜索。 # 点击首页搜索入口 self.click_by_text(搜索) # 进入搜索页后输入框通常是页面第一个 EditText sleep(1) edit_box self.driver.find_element( AppiumBy.ANDROID_UIAUTOMATOR, new UiSelector().className(android.widget.EditText) ) edit_box.send_keys(keyword) # 模拟键盘上的“搜索”按键 self.driver.press_keycode(66) # 返回搜索结果页由外层传入的 Page 继续处理 return ProductListPage(self.driver)为了防止模块循环引用建议把页面类之间的 import 放在方法内部或者单独维护一个页面入口文件。业务逻辑上搜索词建议从外部参数读入而不是写死在类里这样便于同一页面扩展不同搜索词用例。# 文件路径app_demo/pages/product_page.py from time import sleep from appium.webdriver.common.appiumby import AppiumBy from selenium.webdriver.support.ui import WebDriverWait from pages.base_page import BasePage class ProductListPage(BasePage): def open_first_product_by_keyword(self, keyword): 搜索列表中点击第一个包含关键词结果的商品。 # 等待搜索结果的商品标题出现 WebDriverWait(self.driver, 15).until( lambda d: len( d.find_elements( AppiumBy.ANDROID_UIAUTOMATOR, fnew UiSelector().textContains({keyword}) ) ) 0 ) # 点击第一个匹配结果的父级容器避免只点到标题文字 self.click_android(ftextContains({keyword})) return ProductDetailPage(self.driver) class ProductDetailPage(BasePage): def add_to_cart(self): 加入购物袋/购物车按文案包含匹配以提高容错率。 self.click_android(textContains(加入购物), timeout15) sleep(1) def go_to_cart(self): 从详情页返回再进入底部购物车 Tab。 self.driver.press_keycode(4) # Android Back sleep(1) self.click_by_text(购物车)5.4 测试用例全流程用例按“启动首页 - 搜索 - 打开商品 - 加购 - 购物车验证”的顺序执行。# 文件路径app_demo/tests/test_yanxuan_flow.py from pages.home_page import HomePage from pages.product_page import ProductDetailPage, ProductListPage def test_app_home_smoke(driver): TC-001 App 启动后应展示首页底部导航。 home_page HomePage(driver) assert home_page.is_text_visible(首页) assert home_page.is_text_visible(购物车) def test_search_to_cart_flow(driver): TC-002 到 TC-005 搜索指定商品 - 进入商品详情 - 加入购物车 - 购物车中可见商品。 这里使用独立关键词执行避免依赖上一个用例的页面状态。 keyword 旅行箱 home_page HomePage(driver) product_list_page home_page.search(keyword) product_detail_page product_list_page.open_first_product_by_keyword(keyword) product_detail_page.add_to_cart() # 进入购物车并校验商品存在 cart_page ProductDetailPage(driver) cart_page.go_to_cart() # 购物车页面检查文案匹配即可视为加购成功。 # 实际项目中可以在购物车里查找具体商品标题或数量控件。 assert cart_page.is_text_visible(keyword), 购物车没有出现目标商品加购流程失败这段代码的分层节奏可以看出 Page Object 的价值测试函数里没有出现任何 Appium 底层 API也没有出现定位符。如果页面结构变化只需要修改对应 Page 类。测试函数读起来像用例描述后续任何人接手都容易理解。5.5 用例的依赖管理与数据隔离上面的代码虽然写成了两个用例但实际运行时可能会遇到一个问题第二个用例test_search_to_cart_flow依赖 App 启动后处于首页。如果先执行 TC-001 后首页还停留在首页那么没有问题。但如果未来增加更多用例页面状态会被相互污染。更好的做法是为每个用例准备独立的启动条件。比如每个测试开始前都回到首页必要时使用driver.reset()或者在 conftest 里加入一个自动化的前置步骤每次测试前先执行adb shell am force-stop再重新启动 App。这样虽然会损失
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门