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

二.特点:

1.二次封装了selenium,编写Case更加方便。

2.采用PO设计思想,一个页面一个Page.py,在其中定义元素和操作方法;从而在用例TestCase中直接调用页面中封装好的操作方法操作页面。

3.一次测试只启动一次浏览器,节约时间提高效率。

4.增强pytest-html报告内容,加入失败截图、用例描述列、运行日志。

5.支持命令行参数。

6.支持邮件发送报告。

三.目录结构

1.config配置

1.1.config.py:存放全局变量,各种配置、driver等。

2.drive文件

2.1.各浏览器驱动文件,如chromedriver.exe

3.file文件

3.1.download:下载文件夹
3.2.screenshot:截图文件夹
3.3.upload:上传文件夹

4.page_object文件:一个页面一个.py,存放页面对象、操作方法

4.1.base_page.py:基础页面,封装selenium的各种操作

4.2.hao123_page.py:hao123页面

4.3.home_page.py:百度首页

4.4.news_page.py:新闻首页

4.5.search_page.py:搜索结果页

6.report文件

6.1.report.html:pytest-html生成的报告

7.test_case文件

7.1.conftest.py:pytest特有文件,在里面增加了报告失败截图、用例描述列

7.2.test_home.py:百度首页测试用例

7.3.test_news.py:新闻首页测试用例

7.4.test_search.py:搜索结果页测试用例

7.5.demo1.py:通过加cookies免登陆的Demo

7.6.demo2.py:在已打开的Chrome浏览器进行调试的Demo

8.util工具包文件

8.1.log.py:封装了日志模块

8.2.mail.py:封装了邮件模块,使用发送报告邮件功能需要先设置好相关配置,如用户名密码

9.run.py文件

9.1.做为运行入口,封装了pytest运行命令;实现所有测试用例共用一个driver;实现了运行参数化(结合Jenkins使用);log配置初始化;可配置发送报告邮件。

四.代码实现

1.config.py代码

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

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

import osdef init():# 1.定义了全局的字典,用来存放全局变量,其key为变量名,value为变量值,可跨文件、跨用例传递参数。# 2.其中set_value、get_value分别用来存、取全局变量。global _global_dict_global_dict = {}# 代码根目录root_dir = os.getcwd()print(root_dir)# 存放程序所在目录_global_dict['root_path'] = root_dir# 存放正常截图文件夹_global_dict['screenshot_path'] = "{}\\file\\screenshot\\".format(root_dir)# 下载文件夹_global_dict['download_path'] = "{}\\file\\download\\".format(root_dir)# 上传文件夹_global_dict['upload_path'] = "{}\\file\\upload\\".format(root_dir)# 存放报告路径_global_dict['report_path'] = "{}\\report\\".format(root_dir)# 保存driver_global_dict['driver'] = None#_global_dict['driver'] = "{}\\driver\\".format(root_dir)# 设置运行环境网址主页_global_dict['site'] = 'https://www.baidu.com/'# 运行环境,默认preview,可设为product_global_dict['environment'] = 'preview'def set_value(name, value):"""存放全局变量的值:param name: 变量名:param value: 变量值"""_global_dict[name] = valuedef get_value(name, def_val='no_value'):"""获取全局变量的值:param name: 变量名:param def_val: 默认变量值:return: 变量存在时返回其值,否则返回'no_value'"""try:return _global_dict[name]except KeyError:return def_valif __name__ == '__main__':init()

2.log.py代码实现

