12 web 自动化之基于关键字+数据驱动-反射自动化框架搭建

news2025/5/17 5:20:58

文章目录

      • 一、如何实现一条用例,实现覆盖所有用例的测试
        • 1、结合数据驱动:编辑一条用例,外部导入数据实现循环测试
        • 2、用例体:实现不同用例的操作步骤+对应的断言
      • 二、实战
        • 1、项目路径总览
        • 2、common 文件夹下的代码文件
        • 3、keywords 文件夹下的代码文件
        • 4、testcases 文件夹下的代码文件
        • 4、testdata 文件夹下的 case_data.xlsx 文件
        • 5、config.py 文件
      • 三、web 自动化测试完结-项目代码(总)

一、如何实现一条用例,实现覆盖所有用例的测试

1、结合数据驱动:编辑一条用例,外部导入数据实现循环测试
2、用例体:实现不同用例的操作步骤+对应的断言
  • 封装对应的方法,可以执行所有用例的操作步骤+断言

二、实战

通过读取 excel 数据,进行数据驱动自动化测试
通过反射函数,实现关键字驱动
即使不懂代码的人,也能通过编辑 excel 数据进行测试

1、项目路径总览

2、common 文件夹下的代码文件

2.1 excel_operator.py 文件
是对 excel 操作进行封装的方法

import os.path
import time

import openpyxl
from TestKDT import config
from openpyxl.styles import Font,PatternFill,colors

class ExcelOperator:
    """
    操作 excel 文件
    """
    def __init__(self,filename=os.path.join(config.testdata_dir,"case_data.xlsx")):
        # 获取到测试用例 excel 的文件路径
        self.file_path = filename
        # 获取到测试用例 excel 工作簿
        self.wk = openpyxl.load_workbook(filename)

    def get_case_data(self):
        # """获取 excel 工作簿中所有工作表的数据"""
        # self.sheetnames = self.wk.sheetnames
        """获取 excel 工作簿中要执行的用例工作表的数据"""
        self.sheetnames = self.get_cases_name()
        values= []
        # 循环每个工作表
        for sheet_name in self.sheetnames:
            # 获取某个工作表某个区间列的数据
            value = self.get_startcol_endcol_value(sheet_name)
            case_data = {'case_name': sheet_name, 'steps_data': value}
            values.append(case_data)
        return values

    # 获取执行用例的工作表名称
    def get_cases_name(self):
        # 获取汇总用例的工作表
        cases_sheet = self.wk[config.cases_sheet_name]
        cases_name = []
        # 根据是否执行,取到用例的名称
        for row in range(2,cases_sheet.max_row+1):
            # 循环汇总表每一行数据
            is_execute = cases_sheet.cell(row, config.case_is_execute).value
            if is_execute=="y":
                case_name = cases_sheet.cell(row, config.case_name).value
                cases_name.append(case_name)
        return cases_name


    # 获取某个工作表某个区间列的数据
    def get_startcol_endcol_value(self,sheetname,startcol=config.keyword_col,endcol=config.action_col):
        # 获取工作表
        sheet = self.wk[sheetname]
        values = []
        # 循环对应工作表中的每一行数据
        for row in range(2,sheet.max_row+1):
            step_data = []
            for col in range(startcol,endcol+1):
                value = sheet.cell(row=row,column=col).value
                if value is not None:
                 step_data.append(value)
            values.append(step_data)
        return values

    # 将测试步骤的结果写入 excel 文件的用例工作表中
    def write_step_result(self, sheet_name, row, col, result):
        """写入测试步骤的结果"""
        case_sheet = self.wk[sheet_name]
        # 写入每步操作步骤结束时间
        self.write_current_time(case_sheet,row,config.step_end_time)
        # 写入测试步骤的结果
        case_sheet.cell(row, col).value = result
        # 颜色填充 绿色通过 红色失败
        red_fill = PatternFill(fill_type="solid", fgColor="00FF0000")
        green_fill = PatternFill(fill_type="solid", fgColor="0000FF00")
        if result == 'FAIL':
            case_sheet.cell(row, col).fill = red_fill
        else:
            case_sheet.cell(row, col).fill = green_fill
        self.wk.save(self.file_path)

    # 将测试用例的结果写入 excel 文件的用例汇总表中
    def write_cases_result(self, case_name, sheet_name=config.cases_sheet_name, col=config.case_result,):
        """写入测试用例的结果"""
        cases_sheet = self.wk[sheet_name]
        for row in range(2,cases_sheet.max_row+1):
            # 循环汇总表的每一行
            col_case_name = cases_sheet.cell(row, config.case_name).value
            if col_case_name == case_name:
                # 获取执行的用例
                case_name = cases_sheet.cell(row, config.case_name).value
                sheet = self.wk[case_name]
                # 写入每条测试用例的结束时间
                self.write_current_time(cases_sheet, row, config.case_end_time)
                # 获取每条执行用例的结果
                steps_result = self.get_sheet_col_value(sheet=sheet, col=config.step_result)
                # 颜色填充 绿色通过 红色失败
                red_fill = PatternFill(fill_type="solid", fgColor="00FF0000")
                green_fill = PatternFill(fill_type="solid", fgColor="0000FF00")
                # 将结果写入汇总表中
                if 'FAIL' in steps_result:
                    cases_sheet.cell(row, col).value = 'FAIL'
                    cases_sheet.cell(row, col).fill = red_fill
                else:
                    cases_sheet.cell(row, col).value = 'PASS'
                    cases_sheet.cell(row, col).fill = green_fill
        self.wk.save(self.file_path)

    def get_sheet_col_value(self, sheet, col):
        """获取某个工作表某列的所有数据"""
        values = []
        for row in range(2, sheet.max_row + 1):
            step_col_value = sheet.cell(row, col).value
            values.append(step_col_value)
        return values

    def write_current_time(self, sheet, row, col):
        """将当前时间写入某个表格中"""
        current_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
        sheet.cell(row, col).value = current_time

