Appium

Appium的使用

移动端测试的八大过程

安装/卸载

  1. 真机上安装、卸载、高版本覆盖安装、低版本覆盖安装、卸载后安装高版本;
    1. 安装关注点:版本号、渠道号、数字签名(用抓包工具辅助查看)、安装成功后启动向导、安装过程中对意外情况的处理(取消、死机、重启、断电、内存不足、断网)、安装进度条、主要功能流程;
    2. 卸载关注点:卸载过程中的意外情况处理(取消、死机、重启、断电、内存不足、断网)、卸载进度条;
  2. 第三方软件协助安装、卸载、高版本覆盖安装、低版本覆盖安装、卸载后安装高版本;
  3. 在线升级:
    1. 升级注意点:升级提示、取消更新/强制更新、后台更新(ios的自动更新)、跨版本升级、升级过程中异常情况的处理(取消、死机、重启、断电、内存不足、断网)、升级进度、不同网络下升级;
    2. 第三方软件支持:itools、豌豆荚、91助手、华为助手、360、应用宝等;

业务功能测试

  1. 根据需求文档、原型图和设计稿验证app各个功能的实现;   
  2. 共性功能:
    1. 注册:用户名密码的输入(同文本框编辑“2-1)”)、用户名密码长度限制、注册后的页面提示(手机短信提示)、前台和后台数据一致、;
    2. 登录:用户名密码的输入(同登录“1-1)”)、非法登录次数限制、多设备登录(MTOP现有原则,一个应用同时只允许一台设备登录)、禁用账号登录、登录成功信息、登录后有注销按钮、登录超时处理、登录过程断网处理、登录过程切换网络;
    3. 注销:注销后新账号登录、取消注销;
    4. 应用前后台切换:app前后台切换、锁屏解屏、电话中断后回到app、必须处理的提示框处理后回到app、杀掉进程后重新启动app、有数据交换的页面注意进行前后台切换以及锁屏解屏;
    5. 免登录:登录后杀掉进程重新启动app、无网络、切换用户登录、密码更换、主动退出登录下次启动app、卸载重装、在线更新、覆盖安装、跨版本安装、;
    6. 数据更新:手动或自动刷新、从后台切换到前台时数据更新、实时更新、定时更新、数据展示的处理逻辑(服务器获取、本地缓存)、更新异常处理(弱网、断网、服务器响应异常、数据为空);
    7. 定位、相机、语音、蓝牙等服务:已开启、未开启根据提示开启、未开启并拒绝开启;
    8. 时间测试:修改手机时区;
    9. 推送测试:推送消息内容、推送消息链接跳转、免打扰或拒绝接收;
      10.交叉事件测试

兼容测试

  1. 分辨率;
    1. 主流分辨率:10801920、7201280、8001280、25601440、 2040x1080等
    2. 非主流:1080*1800等
  2. 主流系统版本 ios:ios10、ios9、ios8、ios7;android:android6.0、android5.0、android4.*等;
    1. 不同厂家定制 iphone、华为、小米、oppo、vivo、魅族等
    2. 不同尺寸:6寸、5寸、5.5寸、5.7寸、4.7寸、4寸等

稳定性测试

  1. monkey结合友盟持续使用8小时以上统计crash率;
  2. 各种事件打扰,如插拔数据线、电话打扰、收发短信、切换网络、浏览网络、使用蓝牙传送/接收数据、相机等;
  3. 多个运行中app切换测试;

性能测试

  1. monkey结合性能测试工具监控cpu、内存、流量、耗电量,性能测试工具如anothermoniter、腾讯的GT;
  2. 评估典型用户应用场景下,系统资源的使用情况;
  3. 大数据测试(如需要读取用户通讯录的情况);
  4. 不同网络响应速度、服务器接口压力测试;
  5. 与竞品的Benchmarking(基线测试);

网络测试

  1. 无网络测试;
  2. 弱网测试;
  3. 外网测试;

界面易用性测试

  1. 符合android或ios体验规范;
    1. android体验规范:长按弹出删除选项(一时想不起来还有哪些,平时还是要多做总结);
    2. ios体验规范:左滑弹出删除选项、左右滑动可翻页 
  2. 符合用户体验规范:
    1. 是否有空数据界面设计,引导用户去执行操作。
    2. 是否滥用用户引导。
    3. 是否有不可点击的效果,如:你的按钮此时处于不可用状态,那么一定要灰掉,或者拿掉按钮,否则会给用户误导
    4. 菜单层次是否太深;
    5. 交互流程分支是否太多;
    6. 相关的选项是否离得很远;
    7. 界面中按钮可点击范围是否适中;
    8. 当切换标签的时候,内容跟着切换;
    9. 是否定义Back的逻辑。涉及软硬件交互时,Back键应具体定义
    10. 是否有横屏模式的设计,应用一般需要支持横屏模式,即自适应设计; 
    11. 在不同的页面是否有导航连接、导航与页面风格一致;
  3. 是否需要搜索;
  4. 图片质量、同一页面图片颜色不宜过多、同一页面标签风格统一;
  5. 文案:输入框中说明文字、页面文字正确性、敏感词汇、敏感图片(设计专利、版权、隐私等);