import logging
import time
import config.config as cfclass Logger(object):"""封装的日志模块"""def __init__(self, logger, cmd_level=logging.DEBUG, file_level=logging.DEBUG):try:self.logger = logging.getLogger(logger)self.logger.setLevel(logging.DEBUG)  # 设置日志输出的默认级别'''pytest报告可以自动将log整合进报告,不用再自己单独设置保存# 日志输出格式fmt = logging.Formatter('%(asctime)s[%(levelname)s]\t%(message)s')# 日志文件名称curr_time = time.strftime("%Y-%m-%d %H.%M.%S")log_path = cf.get_value('log_path')self.log_file = '{}log{}.txt'.format(log_path, curr_time)# 设置控制台输出sh = logging.StreamHandler()sh.setFormatter(fmt)sh.setLevel(cmd_level)# 设置文件输出fh = logging.FileHandler(self.log_file)fh.setFormatter(fmt)fh.setLevel(file_level)# 添加日志输出方式self.logger.addHandler(sh)self.logger.addHandler(fh)'''except Exception as e:raise edef debug(self, msg):self.logger.debug(msg)def info(self, msg):self.logger.info(msg)def error(self, msg):self.logger.error(msg)def warning(self, msg):self.logger.warning(msg)

3.mail.py代码实现

import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.header import Header
import config.config as cfdef send_mail(sendto):"""发送邮件:param sendto:收件人列表,如['22459496@qq.com']"""mail_host = 'smtp.sohu.com'  # 邮箱服务器地址username = 'test@sohu.com'  # 邮箱用户名password = 'test'  # 邮箱密码receivers = sendto  # 收件人# 创建一个带附件的实例message = MIMEMultipart()message['From'] = Header(u'UI自动化', 'utf-8')message['subject'] = Header(u'UI自动化测试结果', 'utf-8')  # 邮件标题message.attach(MIMEText(u'测试结果详见附件', 'plain', 'utf-8'))# 邮件正文# 构造附件report_root = cf.get_value('report_path')  # 获取报告路径report_file = 'report.html'  # 报告文件名称att1 = MIMEText(open(report_root + report_file, 'rb').read(), 'base64', 'utf-8')att1["Content-Type"] = 'application/octet-stream'att1["Content-Disposition"] = 'attachment; filename={}'.format(report_file)message.attach(att1)try:smtp = smtplib.SMTP()smtp.connect(mail_host, 25)  # 25为 SMTP 端口号smtp.login(username, password)smtp.sendmail(username, receivers, message.as_string())print u'邮件发送成功'except Exception, e:print u'邮件发送失败'raise e

4.base_page.py代码实现

