小言_互联网的博客

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

271人阅读  评论(0)

结构介绍

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

结构.png

 

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

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

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

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

     

     

    report.png

  • logs 存放日志

     

     

    logs.png

     

     

    logdetail.png

  • 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: 返回次数或者上滑次数


  
  1. testinfo:
  2. - id: cm001
  3. title: 新增终端门店
  4. execute: 1
  5. testcase:
  6. -
  7. element_info: 客户
  8. find_type: text
  9. operate_type: click
  10. -
  11. element_info: com.fiberhome.waiqin365.client:id/cm_topbar_tv_right
  12. find_type: id
  13. operate_type: click
  14. -
  15. element_info: com.fiberhome.waiqin365.client:id/custview_id_singletv_inputtext
  16. find_type: ids
  17. operate_type: send_keys
  18. send_content: auto0205
  19. index: 0
  20. -
  21. element_info:
  22. find_type:
  23. operate_type: swipe_up
  24. times: 1
  25. -
  26. element_info: 提交
  27. find_type: text
  28. operate_type: click
  29. -
  30. element_info:
  31. find_type:
  32. operate_type: back
  33. times: 1

代码部分

公共部分

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

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

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

  
  1. #coding=utf-8
  2. #author='Shichao-Dong'
  3. import sys
  4. reload(sys)
  5. sys.setdefaultencoding( 'utf8')
  6. import yaml
  7. import codecs
  8. class getyaml:
  9. def __init__(self,path):
  10. self.path = path
  11. def getYaml(self):
  12. '''
  13. 读取yaml文件
  14. :param path: 文件路径
  15. :return:
  16. '''
  17. try:
  18. f = open(self.path)
  19. data =yaml.load(f)
  20. f.close()
  21. return data
  22. except Exception:
  23. print( u"未找到yaml文件")
  24. def alldata(self):
  25. data =self.getYaml()
  26. return data
  27. def caselen(self):
  28. data = self.alldata()
  29. length = len(data[ 'testcase'])
  30. return length
  31. def get_elementinfo(self,i):
  32. data = self.alldata()
  33. # print data['testcase'][i]['element_info']
  34. return data[ 'testcase'][i][ 'element_info']
  35. def get_findtype(self,i):
  36. data = self.alldata()
  37. # print data['testcase'][i]['find_type']
  38. return data[ 'testcase'][i][ 'find_type']
  39. def get_operate_type(self,i):
  40. data = self.alldata()
  41. # print data['testcase'][i]['operate_type']
  42. return data[ 'testcase'][i][ 'operate_type']
  43. def get_index(self,i):
  44. data = self.alldata()
  45. if self.get_findtype(i)== 'ids':
  46. return data[ 'testcase'][i][ 'index']
  47. else:
  48. pass
  49. def get_send_content(self,i):
  50. data = self.alldata()
  51. # print data['testcase'][i]['send_content']
  52. if self.get_operate_type(i) == 'send_keys':
  53. return data[ 'testcase'][i][ 'send_content']
  54. else:
  55. pass
  56. def get_backtimes(self,i):
  57. data = self.alldata()
  58. if self.get_operate_type(i)== 'back' or self.get_operate_type(i)== 'swipe_up':
  59. return data[ 'testcase'][i][ 'times']
  60. else:
  61. pass
  62. def get_title(self):
  63. data = self.alldata()
  64. # print data['testinfo'][0]['title']
  65. return data[ 'testinfo'][ 0][ 'title']
  • 启动appium服务 StartAppiumServer.py
    主要是启动appium并返回端口port,这个port在下面的driver中需要

  
  1. #coding=utf-8
  2. #author='Shichao-Dong'
  3. from logs import log
  4. import random,time
  5. import platform
  6. import os
  7. from GetDevices import devices
  8. log = log()
  9. dev = devices().get_deviceName()
  10. class Sp:
  11. def __init__(self, device):
  12. self.device = device
  13. def __start_driver(self, aport, bpport):
  14. """
  15. :return:
  16. """
  17. if platform.system() == 'Windows':
  18. import subprocess
  19. subprocess.Popen( "appium -p %s -bp %s -U %s" %
  20. (aport, bpport, self.device), shell= True)
  21. def start_appium(self):
  22. """
  23. 启动appium
  24. p:appium port
  25. bp:bootstrap port
  26. :return: 返回appium端口参数
  27. """
  28. aport = random.randint( 4700, 4900)
  29. bpport = random.randint( 4700, 4900)
  30. self.__start_driver(aport, bpport)
  31. log.info(
  32. 'start appium :p %s bp %s device:%s' %
  33. (aport, bpport, self.device))
  34. time.sleep( 10)
  35. return aport
  36. def main(self):
  37. """
  38. :return: 启动appium
  39. """
  40. return self.start_appium()
  41. def stop_appium(self):
  42. '''
  43. 停止appium
  44. :return:
  45. '''
  46. if platform.system() == 'Windows':
  47. os.popen( "taskkill /f /im node.exe")
  48. if __name__ == '__main__':
  49. s = Sp(dev)
  50. s.main()
  • 获取driver GetDriver.py
    platformName、deviceName、appPackage、appActivity这些卸载配置文件config.ini文件中,可以直接通过readconfig.py文件读取获得。
    appium_port有StartAppiumServer.py文件返回

  
  1. s = Sp(deviceName)
  2. appium_port = s.main()
  3. def mydriver():
  4. desired_caps = {
  5. 'platformName' :platformName, 'deviceName' :deviceName, 'platformVersion' :platformVersion,
  6. 'appPackage' :appPackage, 'appActivity' :appActivity,
  7. 'unicodeKeyboard' :True, 'resetKeyboard' :True, 'noReset' :True
  8. }
  9. try:
  10. driver = webdriver.Remote( 'http://127.0.0.1:%s/wd/hub'%appium_port,desired_caps)
  11. time.sleep( 4)
  12. log.info( '获取driver成功')
  13. return driver
  14. except WebDriverException:
  15. print 'No driver'
  16. if __name_ _ == "__main__":
  17. mydriver()
  • 重新封装find等命令,BaseOperate.py
    里面主要是一些上滑、返回、find等一些基础操作

  
  1. #coding=utf-8
  2. #author='Shichao-Dong'
  3. from selenium.webdriver.support.ui import WebDriverWait
  4. from logs import log
  5. import os
  6. import time
  7. '''
  8. 一些基础操作:滑动、截图、点击页面元素等
  9. '''
  10. class BaseOperate:
  11. def __init__(self,driver):
  12. self.driver = driver
  13. def back(self):
  14. '''
  15. 返回键
  16. :return:
  17. '''
  18. os.popen( "adb shell input keyevent 4")
  19. def get_window_size(self):
  20. '''
  21. 获取屏幕大小
  22. :return: windowsize
  23. '''
  24. global windowSize
  25. windowSize = self.driver.get_window_size()
  26. return windowSize
  27. def swipe_up(self):
  28. '''
  29. 向上滑动
  30. :return:
  31. '''
  32. windowsSize = self.get_window_size()
  33. width = windowsSize.get( "width")
  34. height = windowsSize.get( "height")
  35. self.driver.swipe(width/ 2, height* 3/ 4, width/ 2, height/ 4, 1000)
  36. def screenshot(self):
  37. now=time.strftime( "%y%m%d-%H-%M-%S")
  38. PATH = lambda p: os.path.abspath(
  39. os.path.join(os.path.dirname(__file__), p)
  40. )
  41. screenshoot_path = PATH( '../results/screenshoot/')
  42. self.driver.get_screenshot_as_file(screenshoot_path+now+ '.png')
  43. def find_id(self,id):
  44. '''
  45. 寻找元素
  46. :return:
  47. '''
  48. exsit = self.driver.find_element_by_id(id)
  49. if exsit :
  50. return True
  51. else:
  52. return False
  53. def find_name(self,name):
  54. '''
  55. 判断页面是否存在某个元素
  56. :param name: text
  57. :return:
  58. '''
  59. findname = "//*[@text='%s']"%(name)
  60. exsit = self.driver.find_element_by_xpath(findname)
  61. if exsit :
  62. return True
  63. else:
  64. return False
  65. def get_name(self,name):
  66. '''
  67. 定位页面text元素
  68. :param name:
  69. :return:
  70. '''
  71. # element = driver.find_element_by_name(name)
  72. # return element
  73. findname = "//*[@text='%s']"%(name)
  74. try:
  75. element = WebDriverWait(self.driver, 10).until( lambda x: x.find_element_by_xpath(findname))
  76. # element = self.driver.find_element_by_xpath(findname)
  77. self.driver.implicitly_wait( 2)
  78. return element
  79. except:
  80. self.screenshot()
  81. log.error( '未定位到元素:'+ '%s')%(name)
  82. def get_id(self,id):
  83. '''
  84. 定位页面resouce-id元素
  85. :param id:
  86. :return:
  87. '''
  88. try:
  89. element = WebDriverWait(self.driver, 10).until( lambda x: x.find_element_by_id(id))
  90. # element = self.driver.find_element_by_id(id)
  91. self.driver.implicitly_wait( 2)
  92. return element
  93. except:
  94. self.screenshot()
  95. log.error( '未定位到元素:'+ '%s')%(id)
  96. def get_xpath(self,xpath):
  97. '''
  98. 定位页面xpath元素
  99. :param id:
  100. :return:
  101. '''
  102. try:
  103. element = WebDriverWait(self.driver, 10).until( lambda x: x.find_element_by_xpath(xpath))
  104. # element = self.driver.find_element_by_xpath(xpath)
  105. self.driver.implicitly_wait( 2)
  106. return element
  107. except:
  108. self.screenshot()
  109. log.error( '未定位到元素:'+ '%s')%(xpath)
  110. def get_ids(self,id):
  111. '''
  112. 定位页面resouce-id元素组
  113. :param id:
  114. :return:列表
  115. '''
  116. try:
  117. # elements = self.driver.find_elements_by_id(id)
  118. elements = WebDriverWait(self.driver, 10).until( lambda x: x.find_elements_by_id(id))
  119. self.driver.implicitly_wait( 2)
  120. return elements
  121. except:
  122. self.screenshot()
  123. log.error( '未定位到元素:'+ '%s')%(id)
  124. def page(self,name):
  125. '''
  126. 返回至指定页面
  127. :return:
  128. '''
  129. i= 0
  130. while i< 10:
  131. i=i+ 1
  132. try:
  133. findname = "//*[@text='%s']"%(name)
  134. self.driver.find_element_by_xpath(findname)
  135. self.driver.implicitly_wait( 2)
  136. break
  137. except :
  138. os.popen( "adb shell input keyevent 4")
  139. try:
  140. findname = "//*[@text='确定']"
  141. self.driver.find_element_by_xpath(findname).click()
  142. self.driver.implicitly_wait( 2)
  143. except:
  144. os.popen( "adb shell input keyevent 4")
  145. try:
  146. self.driver.find_element_by_xpath( "//*[@text='工作台']")
  147. self.driver.implicitly_wait( 2)
  148. break
  149. except:
  150. os.popen( "adb shell input keyevent 4")
  • Operate.py
    我认为最关键的一步了,后面没有page都是调用这个文件进行测试,主要是根据读取的yaml文件,然后进行if...else...判断,根据对应的operate_type分别进行对应的click、sendkeys等操作

  
  1. #coding=utf-8
  2. #author='Shichao-Dong'
  3. from GetYaml import getyaml
  4. from BaseOperate import BaseOperate
  5. class Operate:
  6. def __init__(self,path,driver):
  7. self.path = path
  8. self.driver = driver
  9. self.yaml = getyaml( self.path)
  10. self.baseoperate=BaseOperate(driver)
  11. def check_operate_type(self):
  12. '' '
  13. 读取yaml信息并执行
  14. element_info:定位元素信息
  15. find_type:属性,id、xpath、text、ids
  16. operate_type: click、sendkeys、back、swipe_up 为back就是返回,暂时就三种
  17. 上面三个必填,operate_type必填!!!!!!
  18. send_content:send_keys 时用到
  19. index:ids时用到
  20. times:
  21. :return:
  22. ' ''
  23. for i in range( self.yaml.caselen()):
  24. if self.yaml.get_operate_type(i) == 'click':
  25. if self.yaml.get_findtype(i) == 'text':
  26. self.baseoperate.get_name( self.yaml.get_elementinfo(i)).click()
  27. elif self.yaml.get_findtype(i) == 'id':
  28. self.baseoperate.get_id( self.yaml.get_elementinfo(i)).click()
  29. elif self.yaml.get_findtype(i) == 'xpath':
  30. self.baseoperate.get_xpath( self.yaml.get_elementinfo(i)).click()
  31. elif self.yaml.get_findtype(i) == 'ids':
  32. self.baseoperate.get_ids( self.yaml.get_elementinfo(i))[ self.yaml.get_index(i)].click()
  33. elif self.yaml.get_operate_type(i) == 'send_keys':
  34. if self.yaml.get_findtype(i) == 'text':
  35. self.baseoperate.get_name( self.yaml.get_elementinfo(i)).send_keys( self.yaml.get_send_content(i))
  36. elif self.yaml.get_findtype(i) == 'id':
  37. self.baseoperate.get_id( self.yaml.get_elementinfo(i)).send_keys( self.yaml.get_send_content(i))
  38. elif self.yaml.get_findtype(i) == 'xpath':
  39. self.baseoperate.get_xpath( self.yaml.get_elementinfo(i)).send_keys( self.yaml.get_send_content(i))
  40. elif self.yaml.get_findtype(i) == 'ids':
  41. self.baseoperate.get_ids( self.yaml.get_elementinfo(i))[ self.yaml.get_index(i)].send_keys( self.yaml.get_send_content(i))
  42. elif self.yaml.get_operate_type(i) == 'back':
  43. for n in range( self.yaml.get_backtimes(i)):
  44. self.baseoperate.back()
  45. elif self.yaml.get_operate_type(i) == 'swipe_up':
  46. for n in range( self.yaml.get_backtimes(i)):
  47. self.baseoperate.swipe_up()
  48. def back_home(self):
  49. '' '
  50. 返回至工作台
  51. :return:
  52. ' ''
  53. self.baseoperate.page( '工作台')

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