if __name__ == '__main__':
    # print(ExcelOperator().get_startcol_endcol_value("登录",3,6))
    print(ExcelOperator().get_cases_name())

2.2、logger.py 文件
和之前 POM 的日志一样

import logging
import os
import time
from TestKDT import config

class FrameLogger:
    def get_logger(self):
        # 创建日志器
        logger = logging.getLogger("logger")
        # 日志输出当前级别及以上级别的信息,默认日志输出最低级别是warning
        if not logger.handlers:
            logger.setLevel(logging.INFO)
            # 创建控制台处理器----》输出控制台
            SH = logging.StreamHandler()
            # 创建文件处理器----》输出文件
            log_path = os.path.join(config.logs_dir, f"log_{time.strftime('%Y%m%d%H%M%S', time.localtime())}.txt")
            FH = logging.FileHandler(log_path,mode="w",encoding="utf-8")
            # 日志包含哪些内容    时间  文件  日志级别 :事件描述/问题描述
            formatter = logging.Formatter(fmt="[%(asctime)s] [%(filename)s] %(levelname)s :%(message)s",
                                          datefmt='%Y/%m/%d %H:%M:%S')
            logger.addHandler(SH)
            logger.addHandler(FH)
            SH.setFormatter(formatter)
            FH.setFormatter(formatter)
        return logger
3、keywords 文件夹下的代码文件

3.1 library.py 文件
是对各种关键字函数的封装

import os
from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from TestKDT import config
from TestKDT.common.logger import FrameLogger as log