from selenium.common.exceptions import TimeoutException
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.action_chains import ActionChains
import os
import inspect
import config.config as cf
import logging
import timelog = logging.getLogger('szh.BasePage')class BasePage(object):def __init__(self):self.driver = cf.get_value('driver')  # 从全局变量取driver#global driverdef split_locator(self, locator):"""分解定位表达式,如'css,.username',拆分后返回'css selector'和定位表达式'.username'(class为username的元素):param locator: 定位方式+定位表达式组合字符串,如'css,.username':return: locator_dict[by], value:返回定位方式和定位表达式"""by = locator.split(',', 1)[0]value = locator.split(',', 1)[1]locator_dict = {'id': 'id','name': 'name','class': 'class name','tag': 'tag name','link': 'link text','plink': 'partial link text','xpath': 'xpath','css': 'css selector',}if by not in locator_dict.keys():raise NameError("wrong locator!'id','name','class','tag','link','plink','xpath','css',exp:'id,username'")return locator_dict[by], valuedef wait_element(self, locator, sec=30):"""等待元素出现:param locator: 定位方法+定位表达式组合字符串,用逗号分隔,如'css,.username':param sec:等待秒数"""by, value = self.split_locator(locator)try:WebDriverWait(self.driver, sec, 1).until(lambda x: x.find_element(by=by, value=value),message='element not found!!!')log.info(u'等待元素:%s' % locator)return Trueexcept TimeoutException:return Falseexcept Exception, e:raise edef get_element(self, locator, sec=60):"""获取一个元素:param locator: 定位方法+定位表达式组合字符串,用逗号分隔,如'css,.username':param sec:等待秒数:return: 元素可找到返回element对象,否则返回False"""if self.wait_element(locator, sec):by, value = self.split_locator(locator)try:element = self.driver.find_element(by=by, value=value)log.info(u'获取元素:%s' % locator)return elementexcept Exception, e:raise eelse:return Falsedef get_elements(self, locator):"""获取一组元素:param locator: 定位方法+定位表达式组合字符串,用逗号分隔,如'css,.username':return: elements"""by, value = self.split_locator(locator)try:elements = WebDriverWait(self.driver, 60, 1).until(lambda x: x.find_elements(by=by, value=value))log.info(u'获取元素列表:%s' % locator)return elementsexcept Exception, e:raise edef get_url(self):"""获取当前网址:return: 网址连接"""log.info(u'获取当前网址:%s' % self.driver.current_url)return self.driver.current_urldef open(self, url):"""打开网址:param url: 网址连接"""self.driver.get(url)log.info(u'打开网址:%s' % url)def clear(self, locator):"""清除元素中的内容:param locator: 定位方法+定位表达式组合字符串,用逗号分隔,如'css,.username'"""self.get_element(locator).clear()log.info(u'清空内容:%s' % locator)def type(self, locator, text):"""在元素中输入内容:param locator: 定位方法+定位表达式组合字符串,用逗号分隔,如'css,.username':param text: 输入的内容"""self.get_element(locator).send_keys(text)log.info(u'向元素 %s 输入文字:%s' % (locator, text))def type_all(self, locator, text):"""在符合条件的所有元素中输入内容,依次循环输入text1,text2……:param locator: 定位方法+定位表达式组合字符串,用逗号分隔,如'css,.username':param text: 输入的内容"""allt = self.get_elements(locator)i = 1log.info(u'开始执行type_all,共%s个元素' % (len(allt)))for ele in allt:newtext = text + str(i)ele.send_keys(newtext)log.info(u'向第 %s 个元素输入文字:%s' % (i, newtext))i += 1def enter(self, locator):"""在元素上按回车键:param locator: 定位方法+定位表达式组合字符串,用逗号分隔,如'css,.username'"""self.get_element(locator).send_keys(Keys.ENTER)log.info(u'在元素 %s 上按回车' % locator)def click(self, locator, repeat=0):"""在元素上单击:param repeat: 重复次数标记,不要填写:param locator: 定位方法+定位表达式组合字符串,用逗号分隔,如'css,.username'"""try:repeat += 1self.get_element(locator).click()log.info(u'点击元素:%s' % locator)except Exception, e:log.info(u'点击元素:%s 第%s次执行失败' % (locator, repeat))if repeat > 2:raise eself.click(locator, repeat)def click_all(self, locator):"""点击所有符合条件的元素:param locator: 定位方法+定位表达式组合字符串,用逗号分隔,如'css,.username'"""allc = self.get_elements(locator)i = 0log.info(u'开始执行click_all,共%s个元素' % (len(allc)))for ele in allc:self.sleep(0.3)ele.click()i += 1log.info(u'点击第 %s 个元素' % i)def double_click_all(self, locator):"""双击所有符合条件的元素:param locator: 定位方法+定位表达式组合字符串,用逗号分隔,如'css,.username'"""allc = self.get_elements(locator)i = 0log.info(u'开始执行double_click_all,共%s个元素' % (len(allc)))for ele in allc:ActionChains(self.driver).double_click(ele).perform()i += 1log.info(u'点击第 %s 个元素' % i)def get_element_offset(self, locator):"""获取元素坐标:param locator: 定位方法+定位表达式组合字符串,用逗号分隔,如'css,.username':return: x,y"""element = self.get_element(locator)loc = element.locationx = loc['x']y = loc['y']log.info(u'获取元素坐标:%s,%s' % (x, y))return x, ydef get_element_offset_click(self, locator):"""获取元素坐标并点击中间位置,适用于:元素A中套着元素B,元素B无法定位但元素A可以定位:param locator: 定位方法+定位表达式组合字符串,用逗号分隔,如'css,.username'"""element = self.get_element(locator)loc = element.locationx = loc['x']y = loc['y']size = element.sizewidth = size['width']height = size['height']x += widthy += heightself.click_offset(x, y)def click_offset(self, x, y):"""点击坐标:param x: x坐标':param y: y坐标'"""ActionChains(self.driver).move_by_offset(x, y).click().perform()log.info(u'点击坐标%s,%s' % (x, y))def right_click(self, locator):"""鼠标右击元素:param locator: 定位方法+定位表达式组合字符串,用逗号分隔,如'css,.username'"""element = self.get_element(locator)ActionChains(self.driver).context_click(element).perform()log.info(u'在元素上右击:%s' % locator)def double_click(self, locator):"""双击元素:param locator: 定位方法+定位表达式组合字符串,用逗号分隔,如'css,.username'"""element = self.get_element(locator)ActionChains(self.driver).double_click(element).perform()log.info(u'在元素上双击:%s' % locator)def move_to_element(self, locator):"""鼠标指向元素:param locator: 定位方法+定位表达式组合字符串,用逗号分隔,如'css,.username'"""element = self.get_element(locator)ActionChains(self.driver).move_to_element(element).perform()log.info(u'指向元素%s' % locator)def drag_and_drop(self, locator, target_locator):"""拖动一个元素到另一个元素位置:param locator: 要拖动元素的定位:param target_locator: 目标位置元素的定位"""element = self.get_element(locator)target_element = self.get_element(target_locator)ActionChains(self.driver).drag_and_drop(element, target_element).perform()log.info(u'把元素 %s 拖至元素 %s' % (locator, target_locator))def drag_and_drop_by_offset(self, locator, xoffset, yoffset):"""拖动一个元素移动x,y个偏移量:param locator: 定位方法+定位表达式组合字符串,用逗号分隔,如'css,.username':param xoffset: X offset to move to:param yoffset: Y offset to move to"""element = self.get_element(locator)ActionChains(self.driver).drag_and_drop_by_offset(element, xoffset, yoffset).perform()log.info(u'把元素 %s 拖至坐标:%s %s' % (locator, xoffset, yoffset))def click_partial_text_link(self, text):"""按部分链接文字查找并点击链接:param text: 链接的部分文字"""self.get_element('plink,' + text).click()log.info(u'点击连接:%s' % text)def alert_text(self):"""返回alert文本:return: alert文本"""log.info(u'获取弹框文本:%s' % self.driver.switch_to.alert.text)return self.driver.switch_to.alert.textdef alert_accept(self):"""alert点确认"""self.driver.switch_to.alert.accept()log.info(u'点击弹框确认')def alert_dismiss(self):"""alert点取消"""self.driver.switch_to.alert.dismiss()log.info(u'点击弹框取消')def get_attribute(self, locator, attribute):"""返回元素某属性的值:param locator: 定位方法+定位表达式组合字符串,用逗号分隔,如'css,.username':param attribute: 属性名称:return: 属性值"""value = self.get_element(locator).get_attribute(attribute)log.info(u'获取元素 %s 的属性值 %s 为:%s' % (locator, attribute, value))return valuedef get_ele_text(self, locator):"""返回元素的文本:param locator: 定位方法+定位表达式组合字符串,用逗号分隔,如'css,.username':return: 元素的文本"""log.info(u'获取元素 %s 的文本为:%s' % (locator, self.get_element(locator).text))return self.get_element(locator).textdef frame_in(self, locator):"""进入frame:param locator: 定位方法+定位表达式组合字符串,如'css,.username'"""e = self.get_element(locator)self.driver.switch_to.frame(e)log.info(u'进入frame:%s' % locator)def frame_out(self):"""退出frame返回默认文档"""self.driver.switch_to.default_content()log.info(u'退出frame返回默认文档')def open_new_window_by_locator(self, locator):"""点击元素打开新窗口,并将句柄切换到新窗口:param locator: 定位方法+定位表达式组合字符串,如'css,.username'"""self.get_element(locator).click()self.driver.switch_to.window(self.driver.window_handles[-1])log.info(u'点击元素 %s 打开新窗口' % locator)# old_handle = self.driver.current_window_handle# self.get_element(locator).click()# all_handles = self.driver.window_handles# for handle in all_handles:#     if handle != old_handle:#         self.driver.switch_to.window(handle)def open_new_window_by_element(self, element):"""点击元素打开新窗口,并将句柄切换到新窗口:param element: 元素对象"""element.click()self.driver.switch_to.window(self.driver.window_handles[-1])log.info(u'点击元素打开新窗口')def js(self, script):"""执行JavaScript:param script:js语句"""self.driver.execute_script(script)log.info(u'执行JS语句:%s' % script)def scroll_element(self, locator):"""拖动滚动条至目标元素:param locator: 定位方法+定位表达式组合字符串,如'css,.username'"""script = "return arguments[0].scrollIntoView();"element = self.get_element(locator)self.driver.execute_script(script, element)log.info(u'滚动至元素:%s' % locator)def scroll_top(self):"""滚动至顶部"""self.js("window.scrollTo(document.body.scrollHeight,0)")log.info(u'滚动至顶部')def scroll_bottom(self):"""滚动至底部"""self.js("window.scrollTo(0,document.body.scrollHeight)")log.info(u'滚动至底部')def back(self):"""页面后退"""self.driver.back()log.info(u'页面后退')def forward(self):"""页面向前"""self.driver.forward()log.info(u'页面向前')def wait_text(self, text, per=3, count=10):"""判断给定文本是否在页面上:param text: 要判断的文本:param per: 每次判断间断时间:param count: 判断次数:return: 存在返回True,不存在返回False"""for i in range(count):if text in self.driver.page_source:log.info(u'判断页面上有文本:%s 第%s次' % (text, i+1))return Trueself.sleep(per)log.info(u'判断页面上没有文本:%s 共%s次' % (text, i+1))return Falsedef refresh(self):"""刷新页面"""self.driver.refresh()log.info(u'刷新页面')def screenshot(self, info='-'):"""截图,起名为:文件名-方法名-注释:param info: 截图说明"""catalog_name = cf.get_value('screenshot_path')  # 从全局变量取截图文件夹位置if not os.path.exists(catalog_name):os.makedirs(catalog_name)class_object = inspect.getmembers(inspect.stack()[1][0])[-3][1]['self']  # 获得测试类的objectclassname = str(class_object).split('.')[1].split(' ')[0]  # 获得测试类名称testcase_name = inspect.stack()[1][3]  # 获得测试方法名称filepath = catalog_name + classname + "@" + testcase_name + info + ".png"self.driver.get_screenshot_as_file(filepath)log.info(u'截图:%s.png' % info)def close(self):"""关闭当前页"""self.driver.close()self.driver.switch_to.window(self.driver.window_handles[0])log.info(u'关闭当前Tab')def sleep(self, sec):time.sleep(sec)log.info(u'等待%s秒' % sec)if __name__ == '__main__':bp = BasePage()bp.open('http://www.baidu.com')

