环境:Python2.7.10, selenium3.141.0, pytest4.6.6, pytest-html1.22.0, Windows-7-6.1.7601-SP1

特点:
- 二次封装了selenium,编写Case更加方便。
- 采用PO设计思想,一个页面一个Page.py,并在其中定义元素和操作方法;在TestCase中直接调用页面中封装好的操作方法操作页面。
- 一次测试只启动一次浏览器,节约时间提高效率(适合公司业务的才是最好的)。
- 增强pytest-html报告内容,加入失败截图、用例描述列、运行日志。
- 支持命令行参数。
- 支持邮件发送报告。

目录结构:
- config
    - config.py:存放全局变量,各种配置、driver等
- drive:各浏览器驱动文件,如chromedriver.exe
- file
    - download:下载文件夹
    - screenshot:截图文件夹
    - upload:上传文件夹
- page_object:一个页面一个.py,存放页面对象、操作方法
    - base_page.py:基础页面,封装了selenium的各种操作
    - hao123_page.py:hao123页面
    - home_page.py:百度首页
    - news_page.py:新闻首页
    - search_page.py:搜索结果页
- report:
    - report.html:pytest-html生成的报告
- test_case
    - conftest.py:pytest特有文件,在里面增加了报告失败截图、用例描述列
    - test_home.py:百度首页测试用例
    - test_news.py:新闻首页测试用例
    - test_search.py:搜索结果页测试用例
- util:工具包
    - log.py:封装了日志模块
    - mail.py:封装了邮件模块,使用发送报告邮件功能需要先设置好相关配置,如用户名密码
- run.py:做为运行入口,封装了pytest运行命令;实现所有测试用例共用一个driver;实现了运行参数化(结合Jenkins使用);log配置初始化;可配置发送报告邮件。

代码实现:

  1. # coding=utf-8
  2.  
  3. import os
  4.  
  5. def init():
  6. global _global_dict
  7. _global_dict = {}
  8.  
  9. # 代码根目录
  10. root_dir = os.getcwd()
  11.  
  12. # 存放程序所在目录
  13. _global_dict['root_path'] = root_dir
  14. # 存放正常截图文件夹
  15. _global_dict['screenshot_path'] = "{}\\file\\screenshot\\".format(root_dir)
  16. # 下载文件夹
  17. _global_dict['download_path'] = "{}\\file\\download\\".format(root_dir)
  18. # 上传文件夹
  19. _global_dict['upload_path'] = "{}\\file\\upload\\".format(root_dir)
  20. # 存放报告路径
  21. _global_dict['report_path'] = "{}\\report\\".format(root_dir)
  22.  
  23. # 保存driver
  24. _global_dict['driver'] = None
  25.  
  26. # 设置运行环境网址主页
  27. _global_dict['site'] = 'https://www.baidu.com/'
  28. # 运行环境,默认preview,可设为product
  29. _global_dict['environment'] = 'preview'
  30.  
  31. def set_value(name, value):
  32. """
  33. 修改全局变量的值
  34. :param name: 变量名
  35. :param value: 变量值
  36. """
  37. _global_dict[name] = value
  38.  
  39. def get_value(name, def_val='no_value'):
  40. """
  41. 获取全局变量的值
  42. :param name: 变量名
  43. :param def_val: 默认变量值
  44. :return: 变量存在时返回其值,否则返回'no_value'
  45. """
  46. try:
  47. return _global_dict[name]
  48. except KeyError:
  49. return def_val

config.py

定义了全局的字典,用来存放全局变量,其key为变量名,value为变量值,可跨文件、跨用例传递参数。

