启动一个软件页面
# coding=gbk
# 1.先设置编码,utf-8可支持中英文,如上,一般放在第一行
from appium import webdriver
import time
#创建一个字典
desired_caps = dict()
#系统种类 ios程序填'ios'
desired_caps['platformName'] = 'Android'
#系统版本 Android6.1
desired_caps['platformVersion'] = '6.0.1'
#设备名字 安卓随意
desired_caps['deviceName'] = 'MI NOTE 3'
#软件包名
desired_caps['appPackage'] = 'com.v2ray.ang'
#页面名
desired_caps['appActivity'] = '.ui.MainActivity'
#使用unicodeKeyboard的编码方式来发送字符串
desired_caps['unicodeKeyboard'] = True
#将键盘给隐藏起来
desired_caps['resetKeyboard'] = True
#不重置app
desired_caps['noReset'] = True
#生成 driver 对象并启动
driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
获取app的包名和界面名
# 获取包名
driver.current_package
# 获取界面名
driver.current_activity
通过代码跳转到其他app
-
通过driver对象调用 start_activity 的方法
start_activity("包名", "界面名")
如果通过代码获取 app 的包名和界面名
- 通过 driver 对象调用 current_package 属性
- 通过 driver 对象调用 current_activity 属性
关闭 app 和驱动程序
# 关闭当前操作的app, 不会关闭驱动对象
driver.close_app
# 关闭驱动对象, 同时关闭所有关联的app
driver.quit()
安装和卸载以及是否安装 app
应用场景
一些应用市场的软件可能会有一个按钮, 如果某个程序已经安装则卸载, 如果没有安装则安装
# 安装app
# 参数:
# app_path: apk路径
driver.install_app(app_path)
# 卸载app
# 参数:
# app_id: 应用程序包名
driver.remove(app_id)
# 判断app是否已经安装
# 参数:
# app_id: 应用程序包名
# 返回值:
# 布尔类型: True为安装, False为没有安装
driver.is_app_installed(app_id)
样例
# coding=gbk
from time import time
from appium import webdriver
import time
desired_caps = dict()
desired_caps['platformName'] = "Android"
desired_caps['platformVersion'] = '6.0.1'
desired_caps['deviceName'] = 'mushan'
desired_caps['appPackage'] = 'com.android.settings'
desired_caps['appActivity'] = '.Settings'
driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
driver.close_app()
# 如果存在就卸载, 否则就安装
if driver.is_app_installed("com.v2ray.ang"):
driver.remove_app("com.v2ray.ang")
print("已卸载")
else:
driver.install_app("C:/Users/lenovo/Downloads/Programs/v2rayNG.apk")
print("已安装")
driver.quit()
元素定位操作 API
当我们选择一个控件, 可以在软件右下角查看控件相关信息, 通过这些内容来定位一个控件
应用场景
想要对按钮进行点击, 输入框进行输入, 首先要定位按钮和文本框, 定位元素是自动化操作必须要使用的方法, 只有获取元素之后, 才能对这个元素进行操作
方法名
# 通过ID定位一个元素
# 参数:
# id_val: 元素的resource-id的属性值
# 返回值:
# 定位到单个元素
driver.find_element_by_id(id_value)
# 通过class_name定位一个元素
# 参数:
# class_value: 元素的class属性值
# 返回值:
# 定位到的单个元素
driver.find_element_by_class_name(class_value)
# 通过xpath定位一个元素
# 参数:
# xpath_value: 定位元素的xpath表达式
# 返回值:
# 定位到单个元素
driver.find_element_by_xpath(xpath_value)
# xpath_value 格式
# “//*[@键='值']"
示例
模拟 v2rayNG, 节点订阅
通过 id 的形式, 定位左上角 " 三个杠 " 按钮并点击
点击订阅设置
点击右上角加号
定位备注输入框, 输入"木杉"
定位节点地址, python中获取节点并输入
返回起始界面
定位并点击"三个点"
点击更新订阅
代码:
# coding=gbk
from time import time
from appium import webdriver
import time
desired_caps = dict()
Data = "http://121.4.209.91:8888/down/b6OPLBLTdci9"
desired_caps['platformName'] = "Android"
desired_caps['platformVersion'] = '6.0.1'
desired_caps['deviceName'] = 'mushan'
desired_caps['appPackage'] = 'com.v2ray.ang'
desired_caps['appActivity'] = '.ui.MainActivity'
driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
# class定位菜单图标
Menu_button = driver.find_element_by_class_name("android.widget.ImageButton")
Menu_button.click() # 单击按钮
# id 定位节点订阅按钮
Subscribe_button = driver.find_element_by_id("com.v2ray.ang:id/design_menu_item_text")
Subscribe_button.click()
# id 定位节点添加订阅按钮
Add_config = driver.find_element_by_id("com.v2ray.ang:id/add_config")
Add_config.click()
# id 定位备注输入框, 并输入"木杉乀"
driver.find_element_by_id("com.v2ray.ang:id/et_remarks").send_keys("MuShan")
# id 定位节点地址输入框, 并输入节点地址内容
driver.find_element_by_id("com.v2ray.ang:id/et_url").send_keys(Data)
# 单击确认
driver.find_element_by_id("com.v2ray.ang:id/save_config").click()
# 跳转到起始页
driver.start_activity("com.v2ray.ang", ".ui.MainActivity")
# 定位三个点并单击
driver.find_element_by_class_name("android.widget.ImageView").click()
# 更新订阅
driver.find_element_by_xpath("//*[@text='更新订阅']").click()
time.sleep(5)
driver.quit()
如何定位一组元素
与定位一个元素相似, 唯一的不同点就是将, find_element
变成了 find_elements
如果通过一组的方式定位, 获取到的不是第一个元素, 而是符合该特征的所有元素存放在一个集合里返回
定位元素注意点
find_element_by_xx 或 find_elements_by_xx 分别传入一个没有的特征会怎样
核心代码
driver.find_element_by_id("xxx")
driver.find_elements_by_id("xxx")
小结
- 如果使用
find_element_by_xx
方法, 传入一个没有的特征, 会报错: “没有找到” - 如果使用
find_elements_by_xx
方法, 传入一个没有的特征, 不会报错, 会返回一个空列表
元素等待
应用场景
因为某些原因, 导致我们想要找的元素没有立刻出来, 此时如果直接定位可能会报错, 比如以下原因;
- 由于网络速度原因
- 服务器处理请求原因
- 电脑配置原因
此时不能让程序因为这些问题导致返回错误结果, 那么就需要使用元素等待
概念
WebDriver定位元素是如果未找到, 会在指定的时间内一直等待过程
元素等待分为两种
- 显式等待
- 隐式等待
隐式等待
应用场景
针对所有定位元素的超时时间为同一个值的时候
概念
等待元素加载指定时长, 如果在规定时间内找到就正常返回, 否则抛出NoSuchElemnetException异常
步骤
在获取 driver 对象后, 使用 driver 调用 implicitly_wait 方法即可
核心代码
from selenium.webdriver.support.wait import WebDriverWait
driver.implicitly_wait(5) # 此方法使用后, 后面所有元素都会采用该等待
search_button = driver.find_element_by_xpath("//*[contains(@content-desc, '收起')]")
search_button.click()
显式等待
应用场景
针对所有定位元素的超时时间设置为不同的值的时候
概念
等待元素加载指定的时长, 超出时长抛出TimeoutException异常
步骤
- 导包
- 创建 WebDriverWait 对象
- 调用 WebDriverWait 对象 until 方法
核心代码
from selenium.webdriver.support.wait import WebDriverWait
wait = WebDriverWait(driver, 5, poll_frequency=1) # 默认0.5秒
back_button = wait.until(lambda x: x.find_element_by_id("com.v2ray.ang:id/fab"))
back_button.click
driver.quit()
创建wait对象: WebDriverWait的参数:
- driver对象
- 等待的时间(秒)
- 测试的频率(每间隔多长时间调用一次方法, 默认为0.5s)
与隐式等待的一个区别就是控制单个方法的等待
WebDriverWait(driver, 20, 3).until(lambda x : x.find_element_by_id("cpm.v2ray.ang:id/fab"))
将应用置于后台
应用场景
银行类 app 会进入后台一定时间后, 如果回到前台页面会重新输入密码, 如果需要自动化测试这种功能, 可以使用这个 api 进行测试
方法
# app放置到后台一定时间后再回到前台, 模拟热启动
# 参数:
# seconds: 后台停留多少秒
driver.background_app(seconds)
示例
打开 《设置》应用, 进入后台 5 秒, 再回到前台
# coding=gbk
from time import time
from appium import webdriver
import time
desired_caps = dict()
desired_caps['platformName'] = "Android"
desired_caps['platformVersion'] = '6.0.1'
desired_caps['deviceName'] = 'mushan'
desired_caps['appPackage'] = 'com.android.settings'
desired_caps['appActivity'] = '.Settings'
driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
print("---- 准备进入后台 ----")
driver.background_app(5)
print("---- 回到前台 ----")
driver.quit()
元素操作API
点击元素
方法名:
element.click()
输入和清空输入框内容
方法名:
# 输入框输入value
element.send_keys(value)
# 清空输入框内容
element.clear()
注意:
默认输入中文是会出问题的, 需要在连接手机的参数中添加两行代码
desired_caps['unicodeKeyboard'] = True
desired_caps['resetKeyboard'] = True
获取元素文本内容
# 获取所有节点地址以及端口号
driver.implicitly_wait(3)
title = driver.find_elements_by_id("com.android.settings:id/title")
for i in title:
print(i.text)
获取元素的位置和大小
# 获取element的位置
# 返回值:
# 字典: x: x坐标, y: y坐标
element.location
# 获取element的大小
# 返回值:
# 字典: width: 宽度, height: 高度
element.size
get——attribute(“属性名”)获取属性
content-desc属性获取
get_attribute(“name”) 获取content-desc属性,这里注意了,如果content-desc属性为空,那么获取的就是text属性,不为空获取的才是content-desc属性
备注:content-desc属性也可以这样获取:get_attribute(“contentDescription”)
id,calss,text属性获取
driver.find_element_by_id("com.baidu.yuedu:id/lefttitle").get_attribute("resourceId")
driver.find_element_by_id("com.baidu.yuedu:id/lefttitle").get_attribute("className")
driver.find_element_by_id("com.baidu.yuedu:id/lefttitle").get_attribute("text")
其它属性获取
driver.find_element_by_id("com.baidu.yuedu:id/lefttitle").get_attribute("checkable")
driver.find_element_by_id("com.baidu.yuedu:id/lefttitle").get_attribute("clickable")
滑动事件
swipe 滑动
driver.swipe(start_x坐标, start_y坐标, end_x坐标, end_y坐标, 持续时长)
# 时长参数含默认值
# 长度相同, 滑动时长越长, 惯性越小
# 长度相同, 滑动时长越短, 惯性越大
例:
driver.swipe(89, 865, 89, 371, 5000)
# 从坐标(89, 865) -> 坐标(89, 371) 滑动时长5000ms
scroll 滑动
driver.scroll(元素1, 元素2)
# 从元素1 滑动到 元素2
# 不能设置持续时长, 惯性很大
例:
save_button = driver.find_element_by_xpath("//*[@text='存储设备和 USB']")
more_button = driver.find_element_by_xpath("//*[@text='更多']")
driver.scroll(save_button, more_button)
drag_and_drop 拖拽
driver.drag_and_drop(元素1, 元素2)
# 从元素1 拖拽到 元素2
# 不能设置时长, 惯性极小
# 第二个元素替代第一个元素原本屏幕上的位置
例:
save_button = driver.find_element_by_xpath("//*[@text='存储设备和 USB']")
more_button = driver.find_element_by_xpath("//*[@text='更多']")
driver.drag_and_drop(save_button, more_button)
TouchAction类
from appium import webdriver
# 需要导入模块TouchAction
from appium.webdriver.common.touch_action import TouchAction
desired_caps = {
"platformName": "Android",
"platformVersion": "10",
"deviceName": "PCT_AL10",
"appPackage": "com.ss.android.article.news",
"appActivity": ".activity.MainActivity",
"automationName": "uiautomator2",
"unicodeKeyboard": True,
"resetKeyboard": True,
"noReset": False,
}
# 启动app
driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
# 构造TouchAction实例对象
action = TouchAction(driver)
按住点(x1, y1),等待1000ms,滑动至点(x2, y2),释放
action.press(x=x1, y=y1).wait(1000).move_to(x=x2, y=y2).release()
# 执行操作
action.perform()
tap点击:
# 点击元素的中心点
tap(element=ele1)
# 点击坐标(x1, y1)
tap(x=x1, y=y1)
# 以元素ele1左上角的x坐标向右移动x2单位,y坐标向下移动y2单位,在点(x+x2, y+y2)上点击
tap(element=ele1, x=x2, y=y2)
press短按:
# 按压元素
press(el=ele1)
# 按压坐标
press(x=x1, y=y1)
# 以元素ele1左上角的x坐标向右移动x2单位,y坐标向下移动y2单位,在点(x+x2, y+y2)上按压
press(el=ele1, x=x2, y=y2)
long_press长按
# 按压元素,默认1000ms
long_press(el=ele1)
# 按压坐标500ms
long_press(x=x1, y=y1, duration=500)
# 以元素ele1左上角的x坐标向右移动x2单位,y坐标向下移动y2单位,在点(x+x2, y+y2)上按压
long_press(el=ele1, x=x2, y=y2)
move_to移动至目标点
# 该方法需要与press()、long_press()结合使用
# 从另一个点移动至目标元素ele1
move_to(el=ele1)
# 从另一个点移动至点(x1, y1)
move_to(x=x1, y=y1)
# 从另一个点移动至点(x+x2, y+y2), (x, y)为元素ele1左上角的坐标
move_to(el=ele1, x=x2, y=y2)
wait等待
# 等待,如等待500ms
wait(500)
release释放
# 释放操作,与按压、长按结合使用
release()
perform()执行
# 将动作命令发送至服务器来执行该动作,如:
action = TouchAction(driver).press(x=x1, y=y1).move_to(x=x2, y=y2).release()
执行滑动操作
action.perform()
多点触控MultiAction类
- add(self, touch_actions),参数touch_actions为触摸操作集合,将一个或多个触摸操作添加至当前的多点触控实例中
- perform(self),执行多点触控操作
使用场景,如页面的放大、缩小等
from appium import webdriver
from appium.webdriver.common.touch_action import TouchAction
# 需要导入模块MultiAction
# from appium.webdriver.common.multi_action import MultiAction
desired_caps = {
"platformName": "Android",
"platformVersion": "10",
"deviceName": "PCT_AL10",
"appPackage": "com.ss.android.article.news",
"appActivity": ".activity.MainActivity",
"automationName": "uiautomator2",
"unicodeKeyboard": True,
"resetKeyboard": True,
"noReset": False,
}
# 启动app
driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps
# 创建两个触摸事件
action = TouchAction(driver)
action1 = action.press(ele1).move_to(ele2).release()
action2 = action.press(x=50, y=50).move_to(x=100, y=200).release()
# 创建MultiAction实例对象
multi_action = MultiAction(driver)
# 将触摸事件加入TouchAction对象
multi_action.add(action1, action2)
# 执行事件
multi_action.perform()
手机API操作
获取手机分辨率
- 关键方法:
- driver.get_window_size()
- 返回值:
- 字典
- 两个key, 分别是 width 和 height
- 宽和高的值是 int 类型的
如何截图
- 关键方法:
- driver.get_screenshot_as_file(“路径”)
- 参数:
- 文件的路径
- 如果直接写文件名, 默认保存在项目目录下
获取和设置手机网络
手机网络三个状态:
- 流量
- wifi
- 飞行模式
由0, 1, 2, 4, 6 表示这三个状态
获取手机网络:
核心: driver.network_connection()
设置手机网络:
头文件: from appium.webdriver.connectiontype import ConnectionType
核心:driver.set_network_connection()
参数可以用数字, 也可以用对应宏
NO_CONNECTION = 0
AIRPLANE = 1
WIFI_ONLY = 2
DATA_ONLY = 4
ALL_NETWORK_ON = 6
发送键到设备
模拟"返回键" "home键"等操作
方法名
# 发送键到设备
# 参数:
# keycode: 发送给设备的关键码
# metastate: 关于被发送的关键代码的元信息, 一般为默认值
driver.press_keycode(keycode, metastate = None)
操作手机通知栏
方法名
# 打开手机通知栏
driver.open_notifications()
API中没有关闭通知栏的语法, 通过手指滑动等自动化操作进行关闭
{
"platformName": "Android",
"platformVersion": "6.0.1",
"deviceName": "MI NOTE 3",
"appPackage": "com.tencent.wework",
"appActivity": ".launch.WwMainActivity",
"unicodeKeyboard": true,
"resetKeyboard": true,
"noReset": true
}