Python+Appium实战:从环境搭建到网易严选App自动化测试
2026/9/22 2:34:12 网站建设 项目流程

很多做移动端测试的同学,第一次接触 App 自动化时会发现:网上资料很多,但版本混杂,照着旧教程做完,Appium Inspector 连不上手机,Python 脚本跑起来一堆报错,最后连一个真实的业务流程都没跑通。如果你最近正在了解 Python + Appium 这套技术栈,打算给真实 App 做 UI 自动化测试,那么今天这篇文章会比较适合你。

文章会用网易严选 App 作为被测对象,从环境搭建、核心原理、元素定位,到 Page Object 封装、pytest 用例执行和问题排查,完整走一遍企业级 APP 自动化测试的落地流程。阅读时可以跟着步骤边配置边验证。掌握这套流程后,换到商城、资讯、工具类 App,核心思路基本都能迁移使用。

1. 背景与核心概念

1.1 Appium 到底解决什么问题

App 自动化测试的核心痛点,是“重复操作太多”。传统手工回归测试里,每次版本发布前,测试人员都要在手机上反复执行启动、搜索、下单、退出等流程,工作量巨大,而且容易漏测。Appium 解决的就是这个重复执行问题:它允许我们通过 Python、Java、JavaScript 等语言编写脚本,像真实用户一样操控 Android 或 iOS 设备上的 App。

和很多自动化框架不同,Appium 不是针对某一个 App 写死的脚本库,而是一个通用测试服务。你可以理解成它给手机提供了一个“遥控中心”,测试代码通过 HTTP 请求告诉 Appium:启动哪个应用、点击哪个控件、输入什么文字、断言页面是否出现。正因为这种通用性,Appium 能适配不同 App,企业里也常把它和 pytest、Jenkins 等工具结合,形成夜间自动化回归体系。

对于 Python 技术栈的测试团队来说,Appium 的优势还在于语言生态。Python 能做数据构造、接口调用、测试报告生成,再配合 pytest 断言体系,很容易把 UI 自动化用例纳入一套完整的质量保障流程中。

1.2 Appium 自动化测试的工作方式

Appium 不是直接在手机上运行 Python 脚本,它底层采用 Client-Server 架构。测试代码里的 Python 是客户端,通过 WebDriver 协议把操作命令发送给 Appium Server,Appium Server 再把命令转换成对应移动平台的原生指令。

以 Android 为例,常见的驱动是 UiAutomator2。Appium Server 收到点击命令后,会交给 UiAutomator2 Server 去执行元素查找、点击、输入等操作。iOS 端则使用 XCUITest 驱动完成类似事情。这套设计让测试用例和底层设备实现解耦,写脚本时基本不用关心手机系统如何执行点击,只需要关心业务元素。

这里有个容易混淆的点:Appium 2.x 与旧版 Appium 1.x 的驱动安装方式不一样。1.x 时代,UiAutomator2 等功能往往内置在 Appium Desktop 安装包里;Appium 2.x 之后,Server 和 Driver 分离了,需要按驱动单独安装。现在学习建议直接使用 Appium 2.x 的稳定版本,下面章节也会按照 2.x 的思路来准备环境。

1.3 企业级 App 自动化测试的分层思路

刚接触自动化时,很多人以为“能跑一条脚本”就是自动化完成了。到真实项目里,企业级 App 自动化通常不是单条脚本,而是一个分层体系:

分层作用常见技术
用例层描述业务流程和断言pytest、unittest
页面对象层把页面元素和操作封装成类Page Object 模式
驱动与能力层管理设备、Appium Server、Desired CapabilitiesAppium Client
基础设施层设备管理、报告、失败重跑、CI 触发pytest-html、Allure、Jenkins

本文后面的实战会覆盖用例层、页面对象层和驱动能力层,基础设施层会给出建议和扩展方向。这样即使被测 App 变化,维护成本也能控制在可接受范围。

2. 没有基础,如何 3 天上手

“3 天搞定企业级 APP 自动化”并不是说 3 天能成为自动化测试专家,而是指按合理路线,3 天足够从零搭建一套能够跑通真实业务用例的工程。如果之前只会 Python 基础语法,没写过移动自动化,可以按下面的节奏执行。

2.1 Day1:跑通环境并启动网易严选 App

第一天不追求写复杂逻辑,重点是把环境跑通。需要完成 Python 安装、Appium Server 安装、Android 设备连接、网易严选 App 安装,最终通过脚本启动网易严选并打印当前页面信息。

