构建现代化的Web UI自动化测试框架:从图片上传测试实践说起

构建现代化的Web UI自动化测试框架:从图片上传测试实践说起

如何设计一个可维护、可扩展的Web UI自动化测试框架?本文通过一个图片上传测试实例,详细介绍专业测试框架的搭建与实践。

当前测试框架结构

首先,让我们了解一下当前的测试框架结构:

autotests_baidu/
├── conftest.py              # pytest配置文件
├── testwebui/               # Web UI测试目录
│   ├── test_upload.py       # 图片上传测试
├── test_files/              # 测试文件目录
├── pages/                   # 页面对象目录
│   ├── agent_page.py        # 智能体页面对象
│   └── base_page.py         # 基础页面对象
├── data/                    # 测试数据目录
├── config/                  # 配置文件目录
└── requirements.txt         # 依赖包列表

这是一个典型的页面对象模型(Page Object Model)测试框架,具有良好的结构和可扩展性。

原始测试代码分析

原始的上传测试代码虽然功能完整,但有几个可以改进的地方:

# test_upload.py 原始代码
@allure.feature("图片上传测试")
class TestUploadImage:def setup_method(self):self.test_file_path = os.path.abspath("test_files/1.png")assert os.path.exists(self.test_file_path), f"测试文件不存在: {self.test_file_path}"@allure.story("AI头像图片上传")def test_ai_avatar_upload(self, browser):with allure.step("导航到AI头像上传页面"):browser.get("https://www.baidu.com/upload")with allure.step("上传图片文件"):file_input = browser.find_element(By.XPATH,'//input[@name="file" and @type="file"]') #在上传页面查找该元素,一定是input才能上传file_input.send_keys(self.test_file_path)logger.info(f"文件上传成功: {self.test_file_path}")with allure.step("等待处理完成"):import timetime.sleep(20)  # 硬编码等待,需要优化allure.attach(browser.get_screenshot_as_png(),name="上传完成状态",attachment_type=allure.attachment_type.PNG)

优化后的测试框架实现

1. 配置文件 (config/config.py)

import os
from pathlib import Pathclass Config:BASE_DIR = Path(__file__).parent.parentTEST_FILES_DIR = BASE_DIR / "test_files"SCREENSHOTS_DIR = BASE_DIR / "screenshots"REPORTS_DIR = BASE_DIR / "reports"# 浏览器配置BROWSER = "chrome"HEADLESS = FalseIMPLICIT_WAIT = 10# 应用URLBASE_URL = "https://www.baidu.com"UPLOAD_URL = f"{BASE_URL}/upload"# 测试文件路径TEST_IMAGE = TEST_FILES_DIR / "1.png"@classmethoddef setup_directories(cls):"""创建必要的目录"""for directory in [cls.TEST_FILES_DIR, cls.SCREENSHOTS_DIR, cls.REPORTS_DIR]:directory.mkdir(exist_ok=True)Config.setup_directories()

2. 基础页面对象 (pages/base_page.py)

from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException, NoSuchElementException
import allure
import logginglogger = logging.getLogger(__name__)class BasePage:def __init__(self, driver):self.driver = driverself.wait = WebDriverWait(driver, 10)def find_element(self, by, value, timeout=10):"""查找元素,带显式等待"""try:wait = WebDriverWait(self.driver, timeout)return wait.until(EC.presence_of_element_located((by, value)))except TimeoutException:logger.error(f"元素未找到: {by}={value}")allure.attach(self.driver.get_screenshot_as_png(),name=f"element_not_found_{by}_{value}",attachment_type=allure.attachment_type.PNG)raisedef click(self, by, value):"""点击元素"""element = self.find_element(by, value)element.click()logger.info(f"点击元素: {by}={value}")def input_text(self, by, value, text):"""输入文本"""element = self.find_element(by, value)element.clear()element.send_keys(text)logger.info(f"在元素 {by}={value} 输入文本: {text}")def take_screenshot(self, name):"""截图并附加到Allure报告"""allure.attach(self.driver.get_screenshot_as_png(),name=name,attachment_type=allure.attachment_type.PNG)def wait_for_element_visible(self, by, value, timeout=10):"""等待元素可见"""try:wait = WebDriverWait(self.driver, timeout)return wait.until(EC.visibility_of_element_located((by, value)))except TimeoutException:logger.error(f"元素不可见: {by}={value}")self.take_screenshot(f"element_not_visible_{by}_{value}")raise

3. 上传页面对象 (pages/upload_page.py)