其中set_value、get_value分别用来存、取全局变量。

  1. # coding=utf-8
  2.  
  3. import logging
  4. import time
  5. import config.config as cf
  6.  
  7. class Logger(object):
  8. """封装的日志模块"""
  9.  
  10. def __init__(self, logger, cmd_level=logging.DEBUG, file_level=logging.DEBUG):
  11. try:
  12. self.logger = logging.getLogger(logger)
  13. self.logger.setLevel(logging.DEBUG) # 设置日志输出的默认级别
  14. '''pytest报告可以自动将log整合进报告,不用再自己单独设置保存
  15. # 日志输出格式
  16. fmt = logging.Formatter(
  17. '%(asctime)s[%(levelname)s]\t%(message)s')
  18. # 日志文件名称
  19. curr_time = time.strftime("%Y-%m-%d %H.%M.%S")
  20. log_path = cf.get_value('log_path')
  21. self.log_file = '{}log{}.txt'.format(log_path, curr_time)
  22. # 设置控制台输出
  23. sh = logging.StreamHandler()
  24. sh.setFormatter(fmt)
  25. sh.setLevel(cmd_level)
  26. # 设置文件输出
  27. fh = logging.FileHandler(self.log_file)
  28. fh.setFormatter(fmt)
  29. fh.setLevel(file_level)
  30. # 添加日志输出方式
  31. self.logger.addHandler(sh)
  32. self.logger.addHandler(fh)
  33. '''
  34. except Exception as e:
  35. raise e
  36.  
  37. def debug(self, msg):
  38. self.logger.debug(msg)
  39.  
  40. def info(self, msg):
  41. self.logger.info(msg)
  42.  
  43. def error(self, msg):
  44. self.logger.error(msg)
  45.  
  46. def warning(self, msg):
  47. self.logger.warning(msg)

log.py

封装的log模块

  1. # coding=utf-8
  2.  
  3. import smtplib
  4. from email.mime.text import MIMEText
  5. from email.mime.multipart import MIMEMultipart
  6. from email.header import Header
  7. import config.config as cf
  8.  
  9. def send_mail(sendto):
  10. """
  11. 发送邮件
  12. :param sendto:收件人列表,如['22459496@qq.com']
  13. """
  14. mail_host = 'smtp.sohu.com' # 邮箱服务器地址
  15. username = 'test@sohu.com' # 邮箱用户名
  16. password = 'test' # 邮箱密码
  17. receivers = sendto # 收件人
  18.  
  19. # 创建一个带附件的实例
  20. message = MIMEMultipart()
  21. message['From'] = Header(u'UI自动化', 'utf-8')
  22. message['subject'] = Header(u'UI自动化测试结果', 'utf-8') # 邮件标题
  23. message.attach(MIMEText(u'测试结果详见附件', 'plain', 'utf-8'))# 邮件正文
  24. # 构造附件
  25. report_root = cf.get_value('report_path') # 获取报告路径
  26. report_file = 'report.html' # 报告文件名称
  27. att1 = MIMEText(open(report_root + report_file, 'rb').read(), 'base64', 'utf-8')
  28. att1["Content-Type"] = 'application/octet-stream'
  29. att1["Content-Disposition"] = 'attachment; filename={}'.format(report_file)
  30. message.attach(att1)
  31.  
  32. try:
  33. smtp = smtplib.SMTP()
  34. smtp.connect(mail_host, 25) # 25为 SMTP 端口号
  35. smtp.login(username, password)
  36. smtp.sendmail(username, receivers, message.as_string())
  37. print u'邮件发送成功'
  38. except Exception, e:
  39. print u'邮件发送失败'
  40. raise e

mail.py