这一天最容易失败的是环境问题,比如 adb 连接不到手机、Appium Inspector 配置错误、Desired Capabilities 不完整。建议遇到一个解决一个,并记录到自己的笔记里。环境通了,后面学习才会顺畅。

2.2 Day2:理解元素定位并完成搜索主流程

第二天开始理解 UI 自动化怎么找控件。打开 Appium Inspector,像查看微信开发者工具一样查看网易严选界面元素,弄清 resource-id、text、content-desc、xpath 之间的关系。然后编写搜索流程:启动 App、点击搜索框、输入保温杯、点击搜索结果、进入商品详情页。

建议不要把目标定得太宽。搜索并进入详情页,已经覆盖启动、点击、输入、断言四个核心动作。学会这条链路,注册、登录、下单等流程基本就是重复同样套路。

2.3 Day3:走向工程化

第三天做工程化改造:把重复代码封装成页面对象类,加入显示等待,补充失败截图,接入 pytest 生成测试报告,并处理常见的无法定位、输入不生效问题。

3 天结束后,应该拥有一个可以直接运行的 Python 自动化项目,而不是一堆粘贴在临时 py 文件里的脚本。

3. 环境准备

3.1 Python、编辑器与依赖

如果在 Windows 上学习,先从 Python 官网下载稳定版本安装包。安装时务必勾选“Add Python to PATH”,否则后面在 cmd 中运行 python 命令会提示找不到。macOS 上可直接通过 Homebrew 安装,Linux 则可以按发行版使用包管理器或源码编译。

安装完成后,在终端里执行:

python --version pip --version

如果两个命令都能正常输出版本号,说明 Python 环境没问题。编辑器推荐使用 VS Code 或 PyCharm。VS Code 里需要安装 Python 扩展,按 Ctrl+Shift+P 输入 Python: Select Interpreter,选择当前 Python 环境;PyCharm 则在 Settings 中新建虚拟环境即可。

后面创建自动化项目时,建议使用 venv 或 conda 虚拟环境,避免多个项目依赖互相冲突。本文示例用的是 pip + venv 思路。

3.2 安装 Appium Server 与 Appium Inspector

Appium 2.x 的 Server 基于 Node.js 运行,所以电脑上需要先安装 Node.js。安装后打开终端执行:

npm install -g appium appium --version

如果输出了 Appium 版本号,说明安装成功。第二步安装 Android 驱动:

appium driver install uiautomator2

执行appium driver list可以查看当前已经安装了哪些驱动。Appium Inspector 是独立的可视化元素定位工具,可以把它理解成移动端自动化的“放大镜”,通过它查看当前界面的控件树和属性。可以从官方 GitHub Releases 页面下载对应操作系统的安装包,安装后打开。

启动 Appium Server,在终端执行appium,如果看到 Appium 服务跑在 4723 端口,就说明 Server 已经就绪。Appium Inspector 默认连接地址也是127.0.0.1:4723,很少需要修改。

3.3 Android 设备准备与获取包名信息

Android 自动化测试既可以连接真机,也可以使用模拟器。真机测试需要在手机中开启开发者选项和 USB 调试,并通过数据线连接电脑。连接后执行:

adb devices

如果设备列表里显示device状态,说明 adb 识别正常。接下来需要确认被测应用是否安装。网易严选的包名通常是com.netease.yanxuan,不同渠道版本可能不同,可以通过如下命令过滤:

adb shell pm list packages | grep yanxuan

拿到包名后,再获取当前界面 Activity。先在手机上手动打开网易严选,回到终端执行:

adb shell dumpsys activity activities | grep -E 'mResumedActivity|mFocusedApp'

输出会包含类似包名/Activity名的信息。由于 Android 版本不同,命令输出格式会有差异,但思路是一样的:确认包名、确认启动 Activity,再把这两个值填到自动化配置里。如果实在无法从命令中解析,也可以先随便填写,通过 Appium Inspector 连接后查看启动日志,日志里通常会显示 Appium 实际识别的 Activity。

4. 核心配置与必须掌握的原理

4.1 Desired Capabilities:告诉 Appium 要操作什么设备

Desired Capabilities 是 JSON 格式的配置项,用于描述本次自动化会话的属性。它可以告诉 Appium 当前是 Android 还是 iOS、设备名称、应用包名、Activity、是否保留登录状态等。常见的配置如下:

caps = { "platformName": "Android", "appium:automationName": "UiAutomator2", "appium:deviceName": "Android Emulator", "appium:platformVersion": "13", "appium:appPackage": "com.netease.yanxuan", "appium:appActivity": "MainActivity", "appium:noReset": True, "appium:unicodeKeyboard": True, "appium:resetKeyboard": True, }

