100字范文,内容丰富有趣,生活中的好帮手!
100字范文 > 基于python2+selenium3+pytest4的UI自动化框架

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

时间:2022-03-23 10:17:56

相关推荐

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

环境:

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

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

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

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

封装的log模块

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

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

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

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

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

xpath定位:i_keyword = ‘xpath,//input[@id=“kw”]’ # 关键字输入框id定位:b_search = ‘id,su’ # 搜索按钮其他定位方法同上,不再一一举例

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

点赞关注~持续分享,加入我们,642830685,免费领取最新软件测试大厂面试资料和Python自动化、接口、框架搭建学习资料!技术大牛解惑答疑,同行一起交流。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。