构建现代化的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
总结
通过本文的介绍,我们看到了如何从一个简单的测试脚本发展成一个完整的、专业的自动化测试框架。这个框架具有以下特点:
- 结构清晰:遵循页面对象模型设计模式
- 可维护性强:元素定位与业务逻辑分离
- 可扩展性好:易于添加新的测试用例和页面
- 稳定性高:智能等待机制减少flaky tests
- 报告完善:丰富的测试报告和日志
这种框架设计不仅适用于图片上传测试,也可以扩展到其他类型的Web UI自动化测试场景中,为软件质量保障提供强有力的支持。
希望本文对您构建自己的自动化测试框架有所帮助!