封装的邮件模块,报告HTML文件会做为附件发送,这里需要把最上面的4个变量全改成你自己的。

  1. # coding=utf-8
  2.  
  3. from selenium.common.exceptions import TimeoutException
  4. from selenium.webdriver.support.ui import WebDriverWait
  5. from selenium.webdriver.common.keys import Keys
  6. from selenium.webdriver.common.action_chains import ActionChains
  7. import os
  8. import inspect
  9. import config.config as cf
  10. import logging
  11. import time
  12.  
  13. log = logging.getLogger('szh.BasePage')
  14.  
  15. class BasePage(object):
  16. def __init__(self):
  17. self.driver = cf.get_value('driver') # 从全局变量取driver
  18.  
  19. def split_locator(self, locator):
  20. """
  21. 分解定位表达式,如'css,.username',拆分后返回'css selector'和定位表达式'.username'(class为username的元素)
  22. :param locator: 定位方法+定位表达式组合字符串,如'css,.username'
  23. :return: locator_dict[by], value:返回定位方式和定位表达式
  24. """
  25. by = locator.split(',')[0]
  26. value = locator.split(',')[1]
  27. locator_dict = {
  28. 'id': 'id',
  29. 'name': 'name',
  30. 'class': 'class name',
  31. 'tag': 'tag name',
  32. 'link': 'link text',
  33. 'plink': 'partial link text',
  34. 'xpath': 'xpath',
  35. 'css': 'css selector',
  36. }
  37. if by not in locator_dict.keys():
  38. raise NameError("wrong locator!'id','name','class','tag','link','plink','xpath','css',exp:'id,username'")
  39. return locator_dict[by], value
  40.  
  41. def wait_element(self, locator, sec=30):
  42. """
  43. 等待元素出现
  44. :param locator: 定位方法+定位表达式组合字符串,用逗号分隔,如'css,.username'
  45. :param sec:等待秒数
  46. """
  47. by, value = self.split_locator(locator)
  48. try:
  49. WebDriverWait(self.driver, sec, 1).until(lambda x: x.find_element(by=by, value=value),
  50. message='element not found!!!')
  51. log.info(u'等待元素:%s' % locator)
  52. return True
  53. except TimeoutException:
  54. return False
  55. except Exception, e:
  56. raise e
  57.  
  58. def get_element(self, locator, sec=60):
  59. """
  60. 获取一个元素
  61. :param locator: 定位方法+定位表达式组合字符串,用逗号分隔,如'css,.username'
  62. :param sec:等待秒数
  63. :return: 元素可找到返回element对象,否则返回False
  64. """
  65. if self.wait_element(locator, sec):
  66. by, value = self.split_locator(locator)
  67. print by, value
  68. try:
  69. element = self.driver.find_element(by=by, value=value)
  70. log.info(u'获取元素:%s' % locator)
  71. return element
  72. except Exception, e:
  73. raise e
  74. else:
  75. return False
  76.  
  77. def get_elements(self, locator):
  78. """
  79. 获取一组元素
  80. :param locator: 定位方法+定位表达式组合字符串,用逗号分隔,如'css,.username'
  81. :return: elements
  82. """
  83. by, value = self.split_locator(locator)
  84. try:
  85. elements = WebDriverWait(self.driver, 60, 1).until(lambda x: x.find_elements(by=by, value=value))
  86. log.info(u'获取元素列表:%s' % locator)
  87. return elements
  88. except Exception, e:
  89. raise e
  90.  
  91. def open(self, url):
  92. """
  93. 打开网址
  94. :param url: 网址连接
  95. """
  96. self.driver.get(url)
  97. log.info(u'打开网址:%s' % url)
  98.  
  99. def clear(self, locator):
  100. """
  101. 清除元素中的内容
  102. :param locator: 定位方法+定位表达式组合字符串,用逗号分隔,如'css,.username'
  103. """
  104. self.get_element(locator).clear()
  105. log.info(u'清空内容:%s' % locator)
  106.  
  107. def type(self, locator, text):
  108. """
  109. 在元素中输入内容
  110. :param locator: 定位方法+定位表达式组合字符串,用逗号分隔,如'css,.username'
  111. :param text: 输入的内容
  112. """
  113. self.get_element(locator).send_keys(text)
  114. log.info(u'向元素 %s 输入文字:%s' % (locator, text))
  115.  
  116. def enter(self, locator):
  117. """
  118. 在元素上按回车键
  119. :param locator: 定位方法+定位表达式组合字符串,用逗号分隔,如'css,.username'
  120. """
  121. self.get_element(locator).send_keys(Keys.ENTER)
  122. log.info(u'在元素 %s 上按回车' % locator)
  123.  
  124. def click(self, locator):
  125. """
  126. 在元素上单击
  127. :param locator: 定位方法+定位表达式组合字符串,用逗号分隔,如'css,.username'
  128. """
  129. self.get_element(locator).click()
  130. log.info(u'点击元素:%s' % locator)
  131.  
  132. def right_click(self, locator):
  133. """
  134. 鼠标右击元素
  135. :param locator: 定位方法+定位表达式组合字符串,用逗号分隔,如'css,.username'
  136. """
  137. element = self.get_element(locator)
  138. ActionChains(self.driver).context_click(element).perform()
  139. log.info(u'在元素上右击:%s' % locator)
  140.  
  141. def double_click(self, locator):
  142. """
  143. 双击元素
  144. :param locator: 定位方法+定位表达式组合字符串,用逗号分隔,如'css,.username'
  145. """
  146. element = self.get_element(locator)
  147. ActionChains(self.driver).double_click(element).perform()
  148. log.info(u'在元素上双击:%s' % locator)
  149.  
  150. def move_to_element(self, locator):
  151. """
  152. 鼠标指向元素
  153. :param locator: 定位方法+定位表达式组合字符串,用逗号分隔,如'css,.username'
  154. """
  155. element = self.get_element(locator)
  156. ActionChains(self.driver).move_to_element(element).perform()
  157. log.info(u'指向元素%s' % locator)
  158.  
  159. def drag_and_drop(self, locator, target_locator):
  160. """
  161. 拖动一个元素到另一个元素位置
  162. :param locator: 要拖动元素的定位
  163. :param target_locator: 目标位置元素的定位
  164. """
  165. element = self.get_element(locator)
  166. target_element = self.get_element(target_locator)
  167. ActionChains(self.driver).drag_and_drop(element, target_element).perform()
  168. log.info(u'把元素 %s 拖至元素 %s' % (locator, target_locator))
  169.  
  170. def drag_and_drop_by_offset(self, locator, xoffset, yoffset):
  171. """
  172. 拖动一个元素向右下移动x,y个偏移量
  173. :param locator: 定位方法+定位表达式组合字符串,用逗号分隔,如'css,.username'
  174. :param xoffset: X offset to move to
  175. :param yoffset: Y offset to move to
  176. """
  177. element = self.get_element(locator)
  178. ActionChains(self.driver).drag_and_drop_by_offset(element, xoffset, yoffset).perform()
  179. log.info(u'把元素 %s 拖至坐标:%s %s' % (locator, xoffset, yoffset))
  180.  
  181. def click_link(self, text):
  182. """
  183. 按部分链接文字查找并点击链接
  184. :param text: 链接的部分文字
  185. """
  186. self.get_element('plink,' + text).click()
  187. log.info(u'点击连接:%s' % text)
  188.  
  189. def alert_text(self):
  190. """
  191. 返回alert文本
  192. :return: alert文本
  193. """
  194. log.info(u'获取弹框文本:%s' % self.driver.switch_to.alert.text)
  195. return self.driver.switch_to.alert.text
  196.  
  197. def alert_accept(self):
  198. """
  199. alert点确认
  200. """
  201. self.driver.switch_to.alert.accept()
  202. log.info(u'点击弹框确认')
  203.  
  204. def alert_dismiss(self):
  205. """
  206. alert点取消
  207. """
  208. self.driver.switch_to.alert.dismiss()
  209. log.info(u'点击弹框取消')
  210.  
  211. def get_attribute(self, locator, attribute):
  212. """
  213. 返回元素某属性的值
  214. :param locator: 定位方法+定位表达式组合字符串,用逗号分隔,如'css,.username'
  215. :param attribute: 属性名称
  216. :return: 属性值
  217. """
  218. value = self.get_element(locator).get_attribute(attribute)
  219. log.info(u'获取元素 %s 的属性值 %s 为:%s' % (locator, attribute, value))
  220. return value
  221.  
  222. def get_ele_text(self, locator):
  223. """
  224. 返回元素的文本
  225. :param locator: 定位方法+定位表达式组合字符串,用逗号分隔,如'css,.username'
  226. :return: 元素的文本
  227. """
  228. log.info(u'获取元素 %s 的文本为:%s' % (locator, self.get_element(locator).text))
  229. return self.get_element(locator).text
  230.  
  231. def frame_in(self, locator):
  232. """
  233. 进入frame
  234. :param locator: 定位方法+定位表达式组合字符串,如'css,.username'
  235. """
  236. e = self.get_element(locator)
  237. self.driver.switch_to.frame(e)
  238. log.info(u'进入frame:%s' % locator)
  239.  
  240. def frame_out(self):
  241. """
  242. 返回主文档
  243. """
  244. self.driver.switch_to.default_content()
  245. log.info(u'退出frame返回默认文档')
  246.  
  247. def open_new_window_by_locator(self, locator):
  248. """
  249. 点击元素打开新窗口,并将句柄切换到新窗口
  250. :param locator: 定位方法+定位表达式组合字符串,如'css,.username'
  251. """
  252. self.get_element(locator).click()
  253. self.driver.switch_to.window(self.driver.window_handles[-1])
  254. log.info(u'点击元素 %s 打开新窗口' % locator)
  255.  
  256. # old_handle = self.driver.current_window_handle
  257. # self.get_element(locator).click()
  258. # all_handles = self.driver.window_handles
  259. # for handle in all_handles:
  260. # if handle != old_handle:
  261. # self.driver.switch_to.window(handle)
  262.  
  263. def open_new_window_by_element(self, element):
  264. """
  265. 点击元素打开新窗口,并将句柄切换到新窗口
  266. :param element: 元素对象
  267. """
  268. element.click()
  269. self.driver.switch_to.window(self.driver.window_handles[-1])
  270. log.info(u'点击元素打开新窗口')
  271.  
  272. def js(self, script):
  273. """
  274. 执行JavaScript
  275. :param script:js语句
  276. """
  277. self.driver.execute_script(script)
  278. log.info(u'执行JS语句:%s' % script)
  279.  
  280. def scroll_element(self, locator):
  281. """
  282. 拖动滚动条至目标元素
  283. :param locator: 定位方法+定位表达式组合字符串,如'css,.username'
  284. """
  285. script = "return arguments[0].scrollIntoView();"
  286. element = self.get_element(locator)
  287. self.driver.execute_script(script, element)
  288. log.info(u'滚动至元素:%s' % locator)
  289.  
  290. def scroll_top(self):
  291. """
  292. 滚动至顶部
  293. """
  294. self.js("window.scrollTo(document.body.scrollHeight,0)")
  295. log.info(u'滚动至顶部')
  296.  
  297. def scroll_bottom(self):
  298. """
  299. 滚动至底部
  300. """
  301. self.js("window.scrollTo(0,document.body.scrollHeight)")
  302. log.info(u'滚动至底部')
  303.  
  304. def back(self):
  305. """
  306. 页面后退
  307. """
  308. self.driver.back()
  309. log.info(u'页面后退')
  310.  
  311. def forward(self):
  312. """
  313. 页面向前
  314. """
  315. self.driver.forward()
  316. log.info(u'页面向前')
  317.  
  318. def is_text_on_page(self, text):
  319. """
  320. 返回页面源代码
  321. :return: 页面源代码
  322. """
  323. if text in self.driver.page_source:
  324. log.info(u'判断页面上有文本:%s' % text)
  325. return True
  326. else:
  327. log.info(u'判断页面上没有文本:%s' % text)
  328. return False
  329.  
  330. def refresh(self):
  331. """
  332. 刷新页面
  333. """
  334. self.driver.refresh()
  335. log.info(u'刷新页面')
  336.  
  337. def screenshot(self, info='-'):
  338. """
  339. 截图,起名为:文件名-方法名-注释
  340. :param info: 截图说明
  341. """
  342. catalog_name = cf.get_value('screenshot_path') # 从全局变量取截图文件夹位置
  343. if not os.path.exists(catalog_name):
  344. os.makedirs(catalog_name)
  345. class_object = inspect.getmembers(inspect.stack()[1][0])[-3][1]['self'] # 获得测试类的object
  346. classname = str(class_object).split('.')[1].split(' ')[0] # 获得测试类名称
  347. testcase_name = inspect.stack()[1][3] # 获得测试方法名称
  348. filepath = catalog_name + classname + "@" + testcase_name + info + ".png"
  349. self.driver.get_screenshot_as_file(filepath)
  350. log.info(u'截图:%s.png' % info)
  351.  
  352. def close(self):
  353. """
  354. 关闭当前页
  355. """
  356. self.driver.close()
  357. self.driver.switch_to.window(self.driver.window_handles[0])
  358. log.info(u'关闭当前Tab')
  359.  
  360. def sleep(self, sec):
  361. time.sleep(sec)
  362. log.info(u'等待%s秒' % sec)