安全测试

  1. 软件权限: 
    1. 限制/允许使用手机功能接人互联网
    2. 限制/允许使用手机发送接受信息功能
    3. 限制/允许应用程序来注册自动启动应用程序
    4. 限制或使用本地连接
    5. 限制/允许使用手机拍照或录音
    6. 限制/允许使用手机读取用户数据
    7. 限制/允许使用手机写人用户数据
  2. 安装/卸载安全性:
    1. 能够在安装设备驱动程序上找到应用程序的相应图标
    2. 安装路径应能指定
    3. 没有用户的允许, 应用程序不能预先设定自动启动
    4. 卸载是否安全, 其安装进去的文件是否全部卸载
    5. 卸载用户使用过程中产生的文件是否有提示
    6. 其修改的配置信息是否复原
    7. 卸载是否影响其他软件的功能
    8. 卸载应该移除所有的文件
  3. 数据安全性:  
    1. 输人的密码将不以明文形式进行显示
    2. 密码, 信用卡明细, 或其他的敏感数据将不被储存在它们预输人的位置上
    3. 不同的应用程序的密码长度必需至少在4一8 个数字长度之间
    4. 当应用程序处理信用卡明细, 或其他的敏感数据时, 不以明文形式将数据写到其它单独的文件或者临时文件中。以防止应用程序异常终止而又没有侧除它的临时文件, 文件可能遭受人侵者的袭击, 然后读取这些数据信息。
    5. 当将敏感数据输人到应用程序时, 其不会被储存在设备中
    6. 备份应该加密, 恢复数据应考虑恢复过程的异常,通讯中断等, 数据恢复后再使用前应该经过校验
    7. 应用程序应考虑系统或者虚拟机器产生的用户提示信息或安全警告
    8. 应用程序不能忽略系统或者虚拟机器产生的用户提示信息或安全警告, 更不能在安全警告显示前,利用显示误导信息欺骗用户,应用程序不应该模拟进行安全警告误导用户
    9. 在数据删除之前,应用程序应当通知用户或者应用程序提供一个“取消”命令的操作
    10. “ 取消”命令操作能够按照设计要求实现其功能
    11. 应用程序应当能够处理当不允许应用软件连接到个人信息管理的情况
    12. 当进行读或写用户信息操作时, 应用程序将会向用户发送一个操作错误的提示信息
    13. 在没有用户明确许可的前提下不损坏删除个人信息管理应用程序中的任何内容
    14. 应用程序读和写数据正确。
    15. 应用程序应当有异常保护。
    16. 如果数据库中重要的数据正要被重写, 应及时告知用户
    17. 能合理地处理出现的错误
    18. 意外情况下应提示用户

Appium两种连接方式

  1. 连接真机
  2. 连接夜神模拟器→DOS窗口输入:adb connect 127.0.0.1:62001 在输入adb devices命令

相关环境的搭建

JDK的配置

Android-SDK的安装与配置

Genymotion的下载与安装(夜神代替)

Appium自动化测试环境搭建

ADB的常用命令

  1. ADB全名Android Debug Brage,是一个Debug工具,adb是一个标准的C/S结构的工具,是要连接开发电脑和调试手机的.
  2. adb帮助 adb --help
  3. 启动adb server adb start-server
  4. 关闭adb server adb kill-server
  5. 获取设备号 adb devices
  6. 获取系统版本 adb -s 设备号 shell getprop ro.build.version.release
  7. 发送文件到手机 adb push 电脑端文件路径/需要发送的文件 手机端存储的路径 例:将手机/sdcard目录中的xx.png文件,发送到电脑桌面 adb push c:\users\win\Desktop /sdcard/xx.png
  8. 从手机拉取文件 adb pull 手机端的路径/拉取文件名 电脑端存储文件路径 adb pull /sdcard/xx.png c:\Users\win\Desktop
  9. 查看手机运行日志 adb logcat
  10. 手机shell命令行 adb shell
  11. 获取app包名和启动名 在windows终端运行 adb shell dumpsys window | findstr mCurrentFocus 类似于com.android的内容是包名,类似于.settings的内容是启动名
  12. 安装app到手机 adb install 路径/xx.apk
  13. 卸载手机app adb uninstall 包名
  14. 获取app启动时间 adb shell am start -w 包名/启动名
    1. thisTime 该activity启动耗时
    2. totaltime 应用自身启动耗时=thistime+应用application等资源启动时间
    3. waittime 系统启动应用耗时=totaltime+系统资源启动时间

