一、引言
1.1、介绍
- Appium是一款开源、跨平台的移动端自动化测试框架,专为测试iOS/Android移动端应用设计(也支持Windows/macOS桌面应用)。
- 它基于WebDriver协议(W3C标准),允许开发者用Python、Java、JavaScript、Java等主流编程语言编写自动化脚本,无需修改被测应用的源码,即可实现对移动端应用的自动化操作(点击、输入、滑动、断言等)。
- 简单来说:Appium是移动端的Selenium,像Selenium操作网页一样,Appium操作手机APP。
1.2、Appium解析
1.2.1、Appium工作原理
开发者编写的脚本(Client) → Appium Server → 底层自动化引擎 → 移动设备 → 结果返回
1.2.2、Appium底层原理
Appium将脚本代码转换成adb命令来控制设备。
1.2.3、核心组件拆解
|------------------|----------------------------------------------------------------------------------------------------------|
| 组件 | 作用 |
| Appium Client | 各语言的客户端库(如 Python 的 appium-python-client),封装了WebDriver API,让脚本能通过HTTP请求向Server发送指令 |
| Appium Server | 核心中间层(用 Node.js开发),负责: (1)接收Client的指令 (2)根据平台(iOS/Android)转发给对应的底层引擎 (3)接收设备的执行结果,返回给Client |
| 底层自动化引擎 | 真正操作设备的"执行者" Android:UiAutomator2(主流,支持 Android 5+)、Espresso(更轻量) iOS:XCUITest(iOS 9+,替代旧的 UIAutomation) |
| Appium Inspector | 可视化工具(类似浏览器开发者工具),用于查看APP的元素属性(id、xpath、className 等),方便写元素定位表达式 |
二、环境搭建和初始化
2.1、环境安装
2.1.1、安装JDK
2.1.2、安装AndroidSDK
2.1.3、安装Appium
2.2、Appium连接初始化
2.2.1、初始化参数
在Appium中,这些初始化参数被称为Desired Capabilities。你可以把它们理解为一组告诉Appium Server"我想要测试哪个设备、哪个应用,以及用什么方式进行测试"的键值对。Appium Server在接收到这些参数后,会根据它们来启动相应的设备和应用,并初始化测试会话(Session)。
|-----------------------------|------------------------------------------------------------------------|-----------------------------------|
| 参数 | 作用 | 示例 |
| platformName | 指定测试的操作系统 | platformName="Android" |
| platformVersion | 指定设备的操作系统版本 | platformVersion="12" |
| deviceName | 指定测试设备的名称(可以随便填) | deviceName="Mumu" |
| app | 指定要安装和启动的应用的绝对路径或URL | |
| appPackage (Android特有) | 指定应用的包名 | appPackage="com.android.settings" |
| appActivity (Android特有) | 指定应用启动时的主 Activity 名称 | appActivity=".Settings |
| autoGrantPermissions | 可自动授予App所有申请的运行时权限 默认为False,不授权。 | autoGrantPermissions=True |
| automationName | 指定要使用的自动化引擎 | automationName="UiAutomator2" |
| bundleId (iOS特有) | 用于指定要测试的iOS应用的唯一标识符 | |
| noReset | 控制在测试会话开始前是否重置应用状态。 true: 不重置应用的缓存、登录状态等将会被保留。 false (默认): 重置,相当于清除数据。 | noReset=True |
| fullReset | 比noReset更彻底的重置。 它会在测试前后卸载并重新安装应用。 | fullReset=True |
| unicodeKeyboard (Android特有) | 是否启用 Unicode 输入法。 启用后,可以输入任何Unicode字符(如中文、特殊符号等)。 | unicodeKeyboard=True |
| resetKeyboard (Android特有) | 在测试结束后,是否将设备的输入法重置为默认。 通常与unicodeKeyboard配合使用。 | resetKeyboard=True |
2.2.2、案例
在获取连接之前:
- 必须通过adb连接到指定的设备。
- 必须开启Appium Server。
python
def get_app_driver():
"""
获取驱动
"""
desired_caps = dict(
# 获取设备的名字
deviceName="Mumu",
# 获取设备的系统
platformName="Android",
# 获取设备系统版本
platformVersion="12",
# app的名字
appPackage="com.android.settings",
# app的界面名
appActivity=".Settings"
)
# 转换成Options对象
options = UiAutomator2Options().load_capabilities(desired_caps)
# 初始化驱动
driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', options=options)
# 返回驱动
return driver
三、基础操作
3.1、App的基础API
使用方式:driver.xxx
|------------------------|--------------|
| 属性/方法 | 说明 |
| start_activity(包名,界面名) | 应用跳转 |
| current_package | 获取当前的app的包名 |
| current_activity | 获取当前的app的界面名 |
| quit() | 关闭driver驱动 |
| close_app() | 关闭app |
| install_app(app_path) | 安装app |
| remove_app(包名) | 卸载app |
| is_app_installed(包名) | 判断app是否安装 |
| background_app(second) | 将app置于后台 |
3.2、元素定位
元素的定位操作:
3.2.1、id定位
python
el = driver.find_element(By.ID, resource-id属性值)
3.2.2、name定位
python
el = driver.find_element(By.NAME, name属性值)
3.2.3、class定位
python
el = driver.find_element(By.CLASS, class属性值)
3.2.4、xpath定位
python
el = driver.find_element(By.XPATH, xpath属性值)
- xpath匹配规则,使用的是相对路径。
- //表示使用相对路径,匹配指定或所有//后面的标签。
- *代表匹配所有标签,也可以自己指定,button、input、TextView等等。
- @后面的属性也可以自己指定,id、text、class、resource-id等。
|--------------------|----------------|
| xpath表达式 | 说明 |
| //*[@text='登录'] | 匹配文本内容为"登录"的标签 |
| //*[@id='wd'] | 匹配id属性为"wd"的标签 |
3.2、元素基本操作
3.2.1、点击 - click()
python
element = driver.find_element(By.ID, "foo")
element.click()
3.2.2、输入 - send_keys()
python
form_textfield = driver.find_element(By.NAME, "username")
form_textfield.send_keys("admin")
3.2.3、获取文本 - text
python
element = driver.find_element(By.ID, "foo")
print(element.text)
3.2.4、获取位置 - location
- 返回值类型:字典。
- 返回值:元素的x和y坐标。
python
loc = element.location
3.2.5、获取大小 - size
- 返回值类型:字典。
- 返回值:元素的宽和高。
python
size = element.size
3.2.6、获取属性值 - get_attribute()
python
is_active = "active" in target_element.get_attribute("class")
3.3、滑动和拖拽
3.3.1、swipe滑动
python
driver.swipe(start_x,start_y,end_x,end_y,duration)
- 从一个坐标位置滑动到另一个坐标位置,只能是两点之间的滑动。
- 使用时最好与获取屏幕大小,配合使用。
- 注意:坐标只支持整型,duration的单位是ms。
python
size = driver.get_window_size() # 获取屏幕的大小
width = size['width']
height = size['height']
# 执行滑动操作
driver.swipe(int(width*0.8), int(height*0.5), int(width*0.2), int(height*0.5), duration=1000)
3.3.2、scroll滑动
python
driver.scroll(orig_el,dest_el,duration)
# orig_el:起始元素
# dest_el:目的元素
# duration持续时间,单位ms
- 从一个元素滑到另一个元素,直到页面自动停止。
- 惯性很大,有原生滚动惯性。
python
orig_el = driver.find_element(By.XPATH, "//*[@text='壁纸']")
dest_el = driver.find_element(By.XPATH, "//*[@text='网络和互联网']")
driver.scroll(orig_el, dest_el, duration=1000)
3.3.3、drag_and_drop拖拽
python
driver.drag_and_drop(orig_el,dest_el)
- 从一个元素滑动到另一个元素,第二个元素代替第一个元素原本屏幕上的位置。
- 无惯性,匀速拖拽。
python
orig_el = driver.find_element(By.XPATH, "//*[@text='壁纸']")
dest_el = driver.find_element(By.XPATH, "//*[@text='网络和互联网']")
driver.drag_and_drop(orig_el, dest_el)
3.4、自定义手势
3.4.1、使用方式
使用方式:
- 创建ActionBuilder的对象,并且传入driver
- 通过ActionBuilder的对象来获取触摸源
- 通过返回的触摸源来构造以系列操作序列
- 通过ActionBuilder的对象来调用preform()方法执行
3.4.2、W3C Actions API
|---------------|-------------------------------------------------------------------------|
| 对象 | 作用 |
| PointerInput | 定义触摸输入源(如 "手指 1""手指 2"),指定输入类型(kind="touch")和设备类型(pointerType="touch")。 |
| ActionBuilder | 管理多个 PointerInput,组合成同步 / 异步动作序列。 |
| Interaction | 定义单个动作(press/move_to/release/wait),指定时长、坐标等。 |
3.4.3、操作:长按拖拽
python
def long_press_drag_w3c(driver, start_x, start_y, end_x, end_y, press_dur=1000, move_dur=500):
"""
W3C 版长按拖拽(游戏常用)
:param press_dur: 长按时长(ms)
:param move_dur: 移动时长(ms)
"""
# 1. 创建动作构建器
action_builder = ActionBuilder(driver)
# 2. 获取触摸输入源
touch = action_builder.add_pointer_input(interaction.POINTER_TOUCH, "finger1")
# 3. 构建动作序列:按下 → 长按等待 → 移动 → 释放
touch.create_pointer_move(x=start_x, y=start_y, duration=0) # 移动到起始点(瞬时)
touch.create_pointer_down(button=interaction.POINTER_BUTTON_LEFT) # 按下(模拟手指按下)
touch.create_pointer_move(x=start_x, y=start_y, duration=press_dur) # 长按等待(坐标不变)
touch.create_pointer_move(x=end_x, y=end_y, duration=move_dur) # 拖拽到目标点
touch.create_pointer_up(button=interaction.POINTER_BUTTON_LEFT) # 释放
# 4. 执行动作(源码中会转换为 W3C 协议发送给驱动)
action_builder.perform()
# 调用示例:将技能从 (W/2, H*0.8) 拖拽到 (W/2, H*0.4)
long_press_drag_w3c(driver, W/2, H*0.8, W/2, H*0.4, press_dur=1000, move_dur=500)
3.4.4、操作:自定义路径
python
def custom_path_move_w3c(driver, start_x, start_y, path_points, total_dur=1000):
"""
W3C 版自定义路径移动(游戏:角色走位)
:param path_points: 路径坐标列表,如 [(x1,y1), (x2,y2), (x3,y3)]
:param total_dur: 总移动时长(ms)
"""
segment_dur = total_dur // len(path_points) # 每段路径时长
action_builder = ActionBuilder(driver)
touch = action_builder.add_pointer_input(interaction.POINTER_TOUCH, "finger1")
# 起始点按下
touch.create_pointer_move(x=start_x, y=start_y, duration=0)
touch.create_pointer_down(button=interaction.POINTER_BUTTON_LEFT)
# 按路径点依次移动
for (x, y) in path_points:
touch.create_pointer_move(x=x, y=y, duration=segment_dur)
# 释放
touch.create_pointer_up(button=interaction.POINTER_BUTTON_LEFT)
action_builder.perform()
# 调用示例:角色沿三角形路径走位
path = [(W*0.3, H*0.4), (W*0.7, H*0.4), (W*0.5, H*0.6)]
custom_path_move_w3c(driver, W/2, H/2, path, total_dur=1500)
3.5、其他操作
3.5.1、获取屏幕大小
python
size = driver.get_window_size()
# 获取屏幕的长度:size.get("height")
# 获取屏幕的宽度:size.get("width")
3.5.2、获取屏幕分辨率
python
driver.get_window_size()
3.5.3、获取屏幕截图
python
driver.get_screenshot_as_file(filename)
注意事项:使用时,必须先要创建目录,不会自动创建目录。
python
driver.get_screenshot_as_file("../imgs/screenshot.png")
3.5.4、获取网络状态
python
driver.network_connection # 获取手机网络类型
|-------------------|----------|----------|-------------------|
| Value(Alias) | Data | Wifi | Airplane Mode |
| 0(None) | 0 | 0 | 0 |
| 1(Airplane Mode) | 0 | 0 | 1 |
| 2(Wifi only) | 0 | 1 | 0 |
| 4(Data only) | 1 | 0 | 0 |
| 6(All network on) | 1 | 1 | 0 |
3.5.5、手机按键操作
python
driver.press_keycode(keycode) # 手机默认键码
键码表
|-------------|--------|-------------|--------|-------------|--------|
| keycode | 说明 | keycode | 说明 | keycode | 说明 |
| 3 | home键 | 4 | 返回键 | 24 | 音量增加键 |
| 25 | 音量减小键 | 26 | 电源键 | 66 | 回车键 |
3.5.6、通知栏操作
- 打开通知栏的操作就是从顶部下滑手机屏幕,显示信息栏。
- 但是没有关闭操作,可以自定屏幕往上滑或按返回键。
python
driver.open_notifications() # 打开通知栏
3.5.7、获取toast信息
- Toast是Android系统的轻量级提示(显示时间短、无焦点、无法通过常规控件定位)。
- Appium定位Toast需依赖 UiAutomator2 引擎 + XPath 文本匹配,核心是利用Toast的系统属性和文本特征。
- 注意:必须在初始化中加入:automationName="UiAutomator2"。
EC导包:from selenium.webdriver.support import expected_conditions as EC
python
toast = (WebDriverWait(driver, timeout=10, poll_frequency=0.1)
.until(EC.presence_of_element_located((By.XPATH, "//android.widget.toast[@text='登录成功']"))))
print("捕获到toast文本:", toast.text)