python+appium+yaml移动端自动化测试框架实现详解


Posted in Python onNovember 24, 2020

结构介绍

之前分享过一篇安卓UI测试,但是没有实现数据与代码分离,后期维护成本较高,所以最近抽空优化了一下。
不想看文章得可以直接去Github,欢迎拍砖
大致结构如下:

python+appium+yaml移动端自动化测试框架实现详解

testyaml管理用例,实现数据与代码分离,一个模块一个文件夹

public 存放公共文件,如读取配置文件、启动appium服务、读取Yaml文件、定义日志格式等

page 存放最小测试用例集,一个模块一个文件夹

results 存放测试报告及失败截图

python+appium+yaml移动端自动化测试框架实现详解

logs 存放日志

python+appium+yaml移动端自动化测试框架实现详解

python+appium+yaml移动端自动化测试框架实现详解

testcase 存放测试用例runtest.py 运行所有测试用例

yaml格式介绍

首先看下yaml文件的格式,之前也写过一点关于yaml语法学习的文章
testcase部分是重点,其中:

element_info:定位元素信息

find_type:属性,id、xpath、text、ids

operate_type: click、sendkeys、back、swipe_up 为back就是返回,暂时就四种

上面三个必填,operate_type必填!!!!!!

send_content:send_keys 时用到

index:ids时用到

times: 返回次数或者上滑次数

testinfo:
 - id: cm001
  title: 新增终端门店
  execute: 1
testcase:
 -
  element_info: 客户
  find_type: text
  operate_type: click
 -
  element_info: com.fiberhome.waiqin365.client:id/cm_topbar_tv_right
  find_type: id
  operate_type: click
 -
  element_info: com.fiberhome.waiqin365.client:id/custview_id_singletv_inputtext
  find_type: ids
  operate_type: send_keys
  send_content: auto0205
  index: 0
 -
  element_info:
  find_type:
  operate_type: swipe_up
  times: 1
 -
  element_info: 提交
  find_type: text
  operate_type: click
 -
  element_info:
  find_type:
  operate_type: back
  times: 1

代码部分

公共部分

个人觉得核心的就是公共部分,相当于建房子,公共部分搞好了,后面仅仅是调用即可,建房子把架子搭好,后面就添砖加瓦吧。

读取配置文件readconfig.py
设置日志格式logs.py
获取设备GetDevices.py
这几个通用的就不做介绍了

读取yaml文件 GetYaml.py
主要用来读取yaml文件

#coding=utf-8
#author='Shichao-Dong'
 
import sys
reload(sys)
sys.setdefaultencoding('utf8')
import yaml
import codecs
 
class getyaml:
 def __init__(self,path):
  self.path = path
 
 def getYaml(self):
  '''
  读取yaml文件
  :param path: 文件路径
  :return:
  '''
  try:
   f = open(self.path)
   data =yaml.load(f)
   f.close()
   return data
  except Exception:
   print(u"未找到yaml文件")
 
 def alldata(self):
  data =self.getYaml()
  return data
 
 def caselen(self):
  data = self.alldata()
  length = len(data['testcase'])
  return length
 
 def get_elementinfo(self,i):
  data = self.alldata()
  # print data['testcase'][i]['element_info']
  return data['testcase'][i]['element_info']
 
 def get_findtype(self,i):
  data = self.alldata()
  # print data['testcase'][i]['find_type']
  return data['testcase'][i]['find_type']
 
 def get_operate_type(self,i):
  data = self.alldata()
  # print data['testcase'][i]['operate_type']
  return data['testcase'][i]['operate_type']
 
 def get_index(self,i):
  data = self.alldata()
  if self.get_findtype(i)=='ids':
     return data['testcase'][i]['index']
  else:
   pass
 
 def get_send_content(self,i):
  data = self.alldata()
  # print data['testcase'][i]['send_content']
  if self.get_operate_type(i) == 'send_keys':
   return data['testcase'][i]['send_content']
  else:
   pass
 
 def get_backtimes(self,i):
  data = self.alldata()
  if self.get_operate_type(i)=='back' or self.get_operate_type(i)=='swipe_up':
     return data['testcase'][i]['times']
  else:
   pass
 
 def get_title(self):
  data = self.alldata()
  # print data['testinfo'][0]['title']
  return data['testinfo'][0]['title']

