Python+Selenium自动化测试框架搭建:从零到一实现高效UI测试

📅 2026/7/5 14:04:11
Python+Selenium自动化测试框架搭建:从零到一实现高效UI测试
1. 项目概述为什么我们需要一个“简单”的测试框架最近在带团队做项目测试同学天天跟我抱怨说回归测试的工作量太大了每次发版前都要手动点点点枯燥不说还容易漏测。我一看这不行啊效率太低而且人肉测试的稳定性也堪忧。于是我决定带着他们搞一个自动化测试框架。目标很明确简单、好用、能快速落地。我们不追求大而全的“银弹”而是要一个团队成员包括刚入行的测试新人都能快速上手能切实解决当前重复性功能验证问题的工具。这就是“简单实现一个PythonSelenium的自动化测试框架”这个项目的由来。它本质上是一个脚手架帮你把自动化测试中那些繁琐、重复的准备工作比如浏览器驱动管理、元素定位封装、测试报告生成、用例组织都打包好让你能专注于编写具体的测试逻辑。用PythonSelenium是因为这对测试同学来说学习曲线最平缓生态也最成熟。这个框架能做什么简单说就是把你从“打开浏览器-输入网址-点击按钮-断言结果”这一系列手动操作中解放出来用代码自动执行并且生成清晰的测试报告告诉你哪些用例过了哪些挂了为什么挂。适合谁来参考如果你是测试工程师正苦于手工回归如果你是开发想为自己的模块补充一些自动化冒烟测试甚至如果你是项目负责人想提升团队的交付质量和效率这个内容都值得一看。我们不会讲太深奥的设计模式就从最实用的角度出发一步步搭出一个能立刻用起来的框架。2. 框架整体设计与核心思路拆解在动手写代码之前得先想清楚我们要做个什么东西。一个自动化测试框架核心任务是把测试用例执行起来并给出明确的结果。基于这个目标我把它拆解成几个关键模块这也是我们框架的骨架。2.1 核心模块与职责划分一个哪怕再简单的框架也需要清晰的层次。我设计的结构主要分为四层从上到下职责逐渐细化测试用例层这是用户测试工程师主要工作的地方。他们在这里用自然语言结合代码描述测试场景比如“用户登录成功”、“添加商品到购物车”。这一层应该尽量简洁只关心业务逻辑和测试数据。测试逻辑层这一层提供“积木”。我们把常用的页面操作点击、输入、获取文本和业务流登录、退出封装成一个个函数或类方法。用例层直接调用这些“积木”来搭建测试场景。这样做的好处是当页面元素发生变化时通常只需要修改这一层的某个“积木”而不是去改每一个用例。驱动与工具层这是框架的“发动机”和“工具箱”。负责管理WebDriver控制浏览器的核心、读取配置文件如测试URL、浏览器类型、生成测试报告、处理日志等。这些是支撑性的功能为上层提供稳定的服务。基础配置层存放所有环境相关的配置比如config.ini文件、测试数据文件如JSON、Excel、日志存放路径等。实现配置与代码分离不同环境测试、预生产切换起来非常方便。这个分层结构的思想是“高内聚、低耦合”。用例不管浏览器怎么启动的逻辑层不管报告怎么生成的各司其职维护和扩展起来就轻松很多。2.2 技术选型背后的考量为什么是Python Selenium Pytest市面上工具很多我选择这个组合是基于以下几点实实在在的考虑Python语法简洁接近自然语言对测试人员极其友好。庞大的第三方库pytest,requests,openpyxl让解决任何问题数据驱动、报告生成都像“拼积木”一样简单。团队学习成本最低。SeleniumWeb自动化测试的“事实标准”。支持所有主流浏览器API成熟稳定社区资源丰富。遇到问题几乎总能搜到解决方案。对于Web UI自动化来说它是目前最稳妥的选择。Pytest这是我框架的“测试执行引擎”。它比Python自带的unittest更强大、更灵活。pytest的夹具fixture机制能优雅地解决测试前置如初始化浏览器和后置如关闭浏览器、截图操作。它的断言写法更直观直接用assert测试发现规则更智能插件生态如生成allure报告更是杀手锏。注意不要试图自己从头造轮子去管理用例执行、前置后置条件和报告。Pytest已经把这些做得非常好了我们的框架应该是基于Pytest进行扩展和封装而不是替代它。2.3 目录结构规划好的开始是成功的一半代码还没写我们先定好目录结构。清晰的目录能让项目一目了然方便团队协作。我建议的目录结构如下automation_framework/ ├── configs/ # 配置层 │ ├── config.ini # 主配置文件 │ └── test_data.json # 测试数据文件 ├── logs/ # 日志目录运行时生成 ├── reports/ # 测试报告目录运行时生成 ├── screenshots/ # 失败用例截图目录运行时生成 ├── drivers/ # 浏览器驱动目录 │ ├── chromedriver(.exe) │ └── geckodriver(.exe) ├── page_objects/ # 逻辑层页面对象 │ ├── __init__.py │ ├── base_page.py # 基类封装通用操作 │ ├── login_page.py # 登录页面 │ └── main_page.py # 主页 ├── test_cases/ # 用例层 │ ├── __init__.py │ ├── conftest.py # pytest共享夹具配置 │ ├── test_login.py # 登录相关测试用例 │ └── test_cart.py # 购物车相关测试用例 ├── utilities/ # 工具层 │ ├── __init__.py │ ├── logger.py # 日志工具 │ ├── config_reader.py # 配置读取工具 │ └── report_generator.py # 报告生成工具可整合allure └── requirements.txt # 项目依赖包列表这个结构不是一成不变的你可以根据项目复杂度调整。但核心思想是配置、用例、页面对象、工具、输出物各归其位。3. 核心细节解析与实操要点框架的骨架搭好了现在我们开始填充血肉。这一部分我们会深入几个最核心的模块看看它们具体如何实现以及实现过程中有哪些“坑”需要避开。3.1 灵魂所在BasePage基类的设计与封装BasePage是整个页面对象模型Page Object Model, POM的基石。它的核心思想是将页面元素定位和常用操作封装起来让具体的页面类如LoginPage继承它从而获得所有能力并只需关注自己特有的元素和操作。为什么要用POM想象一下如果100个测试用例里都用driver.find_element(By.ID, “username”).send_keys(“admin”)来输入用户名。一旦这个输入框的ID变了你就需要修改这100个地方。而POM模式下你只需要在LoginPage类里修改一次username_input这个属性的定位方式。维护成本天差地别。下面是一个实战中提炼出的BasePage类示例包含了我认为最必要的封装# page_objects/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 utilities.logger as log import os class BasePage: 所有页面对象的基类封装通用Web操作和等待机制 def __init__(self, driver): self.driver driver self.log log.get_logger() # 获取日志记录器 self.wait WebDriverWait(self.driver, 10) # 显式等待超时10秒 def open(self, url): 打开指定URL self.log.info(f打开网址: {url}) self.driver.get(url) def find_element(self, locator): 查找单个元素加入显式等待和日志 try: self.log.debug(f正在查找元素: {locator}) element self.wait.until(EC.presence_of_element_located(locator)) return element except TimeoutException: self.log.error(f查找元素超时: {locator}) raise def click(self, locator): 点击元素 element self.find_element(locator) self.log.info(f点击元素: {locator}) element.click() def input_text(self, locator, text): 向元素输入文本并清空原有内容 element self.find_element(locator) element.clear() self.log.info(f向元素 {locator} 输入文本: {text}) element.send_keys(text) def get_text(self, locator): 获取元素的文本内容 element self.find_element(locator) text element.text self.log.debug(f获取元素 {locator} 的文本: {text}) return text def is_element_visible(self, locator, timeout5): 判断元素是否可见用于断言 try: WebDriverWait(self.driver, timeout).until( EC.visibility_of_element_located(locator) ) self.log.debug(f元素可见: {locator}) return True except TimeoutException: self.log.debug(f元素不可见: {locator}) return False def take_screenshot(self, name): 截取屏幕截图保存到指定目录 screenshot_dir os.path.join(os.getcwd(), screenshots) os.makedirs(screenshot_dir, exist_okTrue) # 目录不存在则创建 file_path os.path.join(screenshot_dir, f{name}.png) self.driver.save_screenshot(file_path) self.log.info(f截图已保存至: {file_path}) return file_path封装要点与避坑指南统一的等待策略Selenium操作元素90%的失败源于“元素未加载完成就进行操作”。我在find_element里强制集成了显式等待WebDriverWait这是最重要的稳定性保障。默认10秒可根据网络情况调整。全面的日志记录每个关键操作打开页面、点击、输入都通过log.info记录错误用log.error。调试时通过日志文件就能清晰还原测试步骤快速定位问题所在。操作原子化click、input_text等方法内部都调用了find_element。这样具体的页面类如LoginPage只需要关心“定位符”locator是什么而不用重复写查找和等待的代码。截图功能集成把截图方法放在基类里非常方便。在用例失败时可以在pytest的夹具中自动调用这个方法为问题排查留下直观证据。3.2 驱动管理告别手动下载和PATH配置让每个团队成员手动下载浏览器驱动、配置系统PATH是协作的噩梦。我们的框架必须解决这个问题。方案自动下载与匹配驱动我们可以使用第三方库webdriver-manager。它会自动检测你电脑上的浏览器版本并下载匹配的驱动到缓存目录无需任何手动干预。# 在conftest.py或专门的driver_manager.py中 from selenium import webdriver from selenium.webdriver.chrome.service import Service as ChromeService from webdriver_manager.chrome import ChromeDriverManager from webdriver_manager.firefox import GeckoDriverManager def get_driver(browser_namechrome): 根据配置创建并返回WebDriver实例 driver None if browser_name.lower() chrome: # 使用webdriver-manager自动管理驱动 service ChromeService(ChromeDriverManager().install()) options webdriver.ChromeOptions() # 常用选项无头模式、忽略证书错误、禁用自动化提示 # options.add_argument(--headless) # 无头模式不打开浏览器窗口 options.add_argument(--ignore-certificate-errors) options.add_experimental_option(excludeSwitches, [enable-automation]) options.add_experimental_option(useAutomationExtension, False) driver webdriver.Chrome(serviceservice, optionsoptions) elif browser_name.lower() firefox: service webdriver.FirefoxService(GeckoDriverManager().install()) options webdriver.FirefoxOptions() driver webdriver.Firefox(serviceservice, optionsoptions) else: raise ValueError(f不支持的浏览器: {browser_name}) # 全局隐性等待辅助主要靠显式等待 driver.implicitly_wait(5) # 最大化窗口 driver.maximize_window() return driver实操心得虽然webdriver-manager很方便但在公司内网环境可能无法连接外网下载。这时备选方案是将常用版本的驱动如chromedriver放在项目drivers/目录下然后在代码中指定驱动路径service ChromeService(executable_path‘./drivers/chromedriver’)。可以在配置文件中增加一个driver_source选项让框架根据配置决定使用哪种方式。3.3 配置文件管理让框架灵活适应不同环境测试环境test、预发布环境staging的URL、数据库连接、账号密码都不同。硬编码在代码里是绝对不可取的。我们用configparser库来读取ini格式的配置文件。configs/config.ini示例[DEFAULT] log_level INFO browser chrome headless false [test] base_url https://test.example.com username test_user password test_pass123 [staging] base_url https://staging.example.com username staging_user password staging_pass456配置读取工具utilities/config_reader.pyimport os import configparser class ConfigReader: 配置文件读取器单例模式确保全局唯一配置 _instance None def __new__(cls): if cls._instance is None: cls._instance super(ConfigReader, cls).__new__(cls) cls._instance.config configparser.ConfigParser() config_path os.path.join(os.path.dirname(__file__), .., configs, config.ini) cls._instance.config.read(config_path, encodingutf-8) return cls._instance def get(self, section, option, fallbackNone): 获取指定section和option的值 return self.config.get(section, option, fallbackfallback) def getboolean(self, section, option, fallbackFalse): 获取布尔值 return self.config.getboolean(section, option, fallbackfallback) # 使用示例 config ConfigReader() base_url config.get(test, base_url) use_headless config.getboolean(DEFAULT, headless)这样当我们需要切换测试环境时只需要在命令行执行用例时传入一个参数或修改配置文件的[DEFAULT]节框架就会自动使用对应环境的配置实现了代码与环境的解耦。4. 实操过程从零搭建框架核心环节理论讲得差不多了现在我们动手把几个最关键的部分串联起来形成一个可运行的闭环。我会以用户登录这个最经典的场景为例展示完整的实现流程。4.1 第一步环境搭建与依赖安装任何项目开始前先准备好环境。我强烈建议使用虚拟环境venv来隔离项目依赖。# 1. 创建项目目录并进入 mkdir automation_framework cd automation_framework # 2. 创建虚拟环境Python3.3内置 python -m venv venv # 3. 激活虚拟环境 # Windows: venv\Scripts\activate # Linux/Mac: source venv/bin/activate # 4. 创建requirements.txt文件并写入核心依赖 # requirements.txt 内容 # selenium4.0.0 # pytest7.0.0 # pytest-html3.0.0 # allure-pytest2.9.0 # webdriver-manager3.8.0 # openpyxl3.0.0 # 如果需要处理Excel测试数据 # 5. 安装依赖 pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple4.2 第二步实现页面对象以登录页面为例基于我们写好的BasePage创建具体的页面类。这里以登录页面为例。首先定义定位符。我习惯用一个单独的类来管理清晰且易于修改。# page_objects/locators/login_page_locators.py class LoginPageLocators: 登录页面所有元素的定位符 # 使用 (By.策略, ‘值’) 的元组形式 USERNAME_INPUT (‘id’, ‘username’) # 假设页面用户名输入框id为‘username’ PASSWORD_INPUT (‘id’, ‘password’) LOGIN_BUTTON (‘xpath’, ‘//button[type“submit”]’) ERROR_MESSAGE (‘css selector’, ‘.alert.alert-danger’) SUCCESS_INDICATOR (‘xpath’, ‘//h1[contains(text(), “Dashboard”)]’)然后实现登录页面类。# page_objects/login_page.py from selenium.webdriver.common.by import By from .base_page import BasePage from .locators.login_page_locators import LoginPageLocators class LoginPage(BasePage): 登录页面操作封装 def __init__(self, driver): super().__init__(driver) # 调用父类初始化传入driver def enter_username(self, username): 输入用户名 self.input_text(LoginPageLocators.USERNAME_INPUT, username) def enter_password(self, password): 输入密码 self.input_text(LoginPageLocators.PASSWORD_INPUT, password) def click_login(self): 点击登录按钮 self.click(LoginPageLocators.LOGIN_BUTTON) def login(self, username, password): 登录完整流程输入用户名、密码、点击登录 self.log.info(f”执行登录操作用户名: {username}“) self.enter_username(username) self.enter_password(password) self.click_login() def get_error_message(self): 获取登录错误提示信息如果存在则返回文本否则返回空字符串 if self.is_element_visible(LoginPageLocators.ERROR_MESSAGE, timeout3): return self.get_text(LoginPageLocators.ERROR_MESSAGE) return ”“ def is_login_successful(self): 通过检查登录后页面特定元素如Dashboard标题来判断是否登录成功 return self.is_element_visible(LoginPageLocators.SUCCESS_INDICATOR, timeout10)可以看到LoginPage类非常干净它只包含这个页面特有的操作。所有与浏览器交互的底层细节等待、查找、点击都被BasePage消化了。这就是封装的魅力。4.3 第三步编写Pytest测试用例与夹具现在我们用pytest来编写和组织测试用例。conftest.py是pytest的本地插件文件在这里我们可以定义全局共享的夹具fixture。# test_cases/conftest.py import pytest from utilities.driver_manager import get_driver # 假设我们把get_driver函数放在这里 from utilities.config_reader import ConfigReader config ConfigReader() pytest.fixture(scope“function”) def driver(): 为每个测试函数提供独立的driver实例 browser config.get(‘DEFAULT’, ‘browser’, fallback‘chrome’) headless config.getboolean(‘DEFAULT’, ‘headless’, fallbackFalse) # 这里可以基于headless参数调整浏览器选项代码略 driver_instance get_driver(browser) yield driver_instance # yield之前是setup之后是teardown # 测试函数执行完毕后执行清理 driver_instance.quit() print(”浏览器已关闭“) pytest.fixture(scope“function”) def login_page(driver): 提供一个初始化好的LoginPage实例 from page_objects.login_page import LoginPage base_url config.get(‘test’, ‘base_url’) page LoginPage(driver) page.open(base_url ‘/login’) # 拼接登录页具体路径 return page夹具driver的作用域是function意味着每个测试用例都会启动和关闭一次浏览器保证用例间的隔离。login_page夹具则直接返回一个已经打开了登录页面的LoginPage对象方便用例使用。接下来编写真正的测试用例。# test_cases/test_login.py import pytest from utilities.config_reader import ConfigReader config ConfigReader() class TestLogin: 登录功能测试集 def test_login_success(self, login_page): 测试正常登录流程 username config.get(‘test’, ‘username’) password config.get(‘test’, ‘password’) login_page.login(username, password) # 断言登录成功后应该能看到成功标识如Dashboard标题 assert login_page.is_login_successful(), f”登录失败未找到成功页面元素“ # 可以添加更多断言如检查URL、页面标题等 # assert “dashboard” in login_page.driver.current_url.lower() def test_login_failure_with_wrong_password(self, login_page): 测试使用错误密码登录 username config.get(‘test’, ‘username’) wrong_password “wrong_password” login_page.login(username, wrong_password) # 断言应该出现错误提示信息 error_msg login_page.get_error_message() assert error_msg ! ”“, “预期出现错误提示但未找到” assert “密码错误” in error_msg or “invalid” in error_msg.lower() # 根据实际提示调整 pytest.mark.parametrize(“username, password, expected”, [ (“”, “test_pass”, “用户名不能为空”), # 用户名空 (“test_user”, “”, “密码不能为空”), # 密码空 (“not_exist”, “test_pass”, “用户不存在”), # 用户不存在 ]) def test_login_validation(self, login_page, username, password, expected): 参数化测试测试各种边界和异常情况 login_page.login(username, password) error_msg login_page.get_error_message() assert expected in error_msg, f”错误提示不符。预期包含‘{expected}’实际为‘{error_msg}’“这个测试文件展示了三种典型的测试用例写法1正常流程测试2异常流程测试3使用pytest.mark.parametrize的参数化测试用一组数据驱动多个测试场景极大减少了代码重复。4.4 第四步生成漂亮的测试报告测试跑完了结果不能只停留在控制台。我们需要一份直观、详细的报告。pytest-html可以生成基础的HTML报告而allure则可以生成非常美观且功能强大的报告。使用pytest-html简单快捷# 运行测试并生成html报告 pytest test_cases/test_login.py -v --htmlreports/report.html --self-contained-html--self-contained-html参数会把CSS等资源打包进一个HTML文件方便分享。使用Allure推荐更专业首先需要安装Allure命令行工具从官网下载或通过包管理器。运行测试时让pytest生成allure结果数据。pytest test_cases/test_login.py -v --alluredir./reports/allure-results生成并打开Allure报告。allure generate ./reports/allure-results -o ./reports/allure-report --clean allure open ./reports/allure-reportAllure报告会展示用例执行时间、通过率、失败截图、步骤详情等非常适合在团队中分享和进行问题分析。我们可以把生成报告的步骤写进一个run_tests.py脚本或Makefile里一键执行。5. 常见问题与排查技巧实录框架搭起来了用例也写了但在实际运行中你一定会遇到各种各样的问题。下面是我在多年实践中总结的一些高频问题和解决思路相当于一份“急救手册”。5.1 元素定位失败自动化测试的头号杀手超过70%的自动化测试失败源于元素定位问题。错误信息通常是NoSuchElementException或TimeoutException。排查清单检查定位符是否正确这是第一步也是最常见的一步。用浏览器的开发者工具F12重新检查元素的id、name、class或XPath是否唯一、是否稳定。警惕动态ID包含随机字符串。检查页面是否加载完成虽然我们用了显式等待但等待条件可能不对。presence_of_element_located只要求元素存在于DOM中但可能不可见或不可交互。如果是要点击或输入尝试改用element_to_be_clickable或visibility_of_element_located。# 更稳健的点击操作封装 def click_safely(self, locator): element self.wait.until(EC.element_to_be_clickable(locator)) element.click()检查是否有iframe如果目标元素在iframe内部你必须先切换到对应的iframe才能定位到里面的元素。# 切换到iframe iframe self.find_element((‘tag name‘ ’iframe‘)) self.driver.switch_to.frame(iframe) # 操作iframe内的元素... # 操作完成后切回主文档 self.driver.switch_to.default_content()检查是否有新窗口/标签页点击某个链接后打开了新窗口driver需要切换过去。# 获取当前所有窗口句柄 original_window self.driver.current_window_handle # 点击打开新窗口的操作... # 等待新窗口出现 WebDriverWait(self.driver, 10).until(EC.number_of_windows_to_be(2)) # 循环找到新窗口句柄并切换 for window_handle in self.driver.window_handles: if window_handle ! original_window: self.driver.switch_to.window(window_handle) break等待时间不足网络慢或页面渲染慢。适当增加显式等待的超时时间或者在操作前加入固定的sleep不推荐应作为最后手段或等待特定条件如某个加载动画消失。实操心得对于难以定位或不稳定的元素不要死磕XPath或CSS。可以尝试与开发沟通给关键测试元素添加固定的id或>pytest.fixture(autouseTrue) def clear_browser_data(self, driver): 每个测试自动执行清理cookies和local storage driver.delete_all_cookies() driver.execute_script(“window.localStorage.clear();”) yield使用不同的测试账号对于需要登录的测试准备一套独立的测试账号池避免多个用例同时操作同一个账号的数据导致冲突。5.3 测试数据的管理与参数化测试数据如用户名、商品ID硬编码在用例里是坏习惯。管理测试数据我推荐两种方式JSON/YAML文件适合结构简单、层次清晰的数据。// test_data/login_data.json { “valid_credentials”: { “username”: “standard_user”, “password”: “secret_sauce” }, “invalid_credentials”: [ {“username”: “”, “password”: “secret_sauce”, “error”: “Username is required”}, {“username”: “locked_user”, “password”: “secret_sauce”, “error”: “Sorry, this user has been locked out.”} ] }Excel/CSV文件适合测试人员维护尤其是大量、表格化的数据。可以使用openpyxl或pandas库读取。pytest.mark.parametrize装饰器对于直接在代码里定义的小规模参数化数据这是最方便的方式如前文示例。最佳实践是将静态的、大量的测试数据放在外部文件JSON/Excel中在conftest.py里读取并转换为pytest可用的参数化格式或者通过自定义夹具提供给用例。5.4 如何在CI/CD流水线中运行无头模式在Jenkins、GitLab CI等持续集成环境中通常没有图形界面。这时需要以“无头模式”运行浏览器。修改get_driver函数增加无头模式选项def get_driver(browser_name“chrome”, headlessFalse): if browser_name.lower() “chrome”: options webdriver.ChromeOptions() if headless: options.add_argument(‘--headlessnew’) # Chrome较新版本推荐使用new options.add_argument(‘--no-sandbox’) # Linux CI环境常需此参数 options.add_argument(‘--disable-dev-shm-usage’) # 解决共享内存问题 options.add_argument(‘--disable-gpu’) # 可选 options.add_argument(‘--window-size1920,1080’) # 设置无头模式下的窗口大小 # ... 其他选项 driver webdriver.Chrome(serviceservice, optionsoptions) # ... firefox 类似在CI的配置文件中通过环境变量或配置文件将headless设置为True即可。5.5 失败用例自动截图与日志追踪测试失败时光有错误堆栈是不够的。自动截图和详尽的日志是快速定位问题的关键。我们可以在pytest的夹具中捕获测试失败的事件并自动调用我们BasePage里的截图方法。# test_cases/conftest.py import pytest from utilities.config_reader import ConfigReader config ConfigReader() pytest.hookimpl(tryfirstTrue, hookwrapperTrue) def pytest_runtest_makereport(item, call): Hook函数用于在测试执行后获取结果并截图 outcome yield report outcome.get_result() # 只关注测试用例不包括setup/teardown的失败情况 if report.when “call” and report.failed: # 从测试用例中获取driver对象假设driver夹具名为‘driver’ driver_fixture item.funcargs.get(‘driver’) if driver_fixture: # 生成唯一的截图文件名 screenshot_name f”{item.name}_{report.when}” try: # 调用driver的截图方法这里需要你的page对象或driver有截图方法 # 假设我们有一个全局可用的截图工具函数 from utilities.screenshot_helper import take_screenshot take_screenshot(driver_fixture, screenshot_name) # 或者如果测试用例使用了page对象可以这样 # page item.funcargs.get(‘login_page’) # if page: # page.take_screenshot(screenshot_name) print(f”失败截图已保存: {screenshot_name}“) except Exception as e: print(f”截图失败: {e}“)同时确保我们的日志配置utilities/logger.py将不同级别的日志输出到文件这样结合Allure报告就能形成一个从“测试步骤日志”到“最终错误截图”的完整问题追溯链。搭建一个“简单”的自动化测试框架远不止是把几行Selenium代码包装起来。它涉及到项目结构设计、代码封装、配置管理、报告生成和持续集成等一系列工程化实践。这个过程中最大的挑战往往不是技术本身而是如何让框架足够健壮以应对千变万化的Web页面以及如何让它足够简单以被团队所有成员接受和使用。我分享的这个框架雏形已经包含了应对这些挑战的核心思路和关键实现。你可以以此为基础根据自己项目的实际需求添加更多功能比如数据库校验、API调用混合测试、分布式测试支持等。记住框架是为人服务的切忌过度设计解决实际问题的框架才是好框架。