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

结构介绍

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

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

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

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

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

logs 存放日志

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部分是最小用例集,一个模块一个文件夹,以客户为例,
目前写了两个用例,一个新增,一个排序,文件如下:

代码如下,非常的简洁,

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 自动化测试 内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

时间: 2020-11-23

Appium+Python自动化测试之运行App程序示例

在上一篇博客中,已经将环境搭建好了.现在,我们利用搭建的环境来运行一条测试脚本,脚本中启动一个计算器的应用,并实现加法的运算. 创建模拟器 在运行App之前,首先需要创建一个Android模拟器,也可以使用真机来运行,效果更佳. 进到Android SDK的目录,双击运行AVD Manager.exe 点击Create创建一个Android模拟器 输入一个模拟器名称,选择设备类型和API版本,点击进行创建.创建完成后会显示当前所有的设备,选择设备,点击Start启动 第一次启动的时候会有点慢,要

Appium+python自动化怎么查看程序所占端口号和IP

简介 这篇博文和分类看似没有多大关系,但是也是从上一篇衍生出来的产物,因为涉及到 FQ工具 Lantern ,就算是给关注和支持的小伙伴们拓展一下眼界和知识面.而且好多人都阅读了上一篇没发现那个参考博客点不开吗?那是因为还没来的急,整理和编写,今天再试一下就可以. 一个软件可能占用多个端口拥有多个目标IP,下面以FQ工具 Lantern 为例,说明端口查看方法: 1.借助第三方软件查看 如果你电脑上安装了360等优化工具,可能会自带查看网络状况的插件如下图: 2.命令行窗口查看 在命令行窗口查看

Python脚本在Appium库上对移动应用实现自动化测试

采用Appium进行自动化的功能性测试最酷的一点是,你可以使用具有最适合你的测试工具的任何一门语言来写你的测试代码.大家选择最多的一个测试编程语言就是Python. 使用Appium和Python为iOS和Android应用编写测试代码非常容易. 在这篇博文中我们将详细讲解使用Appium下的Python编写的测试的例子代码对一个iOS的样例应用进行测试所涉及的各个步骤,而对Android应用进行测试所需的步骤与此非常类似. 开始,先自https://github.com/appium/appi

Appium+python自动化之连接模拟器并启动淘宝APP(超详解)

简介 上一篇讲解完模拟器的安装.配置好以后,就好比我们手机已经买好,并且系统已经做好了,就差我们用数据线和电脑连接开始实战了,这篇宏哥就带着小伙伴们和童鞋们趁热打铁,讲解和分享一下如何连接模拟器(电脑版的虚拟手机),然后再安装一款APP-淘宝为例. 一.appium+pycharm+连接夜神模拟器并启动淘宝APP(推荐) 1.首先打开pycharm并写好代码,这里以淘宝APK作为测试Demo,淘宝APK我是放在电脑桌面(或者放置同代码同个目录下).如下图所示 其中|:driver = webdr

详解基于Android的Appium+Python自动化脚本编写

1.Appium Appium是一个开源测试自动化框架,可用于原生,混合和移动Web应用程序测试, 它使用WebDriver协议驱动iOS,Android和Windows应用程序. 通过Appium,我们可以模拟点击和屏幕的滑动,可以获取元素的id和classname,还可以根据操作生成相关的脚本代码. 下面开始Appium的配置. appPackage和APPActivity的获取 任意下载一个app 解压 但是解压出来的xml文件可能是乱码,所以我们需要反编译文件. 逆向AndroidMan

Python+Appium实现自动化测试的使用步骤

一.环境准备 1.脚本语言:Python3.x IDE:安装Pycharm 2.安装Java JDK .Android SDK 3.adb环境,path添加E:\Software\Android_SDK\platform-tools 4.安装Appium for windows,官网地址http://appium.io/ 点击下载按钮会到GitHub的下载页面,选择对应平台下载 安装完成后,启动Appium,host和port默认的即可,然后设置Android SDk和Java JDK 然后点下

Python+appium框架原生代码实现App自动化测试详解

