在定位APP控件时,需要将APP页面dump到uiautomatorviewer中,然后通过uiautomatorviewer定位控件
能够将手机界面dump出来的前提:
1.打开appium并处于监听状态
2.电脑已开启模拟器或连接真机
3.adb已连接(cmd中输入adb devices,会显示已有设备连接)
- uiautomatorviewer打开方式
双击sdk\tools下的uiautomatorviewer.bat文件,等待出现第二个窗口
APP中的3种属性:text、resource-id、class,在APP中,id/class_name基本上都不是唯一的,xpath可以写成唯一的
定位方式 | 说明 |
---|---|
find_element_by_id | 找到一个,如果当前界面有多个相同id的控件,则找到第一个 |
find_elements_by_id | 找到一组,平行结构的ID可能是一样的,下标从0开始 |
ele1 = driver.find_element_by_id("cn.xiaochuankeji.tieba:id/title")
print(ele1.text) #获取文本信息
ele1.click() #点击
ele2 = driver.find_elements_by_id("cn.xiaochuankeji.tieba:id/title") #找到一组,平行结构的ID可能是一样的
ele2[2].click() #通过下标区分
定位方式 | 说明 |
---|---|
find_element_by_class_name | 找到一个,如果当前界面有多个相同class的控件,则找到第一个 |
find_elements_by_class_name | 找到一组,平行结构的class可能是一样的,下标从0开始 |
#当前界面只有一个相同的class属性控件,使用element
ele3 = driver.find_element_by_class_name("android.widget.TextView")
#当前界面有很多相同的class属性控件,使用elements
# 会把当前所有相同class的控件都找出来,再通过下标定位具体控件
ele4 = driver.find_elements_by_class_name("android.widget.TextView") #ele4此时是一个列表,表中放了很多控件
#对于文本控件,可加下面的日志,查看对应下标
for i in range(0,len(ele4)):
print("第"+str(i)+"个="+ele4[i].text) #将每个控件的文本信息及其下标打印出来(i是整型,无法与字符串直接连接,故转换成字符串)
ele4[3].click() #点击想要的控件
#对于图片控件,根据dom树来确定下标,将没展开的都展开,查看ImageView,下标是从0开始
-使用xpath定位格式://class属性[@resource-id=‘id属性’]
通过xpath定位注意点 |
---|
xpath通过id查找单个元素(定位关注按钮),使用element |
xpath通过text文本定位,如果文本是动态的(比如发的朋友圈动态),尽量不要@text属性 |
xpath通过text文本定位,无控件属性时,可使用正则代替,如//[text=‘图文’] |
xpath可通过class查找单个元素,class既能当一种控件类型,也能当一种属性 |
使用多个属性定位,使用and连接,如//*[@resource-id=‘cn.xiaochuankeji.tieba:id/title’ and @text=‘视频’] |
通过层级定位:父级找子级,先定位到父级,父级后跟/,然后后面跟子级的控件类型,xpath下标从1开始 |
子级定位到父级,可使用…实现 |
使用xpath定位肯定想要路径是唯一的,所以尽量不要使用find_elements这种方法 |
#xpath通过id查找单个元素(定位关注按钮),使用element
ele5 = driver.find_element_by_xpath("//android.widget.TextView[@resource-id='cn.xiaochuankeji.tieba:id/title']")
ele5.click()
#xpath通过text文本定位
#注意:如果文本是动态的(比如发的朋友圈动态),尽量不要@text属性
ele7 = driver.find_element_by_xpath("//android.widget.TextView[@text='关注']")
ele7.click()
#xpath通过text文本定位,无控件属性
ele8 = driver.find_element_by_xpath("//*[text='图文']")
ele8.click()
#xpath通过class查找单个元素(定位发布话题中的edittext)
driver.find_element_by_xpath("//android.widget.EditText").click()
#class既能当一种控件类型,也能当一种属性
driver.find_element_by_xpath("//*[@class='android.widget.EditText']").click()
#组合定位(定位视频,定位搜索)
#使用多个属性定位,使用and连接
driver.find_element_by_xpath("//*[@resource-id='cn.xiaochuankeji.tieba:id/title' and @text='视频']").click()
#通过层级定位:父级找子级,搜索按钮
#先定位到父级,父级后跟/,然后后面跟子级的控件类型,xpath下标从1开始
driver.find_element_by_xpath("//android.widget.FrameLayout[@resource-id='cn.xiaochuankeji.tieba:id/search_b']/android.widget.ImageView[1]")
#定位兄弟级控件1:从子级定位父级(我的按钮),再从父级定位到兄弟级
#我->爸爸->我哥
driver.find_element_by_xpath("//*[@text='我的']/../android.widget.ImageView").click()
#定位兄弟级控件2:先找到父级的父级,然后找到父级的兄弟级的子级(最右按钮上的文案信息)
#我->爸爸->爷爷->大伯->大伯的儿子
driver.find_element_by_xpath("//*[@text='最右']/../../android.view.View[3]/android.widget.TextView").text
使用xpath定位肯定想要路径是唯一的,所以尽量不要使用find_elements这种方法
#xpath通过id查找多个元素(定位发布话题按钮),使用elements,列表下标从0开始
#使用xpath定位肯定想要路径是唯一的,所以尽量不要使用find_elements这种方法
ele6 = driver.find_elements_by_xpath("//android.widget.TextView[@resource-id='cn.xiaochuankeji.tieba:id/title']")
ele6[2].click() #点击第3个
动作 | 代码实现 |
---|---|
控件点击 | .click() |
文本输入 | send_keys() |
清空内容 | .clear() |
获取控件文本信息 | .text |
获取手机屏幕分辨率 | get_window_size() |
#控件点击:.click() 适用于任何控件
ele = driver.find_element_by_id("cn.xiaochuankeji.tieba:id/ic_search_b")
ele.click()
#控件输入:send_keys() 适用于输入框(属性为EditText)
#清空内容:.clear()
a = driver.find_element_by_id("cn.xiaochuankeji.tieba:id/search_input")
a.send_keys('123') #输入内容
a.clear() #清空内容
#获取控件文本信息:.text
ele = driver.find_element_by_id("cn.xiaochuankeji.tieba:id/ic_search_b")
print(ele.text) #获取控件文本信息,经常在断言时使用,很重要!!!
#获取手机屏幕分辨率:get_window_size(),返回的是一个字典类型
height = driver.get_window_size()['height'] #获取高,返回的是float类型
width = driver.get_window_size()['width'] #获取宽,返回的是float类型
print("此手机的分辨率是:"+str(height)+'*'+str(width))
动作 | 代码实现 |
---|---|
获取当前界面activity信息 | .current_activity |
获取控件的属性值 | get_attribute() |
判断控件是否显示 | is_displayed() |
判断控件是否可用 | is_enabled() |
#获取当前界面activity信息
cur_activity = driver.current_activity
print(cur_activity)
#获取控件的属性值:get_attribute
ele = driver.find_element_by_id("cn.xiaochuankeji.tieba:id/ic_search_b")
print(ele.get_attribute("className")) #获取控件的属性值
print(ele.get_attribute("resourceId"))
ele = driver.find_element_by_id("cn.xiaochuankeji.tieba:id/ic_search_b")
print(ele.is_displayed()) #判断控件是否显示,返回布尔类型
print(ele.is_enabled()) #判断控件是否可用,返回布尔类型,可点击返回True,不可点击返回False
#实现页面滑动:swipe
#要实现页面滑动,必须要数据加载出来才可以
driver.implicitly_wait(60) #先等待60秒,直到当前界面的某个控件出现
driver.find_element_by_id("cn.xiaochuankeji.tieba:id/ic_search_b").is_displayed()
time.sleep(5) #再休眠5秒(给加载数据提供时间)
#从上到下:y值变大
#x不变,y从小变大:下滑 x可以取屏幕分辨率宽的中间值,如720的屏幕,x可取360
#x不变,y从大变小:上滑
driver.swipe(500,1000,500,200,3000) #上滑
#500和1000分别是x/y的起始坐标,500和200是x/y的终止坐标,3000:持续时间3000毫秒
driver.swipe(500,200,500,1000,3000) #下滑
#从左到右:x值变大
#y不变,x从大变小:左滑
#y不变,x从小变大:右滑
driver.swipe(400,500,200,500,3000) #左滑
driver.swipe(200,500,400,500,3000) #右滑
driver.swipe(500,200,500,200,100) #点击:x和y不变,持续时间很短
driver.swipe(500,200,500,200,3000) #长按:x和y不变,持续时间较长
#直接乘以坐标所占屏幕的比例,可实现不论手机屏幕分辨率是多少,代码都可以使用
height = driver.get_window_size()['height']
width = driver.get_window_size()['width']
driver.swipe(width*0.555,height*0.855,width*0.555,height*0.14,3000)
(1)查看控件的布局范围:
在Ui Automator Viewer中dump一下界面,然后点击该控件,找到bounds,后面对应的就是控件布局范围,如[17,1083][703,1108] ,x的布局范围是17到703,y的布局范围是1083到1108
(2)坐标的相对位置获取:可在一个正在调试的手机上,如分辨率为1280*720,找到想要想要实现的坐标移动点,如(400,1095,400,180),x/屏宽,y/屏高,可以取比例获取坐标的相对位置,如计算方式:400/720=0.555 1095/1280=0.855 180/1280=0.14
使用TouchAction前需要先导入TouchAction类
from appium.webdriver.common.touch_action import TouchAction
#点击控件
ele = driver.find_element_by_id("cn.xiaochuankeji.tieba:id/ic_search_b")
TouchAction(driver).tap(ele).perform()
#点击坐标
TouchAction(driver).tap(x=100,y=160).perform()
#也可以使用相对比例的坐标
height = driver.get_window_size()['height']
width = driver.get_window_size()['width']
TouchAction(driver).tap(x=width*0.2, y=height*0.5).perform()
#press某个点
TouchAction(driver).press(x=500, y=308).release().perform()
#release:释放,按下去再释放-相当于点击
#长按某个点
TouchAction(driver).long_press(x=500, y=308,duration=5000).release().perform()
#duration=5000:持续时间5秒
#长按某个控件
ele = driver.find_element_by_id("cn.xiaochuankeji.tieba:id/expand_content_view")
TouchAction(driver).long_press(ele).perform()
安卓按键(Android keycode),可以到网上搜索每个按键对应的数值
https://blog.csdn.net/midux/article/details/80064054
常用按键:
driver.keyevent(4) #直接传入对应的键值,传入4代表返回上一层
#截图:get_screenshot_as_file('路径/图片名称.png')
driver.implicitly_wait(60)
el = driver.find_element_by_id("cn.xiaochuankeji.tieba:id/ic_search_b")
driver.get_screenshot_as_file("E:/newCourse/test.png")
desired_caps['unicodeKeyboard'] = 'True' #给手机安装一个appium的输入法
desired_caps['resetKeyboard'] = 'True' #重置键盘,使appium输入法为默认输入法
在做APP自动化时,需要让每条用例都没有相互依赖关系,最简单粗暴的方法就是每次跑完一条用例时,将APP卸载掉,跑下一条用例时,再重新安装。
desired_caps['fullReset'] = 'True' #在初始化时添加这个参数后,每次跑完之后就会把APP卸载掉
desired_caps['app'] = 'E:/newCourse/zuiyou518.apk' #每次开始时会先安装APP
扩展:
APP卸载后,会清除用户操作产生的数据,而用户新建的数据来源于后端,不会被清除。比如收藏时会产生一个收藏标志,登录后会有一个登录记录,会被清除。
而用户自己新建的数据,如果是以用户区分:
登录抖音后收藏了某条视频,然后将APP卸载后,下次安装时收藏数据仍然存在(因为这个数据是记录在用户下的),而登录状态在卸载时是会被清除的,下次安装后需要重新登录。
如果新建了数据,又要使用这个数据,需要将这些放在一条用例中。
如果不想重置APP也可以,需要每次都查看一下登录状态,确认已经登录成功。在“我的”页面检查是否登录成功,如果是未登录状态,就要调用登录方法
是否重置APP需要视具体业务场景而定。 如果业务与本地数据清除没有太大关系,就可以不重置(使用noReset),如果数据依赖关系太强,则需要重置。
强交互的场景是不做自动化的。强交互一般是后端出问题,与UI自动化没有多大关系
对于toast(发送信息后提示发送成功的黑色弹窗),需要加automationName参数,因为appium1.6.3以上版本才支持toast处理,之前装的是Uiautomator一代,所以需要加Uiautomator2对toast进行处理
#在初始化时加automationName参数
desired_caps['automationName'] = 'Uiautomator2'
#需要导入的库
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
#在定位控件、输入内容并点击发送后会弹出toast,toast定位:
toast_loc = ("xpath",'//*[contains(@text,"评论发送成功")]') #将要定位的控件通过元组罗列出来,再把元组传进去
ele = WebDriverWait(driver,20,0.1).until(EC.presence_of_element_located(toast_loc))
#最大等待时间20秒,每隔0.1秒检测1次,直到当前界面存在了评论发送成功弹窗
print(ele.text) #打印控件属性