启动appium服务 StartAppiumServer.py
主要是启动appium并返回端口port,这个port在下面的driver中需要

#coding=utf-8
#author='Shichao-Dong'
 
from logs import log
import random,time
import platform
import os
from GetDevices import devices
 
log = log()
dev = devices().get_deviceName()
 
class Sp:
 def __init__(self, device):
  self.device = device
 
 def __start_driver(self, aport, bpport):
  """
  :return:
  """
  if platform.system() == 'Windows':
   import subprocess
   subprocess.Popen("appium -p %s -bp %s -U %s" %
        (aport, bpport, self.device), shell=True)
 
 def start_appium(self):
  """
  启动appium
  p:appium port
  bp:bootstrap port
  :return: 返回appium端口参数
  """
  aport = random.randint(4700, 4900)
  bpport = random.randint(4700, 4900)
  self.__start_driver(aport, bpport)
 
  log.info(
   'start appium :p %s bp %s device:%s' %
   (aport, bpport, self.device))
  time.sleep(10)
  return aport
 
 def main(self):
  """
  :return: 启动appium
  """
  return self.start_appium()
 
 def stop_appium(self):
  '''
  停止appium
  :return:
  '''
  if platform.system() == 'Windows':
   os.popen("taskkill /f /im node.exe")
 
if __name__ == '__main__':
 s = Sp(dev)
 s.main()

获取driver GetDriver.py
platformName、deviceName、appPackage、appActivity这些卸载配置文件config.ini文件中,可以直接通过readconfig.py文件读取获得。
appium_port有StartAppiumServer.py文件返回

s = Sp(deviceName)
appium_port = s.main()
 
def mydriver():
 desired_caps = {
    'platformName':platformName,'deviceName':deviceName, 'platformVersion':platformVersion,
    'appPackage':appPackage,'appActivity':appActivity,
    'unicodeKeyboard':True,'resetKeyboard':True,'noReset':True
    }
 try:
  driver = webdriver.Remote('http://127.0.0.1:%s/wd/hub'%appium_port,desired_caps)
  time.sleep(4)
  log.info('获取driver成功')
  return driver
 except WebDriverException:
  print 'No driver'
 
if __name__ == "__main__":
 mydriver()

重新封装find等命令,BaseOperate.py
里面主要是一些上滑、返回、find等一些基础操作

#coding=utf-8
#author='Shichao-Dong'
 
from selenium.webdriver.support.ui import WebDriverWait
from logs import log
import os
import time
 
'''
一些基础操作:滑动、截图、点击页面元素等
'''
 