base_page.py

二次封装了selenium常用操作,做为所有页面类的基类。

本框架支持selenium所有的定位方法,为了提高编写速度,改进了使用方法,定义元素时方法名和方法值为一个用逗号隔开的字符串,如:
- xpath定位:i_keyword = 'xpath,//input[@id="kw"]' # 关键字输入框
- id定位:b_search = 'id,su' # 搜索按钮
- 其他定位方法同上,不再一一举例

使用时如上面代码中type()方法,是在如输入框中输入文字,调用时输入type(i_keyword, "输入内容")

type()中会调用get_element()方法,对输入的定位表达式进行解析,并且会等待元素一段时间,当元素出现时立即进行操作。

另外可以看到每个基本操作都加入了日志,下图即是用例运行后报告中记录的日志

  1. # coding=utf-8
  2.  
  3. from page_object.base_page import BasePage
  4.  
  5. class SearchPage(BasePage):
  6. def __init__(self, driver):
  7. self.driver = driver
  8.  
  9. # i=输入框, l=链接, im=图片, t=文字控件, d=div, lab=label
  10. # 含_百度百科的搜索结果
  11. l_baike = 'xpath,//a[(. = "星空物语_百度百科")]'
  12.  
  13. # 下一页
  14. b_next_page = 'link,下一页>'
  15.  
  16. # 上一页
  17. b_up_page = 'xpath,//a[(. = "<上一页")]'
  18.  
  19. # 点击搜索结果的百科
  20. def click_result(self):
  21. self.open_new_window_by_locator(self.l_baike)
  22. self.sleep(3)
  23.  
  24. # 点击下一页
  25. def click_next_page(self):
  26. self.click(self.b_next_page)