class Library:
    # 登录用例 = 多个操作步骤组成 基于每个操作步骤封装对应的关键字函数
    # 登录用例:
    # 1、打开浏览器- 关键字函数- open_browser()
    # 2、加载项目地址- 关键字函数- load_url()
    # 3、输入用户名- 关键字函数- input()
    # 4、输入密码- 关键字函数- input()
    # 5、点击登录- 关键字函数- click()
    def __init__(self):
        self.logger = log().get_logger()

    def open_browser(self,browser):
        """打开浏览器"""
        # 传入的浏览器参数保持首字母大写
        browser = browser.capitalize()
        # 获取不同类型的浏览器驱动
        try:
            self.driver = getattr(webdriver, browser)()
            self.logger.info(f"打开{browser}浏览器成功")
        except:
            self.logger.error(f"打开{browser}浏览器失败")
            raise


    def load_url(self, url):
        """加载地址"""
        try:
            self.driver.get(url)
            self.logger.info(f"加载项目地址{url}成功")
        except:
            self.logger.error(f"加载项目地址{url}失败")
            raise

    # 等待元素可见
    def wait_ele_visibility(self, page_name, loc, timeout=15, poll_fre=0.5):
        try:
            WebDriverWait(self.driver, timeout, poll_fre).until(EC.visibility_of_element_located(loc))
            # WebElement对象
            self.logger.info(f"在[{page_name}]页面,找到元素:{loc}可见")
        except:
            self.logger.error(f"在[{page_name}]页面,未找到元素:{loc}可见!!!")
            raise

    # 等待元素存在
    def wait_ele_presence(self, page_name, loc, timeout=15, poll_fre=0.5):
        try:
            WebDriverWait(self.driver, timeout, poll_fre).until(EC.presence_of_element_located(loc))
            self.logger.info(f"在[{page_name}]页面,找到元素:{loc}存在")
        except:
            self.logger.error(f"在[{page_name}]页面,未找到元素:{loc}存在!!!")
            raise

    def locator(self, page_name, by_type, express):
        # 定位元素
        try:
            el = self.driver.find_element(by_type, express)
            self.logger.info(f"在[{page_name}]页面,通过[{by_type}]方法和[{express}]语句,定位元素成功")
        except:
            self.save_screenshot(by_type)
            self.logger.error(f"在[{page_name}]页面,通过[{by_type}]方法和[{express}]语句,定位元素失败!!!")
            raise
        return el

    def input(self, page_name, by_type, express, text):
        """输入"""
        loc = (by_type, express)
        try:
            self.wait_ele_visibility(page_name, loc)
            self.locator(page_name,by_type, express).send_keys(text)
            self.logger.info(f"在[{page_name}]页面,元素{loc}输入:{text} 成功!")
        except:
            self.logger.error(f"在[{page_name}]页面,元素{loc}输入失败!")
            # 失败截图
            self.save_screenshot(page_name)
            raise

    def click(self, page_name, by_type, express):
        """点击"""
        loc = (by_type, express)
        try:
            self.wait_ele_visibility(page_name, loc)
            self.locator(page_name,by_type, express).click()
            self.logger.info(f"在[{page_name}]页面,元素{loc}点击成功!")
        except:
            self.logger.error(f"在[{page_name}]页面,元素{loc}点击失败!")
            # 失败截图
            self.save_screenshot(page_name)
            raise

    def move_element(self, page_name, by_type, express):
        """移动鼠标"""
        loc = (by_type, express)
        try:
            self.wait_ele_visibility(page_name, loc)
            el = self.locator(page_name, by_type, express)
            # 将鼠标移到元素上
            ActionChains(self.driver).move_to_element(el).perform()
            self.logger.info(f"在[{page_name}]页面,鼠标移到元素{loc}成功!")
        except:
            self.logger.error(f"在[{page_name}]页面,鼠标移到元素{loc}失败!")
            # 失败截图
            self.save_screenshot(page_name)
            raise

    def assert_text(self,page_name, by_type, express, expect):
        """断言"""
        loc = (by_type, express)
        try:
            self.wait_ele_visibility(page_name, loc)
            el = self.locator(page_name, by_type, express)
            fact = el.text
            self.logger.info(f"在[{page_name}]页面,元素{loc}文本内容获取成功!")
        except:
            self.logger.error(f"在[{page_name}]页面,元素{loc}文本内容获取失败!")
            # 失败截图
            self.save_screenshot(page_name)
            raise

        if fact == expect:
            pass
        else:
            raise Exception(f"在[{page_name}]页面,断言失败,assertText:{fact} != {expect}")

    def save_screenshot(self,img_name):
        file_name = os.path.join(config.screenshots_dir, img_name+'.png')
        self.driver.save_screenshot(file_name)
        self.logger.error(f"失败截图,截取当前网页,存储的路径:{file_name}")

    # 封装方法,可以调用当前类下的所有关键字函数
    # *args:不定长参数
    def run(self,keyword,*args):
        print(keyword, args)
        # 实现打开浏览器
        # keyword = "open_browser"
        # args = ("edge",)
        # 调用关键字函数,基于反射
        getattr(self, keyword)(*args)
