移动端app自动化测试

Menu:appium环境安装与架构介绍

目前移动端自动化解决方案
appium介绍

	appium是一个移动端的自动化测试框架,可用于测试原生应用,
移动网页应用和混合应用(原生应用嵌套网页,比如微信小程序),且是跨平台的
	appium是由nodejs开发的,所以先要安装nodejs环境
appium框架介绍

appium生态工具
	adb:android的控制工具,用于获取android的各种数据和控制
	Appium Desktop:内嵌了appium server 和 inspector的综合工具
	Appium Server:appium的核心工具,命令行工具
	Appium client: 各种语言的客户端封装库,用于连接appium server
		python、java、ruby、robotframework-appium介绍
	AppCrawler自动遍历工具

appium环境安装
#测试安卓手机
	Java 1.8版本
	Android sdk 
	#appium是由nodejs开发的,所以先要安装nodejs环境
	Node js (>=10版本),npm(>=6版本) #安装完Node js会自动安装一个npm(包的管理工具)的工具
	python3
	appium-desktop (录制回放工具)
	Appium python client	#通过该库编写自动化脚本

#手机端、客户端、server端
手机端需要支持sdk、jdk
客户端需要安装Appium python client
appium server端需要安装node js和appium server(或者是appium desktop) #appium desktop自带appium server

安装jdk:
	官网下载地址:http://www.oracle.com/technetwork/java/javase/downloads/index.html
	#Java 1.8版本下载路径:https://www.oracle.com/java/technologies/javase/javase-jdk8-downloads.html
	安装(一直点下一步->完成,用默认路径即可)
	配置环境变量(Windows)
		JAVA_HOME D:\Android\Java\jdk1.8.0_25 (注意:这里的JAVA_HOME大写,后面会用到)
		classpath	.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;	#最前面加个点和分号.;
		path		%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;
	检查JAVA环境是否配置好
	进入命令行,输入java -version 或javac -version,输出java版本信息即可
Mac配置环境变量
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210523233218618.PNG?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0FPTElHRUlfMjAxOQ==,size_16,color_FFFFFF,t_70#pic_center)


