本人小白一枚,想着把学习时的东西以博客的方式记录下来,文章中有不正确的地方请大佬多多指点!!共同学习


前期准备

  安装python3、selenium、下载对应版本的webdriver;安装所需的第三方库,不多赘述,最基础的东西,不会的自行跳转^0^

项目介绍

功能简述

  1. 对webdriver常用方法进行二次封装,使用起来更方便,同时会有log记录
  2. log日志会同时打印在控制台和写入log文件中
  3. 测试完成后,会自动发送邮件,邮件信息conf.ini可配置
  4. 采用PO模式编写,元素信息维护在对应页面中
  5. 支持chrome,Firefox,IE浏览器
  6. 支持web和wap模式切换,和-headless模式运行

项目结构

  目录结构如图所示

  该框架采用PO模式编写,PO提供了一种业务流程与页面元素操作分离的模式,这使得测试代码变得更加清晰。页面对象与用例分离,使得我们更好的复用对象,可复用的页面方法代码会变得更加优化,更加有效的命名方式使得我们更加清晰的知道方法所操作的UI元素。当某些页面的元素发生改变时,只需要对应的页面类即可,后面会有详细介绍^

1 log输出模块

  使用python的logging模块分别输出日志到文件和控制台,其实现方法供参考

  1. def __printconsole(self, level, message):
  2. # 创建一个logger
  3. logger = logging.getLogger()
  4. logger.setLevel(logging.DEBUG)
  5. # 创建一个handler,用于写入日志文件
  6. fh = logging.FileHandler(self.logname, 'a', encoding='utf-8')
  7. fh.setLevel(logging.DEBUG)
  8. # 再创建一个handler,用于输出到控制台
  9. ch = logging.StreamHandler()
  10. ch.setLevel(logging.DEBUG)
  11. # 定义handler的输出格式
  12. formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
  13. fh.setFormatter(formatter)
  14. ch.setFormatter(formatter)
  15. # 给logger添加handler
  16. logger.addHandler(fh)
  17. logger.addHandler(ch)
  18. # 记录一条日志
  19. if level == 'info':
  20. logger.info(message)
  21. elif level == 'debug':
  22. logger.debug(message)
  23. elif level == 'warning':
  24. logger.warning(message)
  25. elif level == 'error':
  26. logger.error(message)
  27. logger.removeHandler(ch)
  28. logger.removeHandler(fh)
  29. # 关闭打开的文件
  30. fh.close()
  31.  
  32. def debug(self, message):
  33. self.__printconsole('debug', message)
  34.  
  35. def info(self, message):
  36. self.__printconsole('info', message)
  37.  
  38. def warning(self, message):
  39. self.__printconsole('warning', message)
  40.  
  41. def error(self, message):
  42. self.__printconsole('error', message)

2 读取配置文件

  配置文件我选择的时.ini格式的,其实可以写成一个python文件或者txt文件,对应的读取方法都大同小异。

  我的conf.ini文件如下

  1. [browserType]
  2. ;browserName = Firefox
  3. browserName = Chrome
  4. #browserName = IE
  5.  
  6. [testServer]
  7. URL = https://www.baidu.com
  8.  
  9. [pattern]
  10. options = Web
  11. is_visible = T
  12.  
  13. [Mail]
  14. mail_subject = 邮件主题
  15. mail_server = smtp.xxxx.com
  16. mail_from = xxxx@xxx.com
  17. mail_to = xxxxx@xx.com,xxxxx@xxx.com
  18. mail_from_pwd = xxxxxx

读取的方法也很简单,网上也有很多教程,贴出来仅供参考

  1. class ReadConfig:
  2. """
  3. 专门读取配置文件的,.ini文件格式
  4. """
  5.  
  6. def __init__(self, filename=conf_path):
  7. with open(filename, 'r', encoding='UTF-8') as f:
  8. data = f.read()
  9. if data[:3] == codecs.BOM_UTF8:
  10. data = data[3:]
  11. files = codecs.open(filename, "w")
  12. files.write(data)
  13. files.close()
  14.  
  15. self.cf = configparser.ConfigParser()
  16. self.cf.read(filename, encoding='UTF-8')
  17.  
  18. def getValue(self, env, name):
  19. """读取配置文件中的值"""
  20. return self.cf.get(env, name)