4、testcases 文件夹下的代码文件

4.1 test_case.py 文件
编写测试用例

import time
import unittest
from selenium.webdriver.common.by import By
from TestKDT.keywords.library import Library
from ddt import ddt,data,file_data,unpack
from TestKDT.common.excel_operator import ExcelOperator
from TestKDT import config

@ddt
class TestCase01(unittest.TestCase):
    # 如何实现一条用例,实现覆盖所有用例的测试
    # 每条用例的数据: 关键字函数 + 测试数据
    # case_data = [["open_browser","edge"],["load_url","http://116.62.63.211/shop/user/logininfo.html"],
    #               ["input",(By.NAME, "accounts"), "hc_test"],
    #               ["input",(By.XPATH, '//input[@type="password"]'),"hctest123"],
    #               ["click",(By.XPATH, '//button[text()="登录"]')]]
    excel = ExcelOperator()
    case_data = excel.get_case_data()
    @data(*case_data)
    def test_cases(self,case_data):
        # 打印每条用例的数据
        print(case_data)
        case_name = case_data['case_name']
        steps_data = case_data['steps_data']

        # 用例体 = 操作步骤+ 断言
        # 封装对应的方法,可以执行所有用例的操作步骤 + 断言
        lib = Library()
        for index, step_data in enumerate(steps_data):
            try:
                # 执行用例的操作步骤
                lib.run(*step_data)
                # 当前执行步骤为 PASS
                # index  0  行数 2  因为第一行不是测试数据,而是列说明
                self.excel.write_step_result(sheet_name=case_name, row=index+2, col=config.step_result, result="PASS")
            except Exception as error:
                # 当前执行步骤为 FAIL
                self.excel.write_step_result(sheet_name=case_name, row=index+2, col=config.step_result, result="FAIL")
            self.excel.write_cases_result(case_name)
4、testdata 文件夹下的 case_data.xlsx 文件
  • 4.1 用例汇总统计
  • 4.2 登录成功
  • 4.3 登录失败-1
  • 4.4 登录失败-2
5、config.py 文件

是项目的路径以及其他数据内容

import os

# 根路径
base_dir = os.path.dirname(os.path.abspath(__file__))
# 用例路径
testcases_dir = os.path.join(base_dir, 'testcases')
# 数据路径
testdata_dir = os.path.join(base_dir, 'testdata')
# 测试报告路径
reports_dir = os.path.join(base_dir, 'outputs/reports')
# 日志路径
logs_dir = os.path.join(base_dir, 'outputs/logs')
# 失败截图
screenshots_dir = os.path.join(base_dir, 'outputs/screenshots')
# 测试用例 excel 中关键字所在列
keyword_col = 3
# 测试用例 excel 中操作值所在列
action_col = 7
# 用例汇总表名称
cases_sheet_name = "用例汇总统计"
# 用例汇总表中是否执行所在列数
case_is_execute = 5
# 用例汇总表中用例名所在列数
case_name = 2
# 用例汇总表中测试结果所在列数
case_result = 7
# 用例汇总表中测试结束时间所在列数
case_end_time = 6
# 用例工作表中执行结果所在列数
step_result = 9
# 用例工作表中执行时间所在列数
step_end_time = 8

