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
及很多细节部分,没做详细处理和封装,这里笔者仅仅是提供思路,感兴趣的同学,可自行去尝试进行进一步扩展,如想要源代码的同学可以文末留言或者加我好友领取哦。
Selenium4+Python3系列(十二) - 测试框架的设计与开发的更多相关文章
- SQL Server 2008空间数据应用系列十二:Bing Maps中呈现GeoRSS订阅的空间数据
原文:SQL Server 2008空间数据应用系列十二:Bing Maps中呈现GeoRSS订阅的空间数据 友情提示,您阅读本篇博文的先决条件如下: 1.本文示例基于Microsoft SQL Se ...
- Alamofire源码解读系列(十二)之请求(Request)
本篇是Alamofire中的请求抽象层的讲解 前言 在Alamofire中,围绕着Request,设计了很多额外的特性,这也恰恰表明,Request是所有请求的基础部分和发起点.这无疑给我们一个Req ...
- struts2官方 中文教程 系列十二:控制标签
介绍 struts2有一些控制语句的标签,本教程中我们将讨论如何使用 if 和iterator 标签.更多的控制标签可以参见 tags reference. 到此我们新建一个struts2 web 项 ...
- 爬虫系列(十二) selenium的基本使用
一.selenium 简介 随着网络技术的发展,目前大部分网站都采用动态加载技术,常见的有 JavaScript 动态渲染和 Ajax 动态加载 对于爬取这些网站,一般有两种思路: 分析 Ajax 请 ...
- Web 前端开发精华文章推荐(jQuery、HTML5、CSS3)【系列十二】
2012年12月12日,[<Web 前端开发人员和设计师必读文章>系列十二]和大家见面了.梦想天空博客关注 前端开发 技术,分享各种增强网站用户体验的 jQuery 插件,展示前沿的 HT ...
- 【小梅哥FPGA进阶教程】第十二章 数字密码锁设计
十二.数字密码锁设计 本文由山东大学研友袁卓贡献,特此感谢 实验目的 实现数字密码锁设计,要求矩阵按键输出且数码管显示输入密码,密码输入正确与否均会有相应标志信号产生. 实验平台 芯航线FPGA核心板 ...
- 专题开发十二:JEECG微云高速开发平台-基础用户权限
专题开发十二:JEECG微云高速开发平台-基础用户权限 11.3.4自己定义button权限 Jeecg中.眼下button权限设置,是通过对平台自己封装的button标签(<t:dgFun ...
- 学习ASP.NET Core Razor 编程系列十二——在页面中增加校验
学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...
- SpringBoot系列(十二)过滤器配置详解
SpringBoot(十二)过滤器详解 往期精彩推荐 SpringBoot系列(一)idea新建Springboot项目 SpringBoot系列(二)入门知识 springBoot系列(三)配置文件 ...
- Selenium4+Python3系列(五) - 多窗口处理之句柄切换
写在前面 感觉到很惭愧呀,因为居然在Selenium+Java系列中没有写过多窗口处理及句柄切换的文章,不过也无妨,不管什么语言,其思路是一样的,下面我们来演示,使用python语言来实现窗口句柄的切 ...
随机推荐
- MySQL集群搭建(3)-MMM高可用架构
1 MMM 介绍 1.1 简介 MMM 是一套支持双主故障切换以及双主日常管理的第三方软件.MMM 由 Perl 开发,用来管理和监控双主复制,虽然是双主架构,但是业务上同一时间只允许一个节点进行写入 ...
- 关于click和onclick的区别
click()和onclick()的区别: 1.onclick是绑定事件,告诉浏览器在鼠标点击时候要做什么 click本身是方法作用是触发onclick事件,只要执行了元素的click()方法,就会触 ...
- 前端ajax发送post 请求 json格式 springMVC报错415
如标题所示 后端填坑日记 在使用springMVC的时候发现 后端使用@RequestBody注解会报错415 不支持的媒体类型 相信很多小伙伴都遇到过或者正在面临这个报错 提示错误:The serv ...
- Vue中router路由的使用、router-link的使用(在项目中的实际运用方式)
文章目录 1.先看router中的index.js文件 2.router-link的使用 3.实现的效果 前提:router已经安装 1.先看router中的index.js文件 import Vue ...
- .NET 6学习笔记(4)——如何在.NET 6的Desktop App中使用Windows Runtime API
Windows Runtime API是当初某软为了区别Win32 API,力挺UWP而创建的另一套Windows 10专用的API集合.后来因为一些原因,UWP没火.为了不埋没很有价值的Window ...
- Gitea 1.18 功能前瞻(其三):增强文本预览效果、继续扩展软件包注册中心、增强工单实用功能、完善了用户邀请机制和SEO
今天是 10 月 26 日星期三,Gitea 周期性地发布了 1.18 的第一个 RC0 版本,在此阶段会收集一些功能和使用上的问题,随后还会发布 RC1,新功能的完整性和健壮性会逐步趋近正式版. 继 ...
- web前端学习之旅笔记01--HTML
web前端学习之旅笔记01--HTML HTML最容易上手,但也易忘,实际开发中有时需要查阅官方文档,小伙伴们别忘了哟! HTML 教程 (w3school.com.cn) HTML是网页的骨架负责页 ...
- VBA_BASIC
字符串相关 判断单元格是否包含特定字符串,以"P"为例. if cells(1,1) Like "*P*" Then cells(1,2) = "ha ...
- 介绍一个jmeter录制脚本谷歌插件 —— metersphere-chrome-plugin
该插件可将用户在浏览器操作时的 HTTP 请求记录下来并生成 JMX 文件(JMeter 脚本文件). 1. 插件解压 插件下载链接: https://pan.baidu.com/s/14nGb_s9 ...
- 硬核剖析Java锁底层AQS源码,深入理解底层架构设计
我们常见的并发锁ReentrantLock.CountDownLatch.Semaphore.CyclicBarrier都是基于AQS实现的,所以说不懂AQS实现原理的,就不能说了解Java锁. 上篇 ...