from selenium.webdriver.common.by import By
from .base_page import BasePage
from config.config import Config
import allure
import logginglogger = logging.getLogger(__name__)class UploadPage(BasePage):# 页面元素定位器FILE_INPUT = (By.XPATH, '//input[@name="file" and @type="file"]')UPLOAD_BUTTON = (By.ID, "upload-btn")SUCCESS_MESSAGE = (By.CLASS_NAME, "upload-success")PROGRESS_BAR = (By.ID, "progress-bar")def __init__(self, driver):super().__init__(driver)self.url = Config.UPLOAD_URLdef open(self):"""打开上传页面"""with allure.step("打开上传页面"):self.driver.get(self.url)logger.info(f"打开页面: {self.url}")self.take_screenshot("upload_page_opened")def upload_file(self, file_path):"""上传文件"""with allure.step(f"上传文件: {file_path}"):file_input = self.find_element(*self.FILE_INPUT)file_input.send_keys(str(file_path))logger.info(f"文件上传成功: {file_path}")self.take_screenshot("file_uploaded")def wait_for_upload_complete(self, timeout=30):"""等待上传完成"""with allure.step("等待上传处理完成"):# 等待进度条消失或成功消息出现try:self.wait_for_element_visible(self.SUCCESS_MESSAGE, timeout)logger.info("文件上传处理完成")self.take_screenshot("upload_complete")return Trueexcept TimeoutException:logger.warning("上传处理超时")self.take_screenshot("upload_timeout")return Falsedef is_upload_successful(self):"""检查上传是否成功"""try:success_element = self.find_element(*self.SUCCESS_MESSAGE, timeout=5)return "上传成功" in success_element.textexcept (TimeoutException, NoSuchElementException):return False

4. 优化后的测试用例 (testwebui/test_upload.py)

# -*- coding: utf-8 -*-
import allure
import logging
import pytest
from config.config import Configlogger = logging.getLogger(__name__)@allure.feature("图片上传测试")
class TestUploadImage:"""图片上传测试类"""@pytest.fixture(autouse=True)def setup(self, request):"""测试前置条件"""# 确保测试文件存在assert Config.TEST_IMAGE.exists(), f"测试文件不存在: {Config.TEST_IMAGE}"# 获取页面对象self.upload_page = request.getfixturevalue("upload_page")# 测试后清理def teardown():# 可以添加清理逻辑,如删除上传的文件等passrequest.addfinalizer(teardown)@allure.story("AI头像图片上传 - 成功场景")@allure.severity(allure.severity_level.CRITICAL)def test_ai_avatar_upload_success(self, upload_page):"""测试AI头像图片上传成功场景"""# 打开上传页面upload_page.open()# 上传文件upload_page.upload_file(Config.TEST_IMAGE)# 等待上传完成assert upload_page.wait_for_upload_complete(), "上传处理超时"# 验证上传成功assert upload_page.is_upload_successful(), "上传未成功完成"# 记录成功日志logger.info("AI头像图片上传测试成功")@allure.story("AI头像图片上传 - 大文件测试")@allure.severity(allure.severity_level.NORMAL)def test_ai_avatar_upload_large_file(self, upload_page):"""测试大文件上传处理"""# 实现类似上面但针对大文件的测试pass@allure.story("AI头像图片上传 - 文件格式验证")@allure.severity(allure.severity_level.NORMAL)@pytest.mark.parametrize("file_name,expected", [("1.png", True),("2.jpg", True),("3.gif", True),("4.txt", False),  # 不支持的文件格式])def test_ai_avatar_upload_file_formats(self, upload_page, file_name, expected):"""测试不同文件格式的上传"""file_path = Config.TEST_FILES_DIR / file_nameif file_path.exists():upload_page.open()upload_page.upload_file(file_path)if expected:assert upload_page.wait_for_upload_complete(), f"{file_name} 上传处理超时"assert upload_page.is_upload_successful(), f"{file_name} 上传未成功完成"else:# 验证有错误提示pass

5. 配置文件 (conftest.py)

import pytest
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.firefox.options import Options as FirefoxOptions
from config.config import Config
from pages.upload_page import UploadPage@pytest.fixture(scope="session")
def browser_type():"""返回浏览器类型"""return Config.BROWSER@pytest.fixture(scope="function")
def browser(browser_type):"""初始化浏览器驱动"""driver = Noneif browser_type.lower() == "chrome":options = Options()if Config.HEADLESS:options.add_argument("--headless")options.add_argument("--no-sandbox")options.add_argument("--disable-dev-shm-usage")options.add_argument("--window-size=1920,1080")driver = webdriver.Chrome(options=options)elif browser_type.lower() == "firefox":options = FirefoxOptions()if Config.HEADLESS:options.add_argument("--headless")driver = webdriver.Firefox(options=options)else:raise ValueError(f"不支持的浏览器类型: {browser_type}")# 设置隐式等待driver.implicitly_wait(Config.IMPLICIT_WAIT)yield driver# 测试结束后退出浏览器driver.quit()@pytest.fixture(scope="function")
def upload_page(browser):"""提供上传页面对象"""return UploadPage(browser)

