Selenium4+Python3系列(十二) – 测试框架的设计与开发

前言

自己从未没想过能使用python来做自动化测试框架的设计、开发。

可能有人会好奇说,六哥,你怎么也用python写测试框架了?

领导说:

python你也没有实际工作经验,可能就是自己自学的。

听完,那一刻,我真的特别证明自己,我也行!

框架搭建

整个框架的实现,大约也就1.5天,关于框架的开发并不是很难,主要难在测试报告增加失败自动截图功能echart的饼子图统计功能,两者的整合花了近半天的时间吧。

Selenium4+Python3系列(十二) - 测试框架的设计与开发

效果:

Selenium4+Python3系列(十二) - 测试框架的设计与开发

1、核心思想

延续使用Page ObjectPage Factory思想,使页面、数据、元素、脚本进行分离,此处演示仅仅为了讲解框架搭建思路,并非为我在公司写的那套框架,主要使用selenium4+python3+pytest,这里只贴核心代码,仅供学习交流使用。

目录结构

Selenium4+Python3系列(十二) - 测试框架的设计与开发

2、日志封装

主要用于方便定位用例脚本执行步骤,示例代码如下:

# -*- coding: utf-8 -*- """ @Time : 2022/12/7 19:36 @Auth : 软件测试君 @File :LogUtils.py @IDE :PyCharm @Motto:ABC(Always Be Coding) """ import time import os import logging  currrent_path = os.path.dirname(__file__) log_path = os.path.join(currrent_path, '../logs')   class LogUtils:      def __init__(self, log_path=log_path):         """         通过python自带的logging模块进行封装         """         self.logfile_path = log_path         # 创建日志对象logger         self.logger = logging.getLogger(__name__)         # 设置日志级别         self.logger.setLevel(level=logging.INFO)         # 设置日志的格式         formatter = logging.Formatter('%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s')         """在log文件中输出日志"""         # 日志文件名称显示一天的日志         self.log_name_path = os.path.join(self.logfile_path, "log_%s" % time.strftime('%Y_%m_%d')+".log")         # 创建文件处理程序并实现追加         self.file_log = logging.FileHandler(self.log_name_path, 'a', encoding='utf-8')         # 设置日志文件里的格式         self.file_log.setFormatter(formatter)         # 设置日志文件里的级别         self.file_log.setLevel(logging.INFO)         # 把日志信息输出到文件中         self.logger.addHandler(self.file_log)         # 关闭文件         self.file_log.close()          """在控制台输出日志"""         # 日志在控制台         self.console = logging.StreamHandler()         # 设置日志级别         self.console.setLevel(logging.INFO)         # 设置日志格式         self.console.setFormatter(formatter)         # 把日志信息输出到控制台         self.logger.addHandler(self.console)         # 关闭控制台日志         self.console.close()      def get_log(self):         return self.logger   logger = LogUtils().get_log()  if __name__ == '__main__':     logger.info('123')     logger.error('error') 

3、基础页面

用于存放,控件及API的常用操作,示例代码如下:

# -*- coding: utf-8 -*- """ @Time : 2022/12/7 19:58 @Auth : 软件测试君 @File :BasePage.py @IDE :PyCharm @Motto:ABC(Always Be Coding) """ import time  from selenium.common import TimeoutException from selenium.webdriver.common.by import By from selenium.webdriver.support.wait import WebDriverWait as WD  from util.LogUtils import LogUtils from util.ParseConFile import ParseConFile  logger = LogUtils().get_log()   class BasePage(object):     """控件及API的常用操作"""      cf = ParseConFile()      def __init__(self, driver, timeout=30):         self.byDic = {             'id': By.ID,             'name': By.NAME,             'class_name': By.CLASS_NAME,             'xpath': By.XPATH,             'link_text': By.LINK_TEXT,             'css': By.CSS_SELECTOR         }         self.driver = driver         self.outTime = timeout      def find_element(self, by, locator):         """         通过id, name, xpath, css,class....,查找元素         """         try:             logger.info("通过 " + by + " 定位")             element = WD(self.driver, self.outTime).until(lambda x: x.find_element(self.byDic.get(by), locator))         except TimeoutException as e:             logger.error('请确认元素定位方式,' + e)         else:             return element      def find_elements(self, by, locator):         """         通过id, name, xpath, css,class....,查找一组元素         """         try:             logger.info("通过 " + by + " 定位")             elements = WD(self.driver, self.outTime).until(lambda x: x.find_elements(self.byDic.get(by), locator))         except TimeoutException as e:             logger.error('请确认元素定位方式,' + e)         else:             return elements      def get_text(self, by, locator):         """         获取元素文本/属性信息         """         logger.info("获取元素文本成功!")         return self.find_element(by, locator).text      def open_url(self, url):         """打开浏览器"""         logger.info("打开项目首页:" + url)         self.driver.get(url)      def quit_browser(self):         self.driver.quit()      def send_keys(self, by, locator, keys=''):         """输入操作"""         logger.info("输入:" + keys)         self.find_element(by, locator).clear         self.sleep(1)         self.find_element(by, locator).send_keys(keys)      def click(self, by, locator):         """点击操作"""         logger.info("点击按钮:" + locator)         self.find_element(by, locator).click()      @staticmethod     def sleep(num=0):         """强制等待"""         logger.info("程序等待:" + str(num) + " 秒")         time.sleep(num) 

4、登陆页面