Page部分

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

 

 

file.png

 

代码如下,非常的简洁,


  
  1. import sys
  2. reload(sys)
  3. sys.setdefaultencoding( 'utf8')
  4. import codecs,os
  5. from public.Operate import Operate
  6. from public.GetYaml import getyaml
  7. PATH = lambda p: os.path.abspath(
  8. os.path.join(os.path.dirname(__file_ _), p)
  9. )
  10. yamlpath = PATH( "../../testyaml/cm/cm-001addcm.yaml")
  11. class AddcmPage:
  12. def __init__(self,driver):
  13. self.path = yamlpath
  14. self.driver = driver
  15. self.operate = Operate( self.path, self.driver)
  16. def operateap(self):
  17. self.operate.check_operate_type()
  18. def home(self):
  19. self.operate.back_home()

运行用例

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


  
  1. from page.cm.CmAddcmPage import AddcmPage
  2. from page.cm.CmSortcmPage import SortcmPage
  3. from public.GetDriver import mydriver
  4. driver = mydriver()
  5. import unittest,time
  6. class Cm(unittest.TestCase):
  7. def test_001addcm(self):
  8. '''
  9. 新增客户
  10. :return:
  11. '''
  12. add = AddcmPage(driver)
  13. add.operateap()
  14. add.home()
  15. def test_002sortcm(self):
  16. '''
  17. 客户排序
  18. :return:
  19. '''
  20. sort = SortcmPage(driver)
  21. sort.sortlist()
  22. sort.home()
  23. def test_999close(self):
  24. driver.quit()
  25. time.sleep( 10)
  26. if __name__ == "__main__":
  27. unittest.main()

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


  
  1. import time,os
  2. import unittest
  3. import HTMLTestRunner
  4. from testcase.CmTest import Cm
  5. def testsuit():
  6. suite = unittest.TestSuite()
  7. suite.addTests([unittest.defaultTestLoader.loadTestsFromTestCase(Cm),
  8. ])
  9. # runner = unittest.TextTestRunner(verbosity=2)
  10. # runner.run(suite)
  11. now=time.strftime( "%y-%m-%d-%H-%M-%S")
  12. PATH = lambda p: os.path.abspath(
  13. os.path.join(os.path.dirname(__file__), p)
  14. )
  15. dirpath = PATH( "./results/waiqin365-")
  16. filename=dirpath + now + 'result.html'
  17. fp=open(filename, 'wb')
  18. runner=HTMLTestRunner.HTMLTestRunner(stream=fp,title= 'waiqin365 6.0.6beta test result',description= u'result:')
  19. runner.run(suite)
  20. fp.close()
  21. if __name__ == "__main__":
  22. testsuit()

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

总结

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

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

转载:https://blog.csdn.net/grl18840839630/article/details/109987870
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场