step1:首先介绍下python+appium的框架结构,如下截图所示 (1):apk目录主要放置待测app的apk资源: (2):config目录主要放置配置文件信息,包含:数据库连接配置.UI自动化脚本中所需的页面元素信息及app启动信息.自动化报告邮件发送配置信息.接口请求的对应的url信息等[大家可根据待测app的功能添加或减少配置文件信息]. (3):report目录主要放置测试完成后生成的测试报告信息: (4):src目录下包含的目录如下 common目录:app启动方法的封装文件

Appium Python自动化测试之环境搭建的步骤

Appium简介 Appium是一个自动化测试开源工具,支持IOS和Android平台上的移动原生应用.移动Web应用和混合应用.所谓的"移动原生应用"是指那些用IOS或者Android SDK写的应用:所谓的"移动Web应用"是指使用移动浏览器方位的应用(Appium支持IOS上的Safari和Android上的Chrome):所谓的"混合应用"是指原生代码封装网页视图(原生代码和Web内容交互).更重要的是,Appium是一个跨平台的工具,它

appium+python自动化配置(adk、jdk、node.js)

1.环境配置 1.1node.js https://nodejs.org/zh-cn/ 安装提示下载安装 命令行下输入 node -v 查看是否安装成功,如图所示,则安装成功 1.2sdk 1.2.1安装sdk 下载地址: www.androiddevtools.cn/ 一路点下去,最后点finish. 完成后会自动弹出以下页面 如果没弹出上边的页面,则找到安装路径,打开下图所示的文件,即可弹出上边的页面 勾选图示的文件 安装,接受协议(Accept License). 1.2.2配置环境变量

Nginx设置为Node.js的前端服务器方法总结

1.安装node.js 首先安装node.js安装所需的软件包,并在启动板上添加可用的nodejs的PPA.之后使用以下命令安装nodejs. $ sudo apt-get install python-software-properties python g++ make $ sudo add-apt-repository ppa:chris-lea/node.js $ sudo apt-get update $ sudo apt-get install nodejs 2.安装nginx 现在

吐槽一下我所了解的Node.js

首先是性能,用起来感觉 Node.js 的性能并没有想象中那么好.内存占用差不多和 Ruby 一个等级,比 Python, PHP 要多.计算性能(即 CPU 使用)上,纯计算并没有太大优势,但因为可以很好地控制异步流程,所以总体表现上来看性能要比 PHP 好很多.个人觉得 Node.js 在性能方面是有很大的潜力的,因为有很多大的项目在致力于提高 JavaScript 虚拟机的性能--比如各大浏览器:同时 JavaScript 是一个语法简单的语言,也是一个开放标准,不像 Python 和 P

十大 Node.js 的 Web 框架(快速提升工作效率)

Node.js 系统含有多种不同的结构,如 MVC.全栈.REST API 和生成器等.这些结构不仅提升了 Web 应用的开发效率,也优化了开发过程.在这里,我们收集整理了十个高效的 Node.js 框架,希望对你有帮助. 1.Node.js 开发框架 Sail.js Sails.js 就像是 Node.js 平台上的 Rails 框架.这是一个可靠可伸缩的开发框架,面向服务的架构,提供数据驱动的 API 集合.用来开发多玩家游戏.聊天应用和实时面板引用非常方便,也可用于开发企业级 Node.j

前端自动化开发之Node.js的环境搭建教程

一.为什么我们前端自动化开发 相信任何学开发的,不管学什么语言,老师都这样讲过,作为一名开发人员,你最大的精力应该是放在创造力上面,don't you repeat myself 不要重复自己,而在我们开发过程中,往往存在很多大量的重复操作,所以我们需要为这些操作省去时间,腾出更多的时间来让我们创造. 而自动化开发能带来哪些自动化: 1.自动编译(将less,sass等自动编译) 2.自动合并(将页面引入的多个js文件,或者css文件,合并为同一个且压缩) 3.自动刷新(IDE保存,浏览器不用刷

Node.js 学习笔记之简介、安装及配置

简单的说 Node.js 就是运行在服务端的 JavaScript. Node.js 是一个基于Chrome JavaScript 运行时建立的一个平台. Node.js是一个事件驱动I/O服务端JavaScript环境,基于Google的V8引擎,V8引擎执行Javascript的速度非常快,性能非常好. 谁适合阅读本教程? 如果你是一个前端程序员,你不懂的像PHP.Python或Java等动态编程语言,然后你想创建自己的服务,那么Node.js是一个非常好的选择. Node.js 是运行在服