五.元素定义特别用法说明

1.本框架支持selenium所有的定位方法,为了提高编写速度,改进了定位元素的使用方法,此时把定位元素的方法名和方法值,是用逗号隔开的字符串,比如:

2.xpath定位:i_keyword = ‘xpath,//input[@id=“kw”]’ # 关键字输入框。

3.id定位:b_search = ‘id,su’ # 搜索按钮。

4.其他定位方法同上,不再一一举例。

5.使用上面代码中type()方法,是在输入框中输入文字,调用时输入type(i_keyword, “输入内容”),type()中会调用get_element()方法,对输入的定位表达式进行解析,并且会等待元素一段时间,当元素出现时立即进行操作。

6.在代码中使用上面定义的元素:

6.1.self.type(self.i_keyword, “学好selenium”) # 使用封装过的sendkeys方法输入搜索关键字,详见base_page.py。

6.2.self.click(self.b_search) # 使用封装过的click方法点击搜索按钮。

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

六.search_page.py代码实现

1.PO模式中封装的百度的搜索页,继承了上面的BasePage类;并且页面类中定义了各业务功能和各控件的表达式,并将页面类上定义的各种业务功能和控件操作封装为方法。这样如果有多个用例,调用了此页面类上封装的业务功能和控件的操作方法,将来更新维护只需要在此页面类中修改,就可以所有用例就都更新了。

