目录
-
环境准备
- 安装 Python
- 安装 Appium Server
- 安装 Python 客户端库
- 安装手机驱动/模拟器
- 验证环境
-
核心概念与第一个脚本
- Desired Capabilities (必需能力)
Driver对象- 第一个自动化脚本:启动并关闭 App
-
核心 API 分类详解
- 元素定位
- 元素交互
- 获取信息
- 等待
- 手势操作
- 应用控制
- Toast 消息获取
- 多点触控
-
最佳实践与设计模式
- Page Object Model (POM) 页面对象模型
- 使用
pytest框架 - 日志记录
- 配置管理
-
进阶主题
- 处 Webview (Hybrid App)
- 处理权限弹窗
- 参数化测试
- 在 CI/CD 中使用 Appium
环境准备
1 安装 Python
确保你的系统已安装 Python (推荐 3.7+ 版本),打开终端或命令行,输入:
python --version # 或 python3 --version
如果没有安装,请从 Python 官网 下载并安装。
2 安装 Appium Server
Appium Server 是一个中间件,负责将你的 Python 脚本命令翻译成在移动设备上可以执行的操作。
- 最简单方式 (推荐新手): 下载 Appium Desktop 并安装它,图形化界面方便启动和管理 Server。
- 命令行方式: 使用 Node.js 的包管理器
npm安装。npm install -g appium
安装后,可以通过
appium命令启动 Server。
3 安装 Python 客户端库
这是连接你的 Python 脚本和 Appium Server 的桥梁。
pip install Appium-Python-Client
这个库包含了所有与 Appium 交互所需的类和方法。
4 安装手机驱动/模拟器
你需要一个目标设备来运行你的测试脚本。
- 真机: 确保开启了“开发者模式”和“USB 调试”。
- Android 模拟器: Android Studio 自带的模拟器或 Genymotion。
- iOS 模拟器: Xcode 自带的模拟器 (仅限 macOS)。
5 验证环境
一个简单的验证步骤:
- 启动 Appium Desktop。
- 启动你的 Android 模拟器或连接真机。
- 在 Appium Desktop 的 "Desired Capabilities" 配置中填入基本信息(见下一节),然后点击 "Start Session"。
如果能成功启动一个会话并看到一个黑色的、可交互的屏幕,说明你的环境基本没问题。
核心概念与第一个脚本
1 Desired Capabilities (必需能力)
Desired Capabilities 是一个 JSON 对象,用来告诉 Appium Server 你想启动什么样的会话,比如在哪个设备上、启动哪个 App 等。
常见 Android Capabilities:
desired_caps = {
"platformName": "Android", # 平台名称
"platformVersion": "11", # Android 系统版本
"deviceName": "Pixel_4_API_30", # 设备名称 (可自定义)
"app": "/path/to/your/app.apk", # App 的绝对路径
"automationName": "UiAutomator2", # 自动化引擎 (推荐)
"noReset": False, # 会话结束后是否不重置 App 状态
"fullReset": False, # 会话开始前是否完全重置 App (卸载重装)
"unicodeKeyboard": True, # 使用 Unicode 字符集的键盘
"resetKeyboard": True, # 测试结束后重置键盘状态
"newCommandTimeout": 600 # Appium Server 等待新命令的超时时间(秒)
}
2 Driver 对象
Driver 对象是你与 Appium 交互的入口,所有对设备的操作,如点击、滑动、查找元素等,都是通过 driver 对象来完成的。
3 第一个自动化脚本
这个脚本的功能是:启动计算器 App,点击 5 + 3 =,然后获取结果并打印。
# 导入必要的库
from appium import webdriver
import time
# 定义 Desired Capabilities
desired_caps = {
"platformName": "Android",
"platformVersion": "11",
"deviceName": "Pixel_4_API_30",
"app": "/path/to/your/calculator.apk", # 替换成你的计算器APK路径
"automationName": "UiAutomator2",
"noReset": True,
"fullReset": False
}
# 初始化 driver
try:
driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
print("成功启动 App 会话!")
# --- 开始自动化操作 ---
# 1. 定位数字5并点击
driver.find_element_by_id('com.android.calculator2:id/digit_5').click()
# 2. 定位加号并点击
driver.find_element_by_id('com.android.calculator2:id/op_add').click()
# 3. 定位数字3并点击
driver.find_element_by_id('com.android.calculator2:id/digit_3').click()
# 4. 定位等号并点击
driver.find_element_by_id('com.android.calculator2/id/eq').click() # 注意:这个ID可能需要你用UI Automator Viewer确认
# 5. 获取结果文本
result_element = driver.find_element_by_id('com.android.calculator2:id/result')
result_text = result_element.text
print(f"计算结果是: {result_text}")
# 等待3秒,方便观察
time.sleep(3)
except Exception as e:
print(f"发生错误: {e}")
finally:
# 6. 关闭会话
if 'driver' in locals():
driver.quit()
print("App 会话已关闭。")
如何获取元素 ID/Class Name/...? 强烈推荐使用 Appium Desktop 自带的 Inspector 功能:
- 在 Appium Desktop 中填好
Desired Capabilities。 - 点击 "Start Session" 启动会话。
- 在打开的设备界面中,点击右上角的 "放大镜" 图标。
- 现在你可以点击 App 上的任何元素,Inspector 会实时显示该元素的
resource-id,text,content-desc,class name等信息,这些都是你定位元素的关键。
核心 API 分类详解
1 元素定位
Appium Python Client 支持多种定位策略,优先推荐 ID 和 Accessibility ID。
| 定位方法 | Python 语法 | 描述 | 优先级 |
|---|---|---|---|
| ID | driver.find_element_by_id('id_value') |
通过 resource-id 定位,最快、最稳定。 |
⭐⭐⭐⭐⭐ |
| Accessibility ID | driver.find_element_by_accessibility_id('content-desc') |
通过 content-desc 或 label 定位,语义化强,稳定。 |
⭐⭐⭐⭐⭐ |
| XPath | driver.find_element_by_xpath('//android.widget.TextView[@text="Settings"]') |
强大的路径语言,可定位任何元素,但性能稍差。 | ⭐⭐⭐ |
| Class Name | driver.find_element_by_class_name('android.widget.TextView') |
通过 UI 组件的类名定位,可能不唯一。 | ⭐⭐ |
| Android UI Automator | driver.find_element_by_android_uiautomator('new UiSelector().text("OK")') |
使用 Android UIAutomator2 的语法,非常灵活。 | ⭐⭐⭐⭐ |
注意: find_element_by_* 是查找单个元素,如果找不到会抛出 NoSuchElementException。find_elements_by_* (注意是 elements 复数) 是查找所有匹配的元素,返回一个列表,如果找不到则返回空列表。
2 元素交互
找到元素后,你可以对它进行操作。
| 方法 | 描述 |
|---|---|
.click() |
点击元素 |
.send_keys("文本内容") |
向输入框输入文本 |
.clear() |
清空输入框内容 |
.submit() |
提交表单 (类似点击一个提交按钮) |
示例:
search_box = driver.find_element_by_id('com.example.app:id/search_input')
search_box.clear()
search_box.send_keys("Appium Python")
search_box.submit()
3 获取信息
你可以获取元素的属性或状态信息。
| 属性/方法 | 描述 |
|---|---|
.text |
获取元素的可见文本 |
.get_attribute('attribute_name') |
获取元素的任意属性,如 resource-id, content-desc, enabled, displayed 等 |
.size |
获取元素的尺寸 (字典格式: {'width': 100, 'height': 50}) |
.location |
获取元素在屏幕上的坐标 (字典格式: {'x': 100, 'y': 200}) |
.is_enabled() |
判断元素是否可点击 |
.is_displayed() |
判断元素是否可见 |
示例:
element = driver.find_element_by_id('some_id')
print(f"元素文本: {element.text}")
print(f"元素ID: {element.get_attribute('resource-id')}")
print(f"元素是否可见: {element.is_displayed()}")
4 等待
显式等待 是自动化测试的基石,它会让脚本在指定时间内等待某个条件满足(如元素出现)后再继续执行,而不是死等或直接报错。
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# 等待最多10秒,直到ID为 'my_element' 的元素可见
try:
element = WebDriverWait(driver, 10).until(
EC.visibility_of_element_located(('id', 'my_element'))
)
print("元素已成功加载!")
element.click()
except TimeoutException:
print("等待10秒后,元素仍未出现!")
# 常用的 Expected Conditions:
# EC.presence_of_element_located # 元素存在于DOM中,不一定可见
# EC.visibility_of_element_located # 元素可见
# EC.element_to_be_clickable # 元素可点击contains("标题") # 页面标题包含特定文本
5 手势操作
手势操作需要导入 TouchAction 类。
from appium.webdriver.common.touch_action import TouchAction
from appium.webdriver.common.multi_action import MultiAction
# --- 单点触摸 ---
action = TouchAction(driver)
# 点击坐标 (x, y)
action.tap(x=100, y=200).perform()
# 从元素A滑动到元素B
element_a = driver.find_element_by_id('a')
element_b = driver.find_element_by_id('b')
action.press(element_a).move_to(element_b).release().perform()
# 长按元素
action.long_press(element_a).release().perform()
# --- 多点触控 ---
# 模拟双指缩放
multi_action = MultiAction(driver)
action1 = TouchAction(driver).press(x=100, y=100).wait(1000).move_to(x=200, y=200).release()
action2 = TouchAction(driver).press(x=300, y=300).wait(1000).move_to(x=200, y=200).release()
multi_action.add(action1, action2).perform()
6 应用控制
| 方法 | 描述 |
|---|---|
driver.activate_app(package_name) |
激活/启动 App (如果后台运行则拉到前台) |
driver.terminate_app(package_name) |
关闭 App (不卸载) |
driver.remove_app(package_name) |
卸载 App |
driver.background_app(seconds) |
将 App 切换到后台指定秒数 |
driver.is_app_installed(package_name) |
检查 App 是否已安装 |
driver.install_app('/path/to/app.apk') |
安装 App |
7 Toast 消息获取
Android 的 Toast 消息默认无法通过标准定位方式获取,需要借助 autojs 或使用 UIAutomator 的特定方法。
方法一 (UIAutomator - 推荐且简单):
# Toast 消息通常是一个类名为 'android.widget.Toast' 的文本
toast_element = driver.find_element_by_class_name('android.widget.Toast')
toast_text = toast_element.text
print(f"获取到 Toast: {toast_text}")
方法二 (使用 AutoJS - 更稳定): 这种方法需要在 Appium 启动参数中启用 autojs 服务,并安装 AutoJS App。
# 在 desired_caps 中添加 "autojs": "true" # 然后可以直接执行 AutoJS 脚本来获取 js_script = "toast.getRecentToast()" toast_text = driver.execute_script(js_script)
8 多点触控
请参考 5 手势操作 中的 MultiAction 示例。
最佳实践与设计模式
1 Page Object Model (POM) 页面对象模型
POM 是一种设计模式,它将页面元素和操作页面的方法封装到一个类中,使测试代码更清晰、可维护。
结构:
tests/
|-- pages/
| |-- login_page.py # 登录页面对象
| |-- home_page.py # 首页面对象
|-- test_cases/
| |-- test_login.py # 登录测试用例
|-- conftest.py # pytest 配置
|-- config.py # 配置文件
示例代码:
pages/login_page.py
class LoginPage:
def __init__(self, driver):
self.driver = driver
# 定位器
self.username_field = ('id', 'com.example.app:id/username')
self.password_field = ('id', 'com.example.app:id/password')
self.login_button = ('id', 'com.example.app:id/login_btn')
self.error_message = ('id', 'com.example.app:id/error_msg')
def login(self, username, password):
"""登录操作"""
self.driver.find_element(*self.username_field).send_keys(username)
self.driver.find_element(*self.password_field).send_keys(password)
self.driver.find_element(*self.login_button).click()
def get_error_text(self):
"""获取错误信息"""
return self.driver.find_element(*self.error_message).text
test_cases/test_login.py
import pytest
from appium import webdriver
from pages.login_page import LoginPage
@pytest.fixture
def driver():
# 初始化 driver
desired_caps = {...} # 你的 caps
driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
yield driver
driver.quit()
def test_login_with_invalid_credential(driver):
login_page = LoginPage(driver)
login_page.login("wrong_user", "wrong_pass")
error_text = login_page.get_error_text()
assert "用户名或密码错误" in error_text
2 使用 pytest 框架
pytest 是一个强大的 Python 测试框架,支持简单的测试发现、丰富的插件(如 pytest-html 生成报告)和参数化。
安装:
pip install pytest pytest-html
运行测试并生成报告:
pytest test_cases/ --html=report.html
3 日志记录
使用 Python 内置的 logging 模块记录测试过程,便于调试和追踪。
import logging
# 配置日志
logging.basicConfig(level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
def test_example(driver):
logger.info("开始执行测试用例")
try:
element = driver.find_element_by_id('my_element')
logger.info("成功找到元素")
element.click()
logger.info("成功点击元素")
except Exception as e:
logger.error(f"测试失败: {e}", exc_info=True)
raise
4 配置管理
将 Desired Capabilities、服务器地址等配置信息与测试代码分离。
config.py
APPIUM_SERVER_URL = 'http://localhost:4723/wd/hub'
ANDROID_CAPS = {
"platformName": "Android",
"platformVersion": "11",
"deviceName": "Pixel_4_API_30",
"app": "/path/to/your/app.apk",
"automationName": "UiAutomator2",
"noReset": True,
}
在测试文件中引用:
from appium import webdriver import config driver = webdriver.Remote(config.APPIUM_SERVER_URL, config.ANDROID_CAPS)
进阶主题
1 处理 Webview (Hybrid App)
对于混合应用(原生 + Web),需要切换到 Web 上下文进行操作。
# 1. 获取所有可用的上下文
contexts = driver.contexts
print(f"所有上下文: {contexts}")
# 2. 切换到 NATIVE_APP (原生) 或 WEBVIEW (Web)
# WEBVIEW 的名字是 'WEBVIEW_com.example.app'
webview_context = None
for context in contexts:
if 'WEBVIEW' in context:
webview_context = context
break
if webview_context:
driver.switch_to.context(webview_context)
print("已切换到 Webview 上下文")
# 现在可以使用 Selenium 的 API 操作 Web 元素
driver.find_element_by_tag_name('input').send_keys('Hello Web')
# 操作完成后,切回原生上下文
driver.switch_to.context('NATIVE_APP')
print("已切回原生上下文")
2 处理权限弹窗
不同 Android 版本的权限弹窗处理方式不同。
- Android 6.0-10: 通常可以直接通过点击 "允许" 或 "拒绝" 按钮来处理。
- Android 11+: 权限弹窗可能由系统级控制,Appium 无法直接操作,需要在测试前手动开启权限,或在
desired_caps中使用autoGrantPermissions参数。
示例 (处理旧版弹窗):
# 尝试定位并点击 "允许" 按钮
# 注意:这些定位器需要根据实际情况调整
try:
allow_button = driver.find_element_by_id('com.android.packageinstaller:id/permission_allow_button')
allow_button.click()
print("点击了允许按钮")
except:
print("未找到权限弹窗或已处理")
3 参数化测试
使用 pytest 的 @pytest.mark.parametrize 可以轻松地对同一组操作使用不同的输入数据。
import pytest
data = [
("user1", "pass1", "Success"),
("user2", "pass2", "Success"),
("user3", "wrong", "Failed")
]
@pytest.mark.parametrize("username, password, expected_result", data)
def test_login(driver, username, password, expected_result):
login_page = LoginPage(driver)
login_page.login(username, password)
if expected_result == "Success":
assert "欢迎页" in driver.title
else:
assert "错误" in login_page.get_error_text()
4 在 CI/CD 中使用 Appium
在持续集成/持续部署 (如 Jenkins, GitLab CI) 环境中运行 Appium 测试,关键点在于:
- 环境隔离: 使用 Docker 容器来打包 Appium Server、测试依赖和被测 App,确保环境一致性。
- 设备管理: 使用云设备平台(如 BrowserStack, Sauce Labs)或本地设备农场管理工具(如 Appium Desktop 的 "Edit Configs -> Automatic Server")来提供测试设备。
- 无头模式: Appium Server 可以在无图形界面模式下运行。
- 报告集成: 将
pytest-html等生成的测试报告上传到 CI 系统中。
希望这份详尽的指南能帮助你快速上手 Appium Python 自动化测试!祝你编码愉快!