class BaseOperate:
 def __init__(self,driver):
  self.driver = driver
 
 def back(self):
  '''
  返回键
  :return:
  '''
  os.popen("adb shell input keyevent 4")
 
 def get_window_size(self):
  '''
  获取屏幕大小
  :return: windowsize
  '''
  global windowSize
  windowSize = self.driver.get_window_size()
  return windowSize
 
 def swipe_up(self):
  '''
  向上滑动
  :return:
  '''
  windowsSize = self.get_window_size()
  width = windowsSize.get("width")
  height = windowsSize.get("height")
  self.driver.swipe(width/2, height*3/4, width/2, height/4, 1000)
 
 def screenshot(self):
  now=time.strftime("%y%m%d-%H-%M-%S")
  PATH = lambda p: os.path.abspath(
   os.path.join(os.path.dirname(__file__), p)
  )
  screenshoot_path = PATH('../results/screenshoot/')
  self.driver.get_screenshot_as_file(screenshoot_path+now+'.png')
 
 def find_id(self,id):
  '''
  寻找元素
  :return:
  '''
  exsit = self.driver.find_element_by_id(id)
  if exsit :
   return True
  else:
   return False
 
 def find_name(self,name):
  '''
  判断页面是否存在某个元素
  :param name: text
  :return:
  '''
  findname = "//*[@text='%s']"%(name)
  exsit = self.driver.find_element_by_xpath(findname)
  if exsit :
   return True
  else:
   return False
 
 def get_name(self,name):
  '''
  定位页面text元素
  :param name:
  :return:
  '''
  # element = driver.find_element_by_name(name)
  # return element
 
  findname = "//*[@text='%s']"%(name)
  try:
   element = WebDriverWait(self.driver, 10).until(lambda x: x.find_element_by_xpath(findname))
   # element = self.driver.find_element_by_xpath(findname)
   self.driver.implicitly_wait(2)
   return element
  except:
   self.screenshot()
   log.error('未定位到元素:'+'%s')%(name)
 
 def get_id(self,id):
  '''
  定位页面resouce-id元素
  :param id:
  :return:
  '''
  try:
   element = WebDriverWait(self.driver, 10).until(lambda x: x.find_element_by_id(id))
   # element = self.driver.find_element_by_id(id)
   self.driver.implicitly_wait(2)
   return element
  except:
   self.screenshot()
   log.error('未定位到元素:'+'%s')%(id)
 
 def get_xpath(self,xpath):
  '''
  定位页面xpath元素
  :param id:
  :return:
  '''
  try:
   element = WebDriverWait(self.driver, 10).until(lambda x: x.find_element_by_xpath(xpath))
   # element = self.driver.find_element_by_xpath(xpath)
   self.driver.implicitly_wait(2)
   return element
  except:
   self.screenshot()
   log.error('未定位到元素:'+'%s')%(xpath)
 
 def get_ids(self,id):
  '''
  定位页面resouce-id元素组
  :param id:
  :return:列表
  '''
  try:
   # elements = self.driver.find_elements_by_id(id)
   elements = WebDriverWait(self.driver, 10).until(lambda x: x.find_elements_by_id(id))
   self.driver.implicitly_wait(2)
   return elements
  except:
   self.screenshot()
   log.error('未定位到元素:'+'%s')%(id)
 
 def page(self,name):
  '''
  返回至指定页面
  :return:
  '''
  i=0
  while i<10:
   i=i+1
   try:
    findname = "//*[@text='%s']"%(name)
    self.driver.find_element_by_xpath(findname)
    self.driver.implicitly_wait(2)
    break
   except :
    os.popen("adb shell input keyevent 4")
    try:
     findname = "//*[@text='确定']"
     self.driver.find_element_by_xpath(findname).click()
     self.driver.implicitly_wait(2)
    except:
     os.popen("adb shell input keyevent 4")
    try:
     self.driver.find_element_by_xpath("//*[@text='工作台']")
     self.driver.implicitly_wait(2)
     break
    except:
     os.popen("adb shell input keyevent 4")

Operate.py
我认为最关键的一步了,后面没有page都是调用这个文件进行测试,主要是根据读取的yaml文件,然后进行if...else...判断,根据对应的operate_type分别进行对应的click、sendkeys等操作

#coding=utf-8
#author='Shichao-Dong'
 
from GetYaml import getyaml
from BaseOperate import BaseOperate
 
class Operate:
 def __init__(self,path,driver):
  self.path = path
  self.driver = driver
  self.yaml = getyaml(self.path)
  self.baseoperate=BaseOperate(driver)
 
 def check_operate_type(self):
  '''
  读取yaml信息并执行
  element_info:定位元素信息
  find_type:属性,id、xpath、text、ids
  operate_type: click、sendkeys、back、swipe_up 为back就是返回,暂时就三种
  上面三个必填,operate_type必填!!!!!!
  send_content:send_keys 时用到
  index:ids时用到
  times:
  :return:
  '''
 
  for i in range(self.yaml.caselen()):
   if self.yaml.get_operate_type(i) == 'click':
    if self.yaml.get_findtype(i) == 'text':
     self.baseoperate.get_name(self.yaml.get_elementinfo(i)).click()
    elif self.yaml.get_findtype(i) == 'id':
     self.baseoperate.get_id(self.yaml.get_elementinfo(i)).click()
    elif self.yaml.get_findtype(i) == 'xpath':
     self.baseoperate.get_xpath(self.yaml.get_elementinfo(i)).click()
    elif self.yaml.get_findtype(i) == 'ids':
     self.baseoperate.get_ids(self.yaml.get_elementinfo(i))[self.yaml.get_index(i)].click()
 
   elif self.yaml.get_operate_type(i) == 'send_keys':
    if self.yaml.get_findtype(i) == 'text':
     self.baseoperate.get_name(self.yaml.get_elementinfo(i)).send_keys(self.yaml.get_send_content(i))
    elif self.yaml.get_findtype(i) == 'id':
     self.baseoperate.get_id(self.yaml.get_elementinfo(i)).send_keys(self.yaml.get_send_content(i))
    elif self.yaml.get_findtype(i) == 'xpath':
     self.baseoperate.get_xpath(self.yaml.get_elementinfo(i)).send_keys(self.yaml.get_send_content(i))
    elif self.yaml.get_findtype(i) == 'ids':
     self.baseoperate.get_ids(self.yaml.get_elementinfo(i))[self.yaml.get_index(i)].send_keys(self.yaml.get_send_content(i))
 
   elif self.yaml.get_operate_type(i) == 'back':
    for n in range(self.yaml.get_backtimes(i)):
     self.baseoperate.back()
 
   elif self.yaml.get_operate_type(i) == 'swipe_up':
    for n in range(self.yaml.get_backtimes(i)):
     self.baseoperate.swipe_up()
 
 def back_home(self):
  '''
  返回至工作台
  :return:
  '''
  self.baseoperate.page('工作台')