3 选择浏览器

  这部分要做到的是根据配置文件选择要使用的浏览器,以及相应的运行模式,先上代码*

  

  1. def open_browser(self, driver):
  2. read = ReadConfig()
  3. browser = read.getValue("browserType", "browserName")
  4. options = read.getValue("pattern", "options")
  5. url = read.getValue("testServer", "URL")
  6. is_visible = read.getValue("pattern", "is_visible")
  7.  
  8. if options.lower() == 'web':
  9. if browser == "Firefox":
  10. if is_visible == 'F':
  11. options = webdriver.FirefoxOptions()
  12. options.add_argument('-headless')
  13. # options.add_argument('--disable-gpu')
  14. driver = webdriver.Firefox(executable_path=firefox_driver, options=options)
  15. else:
  16. driver = webdriver.Firefox(executable_path=firefox_driver)
  17. log.info("启动{}浏览器".format(browser))
  18.  
  19. elif browser == "Chrome":
  20. if is_visible == 'F':
  21. option = webdriver.ChromeOptions()
  22. option.add_argument('headless')
  23. driver = webdriver.Chrome(chrome_driver, chrome_options=option)
  24. else:
  25. driver = webdriver.Chrome(chrome_driver)
  26. log.info("启动{}浏览器".format(browser))
  27.  
  28. elif options.lower() == 'wap':
  29. mobileEmulation = {"deviceName": "iPhone 6"}
  30. options = webdriver.ChromeOptions()
  31. options.add_experimental_option('mobileEmulation', mobileEmulation)
  32. options.add_argument('--disable-search-geolocation-disclosure')
  33. driver = webdriver.Chrome(chrome_options=options)

  通过读取配置文件获取相应的信息,选择相应的浏览器,支持web和wap两种模式,如果你想运行时,隐藏浏览器窗口可以使用浏览器的headless模式,代码中 if is_visible == 'F' 时,执行的就行浏览器的headless模式,浏览器在后台运行,不影响测试效果。

4 二次封装selenium方法

  二次封装selenium中的常用方法可以让我们更方便的使用,也可以定制化我们的需求,比如加上日志输出,执行耗时等等

    

  1. class BasePage:
  2. """测试基类"""
  3.  
  4. def __init__(self, driver):
  5. self.driver = driver
  6.  
  7. def get_img(self, rq=time.strftime('%Y%m%d%H%M', time.localtime(time.time()))):
  8. """截图"""
  9. path = os.path.join(os.path.abspath('..'), 'report', 'img')
  10. # path = os.path.join(getcwd.get_cwd(), 'screenshots/') # 拼接截图保存路径
  11. # rq = time.strftime('%Y%m%d%H%M', time.localtime(time.time())) # 按格式获取当前时间
  12. screen_name = path + rq + '.png' # 拼接截图文件名
  13. # noinspection PyBroadException
  14. try:
  15. self.driver.get_screenshot_as_file(screen_name)
  16. log.info("截图保存成功{}".format(screen_name))
  17. except BaseException as e:
  18. log.error("截图失败{}".format(e))
  19.  
  20. def find_element(self, selector):
  21. """定位元素"""
  22. by = selector[0]
  23. value = selector[1]
  24. element = None
  25. if by in ['id', 'name', 'class', 'tag', 'link', 'plink', 'css', 'xpath']:
  26. # noinspection PyBroadException
  27. try:
  28. if by == 'id':
  29. element = self.driver.find_element_by_id(value)
  30. elif by == 'name':
  31. element = self.driver.find_element_by_name(value)
  32. elif by == 'class':
  33. element = self.driver.find_element_by_class_name(value)
  34. elif by == 'tag':
  35. element = self.driver.find_element_by_tag_name(value)
  36. elif by == 'link':
  37. element = self.driver.find_element_by_link_text(value)
  38. elif by == 'plink':
  39. element = self.driver.find_element_by_partial_link_text(value)
  40. elif by == 'css':
  41. element = self.driver.find_element_by_css_selector(value)
  42. elif by == 'xpath':
  43. element = self.driver.find_element_by_xpath(value)
  44. else:
  45. log.error('没有找到元素')
  46. log.info('元素定位成功。定位方式:%s,使用的值%s:' % (by, value))
  47. return element
  48. except NoSuchElementException as e:
  49. log.error("报错信息:{}".format(e))
  50. self.get_img() # 调用截图
  51. else:
  52. log.error('输入的元素定位方式错误,参考[id, name, class, tag, link, plink, css,xpath]')
  53.  
  54. def type(self, selector, value):
  55. """输入内容"""
  56. element = self.find_element(selector)
  57. element.clear()
  58. log.info('清空输入内容')
  59. # noinspection PyBroadException
  60. try:
  61. element.send_keys(value)
  62. log.info('输入的内容:%s' % value)
  63. except BaseException as e:
  64. log.error('内容输入报错{}'.format(e))
  65. self.get_img()
  66.  
  67. def click(self, selector):
  68. """点击元素"""
  69. element = self.find_element(selector)
  70. # noinspection PyBroadException
  71. try:
  72. element.click()
  73. log.info('点击元素成功')
  74. except BaseException as e:
  75. display = self.isdisplayed(element)
  76. if display is True:
  77. self.my_sleep(3)
  78. element.click()
  79. log.info('点击元素成功')
  80. else:
  81. self.get_img()
  82. log.error('点击元素报错{}'.format(e))

  这里只贴出一部分的常用的方法的封装示例,可以根据自己的需求DIY

