Python+Selenium+Chrome自动化测试框架:从POM模式到CI/CD集成的工程实践
1. 项目概述为什么我们需要一个PythonSeleniumChrome的自动化测试框架如果你是一名测试工程师或者正在向这个方向转型那你一定对“重复劳动”深恶痛绝。想象一下每次版本迭代你都需要手动打开浏览器点击几十个甚至上百个页面填写表单验证结果检查UI元素……这不仅枯燥而且极易出错尤其是在回归测试阶段。这正是自动化测试框架要解决的核心痛点。今天要聊的就是基于Python、Selenium和Chrome浏览器搭建一个稳定、可维护的自动化测试框架。这不是一个简单的脚本合集而是一个有结构、有规范、能应对复杂场景的工程化解决方案。Python以其简洁的语法和丰富的生态成为了自动化测试的首选语言。Selenium则是浏览器自动化的“行业标准”它提供了一套WebDriver协议允许我们用代码像真人一样操作浏览器。而Chrome浏览器凭借其稳定的性能和广泛的市场占有率是自动化测试最常用的目标环境之一。将这三大件组合起来你就能构建一个覆盖Web应用前端功能验证的强力工具。这个框架的目标是让你从重复的手工测试中解放出来将精力投入到更有价值的测试用例设计、探索性测试和性能分析上去。无论你是零基础的测试新人还是想优化现有测试流程的资深工程师这套组合都能为你提供一个坚实的起点。2. 框架整体设计与核心思路拆解2.1 框架的顶层架构我们到底在构建什么一个健壮的自动化测试框架远不止是写几个find_element和click。我们需要考虑脚本的可读性、可维护性、可扩展性和稳定性。基于这些原则一个典型的PythonSelenium框架通常会采用分层设计的思想。最底层是驱动层负责与Selenium WebDriver交互封装最基本的浏览器操作如打开、关闭、查找元素等。中间层是页面对象层这是框架的核心它将每个Web页面或页面组件抽象成一个类页面的元素定位和基本操作都封装在这个类里。最上层是测试用例层这里编写具体的测试逻辑调用页面对象提供的方法并进行断言验证。为什么选择页面对象模式Page Object Model POM这是血泪教训换来的最佳实践。早期很多人直接把元素定位和操作逻辑写在测试用例里结果就是当页面UI稍有改动比如一个按钮的ID变了你就需要翻遍几十个测试脚本去修改对应的定位器维护成本呈指数级上升。POM模式将变化隔离在页面对象类中UI变了你只需要修改对应的页面类所有引用该页面的测试用例都无需改动。这极大地提升了框架的健壮性。除了POM我们还需要考虑测试数据的管理、测试报告的生成、失败用例的自动重试、并发执行等。一个完整的框架通常会包含以下目录结构config/: 存放配置文件如数据库连接、测试环境URL、浏览器类型等。drivers/: 存放浏览器驱动如chromedriver.exe。pages/: 存放所有页面对象类。test_cases/: 存放具体的测试用例脚本。test_data/: 存放测试数据文件可以是JSON、YAML或Excel。utils/: 存放工具类如读取配置文件、操作数据库、生成日志、发送邮件等。reports/: 存放自动生成的测试报告。logs/: 存放运行日志。这样的结构清晰明了任何新人加入项目都能快速理解代码组织方式便于协作。2.2 工具选型背后的逻辑为什么是PythonSeleniumChromePytestPython: 选择Python不仅仅是因为它简单。在测试领域pytest和unittest这两个强大的测试框架生态是决定性因素。尤其是pytest它支持丰富的插件如pytest-html生成报告、pytest-xdist并行执行、灵活的夹具fixture管理、以及更简洁的断言语法能让我们以极低的成本实现高级功能。Selenium: 它是事实上的Web自动化标准支持所有主流浏览器社区活跃遇到问题几乎都能找到解决方案。虽然也有Playwright和Cypress等后起之秀但Selenium的普适性和稳定性在目前的企业环境中依然不可替代。Chrome: 选择Chrome作为默认测试浏览器是因为其市场占有率最高且ChromeDriver的更新和维护非常及时。对于需要兼容多浏览器的项目框架可以设计成支持通过配置轻松切换浏览器如Firefox、Edge但初期以Chrome为基准能简化很多工作。Pytest vs Unittest: 我强烈推荐pytest。unittest是Python标准库更“正统”但pytest的语法更Pythonic夹具系统更强大。例如用pytest你可以轻松地为一个测试类或模块设置前置和后置操作而unittest需要重写setUp和tearDown方法灵活性稍差。注意不要纠结于寻找“唯一正确”的工具链。这套组合是经过大量项目验证的、平衡了学习成本、社区支持和工程能力的“黄金组合”。先基于此搭建起来跑通核心流程比在选型上徘徊不前要重要得多。3. 环境搭建与核心组件配置详解3.1 Python环境与项目依赖管理第一步是安装Python。建议直接去Python官网下载最新稳定版本如Python 3.9。安装时务必勾选“Add Python to PATH”这样可以在命令行直接使用python和pip命令。安装完成后我们需要为自动化测试项目创建一个独立的虚拟环境。这是Python开发的最佳实践可以避免不同项目间的依赖冲突。打开命令行进入你的项目目录执行以下命令# 创建虚拟环境环境文件夹名为 venv python -m venv venv # 激活虚拟环境Windows venv\Scripts\activate # 激活虚拟环境MacOS/Linux source venv/bin/activate激活后命令行提示符前会出现(venv)字样表示你已进入该虚拟环境。接下来使用pip安装核心依赖。我们将依赖包列表写在一个requirements.txt文件中便于管理和复现环境。# requirements.txt selenium4.0.0 pytest7.0.0 pytest-html3.0.0 pytest-xdist3.0.0 webdriver-manager3.0.0 allure-pytest2.0.0 openpyxl3.0.0 # 用于操作Excel测试数据 PyYAML6.0 # 用于操作YAML配置文件然后执行安装pip install -r requirements.txt这里重点说一下webdriver-manager这个库。它是一个神器能自动下载和管理不同浏览器的驱动如ChromeDriver。以前我们需要手动下载与浏览器版本匹配的驱动并配置路径非常麻烦。有了它你只需要在代码中声明使用webdriver-manager它就会自动处理这一切极大降低了环境配置的复杂度。3.2 Selenium与ChromeDriver的“握手”协议Selenium通过WebDriver协议与浏览器通信。ChromeDriver就是这个协议的实现者它作为一个独立的服务进程运行接收Selenium客户端我们的Python脚本发送的指令如“打开页面”、“点击元素”并将其翻译成浏览器能执行的操作。在selenium 4之前我们需要手动启动ChromeDriver服务。现在借助webdriver-manager代码可以简化到极致from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager # 使用 webdriver-manager 自动管理驱动 service Service(ChromeDriverManager().install()) driver webdriver.Chrome(serviceservice) driver.get(https://www.baidu.com)这段代码会检查当前系统Chrome版本自动下载匹配的ChromeDriver并启动浏览器。但是在实际的自动化测试框架中我们不会把驱动初始化代码散落在各个测试用例里。我们需要将其封装起来。3.3 浏览器启动选项与驱动层封装为了测试的稳定性和一致性我们通常需要对浏览器进行一些配置。例如我们可能不需要看到浏览器GUI图形界面可以在无头模式下运行或者需要忽略SSL证书错误又或者需要禁用一些弹窗。# utils/driver.py from selenium import webdriver from selenium.webdriver.chrome.service import Service from selenium.webdriver.chrome.options import Options from webdriver_manager.chrome import ChromeDriverManager import logging class Driver: _instance None def __new__(cls, headlessFalse): if cls._instance is None: cls._instance super().__new__(cls) cls._instance._init_driver(headless) return cls._instance.driver def _init_driver(self, headless): chrome_options Options() # 常用配置 chrome_options.add_argument(--disable-gpu) # 禁用GPU加速在某些环境下更稳定 chrome_options.add_argument(--no-sandbox) # 在Linux环境下常需要此参数 chrome_options.add_argument(--disable-dev-shm-usage) # 解决共享内存问题 chrome_options.add_experimental_option(excludeSwitches, [enable-logging]) # 禁止控制台日志 chrome_options.add_experimental_option(prefs, { profile.default_content_setting_values.notifications: 2, # 禁用通知 credentials_enable_service: False, # 禁用密码保存提示 profile.password_manager_enabled: False }) if headless: chrome_options.add_argument(--headless) # 无头模式 chrome_options.add_argument(--window-size1920,1080) # 无头模式需指定窗口大小 # 使用webdriver-manager service Service(ChromeDriverManager().install()) self.driver webdriver.Chrome(serviceservice, optionschrome_options) self.driver.implicitly_wait(10) # 设置隐式等待10秒 self.driver.maximize_window() # 最大化窗口 logging.info(Chrome浏览器驱动初始化成功。) classmethod def quit_driver(cls): if cls._instance and cls._instance.driver: cls._instance.driver.quit() cls._instance None logging.info(浏览器驱动已退出。)这个Driver类使用了单例模式确保在整个测试运行过程中我们只初始化一个浏览器实例。它封装了浏览器的初始化过程并提供了统一的退出接口。测试用例只需调用Driver()即可获得驱动对象无需关心底层细节。实操心得隐式等待implicitly_wait是一把双刃剑。它会在查找元素时如果元素没有立即出现会等待设定的时间如10秒。这能解决大部分元素加载慢的问题。但全局设置可能会在某些场景下拖慢测试速度比如你明确知道某个元素不应该出现想立刻断言它不存在。更精细的控制是使用显式等待WebDriverWait我们会在页面对象层介绍。4. 页面对象模式POM的深度实现4.1 基类设计封装通用操作与等待机制在实现具体的页面类之前我们先创建一个所有页面类的基类。这个基类负责初始化驱动并提供一些所有页面都可能用到的通用方法特别是显式等待。# 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 logging class BasePage: def __init__(self, driver): self.driver driver self.wait WebDriverWait(driver, 10) # 显式等待超时时间10秒 def find_element(self, locator): 查找单个元素使用显式等待 try: element self.wait.until(EC.presence_of_element_located(locator)) return element except TimeoutException: logging.error(f查找元素超时: {locator}) raise def find_elements(self, locator): 查找多个元素 try: elements self.wait.until(EC.presence_of_all_elements_located(locator)) return elements except TimeoutException: logging.error(f查找元素列表超时: {locator}) return [] # 返回空列表而不是抛出异常有时更灵活 def click(self, locator): 点击元素等待元素可点击 element self.wait.until(EC.element_to_be_clickable(locator)) element.click() def input_text(self, locator, text): 输入文本先清空再输入 element self.find_element(locator) element.clear() element.send_keys(text) def get_text(self, locator): 获取元素文本 element self.find_element(locator) return element.text def is_element_visible(self, locator, timeout5): 判断元素是否可见短超时 try: WebDriverWait(self.driver, timeout).until(EC.visibility_of_element_located(locator)) return True except TimeoutException: return False显式等待是自动化测试稳定的关键。WebDriverWait配合expected_conditionsEC可以等待各种条件如元素可见、可点击、存在等。这比固定的sleep和全局的隐式等待更精确、更高效。4.2 具体页面类的实现以登录页面为例现在我们来创建一个具体的页面类。假设我们有一个登录页面包含用户名输入框、密码输入框和登录按钮。# pages/login_page.py from pages.base_page import BasePage from selenium.webdriver.common.by import By import logging class LoginPage(BasePage): # 1. 定位器将页面元素定位方式集中管理 USERNAME_INPUT (By.ID, username) PASSWORD_INPUT (By.ID, password) LOGIN_BUTTON (By.XPATH, //button[typesubmit]) ERROR_MSG (By.CLASS_NAME, alert-error) # 2. 页面URL可选便于导航 URL https://your-test-site.com/login def __init__(self, driver): super().__init__(driver) def navigate_to(self): 导航到登录页面 self.driver.get(self.URL) logging.info(f导航到登录页面: {self.URL}) def login(self, username, password): 登录操作输入用户名、密码并点击登录 logging.info(f尝试登录用户名: {username}) self.input_text(self.USERNAME_INPUT, username) self.input_text(self.PASSWORD_INPUT, password) self.click(self.LOGIN_BUTTON) def get_error_message(self): 获取登录错误提示信息 if self.is_element_visible(self.ERROR_MSG, timeout3): return self.get_text(self.ERROR_MSG) return None def is_login_successful(self): 验证登录是否成功示例通过检查是否跳转到首页或出现用户菜单 # 这里需要根据实际应用的成功状态来定义例如检查URL或某个特定元素 # 假设登录成功后页面标题会变化 return Dashboard in self.driver.title在这个类里所有关于登录页面的细节都被封装起来了。测试用例作者不需要知道用户名输入框的ID是什么他只需要调用login_page.login(admin, 123456)。如果前端开发人员把ID从username改成了userName你只需要修改LoginPage类中的USERNAME_INPUT定位器所有测试用例完全不受影响。这就是POM模式维护性高的体现。4.3 元素定位策略与等待的最佳实践元素定位是Selenium自动化中最基础也最容易出问题的一环。定位器优先级从高到低ID: 唯一且稳定是最佳选择。By.IDName: 通常也唯一但不如ID可靠。By.NAMECSS Selector: 功能强大性能好语法灵活。By.CSS_SELECTOR例如#username(ID),.btn-primary(class),input[typetext](属性)XPath: 功能最强大可以遍历整个DOM但性能稍差且容易因页面结构微小变动而失效。慎用。By.XPATH尽量使用相对路径和非索引依赖的表达式。避免使用/html/body/div[3]/div[2]/form/input[1]这种绝对且脆弱的路径。等待策略强制等待(time.sleep()): 除非万不得已如等待一个非Web的桌面弹窗否则禁止使用。它是测试脚本性能的杀手且时间难以精确设定。隐式等待(implicitly_wait): 在驱动层设置一个全局的等待时间。它针对所有的find_element操作。简单但不够灵活可能会在“等待元素不存在”的场景下产生不必要的等待。显式等待(WebDriverWait):推荐使用。针对特定元素和特定条件进行等待。如等待元素可点击、可见、存在等。它提供了最佳的性能和稳定性平衡。注意事项在Page类中封装操作时一个常见的坑是“过时元素引用”StaleElementReferenceException。这通常发生在你找到了一个元素但页面随后发生了刷新或AJAX更新之前找到的元素对象就“过时”了。解决方法是在每次操作前重新查找元素或者使用expected_conditions的staleness_of条件来等待旧元素失效后再获取新元素。5. 测试用例的组织与Pytest高级应用5.1 测试用例结构清晰、独立、可读有了页面对象编写测试用例就变得非常直观。我们使用pytest来组织用例。# test_cases/test_login.py import pytest from pages.login_page import LoginPage from utils.driver import Driver import logging class TestLogin: 登录功能测试集 pytest.fixture(scopeclass) def driver(self): 测试类级别的fixture整个测试类共用同一个浏览器实例 d Driver(headlessTrue) # 无头模式运行不打开GUI适合CI环境 yield d Driver.quit_driver() # 测试类结束后退出浏览器 pytest.fixture def login_page(self, driver): 每个测试用例都会获取一个新的登录页面实例 page LoginPage(driver) page.navigate_to() return page def test_login_success(self, login_page): 测试正常登录流程 login_page.login(valid_user, valid_password) assert login_page.is_login_successful() is True, 登录成功后应跳转到Dashboard页面 logging.info(正常登录测试通过。) def test_login_with_wrong_password(self, login_page): 测试密码错误场景 login_page.login(valid_user, wrong_password) error_msg login_page.get_error_message() assert error_msg is not None, 密码错误时应显示错误信息 assert 密码错误 in error_msg, f错误信息应为‘密码错误’实际是‘{error_msg}’ logging.info(密码错误测试通过。) pytest.mark.parametrize(username, password, [ (, somepassword), # 用户名为空 (someuser, ), # 密码为空 (, ), # 两者都为空 ]) def test_login_with_empty_credentials(self, login_page, username, password): 参数化测试测试用户名或密码为空的边界情况 login_page.login(username, password) # 假设空输入会提示“用户名和密码不能为空” error_msg login_page.get_error_message() assert error_msg is not None assert 不能为空 in error_msg logging.info(f空值测试通过: username{username}, password{password})在这个测试类中我们使用了pytest的fixture。driver fixture是类级别的意味着整个TestLogin类中的所有测试方法都使用同一个浏览器会话这提高了测试速度。login_page fixture是方法级别的每个测试方法都会执行一次navigate_to()确保测试起始状态一致相互隔离。pytest.mark.parametrize装饰器实现了数据驱动测试用一组数据运行同一个测试逻辑避免了写多个重复的测试方法让用例更简洁。5.2 夹具Fixture的妙用管理测试生命周期pytest的夹具系统是其灵魂。除了管理驱动和页面我们还可以用它来做更多事情测试数据准备与清理例如在测试用户创建功能前通过API或数据库创建一个测试用户测试结束后再将其删除。登录状态共享很多测试用例需要已登录的状态。可以写一个logged_in_fixture它内部调用登录页面的登录方法并返回一个已登录状态的主页对象供其他测试用例使用。失败截图写一个夹具在测试用例失败时自动截图并保存到报告目录。# conftest.py (这个文件是pytest的本地插件其中的fixture可以被同一目录及子目录下的所有测试文件使用) import pytest from utils.driver import Driver import allure import os pytest.fixture(scopesession) # 会话级别所有测试只运行一次 def driver(): 全局浏览器驱动 d Driver(headlessFalse) # 调试时可设为False查看浏览器操作 yield d Driver.quit_driver() pytest.hookimpl(tryfirstTrue, hookwrapperTrue) def pytest_runtest_makereport(item, call): 钩子函数用于在测试失败时截图并附加到Allure报告 outcome yield rep outcome.get_result() if rep.when call and rep.failed: # 获取driver fixture driver_fixture item.funcargs.get(driver, None) if driver_fixture is not None: # 截图 screenshot_dir ./reports/screenshots os.makedirs(screenshot_dir, exist_okTrue) screenshot_path os.path.join(screenshot_dir, f{item.name}.png) driver_fixture.save_screenshot(screenshot_path) # 将截图附加到Allure报告 allure.attach.file(screenshot_path, name失败截图, attachment_typeallure.attachment_type.PNG)conftest.py文件非常强大可以定义项目级别的共享夹具和钩子函数。5.3 测试报告与日志让结果一目了然测试执行完了我们需要一份清晰的报告。pytest-html可以生成基础的HTML报告而Allure可以生成非常美观且功能强大的交互式报告。使用pytest-html生成报告pytest test_cases/ -v --html./reports/report.html --self-contained-html--self-contained-html参数会将CSS和JS都打包进一个HTML文件方便传阅。使用Allure生成报告 首先你需要安装Allure命令行工具。然后在运行测试时添加参数pytest test_cases/ -v --alluredir./reports/allure-results运行后生成的是原始数据。需要再用Allure命令生成可查看的HTML报告allure generate ./reports/allure-results -o ./reports/allure-report --clean allure open ./reports/allure-reportAllure报告会展示测试套件、通过率、失败用例、截图、日志、甚至测试步骤的详细时间线对于分析测试结果非常有帮助。日志记录在框架中集成Python标准库的logging模块在关键步骤如初始化驱动、执行操作、断言记录信息。当测试失败时日志文件是排查问题的第一手资料。可以将日志级别设置为INFO用于日常运行在调试时设置为DEBUG以获取更详细的信息。6. 框架的扩展与高级主题6.1 测试数据驱动从代码中分离数据硬编码的测试数据如上面的valid_user不利于维护。我们应该将测试数据外置。常用的格式有JSON、YAML和Excel。使用YAML文件管理测试数据# test_data/login_data.yaml success: username: standard_user password: secret_sauce expected_title: Swag Labs failure: wrong_password: username: standard_user password: wrong expected_error: Epic sadface: Username and password do not match locked_user: username: locked_out_user password: secret_sauce expected_error: Epic sadface: Sorry, this user has been locked out.然后在测试用例中读取import yaml import os def load_login_data(): data_file os.path.join(os.path.dirname(__file__), ../test_data/login_data.yaml) with open(data_file, r, encodingutf-8) as f: return yaml.safe_load(f) class TestLoginWithData: pytest.mark.parametrize(scenario, data, [ (success, load_login_data()[success]), (wrong_password, load_login_data()[failure][wrong_password]), (locked_user, load_login_data()[failure][locked_user]), ]) def test_login_with_data_driven(self, login_page, scenario, data): login_page.login(data[username], data[password]) if scenario success: assert data[expected_title] in login_page.driver.title else: error_msg login_page.get_error_message() assert data[expected_error] in error_msg这样当测试数据需要变更时你只需要修改YAML文件无需触碰测试代码。6.2 并发测试与分布式执行当测试用例成百上千时串行执行会非常耗时。pytest-xdist插件可以实现测试的并行执行。pytest test_cases/ -n auto # 自动检测CPU核心数并行执行 pytest test_cases/ -n 4 # 指定4个worker并行执行需要注意的是并行测试对测试用例的独立性要求极高。用例之间不能有状态依赖比如用例A创建的数据用例B会用到。所有用例都应该能独立运行并且可以以任意顺序运行。这要求我们在fixture中做好测试环境的准备和清理工作例如每个用例使用独立的测试账号。6.3 集成到CI/CD流水线自动化测试只有集成到持续集成/持续部署CI/CD流程中才能最大化其价值。你可以在Jenkins、GitLab CI、GitHub Actions等工具中配置一个任务在每次代码提交或定时触发时执行以下步骤拉取最新代码。创建Python虚拟环境并安装依赖 (pip install -r requirements.txt)。运行测试命令 (pytest --alluredir./results)。生成测试报告 (allure generate。将测试报告发布到内部网站或通过邮件/即时通讯工具通知测试结果。在CI环境中通常会在无头模式下运行测试并且可能需要配置一些额外的Chrome选项以适应无GUI的Linux服务器环境例如前面提到的--no-sandbox和--disable-dev-shm-usage。7. 常见问题排查与实战技巧实录即使框架搭建得再完善在实际运行中还是会遇到各种“坑”。这里记录一些高频问题和解决思路。7.1 元素定位失败自动化测试的头号敌人问题现象NoSuchElementException,TimeoutException。排查思路确认定位器是否正确首先在浏览器的开发者工具F12中使用Console验证你的CSS Selector或XPath。例如输入$$(#username)(CSS) 或$x(//input[idusername])(XPath)看是否能找到元素。检查页面是否加载完成有时脚本执行太快页面元素还没渲染出来。确保使用了合适的等待显式等待优于隐式等待和强制等待。检查等待的条件是否合适presence_of_element_located元素存在 vsvisibility_of_element_located元素可见。检查元素是否在iframe/frame内如果元素位于iframe中你必须先切换到对应的iframe才能定位其中的元素。# 通过ID或索引切换 driver.switch_to.frame(iframe_id) driver.switch_to.frame(0) # 操作iframe内的元素... # 操作完成后切回主文档 driver.switch_to.default_content()检查元素是否在新窗口/标签页点击某个链接后可能打开了新窗口。你需要切换句柄window handle。main_window driver.current_window_handle # 点击打开新窗口的链接... all_windows driver.window_handles new_window [window for window in all_windows if window ! main_window][0] driver.switch_to.window(new_window) # 操作新窗口... driver.close() # 关闭新窗口 driver.switch_to.window(main_window) # 切回原窗口动态ID或类名有些前端框架如React、Vue会生成随机的ID或类名。避免使用这些动态属性定位。寻找其父元素或兄弟元素中稳定的属性或者使用包含部分文本的XPath//button[contains(text(), 提交)]但需注意多语言问题。7.2 异步加载与AJAX等待现代Web应用大量使用AJAX异步加载数据。点击一个按钮后数据可能过一会儿才显示出来。解决方案不要用sleep使用显式等待等待特定元素出现或状态改变。from selenium.webdriver.support.expected_conditions import presence_of_element_located, text_to_be_present_in_element # 等待一个加载中的GIF消失 wait.until(EC.invisibility_of_element_located((By.ID, loading-spinner))) # 等待表格中某行出现特定文本 wait.until(text_to_be_present_in_element((By.XPATH, //table/tbody/tr[1]/td[2]), 期望的文本))7.3 浏览器弹窗与警报框对于JavaScript的alert,confirm,prompt对话框需要使用switch_to.alert。# 点击触发alert的按钮 driver.find_element(...).click() # 切换到alert alert driver.switch_to.alert # 获取文本并接受点击确定 print(alert.text) alert.accept() # 或者取消点击取消 # alert.dismiss() # 对于prompt还可以输入文本 # alert.send_keys(输入的文字) # alert.accept()7.4 文件上传与下载文件上传对于input typefile元素直接使用send_keys传入文件绝对路径即可。千万不要尝试用Selenium去操作系统的文件选择对话框那是操作系统的原生窗口Selenium无法控制。upload_element driver.find_element(By.XPATH, //input[typefile]) upload_element.send_keys(/Users/yourname/Desktop/test_image.jpg)文件下载需要设置Chrome的下载偏好并指定下载目录。chrome_options Options() prefs { download.default_directory: /path/to/your/download/folder, download.prompt_for_download: False, # 禁止下载提示 download.directory_upgrade: True, safebrowsing.enabled: True } chrome_options.add_experimental_option(prefs, prefs)然后通过检查下载目录下是否出现目标文件并结合文件后缀、文件名或等待一段时间来判断下载是否完成。可以使用Python的os.path模块来检查文件。7.5 稳定性提升重试机制与截图网络波动、资源加载慢都可能导致偶发性失败。我们可以为测试用例添加重试机制。pytest有pytest-rerunfailures插件。pip install pytest-rerunfailures pytest test_cases/ --reruns 3 --reruns-delay 2 # 失败后重试3次每次间隔2秒同时结合之前提到的conftest.py中的钩子函数在每次失败时自动截图能为后续分析提供最直接的证据。搭建一个自动化测试框架就像盖房子从打下坚实的地基环境与驱动封装到砌起坚固的墙体页面对象模式再到进行精致的装修测试用例、数据驱动、报告每一步都需要考虑到可维护性和扩展性。这个基于PythonSeleniumChrome的框架方案提供了一套经过实战检验的完整蓝图。记住框架是为你服务的工具不要陷入过度设计的陷阱。先从核心业务流程的自动化开始让它跑起来再随着项目复杂度的提升逐步引入更高级的特性和优化。在不断的“编码-运行-调试-优化”循环中你会对这个框架了如指掌并能根据自己团队的独特需求打造出最趁手的测试利器。