Appium(android与ios皆可测)

  1. 使用python打开android模拟器中的设置界面
  2. 思路:python代码→Appium-python库→Appium→手机
  3. 获取app包名和启动名 current_package print(driver.current_package)
  4. 获取启动名 current_activity print(driver.current_activity)
  5. 脚本内启动其他app driver.start_activity(appPackage,appActivity) 如果一个脚本启动多个app,那么会按顺序依次打开程序
    1. appPackage:包名
    2. appActivity:启动名
  6. 关闭app driver.close_app() 关闭当前操作的app,不会关闭驱动对象
  7. 关闭驱动对象 driver.quit() 关闭驱动对象,同时关闭所有关联的app
  8. 安装apk到手机 driver.install_app(app_path) app_path:脚本机器中apk的文件路径
  9. 手机中移出app driver.remove_app(app_id) app_id:需要卸载的app包名
  10. 判断app是否安装 driver.is_app_installed(app_id) app_id:app包名,返回True表示已安装,false表示未安装
  11. 发送文件到手机
    1. 需要先引入import base64
    2. data=str(base64.b64encode(data.encode('utf-8')),'utf-8') driver.push_file(path,data)
      1. path:表示手机设备上的路径
      2. data:文件内数据,要求base64编码
  12. 从手机中拉取文件
    1. 先引入import base64
    2. data=driver.pull_file(path) 返回数据为base64编码 参数path为手机设备上的路径
    3. print(str(base64.b64decode(data),'utf-8')) base64解码
  13. 获取当前屏幕内元素结构 driver.page_source 作用:返回当前页面的文档结构,判断特定的元素是否存在
  14. 应用于后台事件,app放置后台,模拟热启动 方法:background_app(seconds) seconds表示停留在后台的时间,单位:秒 driver.background_app(seconds) ,app置于后台5s后,再次展示当前页面
# 模板
from appium import webdriver
import time
desired_caps = {}
desired_caps['platformName'] = 'Android' #android的apk还是IOS的ipa
desired_caps['platformVersion'] = '7.0' #android系统的版本号
desired_caps['deviceName'] = '192.168.190.101:5555' #手机设备名称,通过adb devices 查看
desired_caps['appPackage'] = 'com.android.email' #apk的包名
desired_caps['appActivity'] = '.activity.setup.AccountSetupFinal' #apk的launcherActivity
# desired_caps['unicodeKeyboard'] = True # 使用unicodeKeyboard的编码方式来发送字符串
# desired_caps['resetKeyboard'] = True  # # 将键盘给隐藏起来
driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)

UIAutomatorViewer工具(元素定位工具)

用来扫描和分析Android的应用程序的UI控件的工具,如果tools中没有,打开monitor.bat。

操作工具

  1. 进入SDK的tools目录下,找到UIAutomatorViewer.bat并打开
  2. 电脑连接真机或android模拟器
  3. 连接元素定位工具
  4. 注意:如果出现报错有如下几种解决方式:
    1. app服务进程占用,可以暂时关闭adb服务(然后在开启adb服务) adb kill-server(关闭) adb start-server(开启)
    2. 另一种情况就是使用python编写自动化脚本并执行阶段,如果页面时脚本执行出来的页面,在连接元素定位工具的时候同样会报错(解决办法就是关闭执行脚本出来的页面,重新打开即可,亲测有效)
    3. 简单暴力的方法,就是拔掉数据线重新连接(亲测有效)
  5. 会涉及到多次截取页面,所以可以使用页面截取与保存文件,方便快速使用(我使用的monitor.bat我没找到保存的子样)

元素定位API

前置代码

from appium import webdriver
import time
desired_caps = {
    'platformName': 'Android',
    'deviceName': '7cd2ae65',
    'platformVersion': '9',
    'appPackage': 'com.tencent.mm',
    'appActivity': '.ui.LauncherUI',
    'automationName': 'Appium',
    # 运行模拟器中文的问题需要加如下两行
    # 'unicodeKeyboard':True,
    # 'resetKeyboard':True,
    'noReset': True,
    'chromeOptions': {'androidProcess': 'com.tencent.mm:appbrand0'}
}
driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)

通过id定位

需要注意跟css不一样,这里的id值并不是完全唯一的

  1. driver.find.element_by_id("com.tencent.mm:id/s6").click() 元素定位工具中resource-id 对应 com.tencent.mm:id/s6 这里定位单个元素
  2. driver.find.elements_by_id(" ").click()

通过class定位

driver.find.element_by_class_name("android.widget.ImageButton").click() 元素定位工具中class 对应 android.widget.ImageButton

通过xpath定位

  1. 常用的属性定位(id、text、class等固定的情况下)
    1. driver.find_element_by_xpath("//*[@text='扫一扫']").click() 通过xpath使用text去定位
    2. driver.find_element_by_xpath("//*[@resource-id='com.taobao.taobao:id/tv_scan_text']").click() 通过xpath使用id去定位
    3. driver.find_element_by_xpath("//*[@class='android.widget.EditText']").click() 通过xpath使用class去定位
    4. driver.find_element_by_xpath("//*[@resource-id='com.taobao.taobao:id/tv_scan_text'][@text='扫一扫']").click() 通过xpath使用多种方式进行定位
    5. driver.find_element_by_xpath("//*[@content-desc='帮助']").click() 通过xpath使用content-desc进行定位
  2. contains模糊定位(同样可以模糊id与class)
    1. 模糊定位的诉求 对于一个元素的id或者text不是唯一的,但有一部分是唯一的,这种就可以模糊匹配。
    2. 具体代码实现 driver.find_element_by_xpath("//*[contains(@content-desc, '帮助')]").click() 通过xpath使用contains进行模糊定位
  3. 组合定位
    1. 组合定位的诉求 如果一个元素有2个属性,通过xpath也可以同时匹配2个属性,text, resource-id,class ,index,content-desc这些属性都能任意组合定位
    2. 具体的实现
      1. driver.find_element_by_xpath('//android.widget.EditText[@resource-id="com.taobao.taobao:id/home_searchedit"]').click() id和class组合
      2. driver.find_element_by_xpath('//*[@text="注册/登录" and @index="1"]').click() text和index组合
      3. driver.find_element_by_xpath('//android.widget.EditText[@text="请输入手机号码"]').send_keys("512200893") class和text组合
      4. driver.find_element_by_xpath('//*[contains(@resource-id, "aliuser_menu_item_help") and @content-desc="帮助"]').click() id和content-desc组合
  4. 层级定位
    1. 层级定位的诉求 如果一个元素,它除了class属性(class属性肯定会有),其它属性啥都没有,这种情况用上面方法就不适用了,这个时候可以找他父元素,通过父亲定位儿子
    2. 通过父元素定位子元素的具体的实现
      1. driver.find_element_by_xpath('//*[@resoure-id="com.taobao.taobao:id/home_searchbar"]/android.widget.EditText').click() 父元素下其中一个子元素
      2. driver.find_element_by_xpath('//*[@resource-id="com.taobao.taobao:id/ll_navigation_tab_layout"]/android.widget.FrameLayout[2]').click() 父元素下,有多个相同class的儿子时候,可以通过xpath的索引去取对应第几个,xpath是从1开始数的
    3. 通过子元素定位父元素的具体的实现
      1. driver.find_element_by_xpath('//*[@resource-id="com.taobao.taobao:id/tv_scan_text"]/..').click()
      2. driver.find_element_by_xpath('//[@resource-id="com.taobao.taobao:id/tv_scan_text"]/parent::').click()