这里的appium:appPackageappium:appActivity要与上一步从设备上获取的值保持一致。noReset为 True 时,Appium 不会在用例结束后清除 App 数据,能避免每次都要重新登录或重新初始化;但如果用例之间互相依赖登录状态,反而可能掩盖问题,实际项目中需要按团队规范决定。

4.2 元素定位:为什么不能只复制 XPath

UI 自动化最关键的操作是“找到按钮并点击”。Appium Inspector 打开网易严选首页后,会显示当前界面树。左侧是控件列表,右侧是被选元素的属性。定位方式通常有 resource-id、text、content-desc、class name、xpath 等。

尽量优先使用 resource-id 或 text 组合。不要看到 xpath 顺手复制,因为页面顶部控件层级一变,长 xpath 很容易失效。比如一个常见的写法是:

old_xpath = "/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout"

这种路径一旦页面加了新布局就挂。更建议用相对可靠的 text 或 class 组合:

(AppiumBy.ANDROID_UIAUTOMATOR, 'new UiSelector().text("搜索")')

或者退而求其次,使用尽量短的 xpath:

//android.widget.TextView[contains(@text,"加入购物车")]

实际定位复杂页面时,可以先在 Appium Inspector 中点击需要的控件,重点查看元素的resource-idtextcontent-desc。当这几个属性都为空时,才使用控件层级关系。

4.3 显示等待与隐式等待

App 页面加载存在网络延迟,元素并不是启动后立即可见。如果脚本像人一样不等待直接点击,大部分情况会报NoSuchElementException

初学者常见的做法是time.sleep(5),这种固定等待不适合工程化:网络慢时 5 秒不够,网络快时白白等 5 秒。工程中更推荐使用 Selenium 的WebDriverWait显式等待,它会在规定时间内不断轮询,直到元素满足条件:

WebDriverWait(driver, 20).until( EC.presence_of_element_located(locator) )

WebDriverWait里还可以配合visibility_of_element_locatedelement_to_be_clickable等条件使用。后面项目实战代码里会反复见到。

4.4 send_keys 输入失效的常见原因

在 App 输入框里输入文字时,不少同学会遇到两个问题:第一是点击了输入框但光标没有出现;第二是 Python 脚本里 send_keys 中文,手机上显示乱码或者完全不显示。

第一个问题常见于键盘没有弹起,这时可以在输入前用driver.tap()点一下输入框区域,或者使用 UiAutomator2 的focus能力。第二个问题通常和设备默认输入法有关,这也是 Capabilities 中配置unicodeKeyboardresetKeyboard的原因。前者让 Appium 使用 Unicode 输入方式,后者在自动化结束后把键盘重置为设备默认输入法。

input_el = wait.until( EC.visibility_of_element_located(search_input_locator) ) input_el.click() input_el.send_keys("保温杯")

如果搜索结果页面没有“搜索”按钮可点击,也可以在输入完成后发送 Android 回车键触发搜索:

driver.press_keycode(66)

不同品牌手机输入法对回车键的响应不同,但 66 这个按键码在绝大多数原生输入框里都表示 ENTER,很多搜索场景都可以先试这一种。

5. 网易严选实战:从搜索到进入商品详情

5.1 被测流程说明

本文执行的业务链路是:启动网易严选 App → 在首页点击搜索入口 → 输入商品关键词“保温杯” → 触发搜索 → 点击第一个搜索结果 → 等待进入商品详情页并断言“加入购物车”按钮存在。

选择这条链路,是因为它不涉及登录、支付等强账号体系,测试执行更稳定。真实企业中如果要下单、支付,往往要另外准备测试账号和预置商品,而且会涉及更复杂的账号信息安全边界。示例不点击真实购买按钮,只到详情页断言层级,既能覆盖核心 UI 自动化操作,又能避免测试数据污染线上订单系统。

5.2 项目结构

我会用 Page Object Model,也就是页面对象模式来组织代码。把每个页面的元素定位和操作封装成独立类,测试用例只关心业务流程,而不是散落的 find_element。

项目结构如下:

yanxuan_appium_demo/ ├── requirements.txt ├── caps.py ├── pages/ │ ├── __init__.py │ ├── base_page.py │ ├── home_page.py │ ├── search_page.py │ └── goods_page.py └── tests/ ├── __init__.py └── test_yx_search.py

这样可以做到:网易严选首页布局变了,只修改home_page.py;搜索逻辑变了,只修改search_page.py;测试用例本身尽量保持稳定。

5.3 创建依赖文件与基础配置