5 用例筛选

  当你的用例写到一定量时,如果你验证一部分用例,每次全部执行一遍太耗时了,所以加个用例筛选功能,自己选择要执行的用例文件,不想执行的直接注释掉

  

  1. user/test01case
  2. #user/test02case
  3. #user/test03case
  4. #user/test04case
  5. #user/test05case
  6. #shop/test_shop_list
  7. #shop/test_my_shop
  8. #shop/test_new_shop

  然后再写一个方法去逐行读取这个文件,如果行首没有#号,就把这个用例添加到suite中执行即可,具体实现:

  

  1. class AllTest: # 定义一个类AllTest
  2. def __init__(self): # 初始化一些参数和数据
  3. global report_name
  4. report_path = os.path.join(root_path, 'report', 'testreport')
  5. now = time.strftime('%Y-%m-%d_%H_%M_%S')
  6. report_name = os.path.join(report_path, 'TestResult{}.html'.format(now))
  7. self.caseListFile = os.path.join('.\\', "caselist.txt") # 配置执行哪些测试文件的配置文件路径
  8. self.caseFile = os.path.join('.\\', "testcase") # 真正的测试断言文件路径
  9. self.caseList = []
  10. log.info('report_name' + report_name) # 将resultPath的值输入到日志,方便定位查看问题
  11. log.info('caseListFile' + self.caseListFile) # 同理
  12. log.info('caseList' + str(self.caseList)) # 同理
  13.  
  14. def set_case_list(self):
  15. """
  16. 读取caselist.txt文件中的用例名称,并添加到caselist元素组
  17. :return:
  18. """
  19. fb = open(self.caseListFile)
  20. for value in fb.readlines():
  21. data = str(value)
  22. if data != '' and not data.startswith("#"): # 如果data非空且不以#开头
  23. self.caseList.append(data.replace("\n", "")) # 读取每行数据会将换行转换为\n,去掉每行数据中的\n
  24. fb.close()
  25.  
  26. def set_case_suite(self):
  27. """
  28.  
  29. :return:
  30. """
  31. self.set_case_list() # 通过set_case_list()拿到caselist元素组
  32. test_suite = unittest.TestSuite()
  33. suite_module = []
  34. for case in self.caseList: # 从caselist元素组中循环取出case
  35. case_name = case.split("/")[-1] # 通过split函数来将aaa/bbb分割字符串,-1取后面,0取前面
  36. # 批量加载用例,第一个参数为用例存放路径,第一个参数为路径文件名
  37. discover = unittest.defaultTestLoader.discover(self.caseFile, pattern=case_name + '.py', top_level_dir=None)
  38. suite_module.append(discover) # 将discover存入suite_module元素组
  39. if len(suite_module) > 0: # 判断suite_module元素组是否存在元素
  40. for suite in suite_module: # 如果存在,循环取出元素组内容,命名为suite
  41. for test_name in suite: # 从discover中取出test_name,使用addTest添加到测试集
  42. test_suite.addTest(test_name)
  43. else:
  44. log.error('suite_module中没有测试集')
  45. return None
  46. return test_suite # 返回测试集
  47.  
  48. def run(self):
  49. """
  50. run test
  51. :return:
  52. """
  53. try:
  54. suit = self.set_case_suite() # 调用set_case_suite获取test_suite
  55. if suit is not None: # 判断test_suite是否为空
  56. # bf(suite).report(description='用例名称xx', filename=now, log_path=report_path)
  57. with open(report_name, 'wb') as f: # encoding='UTF-8'
  58. # 调用HTMLTestRunner
  59. runner = HTMLTestRunner.HTMLTestRunner(stream=f, title='Test Report',
  60. description='Test Description')
  61. runner.run(suit)
  62. else:
  63. log.error('没有case')
  64. except Exception as e:
  65. log.error('{}'.format(e))

  当然,如果你不想使用这中方式也可以全部执行……  


  源码地址:https://gitee.com/fh1105/selenium_PO  需要的自行clone

  第一次写博客记录学习,不好的地方请指出来,谢谢!

    