search_page.py

PO模式中封装的百度的搜索页,继承了上面的BasePage类;每个页面类中上面定义各控件的表达式,下面将页面上的各种操作封装为方法。这样如果在多个用例中调用了控件或操作方法,将来更新维护只需要在页面类中改一下,所有用例就都更新了。

  1. # coding=utf-8
  2.  
  3. import sys
  4. reload(sys)
  5. sys.setdefaultencoding('utf8')
  6. from page_object.home_page import HomePage
  7. from page_object.search_page import SearchPage
  8. import pytest
  9. import config.config as cf
  10.  
  11. class TestSearch():
  12. """
  13. pytest:
  14. 测试文件以test_开头
  15. 测试类以Test开头,并且不能带有__init__方法
  16. 测试函数以test_开头
  17. 断言使用assert
  18. """
  19. driver = cf.get_value('driver') # 从全局变量取driver
  20. home_page = HomePage(driver)
  21. search_page = SearchPage(driver)
  22.  
  23. def test_click_result(self):
  24. """搜索页-点击首个搜索结果"""
  25. try:
  26. self.home_page.open_homepage()
  27. self.home_page.input_keyword(u'星空物语') # 输入关键字
  28. self.search_page.click_result() # 点击百科
  29. assert self.home_page.is_text_on_page(u'电视剧《一起来看流星雨》片头曲') # 验证页面打开
  30. self.home_page.screenshot(u'打开搜索结果')
  31. self.search_page.close() # 关闭百科页面
  32. except Exception, e:
  33. self.home_page.screenshot(u'打开搜索结果失败')
  34. raise e
  35.  
  36. def test_click_next_page(self):
  37. """搜索页-搜索翻页"""
  38. try:
  39. self.search_page.click_next_page() # 点下一页
  40. assert self.home_page.wait_element(self.search_page.b_up_page) # 上一页出现
  41. self.search_page.scroll_element(self.search_page.b_up_page) # 滚到上一页
  42. self.home_page.screenshot(u'搜索翻页')
  43. except Exception, e:
  44. self.home_page.screenshot(u'搜索翻页失败')
  45. raise e