WebDriverWait显示等待

在一个超时时间范围内,每隔一段时间去搜索一次元素是否存在,如果存在返回定位对象,如果不存在直到超时时间到达,报超时异常错误。

方法:WebDriverWait(driver, timeout, poll_frequency).until(method)

参数:

driver:手机驱动对象
timeout:搜索超时时间
poll_frequency:每次搜索间隔时间,默认时间为0.5s
method:定位方法(匿名函数)  
匿名函数

  lambda x:x

等价于python函数:

  def test(x):

  return x

使用用例:

  from selenium.webdriver.support.wait import WebDriverWait(需要导入)

  WebDriverWait(driver,10).until(lambda x:x.find_element_by_id('elementid'))

解释:

x传入值为:driver,所以才可以使用定位方法
搜索到元素后until返回定位对象,没有搜索到until返回超时异常错误. 

元素操作API

  1. 点击元素 click()
  2. 发送数据到输入框 send_key()
  3. 解决输入中文的问题(这种情况一般在模拟器中会出现,真机暂时未出现)
    1. 'unicodeKeyboard':True
    2. 'resetKeyboard':True
  4. 清空输入框的内容 clear()
  5. 获取元素的文本内容 text
  6. 获取元素的属性值 get_attribute(value)
    1. value值为name时,返回content-desc/text属性值
    2. value值为text时,返回text属性值
    3. value值为classname,返回class属性值
    4. value值为resourceId,返回resource-id属性值

滑动和拖拽事件

真机或者模拟机获取坐标,打开开发者选项点击指针模式

swipe滑动事件

从一个坐标位置滑动到另一个坐标位置,只能是两个点之间的滑动

方法:swipe(start_x,start_y,end_x,end_y,duration=None)

参数:

start_x:起点x轴坐标
start_y:起点y轴坐标
end_x:终点x轴坐标
end_y:终点y轴坐标
duration:滑动这个操作一共持续的时间长度,单位:ms 
相关代码

  driver.swipe(100,2000,100,1000,3000)    时间参数设置越长越精确,一般设置为2-3,3秒最佳

scroll滑动事件

从一个元素滑动到另一个元素,直到页面自动停止

方法:scroll(origin_el,destination_el)

参数:

origin_el    滑动开始的元素
destination_el    滑动结束的元素
相关代码:

  save_button=driver.find_element_by_xpath("//*[contains(@text,'存储')]")

  more_button=driver.find_element_by_xpath("//*[contains(@text,'存储')]")

  driver.scroll(save_button,more_button)

drag滑动事件

和scroll的事件类似

方法drag_and_drop(origin_el,destination_el)

参数:

origin_el    滑动开始的元素
destination_el    滑动结束的元素
相关代码:

  save_button=driver.find_element_by_xpath("//*[contains(@text,'存储')]")

  more_button=driver.find_element_by_xpath("//*[contains(@text,'存储')]")

  driver.drag_and_drop(save_button,more_button)

三种方式的区别:

scroll和drag的区别在于,drag没有惯性,相同点:都是使用元素进行传参
对于swipe传的是元素,主要是以坐标为主

滑动事件进行强化使用

使用循环对滑动相关代码进行优化:

while True:
    try:
        driver.find_element_by_xpath("//*[contains(@text,'位置信息')]").click()  # 如果在滑动期间找到了这个元素的位置信息,那么就点击
        break
    except Exception:
        driver.swipe(100, 2000, 100, 1000, 5000)  # 如果在try中没有找到的时候,那么就会列入循环继续寻找,直到找到之后,才会停止, 如果一直进入死循环,意味着元素不存在

滑动事件适应宽高最终版

def getSize(self):
    x = self.driver.get_window_size()['width']
    y = self.driver.get_window_size()['height']
    return (x, y)