python+selenium 自动化测试框架-学习记录的更多相关文章

  1. 从零开始到设计Python+Selenium自动化测试框架-如何开始

    如何开始学习web ui自动化测试?如何选择一门脚本语言?选择什么自动化测试工具? 本人已经做测试快5年,很惭愧,感觉积累不够,很多测试都不会,三年多功能测试,最近两年才开始接触和学习自动化测试.打算 ...

  2. Python selenium自动化测试框架入门实战--登录测试案例

    本文为Python自动化测试框架基础入门篇,主要帮助会写基本selenium测试代码又没有规划的同仁.本文应用到POM模型.selenium.unittest框架.configparser配置文件.s ...

  3. 《一头扎进》系列之Python+Selenium自动化测试框架实战篇6 - 价值好几K的框架,呦!这个框架还真牛叉哦!!!

    1. 简介 本文开始介绍如何通过unittest来管理和执行测试用例,这一篇主要是介绍unittest下addTest()方法来加载测试用例到测试套件中去.用addTest()方法来加载我们测试用例到 ...

  4. python + selenium 自动化测试框架

    分享一个网站自动化测试框架 结构如下: test_project|--logs|---pages |---register_page.py|      |---base_page.py|---test ...

  5. Eclipse+Python+Selenium自动化测试框架搭建

    1.下载Eclipse:http://www.eclipse.org/downloads/ 2.下载JDK:http://www.oracle.com/technetwork/java/javaee/ ...

  6. 《Selenium自动化测试实战:基于Python》Selenium自动化测试框架入门

    第1章  Selenium自动化测试框架入门 1.1  Selenium自动化测试框架概述 说到目前流行的自动化测试工具,相信只要做过软件测试相关工作,就一定听说过Selenium. 图1-1是某企业 ...

  7. Selenium自动化测试框架入门整理

    ​​关注嘉为科技,获取运维新知 本文主要针对Selenium自动化测试框架入门整理,只涉及总体功能及框架要点介绍说明,以及使用前提技术基础要求整理说明.作为开发人员.测试人员入门参考. 本文参考:Se ...

  8. Jenkins持续集成项目搭建与实践——基于Python Selenium自动化测试(自由风格)

    Jenkins简介 Jenkins是Java编写的非常流行的持续集成(CI)服务,起源于Hudson项目.所以Jenkins和Hudson功能相似. Jenkins支持各种版本的控制工具,如CVS.S ...

  9. Python接口自动化测试框架实战 从设计到开发

    第1章 课程介绍(不要错过)本章主要讲解课程的详细安排.课程学习要求.课程面向用户等,让大家很直观的对课程有整体认知! 第2章 接口测试工具Fiddler的运用本章重点讲解如何抓app\web的htt ...

随机推荐

  1. 在okhttp的callback回调中加Toast出现Cant create handler inside hread that has not called Looper.prepare()...

    2019独角兽企业重金招聘Python工程师标准>>> 分析:callback中回调的response方法中还是在子线程中运行的,所以要调取Toast必须回到主线程中更新ui 解决方 ...

  2. windows右键没有新建选项的解决办法

    1 以管理员身份运行cmd 2 cmd /k reg add "HKEY_CLASSES_ROOT\Directory\Background\shellex\ContextMenuHandl ...

  3. RHCS图形界面建立GFS共享下

    我们上面通过图形界面实现了GFS,我们这里使用字符界面实现 1.1.       系统基础配置 5台节点均采用相同配置. 配置/etc/hosts文件 # vi /etc/hosts 127.0.0. ...

  4. bfs—迷宫问题—poj3984

    迷宫问题 Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 20591   Accepted: 12050 http://poj ...

  5. C++编程入门题目--No.3

    题目:一个整数,它加上100后是一个完全平方数,再加上168又是一个完全平方数,请问该数是多少? 程序分析: 在10万以内判断,先将该数加上100后再开方,再将该数加上268后再开方,如果开方后 的结 ...

  6. 如何使用badboy录制一个脚本并成功的导入jmeter中?

    前言: 虽然,很多人已经不适用这种方式进行录制脚本了,因为不好维护.但是,还是有一些朋友在刚开始学习的过程中使用badboy. 可能有人会好奇了,人家五一都出去玩了,你在家学习吗?正巧前一阵有粉丝留言 ...

  7. 10 微信小程序路由跳转

    一.四种跳转方式 API路由详解 除了tabBar这种底部跳转的方法,我们还有路由跳转,以下四种方式: 1. wx.switchTab() :跳转到 tabBar 页面,并关闭其他所有非 tabBar ...

  8. 用Navicat建MySQL数据库表,动态改变创建时间和更新时间戳

    1.create_time 记录创建的时间,设默认值为:CURRENT_TIMESATMP 注意:不勾选那个[根据当前时间戳更新] 2.operator_time 更新记录的时间,勾选那个[根据当前时 ...

  9. A - Aragorn's Story HDU - 3966 树剖裸题

    这个题目是一个比较裸的树剖题,很好写. http://acm.hdu.edu.cn/showproblem.php?pid=3966 #include <cstdio> #include ...

  10. Android下拉刷新SwipeRefreshLayout简单用法

    之前一直都想用下拉刷新,感觉上是庞大的工程,所以搁置了.现在学习了一下其实真的超级简单. 看了<第一行代码>以及 https://www.jianshu.com/p/3c402a9e4b7 ...