test_search.py

百度搜索页的测试用例,这里我简单写了2个用例,第1个是搜索后点击首个搜索结果可打开,第2个是搜索结果可翻页。用例中的具体操作均是使用的上面页面类中封装好的操作方法。

  1. # coding=utf-8
  2.  
  3. import pytest
  4. from py._xmlgen import html
  5. import config.config as cf
  6. import logging
  7.  
  8. log = logging.getLogger('szh.conftest')
  9.  
  10. @pytest.mark.hookwrapper
  11. def pytest_runtest_makereport(item):
  12. """当测试失败的时候,自动截图,展示到html报告中"""
  13. pytest_html = item.config.pluginmanager.getplugin('html')
  14. outcome = yield
  15. report = outcome.get_result()
  16. extra = getattr(report, 'extra', [])
  17.  
  18. if report.when == 'call' or report.when == "setup":
  19. xfail = hasattr(report, 'wasxfail')
  20. if (report.skipped and xfail) or (report.failed and not xfail):
  21. file_name = report.nodeid.replace("::", "_") + ".png"
  22. driver = cf.get_value('driver') # 从全局变量取driver
  23. screen_img = driver.get_screenshot_as_base64()
  24. if file_name:
  25. html = '<div><img src="data:image/png;base64,%s" alt="screenshot" style="width:600px;height:300px;" ' \
  26. 'onclick="window.open(this.src)" align="right"/></div>' % screen_img
  27. extra.append(pytest_html.extras.html(html))
  28. report.extra = extra
  29. report.description = str(item.function.__doc__)#.decode('utf-8', 'ignore') # 不解码转成Unicode,生成HTML会报错
  30. # report.nodeid = report.nodeid.encode("utf-8").decode("unicode_escape")
  31.  
  32. @pytest.mark.optionalhook
  33. def pytest_html_results_table_header(cells):
  34. cells.insert(1, html.th('Description'))
  35. cells.pop() # 删除报告最后一列links
  36.  
  37. @pytest.mark.optionalhook
  38. def pytest_html_results_table_row(report, cells):
  39. cells.insert(1, html.td(report.description))
  40. cells.pop() # 删除报告最后一列links

