news 2026/6/26 15:26:23

从零构建UI自动化测试框架:三层架构、POM模型与工程化实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从零构建UI自动化测试框架:三层架构、POM模型与工程化实践

1. 项目概述:从零到一,构建你的UI自动化测试“发动机”

最近和几个测试团队的朋友聊天,发现一个挺普遍的现象:很多团队一提UI自动化,要么直接上Selenium、Cypress、Playwright这些现成的轮子,要么就是东拼西凑一些脚本,运行起来问题一堆,维护成本高得吓人。大家好像都默认了“框架”是个黑盒,或者觉得从零搭建是个“造火箭”的工程,望而却步。其实,独立搭建一个UI自动化测试框架,远没有想象中那么复杂和高深。它更像是在组装一台为你团队量身定制的“发动机”,核心目标就一个:让自动化测试脚本的编写、执行和维护变得高效、稳定且可持续。

一个设计良好的UI自动化测试框架,绝不仅仅是封装几个页面对象(Page Object)或者写几个find_element的辅助函数那么简单。它需要系统地解决从脚本开发、数据管理、用例组织、异常处理、报告生成到持续集成(CI)接入的全链路问题。为什么非要自己搭?因为市面上的通用框架或工具,往往无法完美契合你项目的技术栈(比如特定的前端框架、自定义组件)、业务复杂度(比如复杂的多步骤业务流程)以及团队的技术习惯。自己搭建,意味着你拥有完全的掌控权,可以针对痛点做深度优化,比如实现智能等待策略来对抗前端渲染的不确定性,或者设计一套贴合业务领域的断言库。

特别是随着“基于大模型的UI自动化测试框架”这个概念开始被讨论,其核心思路是利用AI来理解页面结构、生成或维护测试脚本,这其实对底层框架的健壮性、可扩展性和数据接口的规范性提出了更高的要求。一个混乱的、耦合度高的脚本集合,是无法有效接入AI能力的。因此,现在动手搭建一个清晰、模块化的框架,也是在为未来可能的技术演进打下坚实的基础。

这篇文章,我就结合自己多次从零搭建和改造UI自动化框架的经验,拆解其中的核心模块、设计思路和实操细节。无论你是测试开发新手想系统学习,还是苦于现有脚本难以维护想重构,相信都能找到可以直接“抄作业”的实用方案。我们不止讲“怎么做”,更重点剖析“为什么这么做”,以及那些只有踩过坑才知道的“注意事项”。

2. 框架核心设计与架构选型

在动手写第一行代码之前,花时间想清楚架构是最高效的投资。一个好的架构应该像乐高积木,模块之间高内聚、低耦合,方便随时替换或升级某个部分,而不会“牵一发而动全身”。

2.1 主流架构模式解析

目前主流的UI自动化测试框架,通常会采用分层架构,其中最经典、最实用的莫过于“页面对象模型(Page Object Model, POM)”的变体与增强。单纯的POM将页面元素定位和操作封装在Page类中,这解决了元素定位与测试逻辑的分离,但还不够。我们需要一个更清晰的分层。

我推荐的是“三层架构”

  1. 基础层(Driver/API Layer):这一层直接与浏览器驱动(如WebDriver)或测试工具(如Playwright的BrowserContext)交互。它的职责是封装最原始的操作,比如click,type,get_text,并提供统一的、健壮的等待和重试机制。这一层应该非常稳定,对上层的变动不敏感。
  2. 页面层/组件层(Page/Component Layer):这是POM的核心体现。我们将每个页面或页面中可复用的复杂组件(如导航栏、模态框、数据表格)抽象成一个类。这个类不包含任何测试断言逻辑,只包含:
    • 元素定位器:使用清晰易读的变量或属性来定义。
    • 页面操作方法:如login(username, password),search(keyword)。这些方法内部调用基础层提供的方法。
    • 页面状态获取方法:如get_error_message(),is_login_successful(),返回可供断言的数据。
  3. 测试用例层(Test Case Layer):这一层包含具体的测试用例。它调用页面层提供的方法来组织业务流程,并包含测试断言(Assert)。这一层应该非常“瘦”,读起来像自然语言描述的测试场景。

为什么选择三层而不是两层?将基础操作从页面层中剥离出来,形成了一个独立的“工具层”。这样做的好处是,当底层测试工具发生重大变更(例如从Selenium迁移到Playwright),你只需要重写或适配基础层,页面层和测试用例层的代码几乎可以无缝迁移,维护成本大大降低。

2.2 核心组件与职责划分

一个完整的框架,除了上述核心三层,还需要一系列支撑组件来保证其可用性和工程化水平。下面这个表格梳理了关键组件及其职责:

组件模块核心职责关键技术选型/实现要点
驱动管理创建、配置、销毁浏览器实例,支持多浏览器并行。使用工厂模式或单例模式管理WebDriver/Playwright实例。集成Docker运行Headless Chrome。
元素定位与等待提供稳定、智能的元素查找机制,应对动态加载。封装WebDriverWaitexpected_conditions。实现自定义等待策略,如轮询查找、元素可见可点击。统一所有定位器(如ID、XPath、CSS)的管理。
测试数据管理将测试数据与测试脚本分离,支持参数化。使用JSON、YAML、Excel或数据库存储数据。结合pytest@pytest.mark.parametrize实现数据驱动。
日志与报告记录执行过程,生成直观的测试报告。集成logging模块,分级别(INFO, ERROR)记录。使用Allurepytest-htmlExtentReports生成包含截图、错误堆栈的HTML报告。
异常处理与截图用例失败时自动捕获现场,便于排查。使用装饰器或pytest钩子函数,在断言失败或异常时自动截屏,并保存到报告或指定目录。
配置管理集中管理环境变量、URL、账号等配置。使用configparserpython-dotenv或YAML文件,区分开发、测试、生产环境。
断言扩展提供更丰富、更贴近业务的断言方式。基于assert语句或pytest的断言,封装如assert_element_text_containsassert_page_title等业务断言函数。

2.3 技术栈选型建议

选型没有绝对的好坏,只有是否适合你的团队和项目。

  • 编程语言Python是首选。其语法简洁,生态丰富(pytest,selenium,playwright支持都极好),学习曲线平缓,非常适合测试领域。Java也不错,更适合与Java后端技术栈深度集成的团队。
  • 测试运行器:强烈推荐pytest。它比unittest更灵活、功能更强大(丰富的Fixture、参数化、插件体系),是目前Python测试领域的事实标准。
  • 浏览器自动化库
    • Selenium WebDriver:老牌、稳定、生态成熟,社区资源多。缺点是速度相对较慢,对于现代复杂SPA(单页应用)的等待处理需要更多技巧。
    • Playwright:微软出品的新星,支持Chromium、Firefox、WebKit三大内核。其自动等待机制是革命性的,能极大减少编写显式等待的代码量,执行速度也更快。如果是新项目,我优先推荐Playwright。
    • Cypress:对前端开发者非常友好,运行在浏览器中,调试体验极佳。但它的架构决定了其更适合E2E测试,且对非JavaScript技术栈支持较弱。
  • 报告工具Allure功能强大,展示美观,能与CI/CD深度集成,是生成专业报告的不二之选。pytest-html则更轻量,开箱即用。

实操心得:不要盲目追求最新最热的技术。评估团队现有技术栈和成员技能。如果团队全是Python背景,选Playwright+Pytest;如果与前端Node.js生态结合紧密,可以看看Cypress。初期,一个“Pytest + Playwright + Allure”的组合,能让你以最小阻力搭建出一个现代化、高效的框架原型。

3. 基础层与页面层构建详解

有了清晰的架构蓝图,我们就可以开始动手搭建了。我们从最底层、最稳定的基础层开始。

3.1 驱动管理:浏览器实例的生命周期

管理浏览器驱动是框架稳定的基石。我们需要一个中心化的地方来创建和获取驱动实例,并确保测试结束后能正确关闭,避免资源泄漏。

# base/driver_factory.py from selenium import webdriver from selenium.webdriver.chrome.options import Options as ChromeOptions from selenium.webdriver.firefox.options import Options as FirefoxOptions import threading class DriverFactory: _local_driver = threading.local() # 使用线程本地存储,支持并行测试 @classmethod def get_driver(cls, browser_name="chrome", headless=False): """获取或创建WebDriver实例""" if not hasattr(cls._local_driver, 'instance') or cls._local_driver.instance is None: if browser_name.lower() == "chrome": options = ChromeOptions() if headless: options.add_argument("--headless=new") # 新版Chrome的headless模式 options.add_argument("--no-sandbox") options.add_argument("--disable-dev-shm-usage") options.add_argument("--window-size=1920,1080") # 可添加其他配置,如禁用GPU、忽略证书错误等 cls._local_driver.instance = webdriver.Chrome(options=options) elif browser_name.lower() == "firefox": options = FirefoxOptions() if headless: options.add_argument("-headless") cls._local_driver.instance = webdriver.Firefox(options=options) else: raise ValueError(f"Unsupported browser: {browser_name}") # 全局隐性等待(非必须,建议与显式等待结合使用) cls._local_driver.instance.implicitly_wait(10) return cls._local_driver.instance @classmethod def quit_driver(cls): """退出并清理WebDriver实例""" if hasattr(cls._local_driver, 'instance') and cls._local_driver.instance: cls._local_driver.instance.quit() cls._local_driver.instance = None # 在pytest的fixture中使用 import pytest @pytest.fixture(scope="function") # 每个测试函数一个独立的浏览器实例 def driver(): driver_instance = DriverFactory.get_driver(headless=True) # 测试环境通常用无头模式 yield driver_instance DriverFactory.quit_driver()

为什么使用threading.localfixture

  • threading.local:为每个线程创建独立的驱动实例存储空间,这是实现pytest-xdist并行执行测试用例的前提。如果不这样做,多个线程会共享同一个驱动实例,导致操作混乱。
  • pytest.fixturescope="function"确保每个测试用例都有干净的浏览器上下文,用例之间互不干扰。yield之前是setup(创建驱动),yield之后是teardown(退出驱动),代码结构非常清晰。

3.2 智能等待与元素定位封装

UI自动化最大的不稳定因素就是“等待”。元素还没加载出来,你的代码就去点击,肯定会失败。单纯的time.sleep是低效且不可靠的。

我们需要封装一个“智能查找”工具,它会在抛出异常前进行多次尝试。

# base/element_locator.py from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.common.exceptions import TimeoutException, StaleElementReferenceException from selenium.webdriver.remote.webelement import WebElement import logging logger = logging.getLogger(__name__) class ElementLocator: def __init__(self, driver, timeout=30, poll_frequency=0.5): self.driver = driver self.wait = WebDriverWait(driver, timeout, poll_frequency=poll_frequency) def find_element(self, locator: tuple, ensure_clickable=False) -> WebElement: """ 查找单个元素,支持等待直至元素出现或可点击。 :param locator: 定位元组,如 (By.ID, "username") :param ensure_clickable: 是否确保元素可点击 :return: WebElement 对象 """ try: if ensure_clickable: condition = EC.element_to_be_clickable(locator) else: condition = EC.presence_of_element_located(locator) element = self.wait.until(condition) logger.debug(f"成功定位到元素: {locator}") return element except TimeoutException: # 失败时自动截图(需集成截图功能) self._take_screenshot_on_failure() logger.error(f"定位元素超时: {locator}") raise except StaleElementReferenceException: # 处理元素过时的异常,可以重试一次 logger.warning(f"元素已过时,尝试重新定位: {locator}") return self.find_element(locator, ensure_clickable) def find_elements(self, locator: tuple): """查找多个元素""" try: elements = self.wait.until(EC.presence_of_all_elements_located(locator)) logger.debug(f"成功定位到多个元素,数量: {len(elements)} for {locator}") return elements except TimeoutException: logger.error(f"定位多个元素超时: {locator}") return [] # 返回空列表,而不是抛出异常,有时更灵活 def _take_screenshot_on_failure(self): # 截图实现,可保存到指定路径或附加到报告 screenshot_path = f"./screenshots/failure_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png" self.driver.save_screenshot(screenshot_path) logger.info(f"失败截图已保存至: {screenshot_path}")

封装的价值:现在,在页面层中,你不再需要写冗长的WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.ID, “submit”))),只需要调用self.locator.find_element((By.ID, “submit”), ensure_clickable=True)。代码更简洁,并且所有查找都内置了等待、重试和错误处理逻辑。

3.3 页面对象(Page Object)的精髓实现

页面层是框架的核心资产。一个好的Page类应该让测试用例编写者像用户一样思考,而不是像程序员一样操作DOM。

# pages/login_page.py from selenium.webdriver.common.by import By from base.element_locator import ElementLocator class LoginPage: """登录页面模型""" # 1. 集中管理定位器 USERNAME_INPUT = (By.ID, "username") PASSWORD_INPUT = (By.ID, "password") LOGIN_BUTTON = (By.XPATH, "//button[@type='submit']") ERROR_MESSAGE_SPAN = (By.CLASS_NAME, "error-message") def __init__(self, driver): self.driver = driver self.locator = ElementLocator(driver) # 注入定位工具 # 2. 封装页面操作 def enter_username(self, username: str): """输入用户名""" username_elem = self.locator.find_element(self.USERNAME_INPUT) username_elem.clear() username_elem.send_keys(username) return self # 支持链式调用 def enter_password(self, password: str): """输入密码""" password_elem = self.locator.find_element(self.PASSWORD_INPUT) password_elem.clear() password_elem.send_keys(password) return self def click_login(self): """点击登录按钮""" login_btn = self.locator.find_element(self.LOGIN_BUTTON, ensure_clickable=True) login_btn.click() # 点击后,页面可能跳转,可以返回下一个页面的Page对象,或者等待某个条件 # from pages.home_page import HomePage # return HomePage(self.driver) # 3. 封装业务场景(组合操作) def login(self, username: str, password: str): """完整的登录业务流""" self.enter_username(username).enter_password(password).click_login() # 通常登录后跳转到首页,这里返回首页Page对象 from pages.home_page import HomePage return HomePage(self.driver) # 4. 封装页面状态获取 def get_error_message(self) -> str: """获取错误提示信息,如果不存在则返回空字符串""" try: error_elem = self.locator.find_element(self.ERROR_MESSAGE_SPAN, timeout=5) # 短时间等待错误信息 return error_elem.text except TimeoutException: return "" # 没有错误信息

设计要点

  1. 定位器集中管理:所有元素定位字符串都在类顶部常量中定义。如果前端ID改了,你只需要修改这一个地方。
  2. 方法返回self:支持链式调用,让代码更流畅,如page.enter_username(“admin”).enter_password(“123”).click_login()
  3. 业务场景封装login(username, password)这样的方法将多个步骤组合成一个有业务含义的操作,这是Page Object的核心价值。
  4. 返回新的Page对象:一个操作导致页面跳转时,方法应返回新页面的Page对象,引导测试用例的自然流转。

避坑指南:不要在Page对象的方法内部进行断言!断言是测试用例层的职责。Page对象只负责“做什么”和“提供什么状态”,不负责“检查对不对”。这保持了清晰的职责分离。

4. 测试用例层、数据驱动与报告集成

框架的“上层建筑”决定了其易用性和可维护性。我们将测试用例、测试数据和报告输出有机结合起来。

4.1 使用Pytest组织优雅的测试用例

pytest的Fixture和参数化功能,能让我们的测试用例非常简洁。

# tests/test_user_login.py import pytest from pages.login_page import LoginPage class TestUserLogin: """用户登录功能测试集""" # 测试正常登录 def test_login_success(self, driver): # 使用fixture注入driver """测试使用正确凭据登录成功""" login_page = LoginPage(driver) driver.get("https://your-app.com/login") # 基础URL可从配置读取 home_page = login_page.login("valid_user", "valid_password") # 断言:验证登录成功后跳转到了首页,并且首页有用户信息 # 假设HomePage有一个方法可以获取欢迎语 welcome_text = home_page.get_welcome_text() assert "valid_user" in welcome_text # 或者断言当前URL包含首页路径 assert "/dashboard" in driver.current_url # 使用参数化进行数据驱动测试 @pytest.mark.parametrize("username, password, expected_error", [ ("", "somepass", "用户名不能为空"), ("admin", "", "密码不能为空"), ("wrong", "wrong", "用户名或密码错误"), ]) def test_login_failure(self, driver, username, password, expected_error): """测试各种错误的登录场景""" login_page = LoginPage(driver) driver.get("https://your-app.com/login") login_page.enter_username(username).enter_password(password).click_login() # 登录失败应停留在登录页,获取错误信息 actual_error = login_page.get_error_message() assert expected_error in actual_error, f"期望错误信息包含'{expected_error}',实际为'{actual_error}'"

为什么这样写好?

  • 清晰:测试用例读起来就像需求文档。
  • 隔离:每个用例都有独立的driverfixture,互不影响。
  • 数据驱动@pytest.mark.parametrize将测试数据与逻辑分离,添加新测试场景只需加一行数据。

4.2 高级数据管理:从文件到数据库

对于复杂场景,测试数据可能来自外部文件。

# test_data/login_data.yaml success_cases: - username: "standard_user" password: "secret_sauce" expected_welcome: "Products" - username: "problem_user" password: "secret_sauce" expected_welcome: "Products" # 即使是有问题的用户,登录行为可能成功 failure_cases: - username: "" password: "secret_sauce" expected_error: "Username is required" - username: "locked_out_user" password: "secret_sauce" expected_error: "Sorry, this user has been locked out."
# conftest.py 或单独的数据加载模块 import yaml import pytest import os def load_yaml_data(file_path): with open(file_path, 'r', encoding='utf-8') as f: return yaml.safe_load(f) @pytest.fixture(params=load_yaml_data('./test_data/login_data.yaml')['success_cases']) def success_login_data(request): """为成功登录用例提供参数化数据""" return request.param # 在测试用例中使用 def test_login_with_yaml_data(driver, success_login_data): login_page = LoginPage(driver) driver.get(BASE_URL) home_page = login_page.login(success_login_data['username'], success_login_data['password']) assert success_login_data['expected_welcome'] in home_page.get_title()

4.3 生成专业测试报告:Allure集成

漂亮的报告不仅能展示结果,更是排查问题的利器。

  1. 安装pip install allure-pytest
  2. 执行测试pytest tests/ --alluredir=./allure-results
  3. 生成报告allure serve ./allure-results(本地查看) 或allure generate ./allure-results -o ./allure-report --clean(生成静态报告)

你可以在代码中通过装饰器增强报告:

import allure import pytest @allure.feature("用户认证模块") @allure.story("用户登录功能") class TestUserLogin: @allure.title("正向用例:使用有效账号密码登录成功") @allure.severity(allure.severity_level.CRITICAL) def test_login_success(self, driver): with allure.step("1. 打开登录页面"): login_page = LoginPage(driver) driver.get(BASE_URL + "/login") allure.attach(driver.get_screenshot_as_png(), name="登录页面截图", attachment_type=allure.attachment_type.PNG) with allure.step("2. 输入用户名和密码"): login_page.enter_username("valid_user") login_page.enter_password("valid_pass") with allure.step("3. 点击登录按钮"): home_page = login_page.click_login() with allure.step("4. 验证登录成功"): assert "Dashboard" in driver.title allure.attach(driver.get_screenshot_as_png(), name="登录后首页截图", attachment_type=allure.attachment_type.PNG)

这样生成的Allure报告会包含清晰的测试层级、步骤描述、严重级别以及关键的截图,一目了然。

5. 高级主题与持续集成

一个成熟的框架还需要考虑更多工程化实践。

5.1 并行测试与分布式执行

当用例成百上千时,串行执行太慢。pytest-xdist插件可以轻松实现并行。

# 安装 pip install pytest-xdist # 运行,使用2个worker并行执行 pytest tests/ -n 2 # 或者根据CPU核心数自动分配 pytest tests/ -n auto

注意事项:并行执行要求测试用例之间完全独立,不能有共享状态(如共享数据库的某条记录)。我们的driverfixture使用threading.localfunctionscope就是为了满足这个条件。同时,测试数据也要注意隔离,避免多个线程操作同一条数据导致冲突。

5.2 集成到CI/CD流水线

自动化测试只有集成到CI/CD中,才能发挥最大价值,实现“质量门禁”。

以GitLab CI为例的.gitlab-ci.yml配置片段:

stages: - test ui-automation-test: stage: test image: python:3.11-slim # 使用带有Python的Docker镜像 before_script: - apt-get update && apt-get install -y wget unzip # 安装Chrome和Chromedriver(以Playwright为例更简单) - pip install playwright pytest pytest-xdist allure-pytest - playwright install chromium --with-deps script: - pytest tests/ --alluredir=./allure-results -n auto # 并行执行并生成结果 after_script: - apt-get install -y default-jre-headless # 安装Java运行Allure - wget https://github.com/allure-framework/allure2/releases/download/2.24.0/allure-2.24.0.zip - unzip allure-2.24.0.zip -d /opt/ - ln -s /opt/allure-2.24.0/bin/allure /usr/bin/allure - allure generate ./allure-results -o ./allure-report --clean artifacts: paths: - ./allure-report/ expire_in: 30 days only: - merge_requests # 仅在合并请求时触发 - main # 或在主分支推送时触发

这样,每次代码合并请求时,都会自动运行UI自动化测试套件,并将生成的Allure报告作为制品保存,评审者可以直接在流水线页面查看测试结果和失败详情。

5.3 面向未来的思考:与大模型结合的潜力

“基于大模型的UI自动化测试”并非空中楼阁。一个设计良好的传统框架,是接入AI能力的最佳底座。我们可以设想几个方向:

  1. 脚本生成与补全:将清晰的页面对象(定位器、方法)和测试用例结构暴露给大模型,它可以学习模式,辅助生成新的测试用例代码片段。
  2. 元素定位维护:前端UI经常变动。大模型可以分析页面截图或DOM变更,建议更新失效的定位器,甚至自动修复。
  3. 自然语言转测试用例:测试人员用自然语言描述场景(如“用户用错误密码登录应该看到错误提示”),框架结合大模型将其转换为对页面对象方法的调用链。
  4. 智能断言与异常分析:测试失败时,大模型可以分析截图、日志和页面状态,推测失败的根本原因,而不仅仅是报出“元素未找到”。

要实现这些,你的框架需要:

  • 结构清晰:模块化的代码让AI更容易理解。
  • 数据规范:测试数据、定位器、操作步骤都有良好的结构化表示。
  • 可观测性强:丰富的日志、截图和页面状态导出功能,为AI提供分析素材。

所以,今天你搭建的每一个清晰、规范的模块,都是在为明天更智能的测试能力铺路。

6. 常见问题排查与实战技巧

框架搭建和脚本编写过程中,你会遇到各种各样的问题。这里记录一些高频问题的解决思路。

6.1 元素定位失败问题排查表

现象可能原因排查步骤与解决方案
NoSuchElementException1. 元素尚未加载完成。
2. 定位器写错了。
3. 元素在iframe或shadow DOM内。
4. 页面发生了跳转或刷新。
1.增加智能等待:使用封装的find_element并确保ensure_clickableensure_visible
2.手动验证:在浏览器开发者工具中,用$x(‘your_xpath’)$(‘your_css’)验证定位器。
3.切换上下文driver.switch_to.frame(frame_element)或处理shadow DOM。
4.等待新页面:在操作后添加对新页面某个元素的等待。
ElementNotInteractableException1. 元素被遮挡(如弹窗)。
2. 元素不可见(display: nonevisibility: hidden)。
3. 元素未处于可交互状态(如disabled)。
1.关闭遮挡物:检查是否有模态框,先关闭它。
2.等待可见:使用EC.visibility_of_element_located
3.检查元素状态:通过JavaScript检查元素属性。
StaleElementReferenceException你持有的元素对象所对应的DOM元素已经失效(页面刷新、AJAX更新导致元素被重新渲染)。重新定位:这是最根本的解决方法。在封装的定位方法中加入对此异常的捕获和重试逻辑(如前文ElementLocator所示)。
脚本在本地通过,在CI上失败1. CI环境是无头(Headless)模式,渲染或行为可能有差异。
2. CI环境资源(CPU/内存)不足,导致渲染慢。
3. 网络或环境依赖不同。
1.本地模拟CI环境:在本地也用headless=True模式运行一遍。
2.增加超时时间:为CI环境适当增加全局等待时间。
3.使用更稳定的定位器:优先使用ID、稳定的data属性,避免使用绝对XPath或依赖动态类名的CSS。
4.记录详细日志和截图:CI失败时务必保存截图和页面源代码,这是最直接的证据。