# ---调试
print(testdata_dir)

三、web 自动化测试完结-项目代码(总)

Test-Web.zip

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

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

相关文章

动态IP赋能业务增效:技术解构与实战应用指南

在数字化转型加速的今天,IP地址作为网络通信的基础设施,其技术特性正深刻影响着企业业务架构的效率与安全性。动态IP(Dynamic IP)作为互联网资源分配的核心机制,早已突破传统认知中的"临时地址"定位&#xf…

【Java ee初阶】http(1)

HTTP 全称为“超文本传输协议”,由名字可知,这是一个基于文本格式的协议,而TCP,UDP,以太网,IP...都是基于二进制格式的协议。 如何区别该协议是基于哪种格式的协议? 形如这种协议格式&#xf…

day18-数据结构引言

一、 概述 数据结构:相互之间存在一种或多种特定关系的数据元素的集合。 1.1 特定关系: 1. 逻辑结构 2.物理结构(在内存当中的存储关系) 逻辑结构物理结构集合,所有数据在同一个集合中,关系平等顺…

我开源了一个免费在线工具!UIED Tools

UIED Tools - 免费在线工具集合 最近更新:修改了文档说明,优化了项目结构介绍 这是设计师转开发的第一个开源项目,bug和代码规范可能有些欠缺。 这是一个功能丰富的免费在线工具集合网站,集成了多种实用工具,包括 AI …

什么时候可以开始学习深度学习?

咱们先来聊聊机器学习和深度学习的关系~ 这个问题其实挺常见的,之前我也跟不少同事、同学聊过。最近有好几个同学也聊过。 简单说,深度学习是机器学习的一个子集,两者不是并列关系,而是“包含”关系。 你可以这么理解&#xff…

初学python的我开始Leetcode题8-5

提示:100道LeetCode热题-8-5主要是二叉树相关,包括三题:路径总和 III、二叉树的最近公共祖先、二叉树中的最大路径和。由于初学,所以我的代码部分仅供参考。 前言 二叉树完结撒花~ 下一次的图论会是一些有趣的应用案例~ 提示&am…

构建RAG混合开发---PythonAI+JavaEE+Vue.js前端的实践

写在前文:之所以设计这一套流程,是因为 Python在前沿的科技前沿的生态要比Java好,而Java在企业级应用层开发比较活跃; 毕竟许多企业的后端服务、应用程序均采用Java开发,涵盖权限管理、后台应用、缓存机制、中间件集成…

08.webgl_buffergeometry_attributes_none ,three官方示例+编辑器+AI快速学习

本实例主要讲解内容 这个Three.js示例展示了无属性几何体渲染技术,通过WebGL 2的gl_VertexID特性和伪随机数生成算法,在着色器中动态计算顶点位置和颜色,而不需要在CPU端预先定义几何体数据。 核心技术包括: WebGL 2的顶点ID特…

26考研 | 王道 | 计算机组成原理 | 一、计算机系统概述

26考研 | 王道 | 计算机组成原理 | 一、计算机系统概述 文章目录 26考研 | 王道 | 计算机组成原理 | 一、计算机系统概述1.1 计算机的发展1.2 计算机硬件和软件1.2.1 计算机硬件的基本组成1.2.2 各个硬件的工作原理1.2.3 计算机软件1.2.4 计算机系统的层次结构1.2.5 计算机系统…

LeetCode100.2 字母异位词分组

观察题目&#xff0c;需要把strs中的元素按照字母进行归类&#xff0c;一个朴素的思路是&#xff1a;遍历strs&#xff0c;对每个元素排序后插入哈希表中&#xff0c;随后再遍历一遍表将其转化为vector<vector<string>>。 class Solution { public:vector<vect…