6. 依赖文件 (requirements.txt)

selenium==4.15.0
pytest==7.4.3
allure-pytest==2.13.2
pytest-html==4.0.2
webdriver-manager==4.0.1
openpyxl==3.1.2

关键改进与优势

1. 页面对象模型(POM)设计

  • 将页面操作封装在独立的类中
  • 提高代码复用性和可维护性
  • 降低UI变化对测试代码的影响

2. 配置集中管理

  • 所有配置项集中在Config类中
  • 易于修改和维护
  • 支持多环境配置

3. 智能等待机制

  • 使用显式等待替代硬编码的sleep
  • 提高测试执行效率
  • 更稳定的测试执行

4. 参数化测试

  • 支持多文件格式测试
  • 减少代码重复
  • 提高测试覆盖率

5. 完善的报告机制

  • 丰富的Allure步骤描述
  • 自动截图功能
  • 详细的日志记录

运行测试

# 运行所有测试并生成Allure报告
pytest testwebui/ --alluredir=./reports/allure-results# 生成HTML报告
allure generate ./reports/allure-results -o ./reports/allure-report --clean# 打开报告
allure open ./reports/allure-report

总结

通过本文的介绍,我们看到了如何从一个简单的测试脚本发展成一个完整的、专业的自动化测试框架。这个框架具有以下特点:

  1. 结构清晰:遵循页面对象模型设计模式
  2. 可维护性强:元素定位与业务逻辑分离
  3. 可扩展性好:易于添加新的测试用例和页面
  4. 稳定性高:智能等待机制减少flaky tests
  5. 报告完善:丰富的测试报告和日志

这种框架设计不仅适用于图片上传测试,也可以扩展到其他类型的Web UI自动化测试场景中,为软件质量保障提供强有力的支持。

希望本文对您构建自己的自动化测试框架有所帮助!

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

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

相关文章

Apache IoTDB:大数据时代时序数据库选型的技术突围与实践指南

摘要:时序数据库在大数据时代迎来爆发式增长,IoTDB作为Apache顶级开源项目展现出显著优势:1. 性能卓越:支持千万级数据点/秒写入,18:1高压缩比,查询延迟低至500ms;2. 创新架构:采用树…

2025年8月16日(星期六):雨骑古莲村游记

清晨,当第一缕微光还未完全驱散夜幕的静谧,我们这群由校长领衔的骑行爱好者已整装待发。咖啡节早市尚未开摊,空气中弥漫着一种期待与宁静交织的氛围,仿佛连时间都在为我们即将开启的旅程而放慢脚步。今天的目标是古莲村&#xff0…

Pandas数据预处理中缺失值处理

一、缺失值的概念表现形式1.数据库中常用null表示2.部分编程语言中用NA表示3.可能表现为空字符串(‘’)或特定数值4.在Pandas中统一用NaN表示(来自NumPy库,NaN、NAN、nan本质一致)NaN的特性1.与任何值都不相等&#xf…

计算机网络:(十五)TCP拥塞控制与拥塞控制算法深度剖析

> 当网络变成"堵城",TCP如何化身智能交通指挥家?揭秘百万级并发背后的流量控制艺术! ### 一、生死攸关:为什么需要拥塞控制? **真实灾难案例**:1986年劳伦斯伯克利实验室网络大崩溃,因缺乏拥塞控制导致全网瘫痪36小时。TCP拥塞控制由此诞生,核心解决**资…

python中的单下划线“_”与双下划线“__”的使用场景及“左右双下划线”(魔术方法:`__xxx__`)

在Python中,单下划线“_”和双下划线“__”的使用场景和含义有显著区别,主要体现在命名约定和语法 一、单下划线“_”的使用场景 单下划线更多是编程约定(而非强制语法),用于传递特定的“暗示”,不影响代码…

我们为什么需要时序数据库?