from page_object.base_page import BasePageclass SearchPage(BasePage):def __init__(self, driver):self.driver = driver# i=输入框, l=链接, im=图片, t=文字控件, d=div, lab=label# 输入框输入星空物语后,自动跳转搜索出来的结果:第一条含_百度百科的搜索结果l_baike = 'xpath,//a[(. = "星空物语_百度百科")]'# 下一页b_next_page = 'link,下一页>'# 上一页b_up_page = 'xpath,//a[(. = "<上一页")]'# 点击搜索结果的百科def click_result(self):self.open_new_window_by_locator(self.l_baike)self.sleep(3)# 点击下一页def click_next_page(self):self.click(self.b_next_page)# 点击上一页def click_previous_page(self):self.click(self.b_up_page)

七.test_search.py代码实现

1.百度搜索页的测试用例:

1.1.第1个是搜索后点击首个搜索结果可打开。

1.2.第2个是搜索结果可翻页,用例中的具体各个业务功能的操作均是调用上面页面类中封装好的操作方法。

import sys
reload(sys)
sys.setdefaultencoding('utf8')
from page_object.home_page import HomePage
from page_object.search_page import SearchPage
import pytest
import config.config as cfclass TestSearch():"""pytest:测试文件以test_开头测试类以Test开头,并且不能带有__init__方法测试函数以test_开头断言使用assert"""driver = cf.get_value('driver')  # 从全局变量取driverhome_page = HomePage(driver)search_page = SearchPage(driver)def test_click_result(self):"""搜索页-点击首个搜索结果"""try:self.home_page.open_homepage()self.home_page.input_keyword(u'星空物语')  # 输入关键字self.search_page.click_result()  # 点击星空物语_百度百科assert self.home_page.wait_text(u'电视剧《一起来看流星雨》片头曲')  # 验证页面打开self.home_page.screenshot(u'打开搜索结果')self.search_page.close()  # 关闭百科页面except Exception, e:self.home_page.screenshot(u'打开搜索结果失败')raise edef test_click_next_page(self):"""搜索页-搜索翻页"""try:self.search_page.click_next_page()  # 点下一页assert self.home_page.wait_element(self.search_page.b_up_page)  # 上一页出现self.search_page.scroll_element(self.search_page.b_up_page)  # 滚到上一页self.home_page.screenshot(u'搜索翻页')except Exception, e:self.home_page.screenshot(u'搜索翻页失败')raise eif __name__ == '__main__':pytest.main(['-v', '-s', 'test_search.py'])# pytest.main(['-v', '-s'])