conftest.py

conftest.py是pytest提供数据、操作共享的文件,其文件名是固定的,不可以修改。

conftest.py文件所在目录必须存在__init__.py文件。

其他文件不需要import导入conftest.py,pytest用例会自动查找

所有同目录测试文件运行前都会执行conftest.py文件

我只在conftest.py中加入了报错截图的功能,如果你有需要在用例前、后执行一些操作,都可以写在这里。

  1. # coding=utf-8
  2.  
  3. import pytest
  4. import config.config as cf
  5. from util.log import Logger
  6. import argparse
  7. from selenium import webdriver
  8. from util.mail import send_mail
  9.  
  10. def get_args():
  11. """命令行参数解析"""
  12. parser = argparse.ArgumentParser(description=u'可选择参数:')
  13. parser.add_argument('-e', '--environment', choices=['preview', 'product'], default='preview', help=u'测试环境preview,线上环境product')
  14. args = parser.parse_args()
  15. if args.environment in ('pre', 'preview'):
  16. cf.set_value('environment', 'preview')
  17. cf.set_value('site', 'http://www.baidu.com/')
  18. elif args.environment in ('pro', 'product'):
  19. cf.set_value('environment', 'preview')
  20. cf.set_value('site', 'https://www.baidu.com/')
  21. else:
  22. print u"请输入preview/product"
  23. exit()
  24.  
  25. def set_driver():
  26. """设置driver"""
  27. # 配置Chrome Driver
  28. chrome_options = webdriver.ChromeOptions()
  29. chrome_options.add_argument('--start-maximized') # 浏览器最大化
  30. chrome_options.add_argument('--disable-infobars') # 不提醒chrome正在受自动化软件控制
  31. prefs = {'download.default_directory': cf.get_value('download_path')}
  32. chrome_options.add_experimental_option('prefs', prefs) # 设置默认下载路径
  33. # chrome_options.add_argument(r'--user-data-dir=D:\ChromeUserData') # 设置用户文件夹,可免登陆
  34. driver = webdriver.Chrome('{}\\driver\\chromedriver.exe'.format(cf.get_value('root_path')), options=chrome_options)
  35. cf.set_value('driver', driver)
  36.  
  37. def main():
  38. """运行pytest命令启动测试"""
  39. pytest.main(['-v', '-s', 'test_case/', '--html=report/report.html', '--self-contained-html'])
  40.  
  41. if __name__ == '__main__':
  42. cf.init() # 初始化全局变量
  43. get_args() # 命令行参数解析
  44. log = Logger('szh') # 初始化log配置
  45. set_driver() # 初始化driver
  46. main() # 运行pytest测试集
  47. cf.get_value('driver').quit() # 关闭selenium driver
  48.  
  49. # 先将util.mail文件send_mail()中的用户名、密码填写正确,再启用发送邮件功能!!!
  50. send_mail(['22459496@qq.com']) # 将报告发送至邮箱

run.py

run.py用来做一些初始化的工作,运行测试,以及测试收尾,具体可以看代码中的注释。

我将浏览器driver的初始化放在了这里,并将driver存入全局变量,这样浏览器只需打开一次即可运行所有的测试。如果你想每个用例都打开、关闭一次浏览器,那可以把定义driver的方法放在conftest.py中。

get_args()是封装的命令行参数解析,方便集成Jenkins时快速定义运行内容。目前只定义了一个环境参数-e, 可设置测试环境preview,线上环境product,你可以根据需要添加更多参数。

调用方法:python run.py -e product

main()封装了pytest的命令行执行模式,你也可以按需修改。

最后放一张运行后的测试报告的截图,我故意将某个用例写错,可以看到,报告中显示了具体的报错信息以及出错时页面的截图

所有代码可去GitHub获取:https://github.com/songzhenhua/selenium_ui_auto

---------------------------------------------------------------------------------

关注微信公众号即可在手机上查阅,并可接收更多测试分享~