主要用于存放控件及元素操作,示例代码如下:

# -*- coding: utf-8 -*- """ @Time : 2022/12/7 20:27 @Auth : 软件测试君 @File :LoginPage.py @IDE :PyCharm @Motto:ABC(Always Be Coding) """ from Page.BasePage import BasePage from util.LogUtils import LogUtils from util.ParseConFile import ParseConFile  logger = LogUtils().get_log()   class LoginPage(BasePage):     """     存放控件及元素操作     """     # 配置文件读取元素     do_conf = ParseConFile()     # 用户名输入框     username = do_conf.get_locator('LoginPage_Elements', 'username')     # 密码输入框     password = do_conf.get_locator('LoginPage_Elements', 'password')     # 登录按钮     loginBtn = do_conf.get_locator('LoginPage_Elements', 'loginBtn')     # 登录失败的提示信息     error_msg = do_conf.get_locator('LoginPage_Elements', 'errorMsg')      def login(self, username, password):         """登录流程"""         self.open()         self.send_username(username)         self.send_password(password)         self.click_login_btn()         msg = self.get_errorMsg()         return msg      def open(self):         self.open_url('http://localhost:8080/login')      def quit(self):         self.quit_browser()      def send_username(self, username):         self.send_keys(*LoginPage.username, username)      def send_password(self, password):         self.send_keys(*LoginPage.password, password)      def click_login_btn(self):         self.click(*LoginPage.loginBtn)      def get_errorMsg(self):         return self.get_text(*LoginPage.error_msg)   if __name__ == "__main__":     pass 

5、业务操作

主要用于记录用例步骤,示例代码如下:

# -*- coding: utf-8 -*- """ @Time : 2022/12/7 20:27 @Auth : 软件测试君 @File :LoginPage.py @IDE :PyCharm @Motto:ABC(Always Be Coding) """ from Page.BasePage import BasePage from util.LogUtils import LogUtils from util.ParseConFile import ParseConFile  logger = LogUtils().get_log()   class LoginPage(BasePage):     """     存放控件及元素操作     """     # 配置文件读取元素     do_conf = ParseConFile()     # 用户名输入框     username = do_conf.get_locator('LoginPage_Elements', 'username')     # 密码输入框     password = do_conf.get_locator('LoginPage_Elements', 'password')     # 登录按钮     loginBtn = do_conf.get_locator('LoginPage_Elements', 'loginBtn')     # 登录失败的提示信息     error_msg = do_conf.get_locator('LoginPage_Elements', 'errorMsg')      def login(self, username, password):         """登录流程"""         self.open()         self.send_username(username)         self.send_password(password)         self.click_login_btn()         msg = self.get_errorMsg()         return msg      def open(self):         self.open_url('http://localhost:8080/login')      def quit(self):         self.quit_browser()      def send_username(self, username):         self.send_keys(*LoginPage.username, username)      def send_password(self, password):         self.send_keys(*LoginPage.password, password)      def click_login_btn(self):         self.click(*LoginPage.loginBtn)      def get_errorMsg(self):         return self.get_text(*LoginPage.error_msg)   if __name__ == "__main__":     pass 

6、测试报告之失败带截图

这块确实很坑,看了很多网上的教程,笔者不才,整了一下午才弄出失败带截图,主要是对conftest.py的设计编写,示例代码如下:

# -*- coding: utf-8 -*- """ @Time : 2022/12/10 18:13 @Auth : 软件测试君 @File :conftest.py @IDE :PyCharm @Motto:ABC(Always Be Coding) """ import pytest from selenium import webdriver from webdriver_manager.chrome import ChromeDriverManager  driver = None   @pytest.hookimpl(hookwrapper=True, tryfirst=True) def pytest_runtest_makereport(item):     pytest_html = item.config.pluginmanager.getplugin('html')     outcome = yield     report = 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"             screen_img = _capture_screenshot()             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_img                 extra.append(pytest_html.extras.html(html))         report.extra = extra   @pytest.fixture(scope='session') def browser():     global driver     if driver is None:         driver = webdriver.Chrome(ChromeDriverManager().install())         driver.maximize_window()     yield driver     driver.quit()     return driver   def _capture_screenshot():     """截图"""     return driver.get_screenshot_as_base64() 

7、执行脚本

主要用于调用测试用例脚本,示例代码如下:

# -*- coding: utf-8 -*- """ @Time : 2022/12/10 18:04 @Auth : 软件测试君 @File :RunTestCase.py @IDE :PyCharm @Motto:ABC(Always Be Coding) """ import sys  import pytest  from config.conf import ROOT_DIR, HTML_NAME   def main():     if ROOT_DIR not in sys.path:         sys.path.append(ROOT_DIR)     # 执行用例     args = ['--html=' + './report/' + HTML_NAME]     pytest.main(args)   if __name__ == '__main__':     main() 

8、测试效果

用例执行效果:

Selenium4+Python3系列(十二) - 测试框架的设计与开发

测试报告:

Selenium4+Python3系列(十二) - 测试框架的设计与开发

总结

其实写框架并不难,掌握核心思路,实现起来就会变得容易很多,与语言无关哦(因为我是Java党)。

关于API及很多细节部分,没做详细处理和封装,这里笔者仅仅是提供思路,感兴趣的同学,可自行去尝试进行进一步扩展,如想要源代码的同学可以文末留言或者加我好友领取哦。

发表评论

评论已关闭。

相关文章