八.conftest.py代码实现

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

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

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

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

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

import pytest
from py._xmlgen import html
import config.config as cf
import logginglog = logging.getLogger('szh.conftest')@pytest.mark.hookwrapper
def pytest_runtest_makereport(item):"""当测试失败的时候,自动截图,展示到html报告中"""pytest_html = item.config.pluginmanager.getplugin('html')outcome = yieldreport = outcome.get_result()extra = getattr(report, 'extra', [])if report.when == 'call' or report.when == "setup":xfail = hasattr(report, 'wasxfail')if (report.skipped and xfail) or (report.failed and not xfail):file_name = report.nodeid.replace("::", "_") + ".png"driver = cf.get_value('driver')  # 从全局变量取driverscreen_img = driver.get_screenshot_as_base64()if file_name:html = '<div><img src="data:image/png;base64,%s" alt="screenshot" style="width:600px;height:300px;" ' \'οnclick="window.open(this.src)" align="right"/></div>' % screen_imgextra.append(pytest_html.extras.html(html))report.extra = extrareport.description = str(item.function.__doc__)#.decode('utf-8', 'ignore')  # 不解码转成Unicode,生成HTML会报错# report.nodeid = report.nodeid.encode("utf-8").decode("unicode_escape")@pytest.mark.optionalhook
def pytest_html_results_table_header(cells):cells.insert(1, html.th('Description'))cells.pop()  # 删除报告最后一列links@pytest.mark.optionalhook
def pytest_html_results_table_row(report, cells):cells.insert(1, html.td(report.description))cells.pop()  # 删除报告最后一列links# @pytest.fixture(scope='function')
# def testcase():
#     log.info(u'\n--------------------用例开始--------------------')
#     yield
#     log.info(u'\n--------------------用例结束--------------------')