def swipeUp(self,t):
    l = self.getSize()
    x1 = int(l[0] * 0.5)  # x坐标
    y1 = int(l[1] * 0.75)  # 起始y坐标
    y2 = int(l[1] * 0.25)  # 终点y坐标
    self.driver.swipe(x1, y1, x1, y2, t)

额外内容(python测试代码优化)

# 获取关于手机里面中,指定一个文本内容是5.1的

for i in eles:
    if i.text == "5.1":  # 或者使用 if "5.1" in i.text:
        print("有!")
        break
    else:
        print("没有")

高级手势TouchAction

TouchAction是AppiumDriver的辅助类,主要针对手势操作,比如滑动、长按、拖动等,原理是将一系列的动作放在一个链条中发送到服务器,服务器接受到该链条后,解析各个动作,逐个执行。
注意的是,所有的手势都要通过执行perform()函数才会运行。

手指轻敲操作

模拟手指轻敲一下屏幕操作

方法:tap(element=None,x=None,y=None)

方法:perform()    # 发送命令到服务器执行操作

参数:

element:被定位到的元素
x:相对于元素左上角的坐标,通常会使用元素的x轴坐标
y:通常会使用元素的y轴坐标
相关代码:

more_button=driver.find_element_by_xpath("//*[contains(@text,'更多')]")

# 轻敲

# 只传入元素,就会点击元素

TouchAction(driver).tap(more_button).perform()

# 只传入右边两个参数值x与y,会点击以屏幕左上角为原点,点击

TouchAction(driver).tap(x=850,y=1500).perform()

# 元素和xy都传,以元素为准

TouchAction(driver).tap(more_button,600,600).perform()

手指按下和抬起操作

方法:press(el=None,x=None,y=None)

方法:release()    # 结束动作,手指离开屏幕

参数:

element:被定位到的元素
x:通常会使用元素的x轴坐标
y:通常会使用元素的y轴坐标
代码实现:

el=driver.find_element_by_xpath("//*[contains(@text,'WLAN')]")

# 通过元素定位

TouchAction(driver).press(el),release().perform()

# 通过坐标方式按下屏幕,WLAN坐标:x=155,y=250

TouchAction(driver).tap(x=155,y=250).release().perform()

 

等待操作只能进行长按元素☆☆☆☆☆

TouchAction(driver).press(el).wait(5000).perform()

强行使用该方法组合去进行长按操作,不能完成,但是不会报错

手指长按操作

用途:类似于长按wifi取消保存这种功能

方法:long_press(el=None,x=None,y=None,duration=1000)

参数:

element:被定为到的元素
x:通常会使用元素的x轴坐标
y:通常会使用元素的y轴坐标
duration:持续时间,默认为1000ms
相关代码:

el=driver.find_element_by_id("android:id/title")

#通过元素定位方式长按元素

TouchAction(driver).long_press(el,duration=5000).release().perform()

#同样可以通过坐标进行定位

TouchAction(driver).long_press(x=600,y=600,duration=5000).release().perform()

手指移动操作

方法:move_to(el=None,x=None,y=None)

参数:

el:定位的元素
x:相对于前一个元素的x轴偏移量☆☆☆☆☆
y:相对于前一个元素的y轴偏移量☆☆☆☆☆
相关代码实现:

save_button=friver.find_element_by_xpath("//*[contains(@text,'存储')]")

more_button=friver.find_element_by_xpath("//*[contains(@text,'更多')]")

# 元素方式滑动

TouchAction(driver).press(save_button).move_to(more_button).release().perform()

# 坐标方式滑动

TouchAction(driver).press(x=240,y=600).wait(100).move_to(x=100,y=100).release().perform()

手机操作API

  1. 获取手机时间 print(device_time)
  2. 获取手机的宽高 print(driver.get_window_size())
  3. 获取手机的宽 print(driver.get_window_size()['width'])

发送键到设备

使用场景:音量键的增加与减少

方法:keyevent(keycode,metastate=None)

方法:press_keycode(keycode,metastate=None)

参数

keycode:发送给设备的关键代码(关键代码可以百度Android keycode)
metastate:关于被发送的关键代码的元信息,一般为默认值 
代码实现:

for i in range(3):

  driver.keyevent(24)

操作手机通知栏

方法:open_notifications()

代码实现:

  driver.open_notifications()

获取手机当前网络

方法:network_connection

代码实现:

  print(driver.open_notifications())

手机截图

方法:get_screenshot_as_file(filename)

参数:filename表示指定路径下,指定格式的图片

代码实现:

  driver.get_screenshot_as_file("./xxx.png")

po模式

page Object Model 测试页面和测试脚本分离,即页面封装成类,供测试脚本进行调用
优点:

  1. 提高测试用例的可读性
  2. 减少了代码的重复
  3. 提高测试用例的可维护性,特别是针对UI频繁变动的项目
  4. 缺点:结构复杂,基于流程做了模块化分析的拆分

封装前置代码

# 先将前置代码封装,代码如下:

from appium import webdriver
def init_driver():
    desired_caps = {
        'platformName': 'Android',
        'deviceName': '7cd2ae65',
        'platformVersion': '9',
        # apk包名
        'appPackage': 'com.tencent.mm',
        # apk的launcherActivity
        'appActivity': '.ui.LauncherUI',
        'automationName': 'Appium',
        'noReset': True,
        'chromeOptions': {'androidProcess': 'com.tencent.mm:appbrand0'},
        # 'unicodeKeyboard': True,
        # # 屏蔽手机软键盘
        # 'resetKeyboard': True
    }
    driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
    return driver

# 其次,调用封装的代码以及注意事项,代码如下:
# 在使用pytest命令的时候,python是不会自动检索自己封装的包(代码)
# 所以需要引入如下的两条,来强制性去检索自己封装的包
import os,sys
sys.path.append(os.getcwd())
from base.base_ini import init_driver
import time
import pytest

class TestAppoint:
    def setup(self):
        self.driver=init_driver()
        time.sleep(2)
        # 第一个参数使用元祖,第二个参数使用列表嵌套元祖
    @pytest.mark.parametrize(("username","password"),[("wupeng","123456"),("wupeng1","123456")])
    def test_search(self,username,password):
        self.driver.find_element_by_id("com.android.settings:id/search").click()
        self.driver.find_element_by_id("android:id/search_src_text").send_keys(username)
        self.driver.find_element_by_id("android:id/search_src_text").send_keys(password)

分离测试脚本

编写步骤
1. 新建page文件夹
2. 新建network_page.py文件
3. 新建display_page.py文件
4. init函数传入driver
5. init进入需要测试的页面
6. page中新建小动作函数
7. 移动代码
8. 修改测试文件中的代码

# 文件目录
#  po模式

- base

- - base_driver.py

- page

- - network_page.py

- - display_page.py

- scripts

- - test_network.py

- - test_display.py

- pytest.ini

# base目录的相关代码 → base_driver.py
from appium import webdriver
def init_driver():
    desired_caps = {
        'platformName': 'Android',
        'deviceName': '127.0.0.1:62001',
        'platformVersion': '5.1.1',
        # apk包名
        'appPackage': 'com.android.settings',
        # apk的launcherActivity
        'appActivity': '.Settings',
        'automationName': 'Appium',
        'noReset': True,
        # 'chromeOptions': {'androidProcess': 'com.tencent.mm:appbrand0'},
        # 'unicodeKeyboard': True,
        # # 屏蔽手机软键盘
        # 'resetKeyboard': True
    }
    driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
    return driver

# page目录的相关代码 → display_page.py
class DisPlayPage:
    def __init__(self,driver):
        self.driver=driver
    def click_display(self):
        # 实现点击设置   点击的内容放到page中
        self.driver.find_element_by_xpath("//*[@text='设置']").click()
    def click_search(self):
        self.driver.find_element_by_id("com.android.settings:id/search").click()
    def input_text(self,text):
        self.driver.find_element_by_id("android:id/search_src_text").send_keys(text)
    def click_return(self):
        self.driver.find_element_by_class_name("android.widget.ImageButton").click()
 
# scripts目录的相关代码 → test_display.py
# 在使用pytest命令的时候,python是不会自动检索自己封装的包(代码)
# 所以需要引入如下的两条,来强制性去检索自己封装的包
import os,sys
sys.path.append(os.getcwd())
from base.base_driver import init_driver
from page.display_page import DisPlayPage
import time
import pytest

class TestAppoint:
    def setup(self):
        self.driver=init_driver()
        self.display_page=DisPlayPage(self.driver)
        time.sleep(2)
    def test_more(self):
        # 执行点击设置的按钮
        self.display_page.click_display()
        time.sleep(2)
        # 执行点击搜索
        self.display_page.click_search()
        time.sleep(2)
        # 执行搜索内容
        self.display_page.input_text("hello")
        # 执行返回
        self.display_page.click_return()

抽取元素的特征

 好处:若特征改了,流程不变,可以直接在上面修改

步骤:

将find_element_by_xxx改为find_element
将方式和具体特征向上移动
文件目录

#  po模式

- base

- - base_driver.py

- page

- - network_page.py

- - display_page.py

- scripts

- - test_network.py

- - test_display.py

- pytest.ini

使用代码实例如下:

base_driver.py代码

from appium import webdriver
def init_driver():
    desired_caps = {
        'platformName': 'Android',
        'deviceName': '127.0.0.1:62001',
        'platformVersion': '5.1.1',
        # apk包名
        'appPackage': 'com.android.settings',
        # apk的launcherActivity
        'appActivity': '.Settings',
        'automationName': 'Appium',
        'noReset': True,
        # 'chromeOptions': {'androidProcess': 'com.tencent.mm:appbrand0'},
        # 'unicodeKeyboard': True,
        # # 屏蔽手机软键盘
        # 'resetKeyboard': True
    }
    driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
    return driver
display_page.py代码

from selenium.webdriver.common.by import By


class DisPlayPage:
    display_click=By.XPATH,"//*[@text='设置']"
    search_click=By.ID,"com.android.settings:id/search"
    input_content=By.ID,"android:id/search_src_text"
    return_click=By.CLASS_NAME,"android.widget.ImageButton"
    def __init__(self,driver):
        self.driver=driver
    def click_display(self):
        self.find_element(self.display_click).click()
    def click_search(self):
        self.find_element(self.search_click).click()
    def input_text(self,text):
        self.find_element(self.input_content).send_keys(text)
    def click_return(self):
        self.find_element(self.return_click).click()
    # 起到辅助的作用,为了方便其他方法只需要传入两个参数
    def find_element(self,loc):
        return self.driver.find_element(loc[0],loc[1])
test_display.py代码

# 在使用pytest命令的时候,python是不会自动检索自己封装的包(代码)
# 所以需要引入如下的两条,来强制性去检索自己封装的包
import os,sys
sys.path.append(os.getcwd())
from base.base_driver import init_driver
from page.display_page import DisPlayPage
import time
import pytest

class TestAppoint:
    def setup(self):
        self.driver=init_driver()
        self.display_page=DisPlayPage(self.driver)
        time.sleep(2)
    def test_more(self):
        # 执行点击设置的按钮
        self.display_page.click_display()
        time.sleep(2)
        # 执行点击搜索
        self.display_page.click_search()
        time.sleep(2)
        # 执行搜索内容
        self.display_page.input_text("hello")
        # 执行返回
        self.display_page.click_return()

PO模式的终结版本

使用封装、分离、抽取元素特征、抽取action以及增肌显示等待整合成最终po模式的移动端自动化测试脚本

文件目录

#  po模式

- base

- - base_driver.py

- - base_action.py

- page

- - display_page.py

- scripts

- - test_display.py

- pytest.ini

具体详细的分解代码如下:

base_driver.py代码

from appium import webdriver
# 多用于封装手机连接的前半部分
def init_driver():
    desired_caps = {
        'platformName': 'Android',
        'deviceName': '127.0.0.1:62001',
        'platformVersion': '5.1.1',
        # apk包名
        'appPackage': 'com.android.settings',
        # apk的launcherActivity
        'appActivity': '.Settings',
        'automationName': 'Appium',
        'noReset': True,
        # 'chromeOptions': {'androidProcess': 'com.tencent.mm:appbrand0'},
        # 'unicodeKeyboard': True,
        # # 屏蔽手机软键盘
        # 'resetKeyboard': True
    }
    driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
    return driver
base_action.py代码

from appium.webdriver.common.touch_action import TouchAction
from selenium.webdriver.support.wait import WebDriverWait


class BaseAction:
    def __init__(self, driver):
        self.driver = driver

    def click(self, loc):
        self.find_element(loc).click()

    def input_text(self, loc, text):
        self.find_element(loc).send_keys(text)
    # 实现Touch的perform的方法
    def touchAction(self,loc):
        TouchAction(self.driver).tap(self.find_element(loc)).perform()
    def touchActionCoord(self,x,y):
        TouchAction(self.driver).tap(x=x,y=y).perform()
    def find_element(self, loc):
        by = loc[0]
        value = loc[1]
        webdriverwarit = WebDriverWait(self.driver, 5, 1).until(lambda x: x.find_element(by, value))
        return webdriverwarit
display_page.py代码

from selenium.webdriver.common.by import By
from base.base_action import BaseAction

class DisPlayPage(BaseAction):
    display_click=By.XPATH,"//*[@text='设置']"
    search_click=By.ID,"com.android.settings:id/search"
    input_content=By.ID,"android:id/search_src_text"
    return_click=By.CLASS_NAME,"android.widget.ImageButton"
    def __init__(self,driver):
        BaseAction.__init__(self,driver)
        self.click_display()
    def click_display(self):
        self.click(self.display_click)
    def click_search(self):
        self.click(self.search_click)
    def input_search_text(self,text):
        self.input_text(self.input_content,text)
    def click_return(self):
        self.click(self.return_click)
test_display.py代码

# 在使用pytest命令的时候,python是不会自动检索自己封装的包(代码)
# 所以需要引入如下的两条,来强制性去检索自己封装的包
import os,sys
sys.path.append(os.getcwd())
from base.base_driver import init_driver
from page.display_page import DisPlayPage
import time
import pytest

class TestAppoint:
    def setup(self):
        self.driver=init_driver()
        self.display_page=DisPlayPage(self.driver)
        time.sleep(2)
    def test_more(self):
        # 执行点击设置的按钮
        self.display_page.click_display()
        time.sleep(2)
        # 执行点击搜索
        self.display_page.click_search()
        time.sleep(2)
        # 执行搜索内容
        self.display_page.input_search_text("hello")
        # 执行返回
        self.display_page.click_return()

Yaml的用法

Yaml数据存储文件

YAML是一种所有编程语言可用的友好的数据序列化标准,语法和其他高阶语言类似,并且可以简单表达清单、散列

语法规则

大小写敏感
使用缩进表示层级关系
缩进时不允许使用Tab键,只允许使用空格(在pycharm中可以使用tab)
缩进的空格数目不重要,只要相同层级的元素左侧对齐即可
 

支持的数据结构

对象:键值对的集合,又称为映射
数组:一组按次序排列的值,又称为序列
纯量:单个的、不可再分的值
 

yaml字典列表之间的相互嵌套

# 字典嵌套字典
name: "wupeng"
people:
  age: "18"
  name: "zhangsan"

# 字典嵌套列表
names: "wupeng1"
peoples:
  - "1"
  - "2"

# 列表嵌套列表
- "1"
- "2"
-
  - "3"
  - "4"
