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读取html中指定元素生成excle文件示例
Apr 03 Python
Python的ORM框架中SQLAlchemy库的查询操作的教程
Apr 25 Python
Python多进程库multiprocessing中进程池Pool类的使用详解
Nov 24 Python
python 地图经纬度转换、纠偏的实例代码
Aug 06 Python
Python 运行 shell 获取输出结果的实例
Jan 07 Python
Django Rest framework认证组件详细用法
Jul 25 Python
解决python明明pip安装成功却找不到包的问题
Aug 28 Python
Python 一行代码能实现丧心病狂的功能
Jan 18 Python
Python 如何测试文件是否存在
Jul 31 Python
Python类型转换的魔术方法详解
Dec 23 Python
matplotlib绘制多子图共享鼠标光标的方法示例
Jan 08 Python
Python基于opencv的简单图像轮廓形状识别(全网最简单最少代码)
Jan 28 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
Zerg剧情介绍
2020/03/14 星际争霸
PHP屏蔽过滤指定关键字的方法
2014/11/03 PHP
两款万能的php分页类
2015/11/12 PHP
如何使用php脚本给html中引用的js和css路径打上版本号
2015/11/18 PHP
PHP基于imagick扩展实现合成图片的两种方法【附imagick扩展下载】
2017/11/14 PHP
Laravel中10个有用的用法小结
2019/05/06 PHP
php实现获取近几日、月时间示例
2019/07/06 PHP
js调用flash的效果代码
2008/04/26 Javascript
验证手机号码的JS方法分享
2013/09/10 Javascript
原生js和jquery中有关透明度设置的相关问题
2014/01/08 Javascript
jQuery实现简单的日期输入格式化控件
2015/03/12 Javascript
Jquery和angularjs获取check框选中的值的方法汇总
2016/01/17 Javascript
js实现的二分查找算法实例
2016/01/21 Javascript
JS实现PC手机端和嵌入式滑动拼图验证码三种效果
2017/02/15 Javascript
js实现加载页面就自动触发超链接的示例
2017/08/31 Javascript
微信小程序实现验证码获取倒计时效果
2018/02/08 Javascript
JavaScript继承定义与用法实践分析
2018/05/28 Javascript
Python的Twisted框架上手前所必须了解的异步编程思想
2016/05/25 Python
Python实现加载及解析properties配置文件的方法
2018/03/29 Python
pandas 读取各种格式文件的方法
2018/06/22 Python
基于python分析你的上网行为 看看你平时上网都在干嘛
2019/08/13 Python
Python time库基本使用方法分析
2019/12/13 Python
Python递归及尾递归优化操作实例分析
2020/02/01 Python
美国儿童珠宝在线零售商:Loveivy
2019/05/22 全球购物
Java程序员面试题
2013/07/15 面试题
酒店管理毕业生自我鉴定
2014/03/02 职场文书
自荐信的格式
2014/03/10 职场文书
《每逢佳节倍思亲》教后反思
2014/04/19 职场文书
汽车销售经理岗位职责
2014/06/09 职场文书
党员个人剖析材料(四风问题)
2014/10/07 职场文书
党的群众路线教育实践活动学习计划
2014/11/03 职场文书
新教师个人总结
2015/02/06 职场文书
大学感恩节活动总结
2015/05/05 职场文书
三好学生主要事迹怎么写
2015/11/03 职场文书
车辆挂靠协议书
2016/03/23 职场文书
Redis如何使用乐观锁(CAS)保证数据一致性
2022/03/25 Redis