九.run.py代码实现

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

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

3.get_args()是封装的命令行参数解析,方便集成Jenkins时快速定义运行内容。

4.get_args()命令行参数

4.1.Demo目前只是定义了一个环境参数-e,可设置测试环境preview,线上环境product,如要在线上运行测试,启动脚本输入:python run.py -e product

4.2.可自行根据实际需要添加更多参数,并在run.py中的get_args()中进行修改。

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

import pytest
import config.config as cf
from util.log import Logger
import argparse
from selenium import webdriver
from util.mail import send_maildef get_args():"""命令行参数解析"""parser = argparse.ArgumentParser(description=u'可选择参数:')parser.add_argument('-e', '--environment', choices=['preview', 'product'], default='preview', help=u'测试环境preview,线上环境product')args = parser.parse_args()if args.environment in ('pre', 'preview'):cf.set_value('environment', 'preview')cf.set_value('site', 'http://www.baidu.com/')elif args.environment in ('pro', 'product'):cf.set_value('environment', 'product')cf.set_value('site', 'https://www.baidu.com/')else:print u"请输入preview/product"exit()def set_driver():"""设置driver"""# 配置Chrome Driverchrome_options = webdriver.ChromeOptions()chrome_options.add_argument('--start-maximized')  # 浏览器最大化chrome_options.add_argument('--disable-infobars')  # 不提醒chrome正在受自动化软件控制prefs = {'download.default_directory': cf.get_value('download_path')}chrome_options.add_experimental_option('prefs', prefs)  # 设置默认下载路径# chrome_options.add_argument(r'--user-data-dir=D:\ChromeUserData')  # 设置用户文件夹,可免登陆driver = webdriver.Chrome('{}\\driver\\chromedriver.exe'.format(cf.get_value('root_path')), options=chrome_options)cf.set_value('driver', driver)def main():"""运行pytest命令启动测试"""pytest.main(['-v', '-s', 'test_case/', '--html=report/report.html', '--self-contained-html'])if __name__ == '__main__':cf.init()  # 初始化全局变量get_args()  # 命令行参数解析log = Logger('szh')  # 初始化log配置set_driver()  # 初始化drivermain()  # 运行pytest测试集cf.get_value('driver').quit()  # 关闭selenium driver# 先将util.mail文件send_mail()中的用户名、密码填写正确,再启用发送邮件功能!!!# send_mail(['22459496@qq.com'])  # 将报告发送至邮箱

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

十一.GitHub代码获取

https://github.com/songzhenhua/selenium_ui_auto

