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

news2024/11/19 7:22:10

框架搭建

整个框架的实现,大约也就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、测试效果

用例执行效果:

测试报告

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/80963.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

RabbitMQ入门(三)消息应答与发布确认

前言&#xff1a; 消息应答与发布确认都是保证消息不丢失。而重复消费问题则是消息幂等性。&#xff08;之后会说幂等性&#xff09; 消息应答&#xff1a; 应答功能属于消费者&#xff0c;消费者在接收到消息并且处理该消息之后&#xff0c;告诉 rabbitmq 它已经处理了&…

深度学习——残差网络(ResNet)笔记

残差网络&#xff1a;经常使用的网络之一 1.随着神经网络的不断加深能改进精度吗&#xff1f; 不一定 ①蓝色五角星表示最优值&#xff0c;Fi闭合区域表示函数&#xff0c;闭合区域的面积代表函数的复杂程度。在这个区域能够找到一个最优的模型&#xff08;区域中的一个点表…

「重学JS」带你一文吃透作用域与闭包

前言 学习了这么久前端&#xff0c;发现自己对于基础知识的掌握并没有那么通透&#xff0c;于是打算重新学一遍JS&#xff0c;借用经济学的一句话&#xff1a;JS基础决定能力高度&#x1f926;&#x1f3fb; 基础很重要&#xff0c;只有基础好才会很少出 bug&#xff0c;大多数…

二叉树的性质

由于二叉树的结构特殊&#xff0c;会有一系列的数学性质 性质一&#xff1a;对于一棵二叉树&#xff0c;第i层的最大结点数量为 个&#xff0c;比如二叉树的第一层只有一个根结点&#xff0c;而二叉树的第三层可以有 个结点。 性质二&#xff1a;对于一棵深度为k的二叉树&am…

【Python】函数

文章目录1. 函数介绍2. 函数的定义与调用3. 函数参数4. 函数返回值5. 变量作用域6. 函数执行过程7. 链式调用8. 嵌套调用9. 函数递归10. 参数默认值11关键字参数1. 函数介绍 编程中的函数不同于数学中的函数&#xff1a; 数学上的函数&#xff0c;比如 y sin x&#xff0c;x…

Vue快速上门|了解MVVM

1.1、先了解下MVVM VUE是基于MVVM思想实现的,❓那什么是MVVM呢?—— MVVM,是Model-View-ViewModel的缩写,是一种软件架构模式。其核心思想就是分离视图、数据、逻辑,VUE框架解决了数据Model到视图View的双向绑定,我们只关注业务逻辑ViewModel即可,极大的提高的编程效率…

BadUSB超详细制作, 实现CobaltStrike远控上线

前言 在2014年美国黑帽大会上&#xff0c;安全研究人员JakobLell和独立安全研究人员Karsten Nohl展示了他们称为“BadUSB”的攻击方法&#xff0c;这种攻击方法让USB安全和几乎所有和USB相关的设备(包括具有USB端口的电脑)都陷入相当危险的状态 现在的USB设备很多&#xff0c…

高级篇之ENC1当作采集卡使用方案推荐

高级篇之ENC1当作采集卡使用0 背景&#xff1a;1 准备工作2 连接示意图3 配置步骤&#xff1a;3.1 在笔记本电脑上安装NDI4工具3.2 ENC1设备连接3.3 配置电脑的USB网卡的IP地址3.4 配置ENC1设备3.5 打开NDI工具的虚拟输入功能0 背景&#xff1a; HDMI视频采集卡分为内嵌式采集…

【GCC编译优化系列】宏定义名称与函数同名是一种什么骚操作?

作者简介 *架构师李肯&#xff08;全网同名&#xff09;**&#xff0c;一个专注于嵌入式IoT领域的架构师。有着近10年的嵌入式一线开发经验&#xff0c;深耕IoT领域多年&#xff0c;熟知IoT领域的业务发展&#xff0c;深度掌握IoT领域的相关技术栈&#xff0c;包括但不限于主流…

​全网最牛的Fiddler系列文章(一):fiddler的介绍及安装​