引言在当今数据驱动的世界中,时间序列数据正以前所未有的速度增长。从物联网设备传感器、金融交易记录到应用程序性能监控,时间序列数据无处不在。传统的关系型数据库在处理这类数据时往往力不从心,这时时序数据库(Time Series Database, TSD…

python-林粒粒的视频笔记1

python的方法和函数指什么 可变类型和不可变类型 不可变类型,比如字符串通过方法调用后,字符串本身的值不改变 要改变需要重新赋值才能进行改变 比如可变数据类型类型,调用方法后可以直接改变原列表 因此,可变数据类型需要再重新赋…

CentOS 7的下载与安装

一 、CentOS 7的下载与安装 注意: CentOS 7 已于2024年6月30日停止维护! 1、下载 由于 centos 7 已经停止维护,部分镜像网站移除了对centos 7的支持,这里找到了部分现在还可以使用的镜像网站 阿里云开源镜像站:http…

矿物分类系统开发笔记(二):模型训练[删除空缺行]

目录 一、阶段衔接与开发目标 二、数据准备 三、模型选择与训练 1. 逻辑回归(LR) 2. 随机森林(RF) 3. 高斯朴素贝叶斯(GNB) 4. 支持向量机(SVM) 5. AdaBoost 6. XGBoost 四…

通信方式:命名管道

一、命名管道 1. 命名管道的原理 有了匿名管道,理解命名管道就非常简单了。 对于普通文件而言,两个进程打开同一个文件,OS是不会将文件加载两次的,这两个进程都会指向同一个文件,那么,也就享有同一份 in…

如何将数据库快速接入大模型实现智能问数,实现chatbi、dataagent,只需短短几步,不需要配置工作流!

智能问数系统初始化操作流程 一、系统初始化与管理员账号创建登录与初始化提示:首次访问系统登录页,若系统未初始化,会弹出 “系统未完成初始化,请初始化管理员账号” 提示,点击【去创建】。填写管理员信息&#xff1a…

告别手写文档!Spring Boot API 文档终极解决方案:SpringDoc OpenAPI

在前后端分离和微服务盛行的今天,API 文档是团队协作的“通用语言”。一份清晰、准确、实时同步的文档,能极大提升开发和联调效率。然而,手动编写和维护 API 文档(如 Word、Markdown 或 Postman)是一场永无止境的噩梦—…

N4200EX是一款全智能超声波检测仪产品简析

N4200EX是一款全智能超声波检测仪,适用于石油、石化、天然气、气体生产等行业的压力管路、阀门、设备的各种防爆场合气体泄漏、真空泄漏、阀门内漏检测。●本安防爆设计,防爆、防尘、防水、抗摔。●适应恶劣环境,可在-25℃超低温环境检测&…

NestJS @Inject 装饰器入门教程

一、核心概念解析 1.1 依赖注入(DI)的本质 依赖注入是一种设计模式,通过 IoC(控制反转)容器管理对象生命周期。在 NestJS 中,Injectable() 标记的类会被容器管理,而 Inject() 用于显式指定依赖项…

网络地址详解

子网划分详解:从 IP 地址结构到实际应用 在计算机网络中,子网划分是一项关键的技术,它能帮助我们更高效地管理 IP 地址资源,优化网络性能。要深入理解子网划分,首先需要从 IP 地址的基本结构说起。 一、IPv4 地址的基…

吾日三省吾身 | 周反思 8.19

上周一览总体来说,上个周是一个被项目驱使而险些丧失自主思考能力的危险阶段。相比任何有机械化工作经验的读者都有类似的体验,在手上打螺丝的无尽循环中,自己的脑子就会逐渐丧失对自身的感知以及自主思考的能力。而这个负循环一旦开始&#…

08.19总结

连通性 在无向图中,若任意两点间均存在路径相连,则该图称为连通图。 若删除图中任意一个顶点后,剩余图仍保持连通性,则该图为点双连通图。 若删除图中任意一条边后,图仍保持连通性,则该图为边双连通图。 在…

车e估牵头正式启动乘用车金融价值评估师编制

8月13日,汽车金融行业职业能力评价规范编制启动工作会议在广州圆满落幕。本次会议由中国机械工业联合会机械工业人才评价中心主办,广州穗圣信息科技有限公司(车e估)承办。会议汇聚了众多行业精英,包括中国机械工业联合…

清空 github 仓库的历史提交记录(创建新分支)

想在 现有仓库中创建一个新分支 master,删除原来的 main,然后把 master 重命名为 main,并且清空历史。可以用下面一条完整的命令序列操作: # 1. 创建一个没有历史的新分支 master git checkout --orphan master# 2. 添加当前所有文…

使用B210在Linux下实时处理ETC专用短程通信数据(2)-CPU单核高速数据处理

在上一篇文章中,使用Octave初步验证了ETC车联数据的格式。然而,Octave无法实时处理20M的采样带宽。我们本节通过C语言,重写 Octave程序,实现实时处理,涉及下面三个关键特点。 文章目录1. 全静态内存2. 使用环状缓存3 无…