前言

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

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

领导说:

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

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

框架搭建

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

效果:

1、核心思想

延续使用Page ObjectPage 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及很多细节部分,没做详细处理和封装,这里笔者仅仅是提供思路,感兴趣的同学,可自行去尝试进行进一步扩展,如想要源代码的同学可以文末留言或者加我好友领取哦。

Selenium4+Python3系列(十二) - 测试框架的设计与开发的更多相关文章

  1. SQL Server 2008空间数据应用系列十二:Bing Maps中呈现GeoRSS订阅的空间数据

    原文:SQL Server 2008空间数据应用系列十二:Bing Maps中呈现GeoRSS订阅的空间数据 友情提示,您阅读本篇博文的先决条件如下: 1.本文示例基于Microsoft SQL Se ...

  2. Alamofire源码解读系列(十二)之请求(Request)

    本篇是Alamofire中的请求抽象层的讲解 前言 在Alamofire中,围绕着Request,设计了很多额外的特性,这也恰恰表明,Request是所有请求的基础部分和发起点.这无疑给我们一个Req ...

  3. struts2官方 中文教程 系列十二:控制标签

    介绍 struts2有一些控制语句的标签,本教程中我们将讨论如何使用 if 和iterator 标签.更多的控制标签可以参见 tags reference. 到此我们新建一个struts2 web 项 ...

  4. 爬虫系列(十二) selenium的基本使用

    一.selenium 简介 随着网络技术的发展,目前大部分网站都采用动态加载技术,常见的有 JavaScript 动态渲染和 Ajax 动态加载 对于爬取这些网站,一般有两种思路: 分析 Ajax 请 ...

  5. Web 前端开发精华文章推荐(jQuery、HTML5、CSS3)【系列十二】

    2012年12月12日,[<Web 前端开发人员和设计师必读文章>系列十二]和大家见面了.梦想天空博客关注 前端开发 技术,分享各种增强网站用户体验的 jQuery 插件,展示前沿的 HT ...

  6. 【小梅哥FPGA进阶教程】第十二章 数字密码锁设计

    十二.数字密码锁设计 本文由山东大学研友袁卓贡献,特此感谢 实验目的 实现数字密码锁设计,要求矩阵按键输出且数码管显示输入密码,密码输入正确与否均会有相应标志信号产生. 实验平台 芯航线FPGA核心板 ...

  7. 专题开发十二:JEECG微云高速开发平台-基础用户权限

      专题开发十二:JEECG微云高速开发平台-基础用户权限 11.3.4自己定义button权限 Jeecg中.眼下button权限设置,是通过对平台自己封装的button标签(<t:dgFun ...

  8. 学习ASP.NET Core Razor 编程系列十二——在页面中增加校验

    学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...

  9. SpringBoot系列(十二)过滤器配置详解

    SpringBoot(十二)过滤器详解 往期精彩推荐 SpringBoot系列(一)idea新建Springboot项目 SpringBoot系列(二)入门知识 springBoot系列(三)配置文件 ...

  10. Selenium4+Python3系列(五) - 多窗口处理之句柄切换

    写在前面 感觉到很惭愧呀,因为居然在Selenium+Java系列中没有写过多窗口处理及句柄切换的文章,不过也无妨,不管什么语言,其思路是一样的,下面我们来演示,使用python语言来实现窗口句柄的切 ...

随机推荐

  1. 第四章:Django表单 - 1:使用表单

    假设你想从表单接收用户名数据,一般情况下,你需要在HTML中手动编写一个如下的表单元素: <form action="/your-name/" method="po ...

  2. 论Elasticsearch数据建模的重要性

    文章转载自: https://mp.weixin.qq.com/s?__biz=MzI2NDY1MTA3OQ==&mid=2247484159&idx=1&sn=731562a ...

  3. 14. Fluentd输出插件:out_forward用法详解

    out_forward是一个带缓存的输出插件,用于向其他节点转发日志事件,并支持转发节点之间的负载均衡和自动故障切换. out_forward支持至多一次和至少一次传输模式,默认为至多一次. out_ ...

  4. .Net下的分布式唯一ID

    分布式唯一ID,顾名思义,是指在全世界任何一台计算机上都不会重复的唯一Id. 在单机/单服务器/单数据库的小型应用中,不需要用到这类东西.但在高并发.海量数据.大型分布式应用中,这类却是构建整个系统的 ...

  5. VLQ & Base64 VLQ 编码方式的原理及代码实现

    目录 VLQ Base64 VLQ VLQ VLQ (Variable-length quantity)是一种通用的,使用任意位数的二进制来表示一个任意大的数字的一种编码方式. 编码实现: ** 对数 ...

  6. java.util.Arrays----操作数组的工具类

    java.util.Arrays操作数组的工具类,里面定义了很多操作数组的方法 1.boolean equals(int[] a,int[] b):判断两个数组是否相等. 2.String toStr ...

  7. 微信小程序专题(一)-----微信后台的相关开发

    本人最近在做微信小程序后端的相关开发工作 接触到微信小程序目前来讲需要两个条件 1.前端通过后台服务器去调用微信平台接口,来获取openid: 2.前端必须调用https 跟域名的形式 不得出现ip加 ...

  8. 齐博x1关于小程序个性源代码的说明

    系统默认推荐商家小程序使用通用型的源码,即框架套壳iframe形式的.这个灵活性更高.但如果有特殊需求的话,也可以设置个性源码,比如配合uni-app使用,针对不同的小程序就使用不同的uni-app风 ...

  9. VSCode设置鼠标滚轮滑动设置字体大小

    1. 打开"文件->首选项->设置 2. 打开settings.json文件 3. 在setting.json 中添加"editor.mouseWheelZoom&qu ...

  10. F118校准(一)-- 安装CA310驱动程序及SDK

    1. 准备工作 下载Ca310_drv.zip文件并解压,备用. http://www.xk-image.com/download/blog/0001_F118校准/Ca310_drv.zip 准备好 ...