显示的图标跟UI界面对应不上。

图片跟UI界面不符合。 要找到对应dp的值。UI的dp要跟代码里的xml文件里的dp要对应起来。 蓝湖里设置一个宽度给对应上。然后把对应的值填入xml. 一个屏幕上的图片到底是用topmarin来设置&#xff0c;还是用bottommarin来设置。 因为第一节&#xff0c;5&#xff0c;7 车厢的…

高并发内存池(三):TLS无锁访问以及Central Cache结构设计

目录 前言&#xff1a; 一&#xff0c;thread cache线程局部存储的实现 问题引入 概念说明 基本使用 thread cache TLS的实现 二&#xff0c;Central Cache整体的结构框架 大致结构 span结构 span结构的实现 三&#xff0c;Central Cache大致结构的实现 单例模式 thr…

在Taro中开发一个跨端Svg组件,同时支持小程序、H5、React Native

Taro系列中一直没有跨端的绘图工具&#xff0c;小程序端支持canvas但是不支持svg&#xff0c;RN端有 react-native-svg 支持svg&#xff0c;但是没有很好原生的canvas插件&#xff0c;社区的canvas都是基于WebView实现的&#xff0c;或者skia&#xff0c;这个插件的书写方式和c…

【办公类-100-01】20250515手机导出教学照片,自动上传csdn+最大化、最小化Vs界面

背景说明&#xff1a; 每次把教学照片上传csdn&#xff0c;都需要打开相册&#xff0c;一张张截图&#xff0c;然后ctrlV黏贴到CSDN内&#xff0c;我觉得太烦了。 改进思路&#xff1a; 是否可以先把所有照片都上传到csdn&#xff0c;然后再一张张的截图&#xff08;去掉幼儿…

Python零基础入门到高手8.4节: 元组与列表的区别

目录 8.4.1 不可变数据类型 8.4.2 可变数据类型 8.4.3 元组与列表的区别 8.4.4 今天彩票没中奖 8.4.1 不可变数据类型 不可变数据类型是指不可以对该数据类型进行原地修改&#xff0c;即只读的数据类型。迄今为止学过的不可变数据类型有字符串&#xff0c;元组。 在使用[]…

深度学习入门:深度学习(完结)

目录 1、加深网络1.1 向更深的网络出发1.2 进一步提高识别精度1.3 加深层的动机 2、深度学习的小历史2.1 ImageNet2.2 VGG2.3 GoogleNet2.4 ResNet 3、深度学习的高速化3.1 需要努力解决的问题3.2 基于GPU的高速化3.3 分布式学习3.4 运算精度的位数缩减 4、深度学习的应用案例4…

使用Scrapeless Scraping Browser的自动化和网页抓取最佳实践

引言&#xff1a;人工智能时代浏览器自动化和数据收集的新范式 随着生成性人工智能、人工智能代理和数据密集型应用程序的快速崛起&#xff0c;浏览器正在从传统的“用户互动工具”演变为智能系统的“数据执行引擎”。在这一新范式中&#xff0c;许多任务不再依赖单一的API端点…

java数组题(5)

&#xff08;1&#xff09;&#xff1a; 思路&#xff1a; 1.首先要对数组nums排序&#xff0c;这样两数之间的差距最小。 2.题目要求我们通过最多 k 次递增操作&#xff0c;使数组中某个元素的频数&#xff08;出现次数&#xff09;最大化。经过上面的排序&#xff0c;最大数…

物联网无线传感方向专业词汇解释

涡旋电磁波(VEMW)&#xff1a;一种具有轨道角动量的电磁波&#xff0c;其特性在于能够在传播过程中携带额外的相位信息&#xff0c;从而增加通信系统的容量和灵活性。波前&#xff1a;波动传播过程中&#xff0c;同一时刻振动相位相同的所有点构成的几何曲面&#xff0c;代表波…

Maven 插件参数注入与Mojo开发详解

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;精通Java编…