先在项目根目录创建requirements.txt

Appium-Python-Client pytest pytest-html selenium

版本不写死的好处是安装时自动找当前兼容版本。如果你希望追求稳定复现,也可以把版本固定下来。然后创建虚拟环境并安装依赖:

python -m venv venv venv/Scripts/activate pip install -r requirements.txt

macOS/Linux 下激活虚拟环境的命令是source venv/bin/activate。安装完成后,编写caps.py

# 文件路径:caps.py def get_caps(): return { "platformName": "Android", "appium:automationName": "UiAutomator2", # 真机或模拟器设备名,Appium 2.x 中不能为空 "appium:deviceName": "Android", # 按当前设备实际 Android 版本填写 "appium:platformVersion": "13", # 通过 adb shell pm list packages 确认 "appium:appPackage": "com.netease.yanxuan", # 通过 adb shell dumpsys activity 获取 "appium:appActivity": "MainActivity", "appium:noReset": True, "appium:unicodeKeyboard": True, "appium:resetKeyboard": True, # 超过 300 秒未收到命令自动关闭会话 "appium:newCommandTimeout": 300, }

这里要特别强调:appPackageappActivity不要照抄。不同版本、不同渠道的网易严选安装包,启动 Activity 可能存在差异。先按 3.3 节的方法从设备拿到正确值,再回填到配置中。

5.4 封装 BasePage

BasePage是所有页面对象的父类,统一封装查找、点击、截图、等待逻辑。这样后续写 HomePage、SearchPage 时,代码会干净很多。

# 文件路径:pages/base_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 BasePage: def __init__(self, driver): self.driver = driver def wait_visible(self, locator, timeout=15): return WebDriverWait(self.driver, timeout).until( EC.visibility_of_element_located(locator) ) def click(self, locator, timeout=15): element = WebDriverWait(self.driver, timeout).until( EC.element_to_be_clickable(locator) ) element.click() def send_keys(self, locator, text, timeout=15): element = self.wait_visible(locator, timeout) element.click() element.send_keys(text) def screenshot(self, file_name="screenshot.png"): self.driver.save_screenshot(file_name) def text_exists(self, text, timeout=8): try: locator = ( AppiumBy.ANDROID_UIAUTOMATOR, f'new UiSelector().textContains("{text}")', ) WebDriverWait(self.driver, timeout).until( EC.presence_of_element_located(locator) ) return True except Exception: return False

text_exists方法用于断言当前页面是否出现指定文本。它使用 UiSelector 的 textContains 匹配,适合“加入购物车”这类容易包含在按钮或标题中的文案。

5.5 编写首页、搜索页与商品详情页

首页页面对象类如下:

# 文件路径:pages/home_page.py from appium.webdriver.common.appiumby import AppiumBy from pages.base_page import BasePage from pages.search_page import SearchPage class HomePage(BasePage): # 网易严选不同版本首页搜索入口文案可能有差异,请用 Appium Inspector 确认 SEARCH_ENTRY = ( AppiumBy.ANDROID_UIAUTOMATOR, 'new UiSelector().textContains("搜索")', ) def open_search(self): self.click(self.SEARCH_ENTRY) return SearchPage(self.driver)

如果首页顶部搜索框文案不是“搜索”,而是“搜索严选商品”或某个输入框 icon,只需要把定位改成实际控件即可。搜索页对象类如下:

# 文件路径:pages/search_page.py from appium.webdriver.common.appiumby import AppiumBy from pages.base_page import BasePage from pages.goods_page import GoodsPage class SearchPage(BasePage): SEARCH_INPUT = ( AppiumBy.ANDROID_UIAUTOMATOR, 'new UiSelector().className("android.widget.EditText")', ) def search(self, keyword): self.send_keys(self.SEARCH_INPUT, keyword) # 部分页面没有搜索按钮,按回车键触发搜索 self.driver.press_keycode(66) def enter_first_result(self, keyword): # 搜索后,通常标题里会包含搜索关键词 result_item = ( AppiumBy.ANDROID_UIAUTOMATOR, f'new UiSelector().textContains("{keyword}")', ) self.click(result_item) return GoodsPage(self.driver)

enter_first_result默认点击第一个包含关键词的文本控件。如果真实页面的搜索结果被广告或推荐位干扰,这里可能需要改成点击某一种商品卡片容器,但原理相同:先等待条件满足,再找到可点击元素。

商品详情页对象类如下:

# 文件路径:pages/goods_page.py from appium.webdriver.common.appiumby import AppiumBy from pages.base_page import BasePage class GoodsPage(BasePage): ADD_CART_TEXT = "加入购物车" def has_add_cart_button(self): return self.text_exists(self.ADD_CART_TEXT)

