Selenium4Python3系列(十二)测试框架的设计与开发
前言
自己从未没想过能使用 python 来做自动化测试框架的设计、开发。
可能有人会好奇说,六哥,你怎么也用 python 写测试框架了?
领导说: ❝
python你也没有实际工作经验,可能就是自己自学的。
❞
听完, 「那一刻,我真的特别证明自己,我也行!」 框架搭建
整个框架的实现,大约也就1.5天,关于框架的开发并不是很难,主要难在 测试报告增加失败自动截图功能 和echart的饼子图 统计功能,两者的整合花了近半天的时间吧。
image.png
效果:
image.png 1、核心思想
延续使用 Page Object 和Page Factory 思想,使页面、数据、元素、脚本进行分离,此处演示仅仅为了讲解框架搭建思路,并非为我在公司写的那套框架,主要使用selenium4+python3+pytest ,这里只贴核心代码,仅供学习交流使用。
「目录结构」
image.png 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 = "" % 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、测试效果
「用例执行效果:」
image.png
「测试报告:」
image.png 总结
其实写框架并不难,掌握核心思路,实现起来就会变得容易很多,与语言无关哦( 因为我是Java党 )。
关于 API 及很多细节部分,没做详细处理和封装,这里笔者仅仅是提供思路,感兴趣的同学,可自行去尝试进行进一步扩展,如想要源代码的同学可以文末留言或者加我好友领取哦。
他们育出有中国芯的苹果来源科技日报原标题他们育出有中国芯的苹果本报记者王延斌通讯员郭翠华名片上,陈学森是作物生物学国家重点实验室副主任国家苹果工程技术研究中心主任山东农业大学教授。在获得国家科技进步奖二
随手花30元买彩票中了100万!接下来的事太意外过年前能中个大奖那太好了,来浙江杭州旅游的崔先生就这么幸运!然而接下来发生的事情简直一波三折。崔先生花30元买的彩票中了百万大奖,正当他为此高兴时,却没想到因为车门未关,差点错失这
人口负增长2022年末我国人口锐减85万为啥小区保安都是大爷?为啥年轻人越来越少?1月17日,国家统计局发布2022年国民经济运行数据。2022年,中国人口出现近61年来的首次人口负增长。2022年年末全国人口(包括31
把准改革风向标激活发展内生力近日,由健康报社主办诺华制药公益支持的高质量发展下的国考全局部署交流会在线上召开。在以评促建行稳致远推动公立医院高质量发展学科群建设分论坛上,相关政策制定者业内专家三级公立医院管理
最后没阳的原来是这几种人!转眼间,全国各地变阳的高峰期已经基本过了,大家发现没有,总有一些人自始至终都没阳过,从头到尾感觉新冠这个病毒和他没有啥关系,真的是很神奇的存在。但依然没有阳的,主要是这几种人!1。
国乒排名更新!男队仅4人进前十,巴西神童一路飙升,王皓有压力1月17日,国际乒联公布了最新一期世界排名。上周乒坛主要经历了多哈世预赛和德班常规挑战赛,其中前者不设置积分,但事关世乒赛名额。由于国乒大部队并未前往非洲南部,欧美参赛选手积分明显
梁子岛复牌,王者能否归来?2023年伊始鄂州旅游业发展迎来开门红梁子岛生态旅游区和杏福园旅游区新晋国家4A级旅游景区说起梁子岛许多人并不陌生从上世纪90年代初设立风景区起旅游业在这里一路蓬勃兴旺梁子岛之名如
渔阳郡遗址,在怀柔和密云交界处,是怀柔区不可移动文物在101国道怀柔和密云交界处不远的地方,即怀柔区北房镇梨园庄村东0。5公里处,有一座古城遗址公园,从101道右转前行500米左右,就可以看到路边立着的一块牌子,上面写着渔阳城遗址,
警惕!德尔塔克戎变异毒株XAY已向全球延蔓,毒性有所增强一波未平一波又起,病毒反复变异,而且毒性在原来的基础上有所增强。近日,世界卫生组织部又检测出新的变异毒株XAY。2,最引人注目的是,它是德尔塔毒株和奥密克戎毒株的合体,由AY。45
公园中的科大讯飞全球总部人工智能是一门广泛交叉的前沿科学,对于一家有全球影响力的中国人工智能科技企业来说,总部园区不仅代表着企业外在的形象,更重要的是展现科技外表下的东方智慧与人文内里。我们尝试通过超级细
白话派币白皮书头条创作挑战赛我在头条搞创作第二期头条群星10月榜经不住朋友的反复安利,决定再好好看看这个派币,网上找到中文版的白皮书,一瞧,确实出乎意料有点料。怪不得受众这么广。派的目标前言里写