前置代码
```python
from appium import webdriver
import time
desired_caps = dict()
# 平台名,大小写不敏感
desired_caps['platformName'] = 'Android'
# android系统版本
desired_caps['platformVersion'] = '5.1'
# 设备名,可通过adb devices命令获取
desired_caps['deviceName'] = '192.168.x.x'
# 要打开的app包名
# 可通过adb shell dumpsys window windows | findstr mFocusedApp获取
desired_caps['appPackage'] = 'com.android.settings'
# 要打开的界面名
# 可通过adb shell dumpsys window windows | findstr mFocusedApp获取
desired_caps['appActivity'] = '.Settings'
driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
time.sleep(5)
driver.quit()
```
1、元素定位
元素定位:
step1、打开uiautomatorviewer工具
step2、打开真机或模拟器
step3、通过uiautomatorviewer工具获取想要选择的元素的Node Detail信息
step4、通过元素定位API进行定位
step5、对元素进行相关操作
1.1、定位一个元素
```python
# 通过id定位一个元素
# 参数:id_value-->元素的resource_id属性值
# 返回值:定位到的单个元素
driver.find_element_by_id(id_value)
```
```python
# 通过class_name定位一个元素
# 参数:class_value-->元素的class属性值
# 返回值:定位到的单个元素
driver.find_element_by_class_name(class_value)
```
```python
# 通过xpath定位一个元素
# 参数:xpath_value-->元素的xpath属性值
# 返回值:定位到的单个元素
driver.find_element_by_xpath(xpath_value)
```
注:如果很多元素的”特征“相同,使用find_element_by_xxx的方法会找到第一个,所以,尽量去找元素特征有唯一性的”特征“来定位
1.2、定位一组元素
```python
# 通过id定位一组元素
# 参数:id_value-->元素的resource_id属性值
# 返回值:定位到的一组元素
driver.find_elements_by_id(id_value)
```
```python
# 通过class_name定位一组元素
# 参数:class_value-->元素的class属性值
# 返回值:定位到的一组元素
driver.find_elements_by_class_name(class_value)
```
```python
# 通过xpath定位一组元素
# 参数:xpath_value-->元素的xpath属性值
# 返回值:定位到的一组元素
driver.find_elements_by_xpath(xpath_value)
```
注:如果通过一组的方式进行定位,获取的返回值不再是一个元素,而是一个列表,列表中装着所有符合这个特征的元素
1.3、定位元素注意点
如果find_element_by_xxx方法,传入了一个没有的条件,会报错,NoSuchElementException
如果find_elements_by_xxx方法,传入了一个没有的条件,不会报错,返回一个空列表
2、元素等待
2.1、隐式等待:
关键方法:
- 通过driver对象调用implicitly_wait方法
- 设置超时时间
作用:
- 在设置了超时时间之后,后续所有的定位元素的方法都会在这个时间内等待元素出现
- 如果出现了,直接进行后续操作(等待跳出)
- 如果没有出现,报错NoSuchElementException
```python
# time为等待时间,单位为秒
driver.implicitly_wait(time)
```
2.2、显式等待:
关键方法:
- 关键类:WebDriverWait
- 关键方法:WebDriverWait对象中的until的方法
作用:
- 在设置了显式等待之后,可以等待一个超时时间,在这个超时时间之内进行查找,默认每0.5秒查找元素一次(查找频率可配置)
- 如果找到元素,直接进行后续操作
- 如果没有找到元素,报错TimeOutException
```python
# 使用显示等待,在30秒时间内,每3秒查询一次,id为xxx的元素
WebDriverWait(driver, 30, 3).until(lambda x: x.find_element_by_id("xxx"))
```
2.3、显式等待和隐式等待的区别
作用域:
- 隐式等待对全局有效,显式等待只对单个元素有效
方法:
- 隐式等待直接通过driver实例化对象调用,显式等待方法封装在WebDriverWait类中
3、元素操作方法
3.1、点击元素
```python
#对element按钮进行点击操作
driver.find_element_by_id("xxx").click()
```
3.2、输入和清空内容
```python
# 在输入框中输入hello
driver.find_element_by_id("xxx").send_keys("hello")
# 清空输入框
driver.find_element_by_id("xxx").clear()
```
注意点:输入框输入中文无效,但不会报错,需要在“前置代码中”增加两个参数
```python
desired_caps['unicodeKeyboard'] = True
desired_caps['resetKeyboard'] = True
```
3.3、获取文本内容
```python
driver.find_element_by_id("xxx").text
```
3.4、获取元素位置和大小
```python
# 获取元素的位置,返回值是字典,{'x': 111, 'y': 222},x和y分别为距离屏幕左上角的距离
driver.find_element_by_id("xxx").location
```
```python
# 获取元素大小,返回值是字典,{'width': 111, 'heigth': 222},width是元素宽度,heigth是元素高度
driver.find_element_by_di('xxx').size
```
3.5、获取元素的属性值
```python
# 获取元素enable的属性值
driver.find_element_by_id("xxx").get_attribute("enable")
# 获取元素text属性值,等价于 driver.find_element_by_id("xxx").text
driver.find_element_by_id("xxx").get_attribute("text")
# 获取元素content_desc/text属性值,如果content_desc属性值不为空,则输出content_desc属性值,为空,则输出text属性值
driver.find_element_by_id("xxx").get_attribute("name")
# 获取元素resource-id属性值
driver.find_element_by_id("xxx").get_attribute("resourceId")
# 获取元素class属性值
driver.find_element_by_id("xxx").get_attribute("className")
```
4、滑动和拖拽事件
4.1、swipe滑动事件
```python
# 从一个位置滑动到另一个坐标位置,两点之间的滑动
# duration:滑动操作的持续时间,单位为毫秒
driver.swipe(start_x, start_y, end_x, end_y, duration=None)
```
小结:距离相同,持续时间越长,惯性越小
4.2、scroll滑动事件
```python
# 从一个元素滑动到另外一个元素,直到页面自动停止(有惯性)
# 从'存储'滑动到'更多'
button1 = driver.find_element_by_xpath("//*[@text='存储']")
button2 = driver.find_element_by_xpath("//*[@text='更多']")
driver.scroll(button1, button2)
```
4.3、drag_and_drop拖拽事件
```python
# 从一个元素滑动到另外一个元素,第二个元素替代第一个元素原本屏幕上的位置(没有惯性)
# 从'存储'拖拽到'更多'
button1 = driver.find_element_by_xpath("//*[@text='存储']")
button2 = driver.find_element_by_xpath("//*[@text='更多']")
driver.drag_and_drop(button1, button2)
```
4.4、滑动和拖拽事件如何选择
滑动和拖拽无非就是考虑是否有“惯性”,以及传递的参数是“元素”还是“坐标”
可以分为一下四种情况:
4.4.1. 有惯性,传入元素
选择 scroll
4.4.2. 无惯性,传入元素
选择 drag_and_drop
4.4.3. 有惯性,传入坐标
选择swipe,设置较短的duration时间
4.4.4. 无惯性,传入坐标
选择swipe,设置较长的duration时间
5、高级手势TouchAction
应用场景:
TouchAction可以实现一些针对手势的操作,比如滑动、长按、拖动等。我们可以将这些基本手势组合成一个相对复杂的手势。比如,手机的手势解锁。
使用步骤:
- 创建TouchAction对象
- 通过对象调用想执行的手势
- 通过perform()执行动作
5.1、轻敲操作--tap
关键方法:tap
参数:
- 元素
- 坐标
- 轻敲次数
```python
# 确定要轻敲的元素
button = driver.find_element_by_xpath("//*[@text='xxx']")
# 1、创建TouchAction对象
touch_action = TouchAction(driver)
# 2、调用想要执行的动作
touch_action = touch_action.tap(button)
# 3、使用perform()执行动作
touch_action.perform()
# 代码简写
button = driver.find_element_by_xpath("//*[@text='xxx']")
TouchAction(driver).tap(button).perform()
```
5.2、按下和抬起操作--press\release
5.2.1、按下--press
```python
# 模拟手指对元素或坐标的按下操作
# 参数:
# el:元素
# x:x坐标
# y:y坐标
TouchAction(driver).press(el=None, x=None, y=None).perform()
```
5.2.2、抬起--release
```python
# 模拟手指对元素或坐标的抬起操作
TouchAction(driver).release().perform()
```
```python
# 在(111,222)坐标处先按下,再抬起操作
TouchAction(driver).press(x=111, y=222).release().perform()
```
5.3、等待操作--wait
```python
# 模拟手指暂停操作
# 参数:ms->暂停的毫秒数
TouchAction(driver).wait(ms=1000).perform()
```
```python
# 点击坐标(100, 200),2秒后,按下(300,400)位置,暂停2s后抬起
TouchAction(driver).tap(x=100, y=200).perform()
time.sleep(2)
TouchAction(driver).press(x=300, y=400).wait(ms=2000).release().perform()
```
5.4、长按操作--long_press
```python
# 模拟手指对元素或坐标的长按操作
# 参数:
# el:元素
# x:x坐标
# y:y坐标
# duration:长按时间,单位毫秒
TouchAction(driver).long_press(el=None, x=None, y=None, duration=1000).perform()
```
```python
# 点击坐标(100, 200),2秒后,按下(300,400)位置,暂停2s后抬起
TouchAction(driver).tap(x=100, y=200).perform()
time.sleep(2)
TouchAction(driver).long_press(x=300, y=400, duration=2000).perform()
```
5.5、移动操作--move_to
```python
# 模拟手指对元素或坐标的移动操作
# 参数:
# el:元素
# x:x坐标
# y:y坐标
TouchAction(driver).move_to(el=None, x=None, y=None).perform()
```
6、手机操作API
6.1、获取手机屏幕分辨率
```python
# 获取手机分辨率
# 返回值为字典,{'heigth': 100, 'width': 200}
driver.get_window_size()
```
6.2、手机截图
```python
# 可以在指定位置保存屏幕截图
# 参数:文件的路径
# 如果直接写文件名,则会在默认保存在当前路径下
get_screenshot_as_file(filename)
```
6.3、获取当前网络
```python
# 获取手机网络
print(driver.network_connection)
```
```python
# 设置手机网络
# 参数:connectionType 网络类型
# 0 --> None
# 1 --> Airplane Mode
# 0 --> Wifi only
# 0 --> Data only
# 0 --> All network on
driver.set_network_connection(connectionType)
```
6.4、发送键到设备
```python
# 发送键到设备
# 参数:
# keycode:发送给设备的关键代码,可百度搜索“Android keycode”
# metastate:关于被发送的关键代码的元信息,一般为默认值
driver.press_keycode(keycode, metastate=None)
```
6.5、操作手机通知栏
```python
# 打开手机通知栏
# appium未提供关闭通知栏的api,需要模拟用户实际操作关闭通知栏,如:手指往上滑、点击返回键
driver.open_notifications()
```
```python
# 打开通知栏,两秒后关闭通知栏
driver.open_notifications()
time.sleep(2)
driver.press_keycode(4)
常用函数
1.获取设备包名、活动名、设备名函数
def get_appPackage(apk):
"""
:param apk:apk的存放路径,例如:D:/apk/1434839438204272641-**Shop-v1.0.0.960-testus-debug-autel.apk
注意:如果apk名称中是带有空格的,直接复制apk名称会无法找到对应的文件,因此需要检查名称并根据需要修改apk命名
实际返回的包名是代码中定义的,与apk叫什么名字无关
:return: 返回appPackage
"""
appPackage_order = "aapt dump badging " + apk
LOG.info("获取appPackage的执行命令为:{}".format(appPackage_order))
re_text= subprocess.Popen(appPackage_order,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE).stdout.read()
#LOG.info("获取到的设备包名及活动名为:{}".format(re_text))
appPackage_pattern = re.compile(r"package: name='(.*?)'")
appPackage = appPackage_pattern.findall(re_text.decode('utf-8'))[0]
LOG.info("获取到的包名为:{}".format(appPackage))
return appPackage
2.获取appActivity的函数
def get_appActivity(apk):
"""
:param apk: apk名称,同上述appPackage中所述
:return: 返回appActivity
"""
appActivity_order = "aapt dump badging " + apk
LOG.info("获取appActivity_order的执行命令为:{}".format(appActivity_order))
re_text = subprocess.Popen(appActivity_order, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).stdout.read()
#LOG.info("获取到的设备包名及活动名为:{}".format(re_text))
appActivity_pattern = re.compile(r"launchable-activity: name='(.*?)'")
appActivity = appActivity_pattern.findall(re_text.decode('utf-8'))[0]
LOG.info("获取到的活动名为:{}".format(appActivity))
return appActivity
3.获取deviceName函数
def get_deviceName():
deviceName_order = "adb devices -l"
re_text = subprocess.Popen(deviceName_order, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).stdout.read()
deviceName_pattern = re.compile(r"model:(.*?) ")
deviceName = deviceName_pattern.findall(re_text.decode('utf-8'))[0]
LOG.info("获取到的设备名称为:{}".format(deviceName))
return deviceName
4.获取安卓设备系统版本号函数
def get_android_version():
version_order = "adb shell getprop ro.build.version.release"
"""这种方式获取到的内容都是字节类型,因此需要通过decode方法解码为字符串
"""
re_text = subprocess.Popen(version_order, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).stdout.read()
version = re_text.decode('utf-8').split('\r')[0]
LOG.info("安卓设备的版本号为:{}".format(version))
return version
5.处理授权弹框函数
def deal_login_permission(driver,number):
"""
:param driver: webdriver
:param number: 根据第一次安装应用时弹出的确认授权框的数量传值
:return:
"""
for i in range(number):
auth = ("xpath", "//*[@text='允许']")
try:
WebDriverWait(driver, 3, 0.5).until(EC.presence_of_element_located(auth)).click()
except:
LOG.info("登录时未弹出确认授权信息")
6.获取弹框中的文本
def toast_exist(driver, toastmessage):
loc = '//*[contains(@text,"{}")]'.format(toastmessage)
# 等待的时候,要用元素存在的条件。不能用元素可见的条件。visibility_of_element_located对toast的处理并不支持,会直接报错
try:
WebDriverWait(driver, 10, 0.01).until(EC.presence_of_element_located((MobileBy.XPATH, loc)))
# 上限10秒就够了,确认toast在页面上存在的时候大概是多久,一般这种提示消息时间很短,如果都没有0.5秒,你去间隔0.5,可能消失了,你还只留在这。
toast_text = driver.find_element_by_xpath(loc).text
LOG.info("toast提示消息为:{}".format(toast_text))
return toast_text
except:
LOG.info("没有找到匹配的toast!!!!")
7.处理页面滑动
def get_size(self):
"""获取屏幕分辨率."""
rect = self.driver.get_window_size()
return rect['width'], rect['height']
def swipe_by_ratio(self, start_x, start_y, direction, ratio, duration=None):
"""
按照屏幕比例的滑动.
:param start_x: 起始横坐标
:param start_y: 起始纵坐标
:param direction: 滑动方向,只支持'up'、'down'、'left'、'right'四种方向参数
:param ratio: 滑动距离与屏幕的比例,范围0到1
:param duration: 滑动时间,单位ms
:return:
"""
direction_list = ['up', 'down', 'left', 'right']
if direction not in direction_list:
LOG.info('滑动方向%s不支持', direction)
width, height = self.get_size()
def swipe_up():
"""上滑."""
end_y = start_y - ratio * height
if end_y < 0:
LOG.info('上滑距离过大')
return False
else:
self.driver.swipe(start_x, start_y, start_x, end_y, duration)
return True
def swipe_down():
"""下滑."""
end_y = start_y + ratio * height
if end_y > height:
LOG.info('下滑距离过大')
return False
else:
self.driver.swipe(start_x, start_y, start_x, end_y, duration)
return True
def swipe_left():
"""左滑."""
end_x = start_x - ratio * width
if end_x < 0:
LOG.info('左滑距离过大')
return False
else:
self.driver.swipe(start_x, start_y, end_x, start_y, duration)
return True
def swipe_right():
"""右滑."""
end_x = start_x + ratio * width
if end_x > width:
LOG.info('右滑距离过大')
return False
else:
self.driver.swipe(start_x, start_y, end_x, start_y, duration)
return True
swipe_dict = {'up': swipe_up, 'down': swipe_down, 'left': swipe_left,
'right': swipe_right}
return swipe_dict[direction]()