6.2 提升脚本稳定性的实战技巧

  1. 优先使用唯一的、不变的属性定位:如>import time from functools import wraps from selenium.common.exceptions import WebDriverException def retry_on_failure(max_attempts=3, delay=1): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): attempts = 0 while attempts < max_attempts: try: return func(*args, **kwargs) except WebDriverException as e: attempts += 1 if attempts == max_attempts: raise print(f"尝试 {func.__name__} 失败,第{attempts}次重试... 错误: {e}") time.sleep(delay) return wrapper return decorator # 在页面方法中使用 class SomePage: @retry_on_failure(max_attempts=2) def click_unstable_button(self): self.locator.find_element(self.UNSTABLE_BTN).click()
    1. 定期清理与维护
      • 清理测试数据:用例执行前后,通过API或数据库操作清理产生的垃圾数据,保证用例独立性。
      • 重构定位器:随着前端迭代,定期审查和更新失效或脆弱的定位器。
      • 代码审查:将测试代码纳入团队的代码审查流程,保证代码质量和风格统一。

    搭建一个UI自动化测试框架,是一个不断迭代和优化的过程。它没有终极的完美形态,只有最适合你当前团队和项目的形态。从最核心的三层架构和页面对象模型开始,逐步添砖加瓦,集成日志、报告、数据驱动和CI/CD。在这个过程中,你会对自动化测试有更深刻的理解,而最终收获的,不仅是一个高效的工具,更是一套保障产品质量的可靠体系。记住,框架是为人服务的,清晰、易维护、易扩展,远比用了多少炫技的技术更重要。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/26 15:21:07

AI高薪岗位爆发!月薪13万?小白也能抓住的AI红利,速收藏!

本文分析了AI领域岗位的快速增长和高薪现象&#xff0c;指出AI取代的是重复性执行层工作&#xff0c;而创造了更多高薪的AI协调与创新层岗位。文章建议普通人可以通过学习AI工具使用、结合现有岗位、考取AI认证等方式进入AI领域&#xff0c;并关注新一线城市的机会。强调AI学习…

作者头像 李华
网站建设 2026/6/26 15:19:26

文件上传漏洞深度剖析:从原理到实战,以狮子鱼CMS为例

1. 项目概述&#xff1a;一次典型CMS文件上传漏洞的深度剖析 最近在梳理一些老旧CMS系统的安全问题时&#xff0c;又一次遇到了“狮子鱼CMS”。这个系统在几年前的一些中小型电商、内容展示类网站中应用还算广泛&#xff0c;但随着技术栈的迭代&#xff0c;其安全问题也逐渐暴露…

作者头像 李华
网站建设 2026/6/26 15:16:56

【2024虚拟化平台迁移避坑白皮书】:从VMware转向Hyper-V的7个致命陷阱,第3个95%运维总监都踩过!

更多请点击&#xff1a; https://intelliparadigm.com 第一章&#xff1a;虚拟化平台迁移的战略认知与决策框架 虚拟化平台迁移绝非单纯的技术替换&#xff0c;而是组织数字化能力重构的关键支点。它牵涉基础设施韧性、应用兼容性、安全合规性与团队技能栈的系统性演进。忽视战…

作者头像 李华
网站建设 2026/6/26 15:02:12

财务知识科普

一、 财务信息的“基石”&#xff1a;会计要素与等式 财务信息的核心由六大基本要素构成&#xff0c;它们被清晰地划分为两大阵营&#xff1a; 1. 反映财务状况&#xff08;底子&#xff09;&#xff1a;包括资产&#xff08;企业拥有或控制的资源&#xff09;、负债&#xff0…

作者头像 李华
网站建设 2026/6/26 15:01:25

热设计客户原始需求挖掘与需求分析

&#x1f393;作者简介&#xff1a;科技自媒体优质创作者 &#x1f310;个人主页&#xff1a;莱歌数字-CSDN博客 211、985硕士&#xff0c;从业16年 从事结构设计、热设计、售前、产品设计、项目管理等工作&#xff0c;涉足消费电子、新能源、医疗设备、制药信息化、核工业等…

作者头像 李华