安装Node js
首先从官网下载Nodejs (https://nodejs.org/en/download/ ),推荐LTS版本,比较稳定 官网下载地址 89
通过命令行确认nodejs安装正确,在命令行运行node -v和npm -v,没有报错并且能够显示对应的版本,说明安装正常,可以进行下一步
#node12版本下载地址: https://nodejs.org/dist/latest-v12.x/
建议安装装node12版本,	#  将C:\Program Files\nodejs 加入环境变量
#参考链接: https://ceshiren.com/t/topic/4004
	
安装SDK
	下载sdk
		Android studio地址 https://developer.android.com/studio/index.html	#通过下载Android studio的方式实现下载sdk
		中文官网下载地址:http://tools.android-studio.org/index.php/sdk		#自己下载sdk
			#zip安装方式
			#  D:\迅雷下载\android-sdk_r24.4.1-windows\android-sdk-windows CMD在该路径下执行下面命令来更新SDK
			#下载完sdk,进行解压,阅读SDK Readme文档,CMD执行指令 tools\android.bat update sdk --no-ui 更新SDK,
	安装sdk
		其实sdk就是个文件夹,下载之后需要手动更新,配上环境变量就可以使用,不需要手动安装
	配置android SDK环境变量如下:
		ANDROID_HOME   D:\adt-bundle-mac-x86_64-20140702\sdk			#指定sdk路径下
		PATH 	%ANDROID_HOME%\tools;%ANDROID_HOME%\platform-tools
	检查是否安装成功,cmd输出
		adb回车或者adb shell 然后回车
		环境变量配置完成后可以输入uiautomatorviewer来查看uiautomatorviewer是否安装正确
		#uiautomatorviewer定位工具使用(ONLY ANDROID)
		
		#如果运行上述命令报错  ‘adb.exe无法运行,提示0xc000007b错误’,需要安装 Microsoft Visual C++ Visual Studio 2017
		# Microsoft Visual C++ Visual Studio 2017
		#X64  
		#https://go.microsoft.com/fwlink/?LinkId=746572
		#x86
		#https://go.microsoft.com/fwlink/?LinkId=746571
		
	#官方网站建议安装sdk:installer_r24.4.1-windows.exe(Recommended)
		sdk安装路径C:\Users\Banana\AppData\Local\Android\android-sdk
		一定注意build-tools 30版本开始build-tools配合jdk1.8会报错,对应jdk1.8建议使用build-tools29.0.3 
		#如果安装版本匹配不上,可以重新安装build-tools29.0.3版本
		#C:\Users\Banana\Downloads\android-sdk_r24.4.1-windows\android-sdk-windows\build-tools\29.0.3
		#例如 build-tools 安装路径 	C:\Users\Banana\Downloads\android-sdk_r24.4.1-windows\android-sdk-windows

		# https://ceshiren.com/t/topic/4001
		
appium环境安装
	安装appium desktop(appium server + appium inspector工具) #前期需要录制功能可考虑使用appium desktop	
		下载对应操作系统的安装包:https://github.com/appium/appium-desktop/releases
		如果不需要appium inspector,也可以通过npm直接安装appium
			官方安装(不推荐)
				npm install -g appium
			淘宝提供(推荐)
				npm install -g cnpm --registry=https://registry.npm.taobao.org
				cnpm install -g appium		#执行该条命令行命令可能会报错,此时连接VPN,运行npm install -g appium ,如果报错提示文件已存在,找到该路径,依次删除重复的文件后再次运行该命令即可
			运行
				appium (不报错说明安装成功)
				
				
安装appium python client
	方式一: pip install appium-python-client(推荐)
	方式二: 下载源码包:
		下载地址:https://github.com/appium/python-client
				  https://pypi.python.org/pypi/Appium-Python-Client
		解压后在命令行中进入python-client-master目录,该目录下包含setup.py文件
		执行命令python setup.py install 命令安装客户端
	检查是否安装成功,import appium 如果没报错则证明安装成功
		
		
		
所有环节安装完成后可安装appium-doctor检测appium的安装环节
	cnpm install appium-doctor
在命令行执行appium-doctor

运行测试用例
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210523233127425.PNG?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0FPTElHRUlfMjAxOQ==,size_16,color_FFFFFF,t_70#pic_center)

Menu:appium用例录制

android自动化前提依赖
appium desktop功能介绍
利用appium desktop生成用例模板
	获取app信息
	配置待测应用的信息(desire capability)
	导出python语言的用例
	添加隐式等待增强用例稳定性
		
android自动化前提依赖
	adb工具
	模拟器or真机
		模拟器:网易mumu(只能对Android 6.0版本进行自动化测试),genimotion,或者sdk自带模拟器(需要安装Android Studio,有一个管理模拟器的工具)
		真机需要root权限
			使用网易mumu模拟器:adb如何连接mumu模拟器?
				【win版】
				adb connect 127.0.0.1:7555
				adb shell

				【mac版】
				adb kill-server && adb server && adb shell
	Appium Desktop:入门学习工具
		
appium desktop功能介绍
	UI分析
	录制用例
	元素查找测试
	Attach已有的session
	云测试(一般是国外的云测平台)
	
	Android: 			#安卓有包和页面的概念
		appActivity		#每一个页面都可以叫做appActivity,一般是欢迎页和首页
		appPackage		#安装包
	
	iOS:  	#iOS和安卓不太一致
	
adb devices							#adb命令,查看所有连接的设备和模拟器
#先运行命令,打开日志,然后启动被测应用,查找日志,获取包名和appActivity
adb logcat | grep -i displayed		#adb命令 'adb logcat' 打印手机上的日志 'grep' 查找日志 


获取app的信息
	app信息
		获取当前界面元素: adb shell dumpsys activity top	(推荐)(重点)
		获取任务列表:	 adb shell dumpsys activity	activities
	app入口
		adb shell dumpsys window | grep mCurrent	#直接获取当前app停留的Activity页面入口 (重点)
		adb logcat | grep -i displayed  (推荐)(重点)
		aapt dump badging mobike.apk | grep launchable-activity
		apkanalyzer最新版本的sdk中才有
	启动应用
		adb shell am start -W -n com.xueqiu.android/.view.WelcomeActivityAlias -S  (重点)
		#com.xueqiu.android/.view.WelcomeActivityAlias  appPackage(包名)和appActivity

元素查找tips:
resource-id   为id查找

#caps设置参数说明
"noReset":True		#对历史数据的处理进行记录,记录上一次的操作信息,不是每次启动应用都清空缓存

Menu:appium元素定位与隐式等待

desirecapability介绍
appium元素定位与隐式等待
隐式等待

测试用例的重要部分
	导入依赖
		from appium import webdriver
	capabilities设置
	初始化driver:
		python webdriver.remote
	隐式等待,增强用例稳定性
	元素定位与操作 find+action	
	断言assert	
		
Capability设置
app 						#apk地址
appPackage 					#包名		
appActivity 				#Activity名字
automationName				#工作引擎,安卓默认使用uiautomator2(安卓默认使用uiautomator2,ios默认使用XCUITest)
noReset fullReset			#是否在测试前后重置相关环境(例如首次打开出现弹框,或者是登录信息),记录上次用户操作完后的信息,假如用户关闭了广告弹框,则下次不会再次出现广告弹框
							#演示雪球的首次启动弹框功能,norest=True,norest=false情况
unicodeKeyBoard 			#支持输入中文参数
resetKeyBoard       		#resetKeyBoard是否需要输入非英文之外的语言并在测试完成后重置输入法
							#举例输入中文,alibaba,阿里巴巴
dontStopAppOnReset			#首次启动的时候,不停止app(可以调试或者运行的时候提升运行速度),页面停留在某个页面上会继续进行操作,不会关闭APP后再打开运行
skipDeviceInitialization	#跳过安装,权限设置等操作(可以调试或者运行的时候提升运行速度)

在实际测试过程中,根据手工测试来进行操作,来判断需要使用几次driver.back()
driver.back()		#返回到上一个页面
				
appium元素定位
常用的俩种定位方式id,accessibility_id
	#对应属性值为resource-id
	driver.find_element_by_id(resource-id)
	#对应属性值为content-desc
	driver.find_element_by_accessibility_id(content-desc)
from appium import webdriver

#adb shell am start -W -n com.xueqiu.android/.view.WelcomeActivityAlias -S      #启动app命令
desired_caps = {}
desired_caps['platformName'] = 'Android'
desired_caps['platformVersion'] = '6.0'
desired_caps['deviceName'] = 'device'
desired_caps['appPackage'] = 'com.xueqiu.android'
#Activity名字 带上包路径,注意路径和Activity名字中间是.
desired_caps['appActivity'] = 'com.xueqiu.android.main.view.MainActivity'


#是否在测试前后重置相关环境(例如首次打开出现弹框,或者是登录信息),记录上次用户操作完后的信息,假如用户关闭了广告弹框,则下次不会再次出现广告弹框
desired_caps['noReset'] = True

#首次启动的时候,不停止app(可以调试或者运行的时候提升运行速度),页面停留在某个页面上会继续进行操作,不会关闭APP后再打开运行
# desired_caps['dontStopAppOnReset'] = True

#跳过安装,权限设置等操作(可以调试或者运行的时候提升运行速度)
desired_caps['skipDeviceInitialization'] = True

#支持输入中文参数
desired_caps['unicodeKeyBoard'] = True
#resetKeyBoard是否需要输入非英文之外的语言并在测试完成后重置输入法
desired_caps['resetKeyBoard'] = True

driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
driver.implicitly_wait(5)

driver.find_element_by_id('com.xueqiu.android:id/home_search').click()
driver.find_element_by_id('com.xueqiu.android:id/search_input_text').send_keys("阿里巴巴")
#在搜索结果里面选择‘阿里巴巴’   通过多个条件resource-id(记住不要写成id)和text来一起定位
driver.find_element_by_xpath('//*[@resource-id="com.xueqiu.android:id/name" and @text = "阿里巴巴"]').click()
#获取‘阿里巴巴’的股价,判断股价的价格>200
assert float(driver.find_element_by_id("com.xueqiu.android:id/current_price").text) >200
# driver.back()         #配合desired_caps["dontStopAppOnReset"]使用,根据手工测试来进行操作,来判断需要使用几次driver.back()
# driver.back()

# driver.quit()

Menu:app控件定位

android/ios基础知识
	Android基础知识:
		Android 布局,通过布局嵌套完成页面比较复杂的操作
		Android四大组件
			activity    		#与用户交互的可视化界面
			service				#实现程序后台运行的解决方案
			content provider	#内容提供者,提供程序所需要的数据
			broadcast receiver	#广播接收器,监听外部事件的到来(比如来电)
		iOS基础知识:
		iOS 去掉了布局的概念,直接用变量之间的相对关系完成位置的计算
		Android与iOS区别:
			dom属性和节点结构类似
			名字和属性的命名不同(比如 android resourceid  = iOS name  , android content-desc = iOS accessibility_id)

		
	dom结构解读 
		元素定位,实际上就是定位控件
		要想同一个脚本同时支持android/iOS 俩个系统,就得保证元素属性(id、aid、xpath等)一致
		
		控件基础知识:
			Android应用的层级结构与html不一样,是一个定制的xml
			(安卓)app source类似于dom,表示app的层级,代表了界面里面的控件数结构
			每个控件都有它的属性(resourceid,xpath,aid),没有css属性
	id、aid、xpath定位方法
		定位方式:
			id定位
			accessibility_id定位
			xpath定位
			classname定位(不推荐)

	uiautomatorviewer定位工具使用(ONLY ANDROID)
		使用uiautomatorviewer定位工具时请注意关闭appium,不然会报错Remote object doesn’t exist!
		3、修改mumu分辨率为竖屏,eg:宽:720,高:1280
	

测试步骤三要素:
	定位、交互、断言

Menu:app控件交互

元素常用方法
元素常用属性

元素常用方法
	点击方法		element.click()
	输入操作		element.send_keys("appium")			#必须是可编辑的元素
	设置元素的值	element.set_value("appium")			#必须是可编辑的元素
	清除操作		element.clear()						#必须是可编辑的元素
	
	是否可见		element.is_displayed()				#返回True/Flase
	是否可用		element.is_enabled()				#返回True/Flase
	是否被选中		element.is_selected()				#返回True/Flase
	
	获取属性值      get_attribute(name)
	
元素常用属性
	获取元素文本 	element.text
	获取元素坐标	element.location
	获取元素尺寸	element.size
	
案例:
1.打开【雪球】应用首页
2.定位首页的搜索框
3.判断搜索框是否可用,并查看搜索框name属性值
4.打印搜索框这个元素的左上角坐标和尺寸
5.搜索框输入‘alibaba’
6.判断 ‘阿里巴巴’是否可见			#在该案例中,元素是否可见通过 get_attribute("displayed")来获取属性值,该属性值为字符串'true',不是BOOL类型,需要注意
7.如果可见,打印“搜索成功”点击,如果不可见,打印“搜索失败”

Menu:触屏操作自动化

TouchAction用法
	wait(200)	#操作过程中使用wait()方法,此处表示等待200ms
	tap()、press()、long_press()、move_to()方法后都可以直接跟wait()方法	
	#栗子:aciton.press(x=142,y=190).wait(200)
#滑动小案例
from time import sleep
from appium import webdriver
from appium.webdriver.common.touch_action import TouchAction

'''
进入雪球应用
再主页从下往上滑动
避免使用坐标(代码用获取屏幕的长宽来解决这个问题)
'''
class TestXueQiu():
    def setup(self):
        self.desire_cap= {
            "platformName":"android",
            "deviceName":"127.0.0.1:7555",
            "appPackage":"com.xueqiu.android",
            "appActivity":".view.WelcomeActivityAlias",
            "noReset":"true",
            "unicodeKeyboard":True
        }
        self.driver=webdriver.Remote("http://127.0.0.1:4723/wd/hub",self.desire_cap)
        self.driver.implicitly_wait(5)

    def test_search(self):
        """
        1.进入雪球应用
        2.再主页从下往上滑动
        3.避免使用坐标
        :return:
        """
        #由于雪球真的是太慢了,所以睡10秒
        sleep(10)
        #定义一个TouchAcion对象
        aciton=TouchAction(self.driver)
        #获取整个屏幕的右下角的坐标
        window_rect=self.driver.get_window_rect()
        #提取屏幕的最大的宽
        width=window_rect["width"]
        #提取屏幕的最大的高度
        height=window_rect['height']
        #x的坐标定义为最大宽的一半,也就是中心的x坐标
        x1=int(width/2)
        #定义起始的y坐标,在4/5的底部位置
        y_start=int(height* 4/5)
        #定义终点的y坐标,在1/5顶部的位置,这样就可以模拟从下往上滑动的动作
        y_end=int(height* 1/5)
        #先press点击初始的坐标,然后按住不放等2秒再move_to到终点坐标,然后再release()释放坐标点,用perform()去执行一系列action操作
        aciton.press(x=x1,y=y_start).wait(2000).move_to(x=x1,y=y_end).release().perform()
        #重复两次,看的效果更明显
        aciton.press(x=x1, y=y_start).wait(2000).move_to(x=x1, y=y_end).release().perform()
        aciton.press(x=x1, y=y_start).wait(2000).move_to(x=x1, y=y_end).release().perform()
        sleep(3)
#滑动多点解锁
from time import sleep
from appium import webdriver
from appium.webdriver.common.touch_action import TouchAction

'''
得下载一个叫手势密码锁的app,百度一下有
进入解锁的页面
设置解锁密码为一个7字
意外发现appium可以指定去不同的初始的activity,好像也是看应用的
'''

class TestTouchAction:

    def setup(self):
        desired_caps = {}
        desired_caps['platformName'] = 'Android'
        desired_caps['platformVersion'] = '6.0'
        desired_caps['deviceName'] = 'device'
        desired_caps['appPackage'] = 'cn.kmob.screenfingermovelock'
        # Activity名字 带上包路径,注意路径和Activity名字中间是.  Activity名字也可以不带包名
        desired_caps['appActivity'] = 'com.samsung.ui.MainActivity'
        self.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
        self.driver.implicitly_wait(5)

    def teardown(self):
        self.driver.quit()

    def test_search(self):
        """
        1.进入解锁的页面
        2.设置解锁密码为一个7字
        3.意外发现appium可以指定去不同的初始的activity,好像也是看应用的
        :return:
        """
        sleep(3)
        #定义一个TouchAcion对象
        aciton=TouchAction(self.driver)
        #找到7个坐标点,通过连续的press,wait,move_to,最后释放手势release(),然后perform()执行即可
        #wait(200)  等待200ms
        aciton.press(x=142,y=190).wait(200).move_to(x=408,y=190).wait(200).move_to(x=678,y=190).wait(200).move_to(x=678,y=464) \
        .wait(200).move_to(x=678,y=740).release().perform()
        sleep(2)

Menu:7.高级定位技巧

xpath定位进阶
	#//*[@resource-id="com.xueqiu.android:id/title_container"] 先找到父节点 
	#//*[@resource-id="com.xueqiu.android:id/title_text"] 再找到子节点
	定位股票元素://*[@resource-id="com.xueqiu.android:id/title_container"]//*[@resource-id="com.xueqiu.android:id/title_text"][2]
	#..   			查找父节点
	#//*			如果是孙子节点要使用相对路径查找

uiautomator定位表达式
	uiautomator查找元素
		用法网站:
	优缺点
		优点
			xpath定位速度慢
			uiautomator是Android的工作引擎,速度快
			滚动查找很方便
		缺点
			表达式书写复杂,容易写错IDE没有提示
	定位方式
		通过resource-id定位
		通过classname定位
		通过content-desc定位
		通过文本定位
		组合定位
		通过父子关系定位
	用法
	driver.find_element_by_android_uiautomator(“表达式”)
	注:外层要用单引号,内层的字符串用双引号,因为本来就是java,java双引号才表示字符串
	通过文本定位
		new UiSelector().text(“text文本”)
	通过textContains模糊匹配
		new UiSelector().textContains(“text文本”)
	通过某个文本开头匹配
		new UiSelector().textStartWith(“text文本”)
	正则表达式匹配
		new UiSelector().textMatches(“text文本”)
	组合定位
		比如id与text的属性组合:driver.find_element_by_android_uiautomator(‘new UiSelector().resourceId(“com.xueqiu.android:id/login_account”).text(“我的”))
	父子关系定位:childSelector,先定位到父类,再用childSelector来定位子类
		driver.find_element_by_android_uiautomator(‘new UiSelector().resourceId(“com.xueqiu.android:id/login_account”).childSelector(text(“股票”)))
	兄弟定位:fromParent
		driver.find_element_by_android_uiautomator(‘new UiSelector().resourceId(“com.xueqiu.android:id/login_account”).fromParent(text(“股票”)))
	
滑动定位
	滑动元素查找
		有一些页面有持续滑动的能力,比如微博,没有分页,可以一直滑动,uiautomator提供了滑动的很好的方法
		driver.find_element_by_android_uiautomator(‘new UiScrollable(new UiSelector().’
		‘scrollable(true).instance(0)).’
		‘scrollIntoView(new UiSelector().textContains(“病人”).’
		‘instance(0));).click()
		#注意:虚拟机和真机不一样,有时候真机的滑动是ok的,有时候虚拟机的不ok
#xpath定位进阶
from time import sleep
from appium import webdriver
from appium.webdriver.common.touch_action import TouchAction

class TestXueQiu():
    def setup(self):
        desired_caps = {}
        desired_caps['platformName'] = 'Android'
        desired_caps['platformVersion'] = '6.0'
        desired_caps['deviceName'] = 'device'
        desired_caps['appPackage'] = 'cn.kmob.screenfingermovelock'
        # Activity名字 带上包路径,注意路径和Activity名字中间是.  Activity名字也可以不带包名
        desired_caps['appActivity'] = 'com.samsung.ui.MainActivity'
        self.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
        self.driver.implicitly_wait(5)

    def teardown(self):
        self.driver.quit()

	#xpath定位进阶 ,查找股票代码09988的股价
    def test_get_current_price(self):
        sleep(10)
        self.driver.find_element_by_id('com.xueqiu.android:id/home_search').click()
        self.driver.find_element_by_id('com.xueqiu.android:id/search_input_text').send_keys("阿里巴巴")
        # 在搜索结果里面选择‘阿里巴巴’   通过多个条件resource-id(记住不要写成id)和text来一起定位
        self.driver.find_element_by_xpath('//*[@resource-id="com.xueqiu.android:id/name" and @text = "阿里巴巴"]').click()

        #定位股票代码09988的股价
        #通过优先找到父节点,然后查找父节点的孙子节点
        # //*如果是孙子节点要使用相对路径查找
        current_price = float(self.driver.find_element_by_xpath("//*[@text='09988']/../../..//*[@resource-id='com.xueqiu.android:id/current_price']").text)
        print(f"当前的股价为{current_price}")
        assert current_price>200
#uiautomator定位表达式
from time import sleep
from appium import webdriver
from appium.webdriver.common.touch_action import TouchAction

class TestFind():
    def setup(self):
        self.desire_cap= {
            "platformName":"android",
            "deviceName":"127.0.0.1:7555",
            "appPackage":"com.xueqiu.android",
            "appActivity":".view.WelcomeActivityAlias",
            "noReset":"true",
            "unicodeKeyboard":True
        }
        self.driver=webdriver.Remote("http://127.0.0.1:4723/wd/hub",self.desire_cap)
        self.driver.implicitly_wait(5)

    def test_search(self):
        """
        1.打开雪球app
        2.点击我的,进入到个人信息页面
        3.点击登录,进入到登录页面
        4.输入用户名,输入密码
        5.点击登录
        6.弹出手机号输入失败的提示,并assert这个提示对不对
        :return:
        """
        #雪球太慢了,只能10秒了,懒得用显示等等
        sleep(10)
        #在首页找到我的元素,然后点击
        self.driver.find_element_by_android_uiautomator('new UiSelector().text("我的")').click()
        #不睡2秒回导致下一个页面的元素刷新太快识别不到
        sleep(2)
        #识别账号密码登录的元素,然后点击
        self.driver.find_element_by_android_uiautomator('new UiSelector().text("帐号密码登录")').click()
        #不睡2秒回导致下一个页面的元素刷新太快识别不到
        sleep(2)
        #输入账号名为tongtong
        self.driver.find_element_by_android_uiautomator('new UiSelector().resourceId("com.xueqiu.android:id/login_account")').send_keys("tongtong")
        #输入密码为tongtong
        self.driver.find_element_by_android_uiautomator('new UiSelector().resourceId("com.xueqiu.android:id/login_password")').send_keys("tongtong")
        #不睡2秒回导致下一个页面的元素刷新太快识别不到
        sleep(2)
        #点击登录按钮
        self.driver.find_element_by_android_uiautomator('new UiSelector().text("登录")').click()
        #不睡2秒回导致下一个页面的元素刷新太快识别不到
        sleep(2)
        #找到错误提示框,里面有一个确定的元素
        login_incorrect=self.driver.find_element_by_android_uiautomator('new UiSelector().text("确定")')
        #当确定的元素可见,表示登录失败,用例pass
        assert login_incorrect.is_displayed()
#滑动元素查找
from time import sleep
from appium import webdriver
from appium.webdriver.common.touch_action import TouchAction

class TestFind():
    def setup(self):
        self.desire_cap= {
            "platformName":"android",
            "deviceName":"127.0.0.1:7555",
            "appPackage":"com.xueqiu.android",
            "appActivity":".view.WelcomeActivityAlias",
            "noReset":"true",
            "unicodeKeyboard":True
        }
        self.driver=webdriver.Remote("http://127.0.0.1:4723/wd/hub",self.desire_cap)
        self.driver.implicitly_wait(5)

    def test_search_scroll(self):
        """
        0.你的雪球app先关注一个人,然后往下滑,找到一个关键字,用textContains来模糊匹配
        1.打开雪球app
        2.点击关注,让屏幕往下滑,直到找到病人的模糊匹配的text元素后点击
        :return:
        """
        #雪球太慢了,只能10秒了,懒得用显示等等
        sleep(10)
        #点击关注的元素,切换到关注页面
        self.driver.find_element_by_android_uiautomator('new UiSelector().text("关注")').click()
        #睡4秒,怕跳转页面太快,搜索不到元素
        sleep(4)
        #查找到元素'病人',进行点击操作
        self.driver.find_element_by_android_uiautomator('new UiScrollable(new UiSelector().'
                                                        'scrollable(true).instance(0)).'
                                                        'scrollIntoView(new UiSelector().textContains("病人").'
                                                        'instance(0));').click()
        sleep(4)

Menu:8.显示等待机制

强制等待、隐式等待、显示等待

三者的特点
	强制等待是sleep,强烈不推荐,设定的时间太固定,如果是模拟器等待3秒,真机可能只需要等待2秒
	driver.implicitly.wat(timeout),贯穿全部元素的等待,只需要设定一次即可,通常是在创建driver的时候后的代码运行,是dom建立之后的等待
	显示等待是在客户端的等待:引用连个包和一个例子
		from selenium.webdriver.support.wait import WebDriverWait
		from selenium.webdriver.support import expected_conditions
		WebDriverWait(self.driver,10).until(expected_conditions.element_to_be_clickable(locator))

显示等待的简介
	显示等待与隐式等待相对,显示等待必须在每一个需要等待的元素前面进行声明
	是针对某个特定的元素设置等待时间,在设置时间内,默认每隔一段时间检测一次当前某个元素是否存在
	如果在规定的时间内找到元素,则直接执行,即找到元素就执行相关操作
	如果超过设置时间检测不到就抛出异常,默认检测频率为0.5s,默认抛出的异常时NoSuchElementException
	用到的两个常用类
	WebDriverWait
	expected_condition
	
为什么要用显示等待,为什么隐式等待无法替代显示等待
	显示等待可以等待动态加载的ajax元素,需要配合expected_condition来检查条件
	一般页面上元素的呈现顺序是
		首先出现title
		然后是dom树的出现,presence还不完整,dom树出现就是隐式等待了,但此时的元素可能还没有是可点击的状态,所以只用隐式等待,使用click方法,肯定会报错的
		css出现:可见visbility
		js的出现,js特效执行:可点击clickable
	html文档是自上而下加载的
	js文件加载会阻塞html内容的加载,有些js异步加载的方式来完成js的加载
	样式表下载完成之后跟之前的样式表一起进行解析,会对之前那的与元素重新渲染
	presence-visibility-clickabe,元素出现-可见-可点击,是元素的三个性质,当DOM树出现时,定位元素可能已经显示出来了,但是可见和可点击的属性可能还没加载出来,这时候元素的一些方法是不可用的,比如element.click(),要等到js渲染出来以后,元素的click属性才可以用
	对应element.is_displayed()
	对应element.is_selected()
	对应element.is_enabled()

js的同步加载和异步加载
	同步加载:同步模式,又称阻塞模式,会阻止浏览器的后续处理,停止了后续的解析,因此停止了后续的文件加载(如图像)、渲染、代码执行。
	异步加载:异步加载又叫非阻塞,浏览器在下载执行 js 同时,还会继续进行后续页面的处理。

WebDriverWait用法
	WebDriverWait(self, driver, timeout, poll_frequency=POLL_FREQUENCY, ignored_exceptions=None)
	driver:浏览器驱动
	timeout:超时时间,单位秒
	poll_frequency:检查的间隔步长,默认是0.5s
	ignored_exceptions:超时最后的抛出的异常,默认是NoSuchElementException
	通常我们只会用到driver和timeout
	WebDriverWait().until(self, method, message=’’) or until_not()的方法:
		method:在等待期间,每个一段时间(__init__中的poll_frequency)调用这个传入的方法,直到返回值不是False
		message:如果超时,抛出TimeoutException,将message传入异常
		until not 当元素不存在或者是不可点击、不可见的时候,根据实际情况来进行选择。正好和until相反
		
expected_conditions类
	appium直接帮我们封装好了类,只需要传参数即可,比如我们使用的是click(),只需要判断这个元素是否可点击属性才继续点击
	用法:expected_conditions.element_to_be_clickable(locator),其中locator就是:(By.ID, "com.xueqiu.android:id/tv_search")
	#手机上的定位操作通过MobileBy
	常用的几个如下:
		expected_conditions.element_to_be_clickable:元素是否可点击
		expected_conditions.presence_of_element_located:元素是否被加到dom树里面
		expected_conditions.visibility_of_element_located:元素是否可见

lambda获取元素
#查看案例
#lambda获取元素

#可以获取到元素
element = WebDriverWait(self.driver, 10).until(lambda x: x.find_element(By.XPATH,'//*[@text="我的"]'))
#这里找到元素后,不用等待,实测证明过了
element.click()
#使用显式等待
from appium import webdriver
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait
from appium.webdriver.common.mobileby import MobileBy as By

class TestFind():
    def setup(self):
        self.desire_cap= {
            "platformName":"android",
            "deviceName":"127.0.0.1:7555",
            "appPackage":"com.xueqiu.android",
            "appActivity":".view.WelcomeActivityAlias",
            "noReset":"true",
            "unicodeKeyboard":True
        }
        self.driver=webdriver.Remote("http://127.0.0.1:4723/wd/hub",self.desire_cap)
        self.driver.implicitly_wait(5)

    def test_search(self):
        """
        1.打开雪球app
        2.点击我的,进入到个人信息页面
        3.点击登录,进入到登录页面
        4.输入用户名,输入密码
        5.点击登录
        6.弹出手机号输入失败的提示,并assert这个提示对不对
        :return:
        """
        #By.name方法不是对应text的,千万不要用
        #self.driver.find_element(By.NAME,"我的")
        #用显示等待,element_to_be_clickable(locator),里面的locator记得用元祖
        WebDriverWait(self.driver, 15).until(expected_conditions.element_to_be_clickable((By.XPATH,'//*[@text="我的"]')))
        #在首页找到我的元素,然后点击
        self.driver.find_element(By.XPATH,'//*[@text="我的"]').click()
        '''
        lambda返回元素
        element = WebDriverWait(self.driver, 10).until(lambda x: x.find_element(By.XPATH,'//*[@text="我的"]'))
        这里找到元素后,不用等待,实测证明过了
        element.click()
        '''
        #显示等待找到账号密码登录的元素
        WebDriverWait(self.driver, 15).until(expected_conditions.element_to_be_clickable((By.XPATH,'//*[@text="帐号密码登录"]')))
        #识别账号密码登录的元素,然后点击
        self.driver.find_element_by_android_uiautomator('new UiSelector().text("帐号密码登录")').click()
        #显示等待找到账号的元素
        WebDriverWait(self.driver, 15).until(
            expected_conditions.element_to_be_clickable((By.XPATH, '//*[@resource-id="com.xueqiu.android:id/login_account"]')))
        #输入账号名为tongtong
        self.driver.find_element_by_android_uiautomator('new UiSelector().resourceId("com.xueqiu.android:id/login_account")').send_keys("tongtong")
        #输入密码为tongtong
        self.driver.find_element_by_android_uiautomator('new UiSelector().resourceId("com.xueqiu.android:id/login_password")').send_keys("tongtong")
        #显示等待找到登录的按钮
        WebDriverWait(self.driver, 15).until(
            expected_conditions.element_to_be_clickable((By.XPATH, '//*[@text="登录"]')))
        #点击登录按钮
        self.driver.find_element_by_android_uiautomator('new UiSelector().text("登录")').click()
        #显示等待找到确定的元素
        WebDriverWait(self.driver, 15).until(
            expected_conditions.element_to_be_clickable((By.XPATH, '//*[@text="确定"]')))
        #找到错误提示框,里面有一个确定的元素
        login_incorrect=self.driver.find_element_by_android_uiautomator('new UiSelector().text("确定")')
        #当确定的元素可见,表示登录失败,用例pass
        assert login_incorrect.is_displayed()

Menu:特殊控件toast识别

Toast   注意使用toast时,需要在desire capability配置automationName:uiautomator2
#automationName:uiautomator2 这个是appium本身的设置就自带的,不需要额外添加,默认就是uiautomator2,所以安卓手机不设置也可以。暂时没研究iOS如何获取toast

含义
	为了给当前视图显示一个浮动的显示块,与dialog不同它永远不会获得焦点
	显示时间有限,根据用户设置的显示时间后自动消失
	本身是个系统级别的控件,它归属系统settings,当一个app发送消息的时候,不是自己造出来的这个弹框,它是发给系统,由系统统一进行弹框,这类的控件不在app内、需要特殊的控件识别方法

toast定位
	appium使用uiautomator底层的机制来分析抓取toast,并且把toast放到控件树里面,但本身并不属于控件
	automationName:uiautomator2 这个是appium本身的设置就自带的,不需要额外添加,默认就是uiautomator2
	getPageSource是无法找到toast的
	必须使用xpath去查找   #定位toast的俩种方式
		#一般一个appActivity只有一个toast,本来不建议通过class来定位,但是只有一个toast,就可以通过class进行定位
		//*[@class="android.widget.Toast"]	#通过class属性进行定位
		//*[contains(@text,"xxxxx")]		#通过text属性进行定位,通过文本包含进行定位
		
实例:appium自带的app测试toast
	adb shell dumpsys window | findstr mCurrent 这个命令可以找到当前的activity,不知道Android高版本是不是还ok,由于api demo权限高,可直接跳到这个activity运行,其他app就不ok了
	#windows使用findstr 
	#Linux使用grep
	#adb shell dumpsys window | grep mCurrent	#直接获取当前app停留的Activity页面入口
	driver.page_source可以打印当前的页面,可以找到toast的伪控件
	#print driver.page_source
	
#打印toast的text出来
#driver.page_source打印出来的东西,包含toast
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<hierarchy index="0" class="hierarchy" rotation="3" width="810" height="1440">
  <android.widget.FrameLayout index="0" package="io.appium.android.apis" class="android.widget.FrameLayout" text="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" long-clickable="false" password="false" scrollable="false" selected="false" bounds="[0,0][810,1440]" displayed="true">
    <android.view.ViewGroup index="0" package="io.appium.android.apis" class="android.view.ViewGroup" text="" resource-id="android:id/decor_content_parent" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" long-clickable="false" password="false" scrollable="false" selected="false" bounds="[0,0][810,1440]" displayed="true">
      <android.widget.FrameLayout index="0" package="io.appium.android.apis" class="android.widget.FrameLayout" text="" resource-id="android:id/action_bar_container" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" long-clickable="false" password="false" scrollable="false" selected="false" bounds="[0,41][810,136]" displayed="true">
        <android.view.ViewGroup index="0" package="io.appium.android.apis" class="android.view.ViewGroup" text="" resource-id="android:id/action_bar" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" long-clickable="false" password="false" scrollable="false" selected="false" bounds="[0,41][810,136]" displayed="true">
          <android.widget.TextView index="0" package="io.appium.android.apis" class="android.widget.TextView" text="Views/Popup Menu" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" long-clickable="false" password="false" scrollable="false" selected="false" bounds="[27,65][324,111]" displayed="true" />
        </android.view.ViewGroup>
      </android.widget.FrameLayout>
      <android.widget.FrameLayout index="1" package="io.appium.android.apis" class="android.widget.FrameLayout" text="" resource-id="android:id/content" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" long-clickable="false" password="false" scrollable="false" selected="false" bounds="[0,136][810,1440]" displayed="true">
        <android.widget.LinearLayout index="0" package="io.appium.android.apis" class="android.widget.LinearLayout" text="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" long-clickable="false" password="false" scrollable="false" selected="false" bounds="[0,136][810,1440]" displayed="true">
          <android.widget.Button index="0" package="io.appium.android.apis" class="android.widget.Button" text="Make a Popup!" content-desc="Make a Popup!" checkable="false" checked="false" clickable="true" enabled="true" focusable="true" focused="false" long-clickable="false" password="false" scrollable="false" selected="false" bounds="[297,136][513,217]" displayed="true" />
        </android.widget.LinearLayout>
      </android.widget.FrameLayout>
    </android.view.ViewGroup>
  </android.widget.FrameLayout>
  
  #这里就找到了Tast的控件了
  <android.widget.Toast index="1" package="com.android.settings" class="android.widget.Toast" text="Clicked popup menu item Search" checkable="false" checked="false" clickable="false" enabled="false" focusable="false" focused="false" long-clickable="false" password="false" scrollable="false" selected="false" bounds="[0,0][0,0]" displayed="false" />
</hierarchy>
#toast使用案例
from appium import webdriver
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait
from appium.webdriver.common.mobileby import MobileBy as By

class TestFind():
    def setup(self):
        self.desire_cap= {
            "platformName":"android",
            "deviceName":"127.0.0.1:7555",
            "appPackage":"io.appium.android.apis",
            #此处请注意,为了简单起见,直接给到的PopupMenu Activity页面入口,由于此演示demo使用的是简单的app程序,为了方便可直接跳过从主页一步一步到该页面
            #现实的app一般是要从主页一步一步往后执行,不能像这样跳过,开发也不会给到该入口
            #adb shell dumpsys window | grep mCurrent 	#直接获取当前app停留的Activity页面入口
            "appActivity":"io.appium.android.apis.view.PopupMenu1",
            "noReset":"true",
            "unicodeKeyboard":True
        }
        self.driver=webdriver.Remote("http://127.0.0.1:4723/wd/hub",self.desire_cap)
        self.driver.implicitly_wait(5)

    def test_search(self):
        """
        1.打开appium的演示app
        2.直接进入到测试toast的界面
        3.点击显示toast的按钮,然后通过driver.page_source获取页面
        4.找到toast的伪控件
        5.打印出toast的值出来
        :return:
        """
        #点击Make a Popup的控件
        self.driver.find_element(By.XPATH,'//*[@text="Make a Popup!"]').click()
        #点击search的控件
        self.driver.find_element(By.XPATH, '//*[@text="Search"]').click()
        #打印整个布局页面的xml出来
        print(self.driver.page_source)
        #打印出toast的值
        	#通过class属性定位
        	#一般一个appActivity只有一个toast,本来不建议通过class来定位,但是只有一个toast,就可以通过class进行定位
        #print(self.driver.find_element(By.XPATH,'//*[@class="android.widget.Toast"]').text)
        	#通过文本包含进行定位,通过text属性值进行定位
        print(self.driver.find_element(By.XPATH, '//*[contains(@text,"popup menu")]').text)

#软件api demos
https://github.com/appium/android-apidemos/releases

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据您提供的信息,我可以给您提供一份移动端app自动化测试的答辩记录综述,希望能帮到您。 本次答辩主要涉及到移动端app自动化测试的设计与实现,以下是答辩记录综述: 1. 答辩人首先介绍了移动端app自动化测试的背景和意义,指出自动化测试可以提高测试效率和测试质量,避免重复性的测试工作。 2. 答辩人详细介绍了移动端app自动化测试的设计思路和实现流程,包括测试框架的选择、测试用例的设计、测试环境的搭建等方面,确保测试过程的完整性和可重复性。 3. 答辩人着重介绍了移动端app自动化测试中常见的测试工具和技术,如Appium、Selenium、Robotium等,以及基于这些工具和技术的测试实现方式和注意事项,从而使得测试结果更加准确和可靠。 4. 答辩人结合实际案例,对移动端app自动化测试进行了演示和讲解,展示了测试用例的设计、测试环境的搭建和测试结果的分析等方面,以及对测试过程中出现的问题的解决方法和经验总结,为后续的测试工作提供了借鉴和参考。 5. 最后,答辩人总结了本次答辩的主要内容和亮点,强调了移动端app自动化测试在实际工作中的重要性和必要性,同时也指出了测试工作中需要注意的问题和解决方法,为移动端app自动化测试的进一步发展提供了思路和支持。 以上是移动端app自动化测试的答辩记录综述,希望能对您有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值