第二十一:基于Python2+Selenium3+Pytest4+Pytest-Html的UI自动化框架相关推荐

  1. 基于python2+selenium3+pytest4的UI自动化框架

    环境: Python2.7.10, selenium3.141.0, pytest4.6.6, pytest-html1.22.0, Windows-7-6.1.7601-SP1 特点: 二次封装了s ...

  2. UI自动化框架 基于selenium+pytest和PO分层思想

    最近在编写UI自动化框架,现在将一些碎片化东西进行梳理,便于记忆 同时,为了方便于各个模块的独立管理,以及秉承高复用,低耦合的思想,这里是根据PO模型编写,同时将所有的模块进行了独立,页面和元素,以及 ...

  3. pytest+yaml设计接口自动化框架过程记录(一步一步记录如何设计,完结撒花),源码提供,视频教程

    pytest+yaml设计接口自动化框架过程记录 第三代框架使用教程,该框架比现在这个完善了很多 框架简介 框架运行演示和功能介绍视频 pytest+yaml框架环境配置和使用教程 0.去年也写了一个 ...

  4. 手把手教你从0到1搭建web ui自动化框架(python3+selenium3+pytest)

    -前期准备 -环境 -实战: 从0开始 前期准备 为更好的学习自动化框架搭建,你需要提前了解以下知识: python基础知识 pytest单元测试框架 PO模式 selenium使用 环境 本次我们自 ...

  5. 基于Appium+WinAppDriver+Python的winUI3应用的自动化框架搭建分享(一)环境配置

    安装WinAppDriver 下载并安装WinAppDriver:来源 https://github.com/Microsoft/WinAppDriver/releases 开启电脑的开发者模式 设置 ...

  6. api/UI自动化框架设计(pytest)

  7. pythonapp自动化_基于python的App UI自动化环境搭建

    Android端Ui 自动化环境搭建 一,安装JDK.SDK 二,添加环境变量 Widows: 1.系统变量→新建 JAVA_HOME 变量 E:\Java\jdk1.7.0 jdk安装目录 2.系统 ...

  8. 基于airtest的安卓ui自动化实践

    在tester home上初识airtest感觉很惊艳,最近想给组里的同学分享一下,仔细研究了一番.分享之后,发现他们练习做的还很不错. 莫非我真的有教书育人的能力?(天真脸) 分为四个部分讲解:ai ...

  9. [你必须知道的.NET]第二十一回:认识全面的null

    <你必须知道的.NET>网站 | Anytao技术博客  [你必须知道的.NET]第二十一回:认识全面的null 发布日期:2008.7.31 作者:Anytao © 2008 Anyta ...

最新文章

  1. wangEditor 上传图片,回调函数 Cannot read properties of undefined
  2. 时间序列(五)股票分析
  3. 水泵怎么做_不是说鱼缸里放置三合一水泵都会起到反作用,也得看什么缸什么鱼...
  4. python能在工程上干嘛-python能干什么?
  5. 学习dubbo(二): 第1个例子
  6. 据说一般人轻易做不了技术支撑…
  7. 3D 音频技术产品介绍(1):Iosono the future of spatial audio
  8. fast_recovery_area无剩余空间(ORA-19815)
  9. 用于混合Spock 1.x和JUnit 5测试的Maven项目设置
  10. define 汉字 error C2001: newline in constant
  11. 加餐:Redis 的可视化管理工具
  12. MySQL Data Manipulation Statements
  13. 华为否认今年将推出搭载鸿蒙系统手机;苹果或在 3 年内推出 5G 基带芯片;Node.js 12.12.0 发布 | 极客头条...
  14. 如何在本机使用正式版的SAP Business One的Common库的Lisence服务
  15. matlab中求方差的,matlab中求方差为什么除以n-1?
  16. java中math是什么意思,java中的Math种
  17. 音频怎么转换mp3格式?
  18. 如何避免字节流读取文本乱码
  19. 带K线的macd选股指标详解 优化MACD王牌指标 通达信macd选股指标源码
  20. 冯唐:职场人35岁以后,方法论比经验重要

热门文章

  1. poj 1743 二分答案+后缀数组 求不重叠的最长重复子串
  2. C#调用系统的复制、移动、删除文件对话框
  3. POJ 1014 Dividing
  4. Google C++ Testing Framework之断言
  5. 中国教育与软件企业的共同误区
  6. python每日经典算法题5(基础题)+1(较难题)
  7. 7.2Python入门(三)
  8. springcloud-eureka简单实现
  9. hdu-1003 Max Sum
  10. ASP.NET MVC RedirectToRoute类[转]