

Selenium4+Python3系列(十二) - 测试框架的设计与开发 - 久曲健
source link: https://www.cnblogs.com/longronglang/p/16972208.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Selenium4+Python3系列(十二) - 测试框架的设计与开发 - 久曲健 - 博客园
自己从未没想过能使用python
来做自动化测试框架的设计、开发。
可能有人会好奇说,六哥,你怎么也用python
写测试框架了?
python你也没有实际工作经验,可能就是自己自学的。
听完,那一刻,我真的特别证明自己,我也行!
整个框架的实现,大约也就1.5天,关于框架的开发并不是很难,主要难在测试报告增加失败自动截图功能
和echart的饼子图
统计功能,两者的整合花了近半天的时间吧。
1、核心思想
延续使用Page Object
和Page Factory
思想,使页面、数据、元素、脚本进行分离,此处演示仅仅为了讲解框架搭建思路,并非为我在公司写的那套框架,主要使用selenium4+python3+pytest
,这里只贴核心代码,仅供学习交流使用。
目录结构
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、测试效果
用例执行效果:
测试报告:
其实写框架并不难,掌握核心思路,实现起来就会变得容易很多,与语言无关哦(因为我是Java党
)。
关于API
及很多细节部分,没做详细处理和封装,这里笔者仅仅是提供思路,感兴趣的同学,可自行去尝试进行进一步扩展,如想要源代码的同学可以文末留言或者加我好友领取哦。
__EOF__
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK