一、引言

1.1、介绍

  1. Appium是一款开源、跨平台的移动端自动化测试框架,专为测试iOS/Android移动端应用设计(也支持Windows/macOS桌面应用)。
  2. 它基于WebDriver协议(W3C标准),允许开发者用Python、Java、JavaScript、Java等主流编程语言编写自动化脚本,无需修改被测应用的源码,即可实现对移动端应用的自动化操作(点击、输入、滑动、断言等)。
  3. 简单来说: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、案例

在获取连接之前:

  1. 必须通过adb连接到指定的设备。
  2. 必须开启Appium Server。
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、元素定位

元素的定位操作:

  1. 定位单个元素:element = driver.find_element(By.XXX, "XXX")
  2. 定位一批元素(返回列表):elements = driver.find_elements(By.XXX, "XXX")

3.2.1、id定位

el = driver.find_element(By.ID, resource-id属性值)

3.2.2、name定位

el = driver.find_element(By.NAME, name属性值)

3.2.3、class定位

el = driver.find_element(By.CLASS, class属性值)

3.2.4、xpath定位

el = driver.find_element(By.XPATH, xpath属性值)
  1. xpath匹配规则,使用的是相对路径。
  2. //表示使用相对路径,匹配指定或所有//后面的标签。
  3. *代表匹配所有标签,也可以自己指定,button、input、TextView等等。
  4. @后面的属性也可以自己指定,id、text、class、resource-id等。
xpath表达式说明
//*[@text='登录']匹配文本内容为“登录”的标签
//*[@id='wd']匹配id属性为"wd"的标签

3.2、元素基本操作

3.2.1、点击 - click()

element = driver.find_element(By.ID, "foo")
element.click()

3.2.2、输入 - send_keys()

form_textfield = driver.find_element(By.NAME, "username")
form_textfield.send_keys("admin")

3.2.3、获取文本 - text

element = driver.find_element(By.ID, "foo")
print(element.text)

3.2.4、获取位置 - location

  1. 返回值类型:字典。
  2. 返回值:元素的x和y坐标。
loc = element.location

3.2.5、获取大小 - size

  1. 返回值类型:字典。
  2. 返回值:元素的宽和高。
size = element.size

3.2.6、获取属性值 - get_attribute()

is_active = "active" in target_element.get_attribute("class")

3.3、滑动和拖拽

3.3.1、swipe滑动

driver.swipe(start_x,start_y,end_x,end_y,duration)
  1. 从一个坐标位置滑动到另一个坐标位置,只能是两点之间的滑动。
  2. 使用时最好与获取屏幕大小,配合使用。
  3. 注意:坐标只支持整型,duration的单位是ms。
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滑动

driver.scroll(orig_el,dest_el,duration)
# orig_el:起始元素
# dest_el:目的元素
# duration持续时间,单位ms
  1. 从一个元素滑到另一个元素,直到页面自动停止。
  2. 惯性很大,有原生滚动惯性。
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拖拽

driver.drag_and_drop(orig_el,dest_el)
  1. 从一个元素滑动到另一个元素,第二个元素代替第一个元素原本屏幕上的位置。
  2. 无惯性,匀速拖拽。
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、使用方式

使用方式:

  1. 创建ActionBuilder的对象,并且传入driver
  2. 通过ActionBuilder的对象来获取触摸源
  3. 通过返回的触摸源来构造以系列操作序列
  4. 通过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、操作:长按拖拽

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、操作:自定义路径

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、获取屏幕大小

size = driver.get_window_size()
# 获取屏幕的长度:size.get("height")
# 获取屏幕的宽度:size.get("width")

3.5.2、获取屏幕分辨率

driver.get_window_size()

3.5.3、获取屏幕截图

driver.get_screenshot_as_file(filename)

注意事项:使用时,必须先要创建目录,不会自动创建目录。

driver.get_screenshot_as_file("../imgs/screenshot.png")

3.5.4、获取网络状态

driver.network_connection  # 获取手机网络类型
Value(Alias)DataWifiAirplane Mode
0(None)000
1(Airplane Mode)001
2(Wifi only)010
4(Data only)100
6(All network on)110

3.5.5、手机按键操作

driver.press_keycode(keycode)  # 手机默认键码

键码表

keycode说明keycode说明keycode说明
3home键4返回键24音量增加键
25音量减小键26电源键66回车键

3.5.6、通知栏操作

  1. 打开通知栏的操作就是从顶部下滑手机屏幕,显示信息栏。
  2. 但是没有关闭操作,可以自定屏幕往上滑或按返回键。
driver.open_notifications()  # 打开通知栏

3.5.7、获取toast信息

  1. Toast是Android系统的轻量级提示(显示时间短、无焦点、无法通过常规控件定位)。
  2. Appium定位Toast需依赖 UiAutomator2 引擎 + XPath 文本匹配,核心是利用Toast的系统属性和文本特征。
  3. 注意:必须在初始化中加入:automationName="UiAutomator2"。

EC导包:from selenium.webdriver.support import expected_conditions as EC

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)

四、KDT封装