Fiddler(1)&#xff1a;fiddler的介绍及安装 Fiddler简介 Fiddler是比较好用的web代理调试工具之一&#xff0c;它能记录并检查所有客户端与服务端的HTTP/HTTPS请求&#xff0c;能够设置断点&#xff0c;篡改及伪造Request/Response的数据&#xff0c;修改hosts&#xff0c;限…

【UEFI实战】Redfish的BIOS实现1

Redfish的BIOS实现 EDK2提供了Redfish框架&#xff0c;用来实现带外的BIOS配置&#xff0c;其基本框架如下&#xff1a; 通过RedfishPkg中提供的Driver&#xff0c;可以实现BIOS与BMC或者其它的软件进行通信。它主要分为两个部分&#xff0c;分别是Client和Foundation。Client…

[论文解析]DREAMFUSION: TEXT-TO-3D USING 2D DIFFUSION

code links&#xff1a;dreamfusion3d.github.io 文章目录OverviewWhat problem is addressed in the paper?What is the key to the solution?What is the main contribution?What can we learn from ablation studies&#xff1f;Potential fundamental flaws; how this w…

MATLB|基于粒子群算法的能源管理系统EMS(考虑光伏、储能 、柴油机系统)

&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️❤️&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清…

原子操作类之18罗汉增强

原子操作类之18罗汉增强 是什么 都是java.util.concurrent.atomic包下的 有红框圈起来的&#xff0c;也有蓝框圈起来的&#xff0c;为什么&#xff1f; 阿里巴巴Java开发手册 为什么说18罗汉增强&#xff0c;却只有16个 再分类 基本类型原子类 AtomicInteger AtomicBoolea…

wpa_supplicant工具移植到嵌入式设备

1、wpa_supplicant源码下载 (1)源码下载地址&#xff1a;http://w1.fi/releases/&#xff1b; (2)本文是以wpa_supplicant-2.6.tar.gz版本进行移植&#xff1b; 2、编译openssl 2.1、确定适配的openssl版本 Optional libraries for EAP-TLS, EAP-PEAP, and EAP-TTLS: - OpenS…

【LeetCode】1827. 最少操作使数组递增

题目描述 给你一个整数数组 nums &#xff08;下标从 0 开始&#xff09;。每一次操作中&#xff0c;你可以选择数组中一个元素&#xff0c;并将它增加 1 。 比方说&#xff0c;如果 nums [1,2,3] &#xff0c;你可以选择增加 nums[1] 得到 nums [1,3,3] 。 请你返回使 nums …

ESXi8.0中NVME硬盘不识别解决方法1,设置直通

目录 1.前言 2.直通设置 3.槽点 1.前言 ESXi8.0删除了很多老版本的硬件的驱动程序&#xff0c;导致NVME1.3及更低协议的固态硬盘均无法被ESXi直接识别正如我手头准备了尚好的服务器专用PM983A却无法识别。本着不折腾先熟悉ESXi8.0的思路另外找了一块盘装了ESXi的系统。本以为…

云原生之使用Docker部署webssh工具sshwifty

云原生之使用Docker部署webssh工具sshwifty一、sshwifty介绍1.sshwifty简介2.shwifty 特点二、检查本地docker环境1.检查docker版本2.检查docker状态三、下载sshwifty镜像四、服务器生成凭证文件五、创建sshwifty容器1.创建部署目录2.创建sshwifty容器3.查看sshwifty容器状态六…

uniapp 之 小程序线上版本一直处于加载状态

前言 最开始小程序都是体验版的&#xff0c;后来应老大需求&#xff0c;把体验版提交审核为正式版本&#xff08;线上版本&#xff09;&#xff0c; 原本以为版本审核得花费几天时间&#xff0c;没想到它这审核速度挺快的&#xff0c;不到3小时就审核通过了&#xff0c;审核…

[go]汇编语言

文章目录计算机结构常量与变量全局变量常量数组字符串函数参数与返回值goroutineGo汇编程序无法独立使用&#xff0c;必须以Go包的方式组织&#xff0c;同时包中至少要有一个Go语言文件用于指明当前包名等基本包信息。如果Go汇编代码中定义的变量和函数要被其它Go语言代码引用&…