基于python2+selenium3+pytest4的UI自动化框架的更多相关文章

  1. 基于PO和单例设计模式用python+selenium进行ui自动化框架设计

    一)框架目录的结构 二)config包当中的config.ini文件主要是用来存项目的绝对路径,是为了后续跑用例和生成测试报告做准备然后目前的配置文件大都会用yaml,ini,excel,还有.py也 ...

  2. ui自动化笔记 selenium_webdriver,ui自动化框架(web)

    Selenium学习笔记 selenium webdriver是业界公认ui自动化测试的标准,其封装的api可以对浏览器的任何地方进行操作 selenium2.0和selenium3.0的区别? 3. ...

  3. 多测师讲解ui自动化框架设计思想_高级讲师肖sir

    UI自动化框架:UI自动化框架可以分为8个模块,conf.data.public.pageobject.testcase.runner.report.log.conf是用来储存系统环境.数据库.邮件的 ...

  4. UI自动化框架搭建之Python3

    UI自动化框架搭建--unittest 使用的代码是Python3版本,与时俱进哈哈 解释一下我的框架目录接口(每个人框架的目录接口不一样,根据实际要求) common目录:公共模块,这个地方可以存放 ...

  5. ATOMac - 基于Python的Mac应用Ui自动化库

    ATOMacTest 一.缘 起 近期工作需要对一款Mac端应用实现常用功能的自动化操作,同事推荐ATOMac这款工具,这几天简单研究了下,同时也发现现网介绍ATOMac的资料非常有限,故在此记录下A ...

  6. UI 自动化框架设想

    测试框架选型: 首先,通过利用TestNG结合csv的使用,将测试用例数据转化为测试代码中的数据,减少了测试人员录入数据和准备数据的工具: 再次,通过对appium的封装,按照面向对象的思想将测试中用 ...

  7. python+selenium封装UI自动化框架

    seleinum框架 框架的思想:  解决我们测试过程中的问题:大量的重复步骤,用自动化来实现    1)配置和程序的分离    2)测试数据和程序的分离    3)不懂编程的人员可以方便使用:使用的 ...

  8. python2.7+RobotFramework的UI自动化环境搭建

    robotFramework是一种比较常见的自动化测试框架,此篇记录环境搭建 目录 1.软件准备 2.执行安装 1.软件准备 python-2.7.15.amd64.msi              ...

  9. 我写的UI自动化框架

    ---------------------------------------------------------------------------------------------------- ...

随机推荐

  1. DevExpress 控件用法笔记(VB)

    1.ChartControl 显示条形图 ChartControl1.Titles.Clear() ChartControl1.Series.Clear() Dim db As DataTable S ...

  2. Java 第一次课堂测验

    周一下午进行了开学来java第一次课堂测验,在课堂上我只完成了其中一部分,现代码修改如下: 先定义 ScoreInformation 类记录学生信息: /** * 信1805-1 * 胡一鸣 * 20 ...

  3. redis最新版本安装及开机自启

    的系统是ubuntu,安装方式有多种,一种是通过apt仓库,一种是下载源码,编译安装 1.通过apt仓库 具体命令: sudo apt-get update sudo apt-get install ...

  4. Serverless 微服务实践-移动应用包分发服务

    背景 阿里云函数计算是事件驱动的全托管计算服务.通过函数计算,您无需管理服务器等基础设施,只需编写代码并上传.函数计算会为您准备好计算资源,以弹性.可靠的方式运行您的代码,并提供日志查询.性能监控.报 ...

  5. postman的测试,用对象接收所有的字符串

    1.post请求 Headers: Content-Type  application/json { "taskId":"1000001161", " ...

  6. 每天玩转3分钟 MyBatis-Plus - 1. 配置环境

    每天玩转3分钟 MyBatis-Plus - 1. 配置环境 每天玩转3分钟 MyBatis-Plus - 2. 普通查询 MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 ...

  7. 研究僧丨Window实用利器分享

    本人CS在读小硕,平时工作环境主要是win10加ubuntu,下面推荐一些我用过且觉得不错的应用. PS:我列举的应用基本被下面的网站收录,大家不妨去里面淘淘看. Windows 绝妙项目 Aweso ...

  8. [bzoj3527] [洛谷P3338] [Zjoi2014]力

    Description 给出n个数qi,给出Fj的定义如下: \[ F_j=\sum\limits_{i<j} \frac{q_iq_j}{(i-j)^2} - \sum\limits_{i&g ...

  9. 使用整体模型模板辅助器 Using Whole-Model Templated Helpers 模板辅助器方法 精通ASP.NET MVC 5

    怎么会

  10. 优雅写Java之四(类与对象)

    一.类相关用法 二.Bean 三.泛型与注解 四.序列化