商品详情页结构复杂,这里只做“加入购物车按钮是否存在”的断言。若页面中该按钮是导航栏上的一个 icon,而文案出现在别的位置,同样可以通过 Inspector 观察后调整。

5.6 编写 pytest 测试用例

测试用例通过 pytest 管理,这里把 driver 初始化放进 fixture,保证用例结束后能正确退出会话。

# 文件路径:tests/test_yx_search.py import pytest from appium import webdriver from caps import get_caps from pages.home_page import HomePage @pytest.fixture(scope="module") def driver(): base_url = "http://127.0.0.1:4723/wd/hub" appium_driver = webdriver.Remote(base_url, get_caps()) yield appium_driver appium_driver.quit() def test_search_goods_to_detail(driver): # 启动后进入网易严选首页 home_page = HomePage(driver) # 点击搜索入口 search_page = home_page.open_search() # 输入关键词并搜索 search_page.search("保温杯") # 进入第一个搜索结果商品详情 goods_page = search_page.enter_first_result("保温杯") # 断言详情页出现“加入购物车”按钮 assert goods_page.has_add_cart_button() # 截图作为测试证据 goods_page.screenshot("goods_detail.png")

测试用例本身非常接近自然语言。这样做的好处是:以后不管底层元素定位怎么变化,只要页面对象类保持不变,测试人员看用例就能理解业务意图。

5.7 运行与结果说明

先确保 Appium Server 在运行,再进入项目虚拟环境执行:

pytest tests/test_yx_search.py -s -v --html=report.html

如果一切正常,终端会类似输出:

PASSED tests/test_yx_search.py::test_search_goods_to_detail

同时会在项目根目录生成report.htmlgoods_detail.png。打开截图,可以看到脚本确实操作到了商品详情页。

假如测试失败,优先检查失败弹窗和页面截图。因为BasePage中用了显示等待,很多失败不是“没有元素”,而是等待超时。超时原因通常是定位表达式的文案与当前页面不一致,或者页面带着开屏广告/弹窗。这时打开 Appium Inspector 人工查看一下当前页面结构,很快能定位问题。

6. 常见问题与排查思路

6.1 问题速查表

问题现象常见原因解决思路
创建 Session 失败Appium Server 未启动或 Capabilities 格式错误检查 Appium 终端日志,确认 4723 端口可访问
找不到设备adb 未识别真机或模拟器执行 adb devices 确认设备状态为 device
点击搜索入口后无反应首页文本和定位表达式不匹配用 Appium Inspector 重新获取实际文本或 resource-id
NoSuchElementException元素尚未加载完成或定位无效使用显式等待,替换更合适的选择器
send_keys 输入中文乱码输入法不是 Appium Unicode 键盘Capabilities 中增加 unicodeKeyboard 和 resetKeyboard
点击元素报 stale element页面刷新后旧元素对象失效重新定位,不要让元素对象跨长时间复用
跑一次成功后第二次失败noReset 导致页面停留在上一个状态用例前置或后置需要恢复初始页面

6.2 遇到 Session 创建失败怎么办

Session 创建失败是最早遇到的高频问题。先看 Appium Server 终端日志,常见错误是Could not find a connected Android deviceActivity used to start app doesn't existUiAutomator2 driver is not installed

如果是设备连接问题,回到adb devices检查。如果是 Activity 问题,回到设备上手动打开网易严选,再用dumpsys activity获取正确 Activity。如果是驱动问题,执行appium driver install uiautomator2

6.3 元素定位超时怎么办

测试失败日志里出现TimeoutException时,不要急着改等待时间。先手动执行到失败那一步,观察页面真实状态。常见情况是:页面弹出了隐私协议弹窗、搜索后出现空结果、不同测试机分辨率导致控件文本重叠。针对弹窗,可以在启动流程中增加关闭弹窗逻辑;针对空结果,需要准备稳定测试关键词或者测试数据。盲目把 timeout 调大只会让用例变慢,并不能真正解决元素不存在问题。

7. 工程化与最佳实践

7.1 坚持 Page Object 模式

刚开始学习 UI 自动化时,很容易把页面的元素定位直接写在测试用例里,比如:

driver.find_element(...).click()

这样写几个用例还能忍受,用例一旦超过 20 条,页面结构调整一次就会改到崩溃。Page Object 模式的核心,是一个页面一个类,页面元素和操作都封装在类里。测试用例和页面细节隔离后,日常维护工作量会

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询