目录
Appium App UI自动化测试
一、Appium App UI自动化测试
App自动化测试背景
二、 Appium自动化测试简介
Appium的特点
Applum的哲学
市面上其他主要的app自动化测试框架
三、Appium App自动化测试环境搭建
1、设置adb环境变量
2、配置adb环境变量
3、校验adb环境变量是否成功
4、Sdk下载
5、Sdk环境搭建
6、下载安装Appium
7、安装其他依赖库
8、安装模拟器,这里采用的是夜神模拟器,然后打开模拟器
9、验证adb连接是否成功
adb报错
四、自动化测试
1、将appium启动
2、 启动模拟器
3、 定位
4、属性操作
5、swipe滑动
6、TouchAction手势操作
7、.get_screenshot_as_file('')截图
8、.keyevent()实现键值操作
9、 .send_keys('')输入中文
10、 重置app
cmd输入
11、toast
五、将case实现自动化
完整版word文档详见主页 资源附件,可自行下载。
https://download.csdn.net/download/rainsghost/88414762?spm=1001.2014.3001.5503
随着移动终端的普及,手机应用越来越多,也越来越重要。App的回归测试用例数量也越来越多,全量回归也越来越消耗时间。另外移动端碎片化严重,尤其是Android端碎片化严重性更为突出,市面上Android机型甚至有几万,几十万款,所以我们也需要通过这种自动化测试帮助我们减少兼容性的测试工作。总之为了减少这种重复的、大量回归到测试任务,我们迫切需要引进一些自动化测试来协助。
Appium是一个开源的,适用于原生(native)或混合移动应用( hybrid mobile apps)的自动化测试框架。Appium应用WebDriver: JSON wire protocol驱动安卓和iOS移动应用。
原生就是纯粹用的安卓的组件或者控件开发的,就叫原生
混合移动应用,在native中嵌入了一些html页面,这样可以将app里面的一些ui显示的图片什么的,可以快速进行更新,而不需要担心各大app商店审核的时间,也不需要用户再自行再去更新软件app,,用户只需在打开app的时候,后台就会自动更新页面,可以使app更快的响应变化,面对一些节日活动时,可以很快更新,,弊端就是,因为后台需要去占用资源更新,自然会使app使用时变得延迟高、用户交互响应慢等,所以不应在一款app内大量使用,
json是一个轻量级的数据交换格式,
√支持多平台(Android、iOS等)
√支持多语言(python、java、ruby、js、c#等
√client server (cs)架构
√ Appium是跨平台的,可以用在OSX,Windows以及Linux桌面系统上运行。
Appium选择了Client/Server的设计模式。只要client能够发送http请求给server,那么的话client用什么语言来实现都是可以的,这就是如何做到
支持多语言的原因;
Appium扩展了WebDriver的协议,没有自己重新去实现一套。这样的好处是以前的WebDriver API能够直接被继承过来,以前的Selenium (WebDriver)各种语言的binding都可以拿来就用,省去了为每种语言开发一个client的工作量。
LS的自动化测试只能用mac电脑,因为它需要用mac底层的一些工具,
c就是调用的一些api请求,然后会由c收集请求后用json格式发给appium的s端,这里的appium的s端会一直监听,当监听到请求后,会推一个bootstrap.jar包给你的手机目录里,然后会启动bootstrap,这个bootstrap会监听转发分析你的请求后,会调用安卓的底层的uiauto mator(自动化测试框架),通过这个框架去实现你的请求,最后返回给你。
开源免费
不需要重新编译或者修改应用
不被一种语言或者框架约束
不重复造轮子
monkeyrunner---调用坐标来定位控件,弊端:只能用python去写,当屏幕不同分辨率时,坐标就会扰乱,当页面没有加载出来时,点击依旧不会报错,只支持安卓
monkeytalk---基于控件去定位的,和selenium定位差不多,,弊端:只能用js去写, 需要app的源代码里插入agent这个代理代码,才可以进行自动化测试,但是要是想插入这个agent,不仅有可能导致app崩溃,还需要全部的源代码才可以插入,安全性降低,只支持安卓
robotium---不仅支持native,还支持hybrid ,弊端:需要重新签名,只能用java去写,只支持安卓
macaca---阿里巴巴开发的,底层封装的还是appium,既能支持安卓,也能支持ios,
robotframwork (rf)---关键字驱动框架,基于关键字驱动,优点:简单,只需要关键字即可,也可以跑app,也可以搞接口
安卓自动化测试和ios测试的代码能用一套吗
不能,整个框架设计可以用一套,具体的case不能,因为安卓一些控件属性比如id,是ios没有的,比如安卓的xpath和ios的xoath肯定是不一样的。
①、下载JDK
jdk下载地址:
https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html 下载并进行安装。
③、 查看安装路径,或者点击更改,更改安装路径到自己指定位置,然后点击“下一步”,
⑤、第二次安装的是java,,可更改你的安装地址,,然后点击下一步,,也可以直接点击下一步,如下图
1、此处以wim10为例,,右键点击微软键,点击系统
2、在弹出的关于页面里,点击高级系统设置,在弹出的系统属性框里,点击环境变量
3、系统变量中点击新建,输入变量名和jdk安装文件夹所在位置,然后点击确定
4、系统变量中点击新建,变量名为SDK_HOME,变量值为sdk文件夹的地址
5、在系统变量中找“Path",然后点击path,再点击编辑,如下图
6、在新打开的path窗口里点击新建,输入”%JAVA_HOME%\bin"之后,
这时可以按微软键 + R,,调出运行窗口,输入cmd,,然后回车,调出cmd窗口,,如下图
输入java -version,点击回车,验证环境是否安装成功,如下图是安装成功
下载地址:http://developer.android.com/sdk/index.html ,一般需要,国内可以通过下载地址:http://www.androiddevtools.cn/
2、在弹出的关于页面里,点击高级系统设置,在弹出的系统属性框里,点击环境变量
3、系统变量中点击新建,输入变量名和sdk文件夹所在位置,然后点击确定
4、在系统变量中找“Path",然后点击path,再点击编辑,如下图
5、在新打开的path窗口里点击新建,输入”%SDK_HOME%\platform-tools"之后,
再点击新建,输入”%SDK_HOME%\tools",,再点击确定
7、这时可以按微软键 + R,,调出运行窗口,输入cmd,,然后回车,调出cmd窗口,,如下图
8、输入adb -devices,点击回车,若出现下图,则证明安装设置成功
下载离线包安装,地址:https://github.com/appium/appium-desktop/releases/tag/v1.21.0 ,
安装后如下图:
在cmd下输入如下命令:
pip install selenium
pip install Appium-Python-Client
如果不行,用下边命令加镜像pip install --index-url https://pypi.douban.com/simple appium-python-client
在cmd下输入adb devices,,如下图证明adb安装成功,
如果模拟器正常情况下,可查看模拟器的USB调试是否打开
我们需要去sdk文件下,找到sdb的安装根路径下的三个文件,如下图,
将他们复制到我们的模拟器根路径下替换掉同名文件即可,如下图
3、如果真机遇到了为空的情况下,
有可能是驱动的问题,这时候可以下载一个手机助手即可(在电脑上),手机助手会自动安装驱动
4、版本不匹配问题:
在百度搜索android api level对照表,找到如下表
然后去sdk文件下找到platforms文件夹,对照上表查看自己的模拟器或者手机的安卓版本,查看文件夹中是否存在API Level的版本,如下图便没有安卓10的版本
然后回到sdk根目录,找到SDK Manager.exe文件,
双击它,等待一会会自动打开,找到安卓10,确认已经全部选定了,点击右下角的更新
将左侧的所有包再次双击选中,
选中后会变成绿色的对钩,然后点击右侧更新即可,然后等待更新
更新完毕后,再次打开sdk根目录下的platforms文件夹,查看是否已经更新成功,如下图证明已经更新成功
如果更新很慢,我们可以直接去下载sdk安装包,直接替换成一个新的sdk即可,
下载地址:http://developer.android.com/sdk/index.html ,一般需要,国内可以通过下载地址:http://www.androiddevtools.cn/
如果显示off-line的情况,这时候一般是真机的情况下,提示离线,这时候拔掉重插就好了,
如果提示 authorization的情况,说明之前弹出授权框的时候没有授权,这时候,从设置里重新授权就好了,
我们需要先把Appium启动,这里建议用管理员启动
然后点击Start Server v1.9.1,来启动监听,监听的本地地址为0.0.0.0,端口是4723
提前安装需要测试的软件,运行代码,这里用最右来代替
from appium import webdriver
desired_caps = {} #定义一个字典
desired_caps['platformName'] = 'Android' #平台名称Android、iOS
desired_caps['platformVersion'] = '7.1' #系统版本
desired_caps['deviceName'] = 'Android Emulator' #设备名称,可以自定义,这里写的是安装模拟器
desired_caps['appPackage'] = 'cn.xiaochuankeji.tieba' #包名 指定我们需要运行的软件包
desired_caps['appActivity'] = '.ui.base.SplashActivity' #指定一个Activity名,页面名,
driver = webdriver.Remote('http://localhost:4723/wd/hub',desired_caps) #就是本地地址,端口要和appium端口写成一致,一般是固定的
driver.implicitly_wait(40)
driver.find_element_by_id('cn.xiaochuankeji.tieba:id/iconTabItem')[1].click() #
如果能正常打开最右,即为环境部署成功,
打开我们需要运行的软件,然 后在cmd里输入adb shell dumpsys activity top|findstr "ACTIVITY"
如图标识就是包名的位置
打开我们需要运行的软件,然 后在cmd里输入adb shell dumpsys activity top|findstr "ACTIVITY"
如图标识就是当前页面名的位置,需要获取多个页面名,执行多次即可
这里需要注意的是: 页面名有可能是很多页面公用的,但是常规是一个页面一个名称
对于我们跑app自动化,我们应该拉第一个界面,,我们一般因为安全的问题,我们只能拉几个页面,所以,我们一般拉取欢迎界面。如下图
但是欢迎界面往往都是很快就会消失,并进入软件,所以我们需要提前写好代码,在欢迎界面快速执行代码,来获取到我们的界面名
app定位首先不考虑绝对路径,因为太长
定位的每个页面都需要重新映射到UI Automator Viewe窗口上,
定位是用sdk/tools文件下的uiautomatorviewer.bat文件,
class即可认为是一个属性,也可认为是一个标签
然后双击,等待一会,会弹出UI Automator Viewer窗口,如下图
然后点击左上角第二个图标,将手机屏幕截图到我们的UI窗口
定位的框架可以直接用web框架,
import os
import unittest
import time
from appium import webdriver
class AndroidTests(unittest.TestCase):
def setUp(self):
desired_caps = {}
desired_caps['platformName'] = 'Android'
desired_caps['platformVersion'] = '7.1'
desired_caps['deviceName'] = 'Android Emulator'
# desired_caps['noReset'] = 'True' #不重置app数据
desired_caps['appPackage'] = 'cn.xiaochuankeji.tieba'
desired_caps['appActivity'] = '.ui.base.SplashActivity'
self.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
def tearDown(self):
#self.driver.quit()
pass
这里需要加入不重置app数据命令= desired_caps['noReset'] = 'True' ,便会将之前操作的数据保留,这样的好处是,对于一些软件重新打开的时候,可能会出现需要输入个性化信息的情况(比如最右每次打开一个新app时,需要选择男生还是女生,年龄等),便可以直接跳过,
但是需要注意的是:在开始自动化时,是不能添加不重置app数据命令的,因为每次操作可能会改变app的标签位置等问题。
这里我们需要去找到id的值在哪里,如下图我们需要定位关注,那么”关注”的id属性值 == resource-id属性值
注意:
id往往不是唯一的,但是只有布局相差差不多的,id才会有可能一样,如下图的关注、推荐、视频、图文便是一样的,再比如不同的用户名称,不同的用户头像等
所以相比较element,我们更多时候用elements
def test_element_by_id(self):
self.driver.implicitly_wait(60)
el = self.driver.find_element_by_id("cn.xiaochuankeji.tieba:id/title") #用id定位
print(el.text)
el.click()
if __name__ == '__main__':
suite = unittest.TestLoader().loadTestsFromTestCase(AndroidTests)
unittest.TextTestRunner(verbosity=2).run(suite)
正如find_element_by_id 里面所言,因为对于app来说,id往往不是唯一的,所以我们更多时候需要用 elements 来获取到所有的id,然后用位置来进行定位,,比如我们定位 ”视频”,
def test_element_by_id(self):
self.driver.implicitly_wait(60)
el = self.driver.find_elements_by_id("cn.xiaochuankeji.tieba:id/title") #id定位
print(el.text)
el[2].click()
class对于app来说,更多的像一个app的标签作用,因为我们所有的文字的class其实都是 android.widget.TextView ,
所以对于整个页面很多文本时,我们去用class定位时,默认去定位第一个.
比如我们需要去定位 “关注” ,
def test_element_by_id(self):
self.driver.implicitly_wait(60)
el = self.driver.find_element_by_class_name("android.widget.TextView") #定位
el.click()
elements_by_class_name 会拿到当前页面的所有同名class的属性,因为同一个界面往往很多同名的class,
所以,我们可以采用第一种方法,去手动调试,然后找到我们需要定位的,如下,可以先把整个页面的文本和下标都打印出来,
然后再去输入 el[3].click() 去定位
def test_elements_by_id(self):
self.driver.implicitly_wait(60)
el = self.driver.find_elements_by_class_name("android.widget.TextView") #id定位
time.sleep(7)
el = self.driver.find_elements_by_class_name("android.widget.TextView")
for i in range(0,len(el)):
print("第" + str(i) + "个 =" + el[i].text)
el[3].click()
第二种方法
我们去树形图里面一个个去找,但是找的时候需要注意的是,有些折叠起来的,需要一个个点开。然后再一个个去找,
随着越往页面下方,会越费时间,,所以对于TextView只适合页面上方一部分查找。
但是对于头像,照片这种,用class定位只能选择这一种
因此,对于页面很多class情况下,还是不建议用class。
因为xpath是一门独立的语言,所以id、class都可以用,app定位首先不考虑绝对路径,因为太长
class即可认为是一个属性,也可认为是一个标签
------------------
操作控件
.click() 点击
send_keys() 输入
.text 读取文本信息
.clear() 清空
.get_window_size() 获取宽和高,如果不去定位,则直接获取手机屏幕分辨率
app在输入框内输入中文是和web不同的,因为app本身自带的输入法是不会自动切换中英文的,所以我们需要去安装一个属于Appium的输入法,
desired_caps['unicodeKeyboard'] ='True' 安装输入法
desired_caps['resetKeyboard'] ='True' 重置输入法
然后就可以去输入我们的中文了,
这里需要注意的是:如果我们是用真机去测试的,我们测试完毕后,还有手工测试的人员需要去用真机,这时候因为Appium输入法是没有键盘的,所以我们需要去点击键盘和输入法下的当前输入法,更改键盘为自带的输入法即可
import os
import unittest
import time
from appium import webdriver
class AndroidTests(unittest.TestCase):
def setUp(self):
desired_caps = {}
desired_caps['platformName'] = 'Android'
desired_caps['platformVersion'] = '7.1'
desired_caps['deviceName'] = 'Android Emulator'
# desired_caps['noReset'] = 'True' #不重置app数据
desired_caps['appPackage'] = 'cn.xiaochuankeji.tieba'
desired_caps['appActivity'] = '.ui.base.SplashActivity'
desired_caps['unicodeKeyboard'] ='True'
desired_caps['resetKeyboard'] ='True'
self.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
def tearDown(self):
#self.driver.quit()
pass
def test_element_by_id(self):
self.driver.implicitly_wait(60)
el = self.driver.find_element_by_id("cn.xiaochuankeji.tieba:id/ic_search_b") #id定位
el.click()
time.sleep(2)
el = self.driver.find_element_by_id("cn.xiaochuankeji.tieba:id/search_input").send_keys('中文')
#输入中文
if __name__ == '__main__':
suite = unittest.TestLoader().loadTestsFromTestCase(AndroidTests)
unittest.TextTestRunner(verbosity=2).run(suite)
因为我们很多情况下,有的时候打开一个新的app时,会发现每次都需要我们去点击一些个性化的设置,所以可以采用desired_caps['noReset'] = 'True' 来不重置数据,
但是不重置数据很明显是有弊端的,比如我们上个case可以不重置,但是下个case便需要重新登录或者,不登录的一种情况去执行,这时候显然我们需要一个新的方法:fullReset 卸载app,
同样的卸载了,便需要安装,才可以执行下一个case,所以往往配合app一起使用
desired_caps['fullReset'] = 'True' 卸载app
desired_caps['app'] = 'E:/newCourse/zuiyou518.apk' 安装app
我们大多情况,都应该采用重置app,而不是不重置
优点:确保每一条case都是从头开始的,
弊端:运行缓慢
import os
import unittest
import time
from appium import webdriver
class AndroidTests(unittest.TestCase):
def setUp(self):
desired_caps = {}
desired_caps['platformName'] = 'Android'
desired_caps['platformVersion'] = '7.1'
desired_caps['deviceName'] = 'Android Emulator'
desired_caps['fullReset'] = 'True'
desired_caps['app'] = 'E:/newCourse/zuiyou518.apk'
desired_caps['appPackage'] = 'cn.xiaochuankeji.tieba'
desired_caps['appActivity'] = '.ui.base.SplashActivity'
self.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
cmd输入需要先点击选中输入框后,再在cmd里面输入
adb shell input text “15127409611” #输入15127409611
toast弹窗按照一般定位是定位不到的,因为它类似于web端的alert弹窗,,都是消失很快,但是定位却完全不同,
desired_caps['automationName'] ="Uiautomator2" 用来定位toast,因为 安卓最早封装是用的instrumention 来封装的,后来用的就是Uiautomator1,但是第一代的Uiautomator是定位不了toast弹窗的,然后就到了如今用的Uiautomator2,Uiautomator2现在是更新了可以定位toast,所以我们采用它,
当我们去先设置一个变量包含toast的文本,然后用WebDriverWait去等待,并且每间隔一个时间就去读取一下,用以获取到我们的toast,如下:
toast_loc = ('xpath','.//*[contains(@text,"评论发送成功")]') 定义变量,包含toast的文本
el = WebDriverWait(self.driver,20,0.1).until(EC.presence_of_element_located(toast_loc)) 总间隔20s,每隔0.1秒 去检测一下设置好的变量
toast一般是黑色的框框,如下
对于蓝色的不是
import os
import unittest
import time
from appium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
class AndroidTests(unittest.TestCase):
def setUp(self):
desired_caps = {}
desired_caps['platformName'] = 'Android'
desired_caps['platformVersion'] = '7.1'
desired_caps['deviceName'] = 'Android Emulator'
desired_caps['fullReset'] = 'True'
desired_caps['app'] = 'E:/newCourse/zuiyou518.apk'
desired_caps['appPackage'] = 'cn.xiaochuankeji.tieba'
desired_caps['appActivity'] = '.ui.base.SplashActivity'
desired_caps['unicodeKeyboard'] ='True'
desired_caps['resetKeyboard'] ='True'
desired_caps['automationName'] ="Uiautomator2"
self.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
def tearDown(self):
#self.driver.quit()
pass
def test_element_by_id(self):
self.driver.implicitly_wait(60)
el = self.driver.find_element_by_id("cn.xiaochuankeji.tieba:id/ic_search_b") #id定位
el.click()
time.sleep(2)
self.driver.find_element_by_id("cn.xiaochuankeji.tieba:id/expand_content_view").click()
time.sleep(6)#截图
self.driver.find_element_by_id("cn.xiaochuankeji.tieba:id/etInput").send_keys('t123')
time.sleep(2)
self.driver.find_element_by_id("cn.xiaochuankeji.tieba:id/send").click()
toast_loc = ('xpath','.//*[contains(@text,"评论发送成功")]')
el = WebDriverWait(self.driver,20,0.1).until(EC.presence_of_element_located(toast_loc))
print(el.text)
time.sleep(2)
self.driver.keyevent(4)
if __name__ == '__main__':
suite = unittest.TestLoader().loadTestsFromTestCase(AndroidTests)
unittest.TextTestRunner(verbosity=2).run(suite)
对应的所有的p0级case和p1级的case进行回归测试,然后去分析哪些case可以执行自动化,然后搭建我们的框架,比如case层、pubilc层,然后在case层下还可以设置app层和web层,再在其下放相应的case文件,,
总体和web很相似,
完整版word文档详见主页 资源附件,可自行下载。
https://download.csdn.net/download/rainsghost/88414762?spm=1001.2014.3001.5503