公共部分的代码就介绍这么多,在编写这个框架的时候,大部分精力都花在这部分,所以个人觉得还是值得好好研究的

Page部分

page部分是最小用例集,一个模块一个文件夹,以客户为例,
目前写了两个用例,一个新增,一个排序,文件如下:

python+appium+yaml移动端自动化测试框架实现详解

代码如下,非常的简洁,

import sys
reload(sys)
sys.setdefaultencoding('utf8')
import codecs,os
from public.Operate import Operate
from public.GetYaml import getyaml
 
PATH = lambda p: os.path.abspath(
 os.path.join(os.path.dirname(__file__), p)
)
yamlpath = PATH("../../testyaml/cm/cm-001addcm.yaml")
 
class AddcmPage:
 
 def __init__(self,driver):
  self.path = yamlpath
  self.driver = driver
  self.operate = Operate(self.path,self.driver)
 
 def operateap(self):
  self.operate.check_operate_type()
 
 def home(self):
  self.operate.back_home()

运行用例

这部分用了unittest,运行所有测试用例和生成报告。
一个模块一个用例,以客户为例:CmTest.py

from page.cm.CmAddcmPage import AddcmPage
from page.cm.CmSortcmPage import SortcmPage
 
 
from public.GetDriver import mydriver
driver = mydriver()
 
import unittest,time
class Cm(unittest.TestCase):
 
 def test_001addcm(self):
  '''
  新增客户
  :return:
  '''
  add = AddcmPage(driver)
  add.operateap()
  add.home()
 def test_002sortcm(self):
  '''
  客户排序
  :return:
  '''
  sort = SortcmPage(driver)
  sort.sortlist()
  sort.home()
 
 def test_999close(self):
  driver.quit()
  time.sleep(10)
 
if __name__ == "__main__":
 unittest.main()

首先从page层将需要运行的用例都import进来,然后用unittest运行即可。
如果想要运行所有的测试用例,需要用到runtest.py

import time,os
import unittest
import HTMLTestRunner
from testcase.CmTest import Cm
 
 
def testsuit():
 suite = unittest.TestSuite()
 suite.addTests([unittest.defaultTestLoader.loadTestsFromTestCase(Cm),
 
 
 
 
])
 
 # runner = unittest.TextTestRunner(verbosity=2)
 # runner.run(suite)
 
 now=time.strftime("%y-%m-%d-%H-%M-%S")
 PATH = lambda p: os.path.abspath(
  os.path.join(os.path.dirname(__file__), p)
 )
 dirpath = PATH("./results/waiqin365-")
 
 filename=dirpath + now +'result.html'
 fp=open(filename,'wb')
 runner=HTMLTestRunner.HTMLTestRunner(stream=fp,title='waiqin365 6.0.6beta test result',description=u'result:')
 
 runner.run(suite)
 fp.close()
 
if __name__ =="__main__":
 testsuit()

这边的思路差不多,也是先导入再装入suite即可

总结

就目前而言,暂时算是实现了数据与用例的分离,但是yaml的编写要求较高,不能格式上出错。
同时也有一些其他可以优化的地方,如:

  • 对弹窗的判断
  • 断开后重连机制
  • 失败后重跑机制