# 列表嵌套字典
- "1"
-
  name: "wupeng"
  age: "18"

纯量

包含:字符串、布尔值、整数、浮点数、Null、日期

# 字符串
name: "123"

# 布尔值
boolean: True

# 整数
number: 1

# 浮点数
number1: 3.1415

# Null值
null: None

# 日期,年月日,时分秒
date: 2018-09-10 05:00:23:111

Python解析yaml文件

PyYAML为python解析yaml的库

安装:pip3 install -U PyYAML

 

yaml的读

import yaml
def main():
    with open("./data.yml","r") as file:
        data=yaml.load(file)
        print(data)
if __name__ == '__main__':
    main()
yaml的写

yaml.dump(data,stream,**kwds)

常用参数:

data:写入数据类型为字典

stream:打开文件对象

encoding='utf-8'    #设置写入编码方式

allow_unicode=True    #是否允许unicode编码

import yaml
def main():
    data={"searchFile":{
        "search1":{"name1":"wupeng","age1":18},
        "search2":{"name2":"wulei","age2":20}
    }}
    with open("./text.yml","w") as file:
        yaml.dump(data,file,encoding="uft-8",allow_unicode=True)
if __name__ == '__main__':
    main()
 锚点和引用    &info为锚点,<<: *info表示引用    引用锚点的时候,会价格锚点后的所有都复制一份到引用的地方

name:
  &info
  value: "12"
  value2: "sdf"
people:
  name: "wulei"
  <<: *info

字符串相关方法

a="20,40,50"
print(a.split(","))    将字符串a分割成一个列表 
删除尾部的字符串    a.rstrip("80")
判断字符串的类型    type(a)

Monkey

Monkey程序介绍

  1. Monkey程序由Androidixtong自带,使用java语言编写,在Android文件系统中的存放路径是/system/framework/monkey.jar
  2. Monkey程序是由名为monkey的shell脚本来启动执行,shell脚本在Android文件系统中的存放路径是:/system/bin/monkey
  3. Monkey的启动方式
    1. 可以通过PC机CMD窗口执行:adb shell monkey{+命令参数}来进行Monkey测试
    2. 在pc上adb shell进入Android系统,通过执行monkey{+命令参数}来进行Monkey测试
    3. 在Android机或者模拟器上直接执行monkey命令,可以在Android上安装Android终端模拟器(Terminal Emulator for Android)

Monkey输出日志

adb shell monkey -p cn.goapk.market 100>路径/log.txt

Monkey基本参数介绍

  1. -p<允许的包名列表>
    1. 用此参数指定一个或多个包,指定包之后,monkey将只允许系统启动指定的app,如果是指定包,monkey将允许系统启动设备中的所有app
    2. 指定一个包 adb sell monkey -p cn.goapl.market 100
    3. 指定多个包 adb shell monkey -p fishjoy.control.menu -p cn.goapk.market 100
  2. -v
    1. 用于指定反馈信息级别(信息级别就是日志的详细程度),总共分3个级别
    2. level 0: adb shell monkey -p cn.goapk.market -v 100 缺省值,仅提供启动提示,测试完成和最终结果等少量信息
    3. level 1: adb shell monkey -p cn.goapk.market -v -v 100 提供较为详细的日志,包括每个发送到Activity的时间信息
    4. level 2: adb shell monkey -p cn.goapk.market -v -v -v 100 最详细的日志,包括了测试中选中/未选中的Activity信息
  3. -s 随机数种子,用于指定伪随机数生成器的seed值,如果seed相同,则两次Monkey测试所产生的而时间序列也相同
  4. --throttle<毫秒> 用于用户操作间的时延,单位是毫秒,如果指定这个参数,monkey会尽可能快的生成和发送消息 adb shell monkey -p cn.goapk.market --throttle 3000 100

Monkey的日志分析

  1. 正常情况 如果Monkey测试顺利执行完成,在log的最后,会打印出当期那执行时间的次数和所花费的时间:monkey finished代表执行完成
  2. 异常情况 monkey测试出现错误后,一般的分析步骤看Monkey的日志(注意第一个swith以及异常信息等)
    1. 程序无响应的问题 在日志中搜索"ANR"
    2. 崩溃问题 在日志中搜索"Exception"(如果出现空指针,NullPointerException),肯定有bug
    3. Monkey执行中断,在log最后也能看到当前执行次数

APP常规测试流程

  1. 测试资源准备
    1. 需求文档
    2. 接口文档
    3. 原型图
    4. 主流机型
  2. 测试用例设计和评审
    1. 尽可能考虑多种情况
    2. 立项会议讨论
  3. ui 和效果图是否一致,如果不一致,以效果图为准,协助两边沟通
  4. 功能 从逻辑的角度与需求文档做对比
  5. 中断测试
    1. 电话,短信,闹钟
    2. 前后台
  6. 兼容性
    1. andriod与ios以及相关机型
    2. 分辨率
    3. 系统版本
  7. 性能测试
    1. 启动时间
    2. 电量消耗
    3. 流量
  8. 压力测试 monkey
  9. 安全测试
    1. 传输数据是否是明文
    2. 密码是不是md5
  10. 测试报告
  11. 自动化
posted @ 2020-11-17 17:47  SunFree  阅读(360)  评论(0编辑  收藏  举报