到此这篇关于python+appium+yaml移动端自动化测试框架实现详解的文章就介绍到这了,更多相关python appium yaml 自动化测试 内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Python 相关文章推荐
python在多玩图片上下载妹子图的实现代码
Aug 13 Python
Python 基础教程之str和repr的详解
Aug 20 Python
Python中实现switch功能实例解析
Jan 11 Python
Python提取支付宝和微信支付二维码的示例代码
Feb 15 Python
Python同步遍历多个列表的示例
Feb 19 Python
Python3.4学习笔记之列表、数组操作示例
Mar 01 Python
Python基础教程之if判断,while循环,循环嵌套
Apr 25 Python
Python实现微信小程序支付功能
Jul 25 Python
python tornado修改log输出方式
Nov 18 Python
pytorch-RNN进行回归曲线预测方式
Jan 14 Python
Python LMDB库的使用示例
Feb 14 Python
python运算符之与用户交互
Apr 13 Python
Python利用myqr库创建自己的二维码
Nov 24 #Python
关于pycharm 切换 python3.9 报错 ‘HTMLParser‘ object has no attribute ‘unescape‘ 的问题
Nov 24 #Python
python中使用.py配置文件的方法详解
Nov 23 #Python
python爬虫使用scrapy注意事项
Nov 23 #Python
python爬虫筛选工作实例讲解
Nov 23 #Python
python爬虫用scrapy获取影片的实例分析
Nov 23 #Python
python爬虫scrapy图书分类实例讲解
Nov 23 #Python
You might like
php去掉字符串的最后一个字符附substr()的用法
2011/03/23 PHP
PHP实现Soap通讯的方法
2014/11/03 PHP
PHP实现UTF-8文件BOM自动检测与移除实例
2014/11/05 PHP
php实现的简单检验登陆类
2015/06/18 PHP
php自定义排序uasort函数示例【二维数组按指定键值排序】
2019/06/19 PHP
extjs tabpanel限制选项卡数量实现思路及代码
2013/04/02 Javascript
解析jquery获取父窗口的元素
2013/06/26 Javascript
JS读取XML文件示例代码
2013/11/15 Javascript
jQuery实现点击该行即可删除HTML表格行
2014/10/17 Javascript
Vue 页面跳转不用router-link的实现代码
2018/04/12 Javascript
vue实现简单瀑布流布局
2020/05/28 Javascript
详解vue3.0 的 Composition API 的一种使用方法
2020/10/26 Javascript
[01:48]帕吉至宝加入游戏,遗迹战场现“千劫神屠”
2018/04/07 DOTA
python实现关键词提取的示例讲解
2018/04/28 Python
利用Python将数值型特征进行离散化操作的方法
2018/11/06 Python
python字符串中匹配数字的正则表达式
2019/07/03 Python
用Python爬取QQ音乐评论并制成词云图的实例
2019/08/24 Python
python 创建一维的0向量实例
2019/12/02 Python
Pyqt5 关于流式布局和滚动条的综合使用示例代码
2020/03/24 Python
Python try except异常捕获机制原理解析
2020/04/18 Python
有趣的Python图片制作之如何用QQ好友头像拼接出里昂
2020/04/22 Python
python中的测试框架
2020/11/13 Python
html5基础教程常用技巧整理
2013/08/20 HTML / CSS
利物浦足球俱乐部官方商店(美国):Liverpool FC US
2019/10/09 全球购物
经济学博士求职自荐信范文
2013/11/23 职场文书
大学生学习自我评价
2014/01/13 职场文书
简洁的英文求职信范文
2014/05/03 职场文书
经营管理策划方案
2014/05/22 职场文书
《秋天的雨》教学反思
2016/02/19 职场文书
七年级作文之《我和我的祖国》观后感作文
2019/10/18 职场文书
节约用水广告语60条
2019/11/14 职场文书
MySQL表的增删改查(基础)
2021/04/05 MySQL
深度学习详解之初试机器学习
2021/04/14 Python
如何用threejs实现实时多边形折射
2021/05/07 Javascript
Pygame Time时间控制的具体使用详解
2021/11/17 Python
css中有哪些方式可以隐藏页面元素及区别
2022/06/16 HTML / CSS