
1. 项目概述为什么是SeleniumPytest如果你正在为Web应用的回归测试、兼容性测试或者日常的冒烟测试而头疼每次手动点点点不仅效率低下还容易出错那么构建一个属于自己的自动化测试框架可能就是你现在最需要投入精力去做的事情。在众多技术选型中Selenium负责模拟浏览器操作Pytest负责组织和管理测试用例这两者的组合几乎成了Python自动化测试领域的“黄金搭档”。这个项目就是要带你从零开始手把手搭建一个结构清晰、易于维护、功能强大的SeleniumPytest自动化测试框架。这不仅仅是写几个测试脚本而是构建一个包含用例管理、数据驱动、失败重试、多浏览器支持、以及美观测试报告在内的完整工程体系。无论你是刚接触自动化测试的新手还是想优化现有测试流程的测试开发工程师这套实战方案都能为你提供直接的、可复现的参考。2. 框架整体设计与核心思路拆解2.1 为什么选择SeleniumPytest这个组合在开始动手之前我们先要搞清楚为什么是它们俩。市面上有很多工具比如Playwright、Cypress等后起之秀但SeleniumPytest的组合依然拥有强大的生命力和广泛的社区基础。Selenium WebDriver的核心价值在于其标准化和跨浏览器能力。它遵循W3C标准这意味着你写的脚本理论上可以在Chrome、Firefox、Edge、Safari等主流浏览器上运行对于需要做浏览器兼容性测试的项目来说这是刚需。虽然像Playwright在性能和内置等待机制上可能有优势但Selenium庞大的用户群、丰富的资料和稳定的API对于团队协作和长期维护来说是一个更稳妥的选择。它就像一把可靠的老钳子可能不是最炫的但你知道它一定能干活并且出了问题很容易找到人一起解决。Pytest则是一个远超unittest的测试框架。它的魅力在于“约定优于配置”和极强的可扩展性。你不需要像unittest那样必须继承某个类一个以test_开头的函数就是一个测试用例。它的fixture机制是精髓所在可以优雅地处理测试前置如启动浏览器、后置如关闭浏览器操作以及测试数据的注入。此外丰富的插件生态如pytest-html生成报告、pytest-rerunfailures失败重试、pytest-xdist分布式执行让你能像搭积木一样构建强大的测试能力。Pytest让测试代码变得简洁而富有表达力。这个组合的架构思路很清晰Pytest作为组织和调度者Selenium作为执行者。我们通过Pytest的fixture来管理WebDriver的生命周期用Pytest的用例发现和参数化功能来驱动测试数据最后用Pytest的插件来增强报告和稳定性。2.2 框架分层目录结构设计一个混乱的目录是项目腐化的开始。一个清晰的分层结构不仅能提升代码可读性更便于团队协作和后期维护。这里我推荐一种经典的POPage Object模式结合Pytest框架的分层目录结构这也是我经过多个项目验证后觉得最实用的一种。your_automation_framework/ ├── conftest.py # Pytest全局配置、共享fixture定义 ├── pytest.ini # Pytest主配置文件 ├── requirements.txt # 项目依赖包列表 │ ├── common/ # 公共层 │ ├── __init__.py │ ├── base_page.py # 页面基类封装通用方法如查找元素、点击 │ ├── webdriver_factory.py # WebDriver工厂负责创建和管理不同浏览器驱动 │ └── logger.py # 自定义日志模块 │ ├── page_objects/ # 页面对象层PO核心 │ ├── __init__.py │ ├── login_page.py # 登录页面对象 │ ├── home_page.py # 主页页面对象 │ └── ... # 其他页面对象 │ ├── test_cases/ # 测试用例层 │ ├── __init__.py │ ├── test_login.py # 登录相关测试用例 │ ├── test_search.py # 搜索相关测试用例 │ └── conftest.py # 测试用例层独有的fixture可选 │ ├── test_data/ # 测试数据层 │ ├── __init__.py │ ├── login_data.yaml # YAML格式的登录测试数据 │ └── users.json # JSON格式的用户数据 │ ├── reports/ # 测试报告输出目录.gitignore忽略 │ └── 2024-05-27_14-30-22.html │ └── screenshots/ # 失败截图输出目录.gitignore忽略 └── test_login_error.png各层职责解析common公共层存放与具体业务无关的底层工具。base_page.py是所有页面对象的父类封装了如find_element、click、input_text等通用操作并加入智能等待、日志记录和失败截图。webdriver_factory.py根据配置决定是启动本地Chrome还是远程Grid节点实现浏览器管理的解耦。page_objects页面对象层这是PO模式的核心。每个页面对应一个类如LoginPage类中属性代表页面元素如用户名输入框类中方法代表页面操作如login(username, password)。测试用例里不直接出现find_element_by_id这样的Selenium原生代码而是调用LoginPage().login(“user”, “pass”)极大提升可维护性。当页面元素ID变更时你只需要修改对应的页面对象类即可。test_cases测试用例层这里存放真正的Pytest测试文件。用例应该非常简洁只包含测试步骤和断言所有页面操作都委托给页面对象层。例如def test_login_success(login_page): login_page.login(“valid_user”, “valid_pass”); assert login_page.is_login_successful()。test_data测试数据层将测试数据从代码中分离出来。使用YAML或JSON文件管理数据便于维护和进行数据驱动测试。Pytest的pytest.mark.parametrize装饰器可以方便地读取这些数据。reports screenshots报告与截图层输出目录存放每次运行的HTML报告和失败用例的截图便于问题回溯。务必将其加入.gitignore。注意conftest.py可以存在于各层目录中作用域不同。根目录的conftest.py中定义的fixture对整个项目生效而test_cases目录下的conftest.py中的fixture只对该目录下的用例生效。这是Pytest一个非常灵活的特性。3. 核心细节解析与实操要点3.1 环境搭建与依赖管理一步都不能错万事开头难环境搭建是第一步也是最容易踩坑的一步。很多人在这里被驱动版本不匹配、环境变量等问题劝退。1. 创建虚拟环境与安装依赖强烈建议使用虚拟环境如venv或conda来隔离项目依赖。在项目根目录下执行# 创建虚拟环境 python -m venv venv # 激活虚拟环境 (Windows) venv\Scripts\activate # 激活虚拟环境 (MacOS/Linux) source venv/bin/activate # 安装核心依赖 pip install selenium pytest # 安装常用插件 pip install pytest-html pytest-rerunfailures pytest-xdist allure-pytest # 安装用于数据读写的库推荐YAML比JSON更易读 pip install pyyaml2. WebDriver驱动管理这是Selenium的核心痛点。你需要下载与你的浏览器版本严格匹配的WebDriver如chromedriver对应Chrome。最佳实践使用webdriver-manager。这个第三方库可以自动下载和管理正确版本的驱动省去手动下载和配置环境变量的麻烦。pip install webdriver-manager在代码中可以这样使用from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager from selenium import webdriver service Service(ChromeDriverManager().install()) driver webdriver.Chrome(serviceservice)手动管理如果你身处内网或对版本有严格要求仍需手动下载。记住驱动版本、浏览器版本、Selenium库版本三者需要兼容。将驱动放在项目目录下如drivers/或系统PATH路径中。3. Pytest基础配置 (pytest.ini):这个文件是Pytest的指挥中心放在项目根目录。[pytest] # 指定测试文件搜索路径 testpaths test_cases # 指定文件名匹配规则 python_files test_*.py # 指定类名匹配规则 python_classes Test* # 指定函数名匹配规则 python_functions test_* # 添加命令行默认参数 addopts -v --htmlreports/report.html --self-contained-html --reruns 1 --reruns-delay 2 # 设置日志格式和级别 log_cli true log_cli_level INFO log_cli_format %(asctime)s [%(levelname)s] %(message)s--htmlreports/report.html使用pytest-html插件生成HTML报告。--self-contained-html将CSS样式内嵌到HTML中生成单个可分享的报告文件。--reruns 1 --reruns-delay 2使用pytest-rerunfailures插件失败用例自动重试1次间隔2秒。这对付Web测试中的偶发性元素加载失败非常有效。3.2 Page Object (PO) 模式深度实践PO模式不是简单地把find_element语句封装到一个类里其核心思想是将页面元素定位与业务操作分离将测试用例与页面细节分离。一个优秀的BasePage类应该包含什么common/base_page.py是你的所有页面对象的“父亲”。它应该提供稳健的基础操作。import logging from datetime import datetime from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.common.exceptions import TimeoutException, NoSuchElementException class BasePage: def __init__(self, driver): self.driver driver self.logger logging.getLogger(__name__) self.wait WebDriverWait(driver, 10) # 显式等待10秒 def find_element(self, locator): 查找单个元素加入显式等待和日志 self.logger.info(f正在查找元素: {locator}) try: element self.wait.until(EC.presence_of_element_located(locator)) self.logger.info(f元素查找成功: {locator}) return element except TimeoutException: self.logger.error(f元素查找超时: {locator}) self._take_screenshot(“element_not_found”) raise def click(self, locator): 点击元素点击前确保元素可点击 element self.find_element(locator) self.wait.until(EC.element_to_be_clickable(locator)) element.click() self.logger.info(f已点击元素: {locator}) def input_text(self, locator, text): 输入文本先清空再输入 element self.find_element(locator) element.clear() element.send_keys(text) self.logger.info(f已在元素 {locator} 输入文本: {text}) def get_text(self, locator): 获取元素文本 element self.find_element(locator) text element.text self.logger.info(f获取元素 {locator} 的文本: {text}) return text def _take_screenshot(self, name): 内部方法截图并保存 timestamp datetime.now().strftime(“%Y%m%d_%H%M%S”) filename fscreenshots/{name}_{timestamp}.png” self.driver.save_screenshot(filename) self.logger.info(f截图已保存至: {filename})实操心得在find_element中统一加入显式等待和日志能从根本上减少因元素未加载完成导致的失败并且让测试执行过程有迹可循。截图功能最好在基类的异常处理中调用而不是在每个用例里写。业务页面对象示例page_objects/login_page.py继承自BasePage。from selenium.webdriver.common.by import By from common.base_page import BasePage class LoginPage(BasePage): # 页面元素定位器统一管理 USERNAME_INPUT (By.ID, “username”) PASSWORD_INPUT (By.ID, “password”) LOGIN_BUTTON (By.XPATH, “//button[type‘submit]”) ERROR_MESSAGE (By.CLASS_NAME, “alert-error”) def __init__(self, driver): super().__init__(driver) self.driver driver def open(self, url): self.driver.get(url) self.logger.info(f“打开登录页面: {url}”) def login(self, username, password): 登录业务操作 self.input_text(self.USERNAME_INPUT, username) self.input_text(self.PASSWORD_INPUT, password) self.click(self.LOGIN_BUTTON) self.logger.info(f“尝试登录用户名: {username}”) def get_error_message(self): 获取错误提示信息 return self.get_text(self.ERROR_MESSAGE) def is_login_successful(self): 判断登录是否成功这里需要根据实际成功后的页面特征来判断 # 例如检查是否跳转到首页或出现用户菜单 # return “dashboard” in self.driver.current_url # 这是一个示例实际逻辑需根据你的应用调整 try: self.find_element((By.ID, “user-menu”)) return True except: return False注意事项定位器如(By.ID, “username”)一定要作为类属性单独定义而不是直接写在方法里。这样一旦页面元素ID变更你只需要修改这一个地方所有用到该元素的方法都会自动生效。这是PO模式可维护性的关键。4. 实操过程与核心环节实现4.1 编写可维护的Pytest测试用例有了稳固的底层和页面对象编写测试用例就变成了一件清晰而愉快的事情。测试用例应该专注于“测试逻辑”而不是“操作细节”。一个完整的测试用例文件示例 (test_cases/test_login.py):import pytest import yaml from page_objects.login_page import LoginPage # 从YAML文件加载测试数据 def load_login_data(): with open(‘test_data/login_data.yaml’, ‘r’, encoding‘utf-8’) as f: data yaml.safe_load(f) return data[‘test_cases’] class TestLogin: 登录功能测试集 pytest.mark.parametrize(“case_id, username, password, expected”, [ (“TC_LOGIN_001”, “admin”, “admin123”, “success”), (“TC_LOGIN_002”, “”, “admin123”, “用户名不能为空”), (“TC_LOGIN_003”, “admin”, “”, “密码不能为空”), (“TC_LOGIN_004”, “wrong”, “wrong”, “用户名或密码错误”), ]) def test_login_with_parametrize(self, browser_driver, case_id, username, password, expected): 使用pytest参数化进行数据驱动测试 login_page LoginPage(browser_driver) login_page.open(“https://your-app.com/login”) login_page.login(username, password) if expected “success”: assert login_page.is_login_successful(), f“用例 {case_id} 登录成功断言失败” else: actual_error login_page.get_error_message() assert expected in actual_error, f“用例 {case_id} 错误信息断言失败。预期: ‘{expected}’ 实际: ‘{actual_error}’” # 使用外部YAML文件进行更复杂的数据驱动 pytest.mark.parametrize(“test_data”, load_login_data()) def test_login_with_yaml_data(self, browser_driver, test_data): 从YAML文件读取数据驱动测试 login_page LoginPage(browser_driver) login_page.open(test_data[‘url’]) login_page.login(test_data[‘credentials’][‘username’], test_data[‘credentials’][‘password’]) # ... 断言逻辑对应的YAML数据文件test_data/login_data.yaml:test_cases: - case_id: “TC_LOGIN_YAML_001” description: “使用有效管理员账号登录” url: “https://your-app.com/login” credentials: username: “admin” password: “admin123” expected: “success” screenshot_on_failure: true - case_id: “TC_LOGIN_YAML_002” description: “使用已锁定账号登录” url: “https://your-app.com/login” credentials: username: “locked_user” password: “123456” expected: “账号已被锁定请联系管理员” screenshot_on_failure: true关键点解析browser_driverfixture这是测试用例的“燃料”。它由根目录的conftest.py提供负责创建和销毁WebDriver实例。用例本身不关心浏览器如何启动和关闭。pytest.mark.parametrize这是Pytest数据驱动的利器。它允许你将多组测试数据直接绑定到一个测试函数上Pytest会自动生成多条测试用例并分别执行。上面的例子展示了两种方式直接在装饰器中定义列表或者调用一个函数从外部文件YAML/JSON加载复杂数据。清晰的断言断言是测试的灵魂。断言信息要明确使用f-string将用例ID、预期值和实际值都包含在失败信息里这样当用例失败时你能一眼看出是哪条数据出了问题。用例独立性每个用例都应该是独立的不依赖于其他用例的执行状态。这就是为什么browser_driverfixture的默认作用域是function每个用例函数执行一次。确保用例开始时都从一个干净的会话状态如未登录的首页开始。4.2 灵魂所在conftest.py 与 Fixture 设计conftest.py是Pytest框架的“配置中心”和“共享资源池”。在这里定义的fixture可以被项目中的所有测试用例使用。合理设计fixture是框架灵活性和稳定性的关键。根目录下的conftest.py示例import pytest import logging from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager from webdriver_manager.firefox import GeckoDriverManager # 配置日志 logging.basicConfig(levellogging.INFO, format‘%(asctime)s - %(name)s - %(levelname)s - %(message)s’) def pytest_addoption(parser): 添加自定义命令行选项 parser.addoption(“--browser”, action“store”, default“chrome”, help“指定浏览器: chrome 或 firefox”) parser.addoption(“--headless”, action“store_true”, defaultFalse, help“是否以无头模式运行”) pytest.fixture(scope“function”) def browser_driver(request): 核心Fixture提供WebDriver实例并确保测试结束后退出 browser_name request.config.getoption(“--browser”) is_headless request.config.getoption(“--headless”) driver None if browser_name.lower() “chrome”: chrome_options webdriver.ChromeOptions() if is_headless: chrome_options.add_argument(“--headless”) chrome_options.add_argument(“--no-sandbox”) # 解决Linux/Docker环境常见问题 chrome_options.add_argument(“--disable-dev-shm-usage”) # 解决共享内存问题 chrome_options.add_argument(“--window-size1920,1080”) # 设置初始窗口大小 # 使用webdriver-manager自动管理驱动 service Service(ChromeDriverManager().install()) driver webdriver.Chrome(serviceservice, optionschrome_options) elif browser_name.lower() “firefox”: firefox_options webdriver.FirefoxOptions() if is_headless: firefox_options.add_argument(“--headless”) service Service(GeckoDriverManager().install()) driver webdriver.Firefox(serviceservice, optionsfirefox_options) else: raise ValueError(f“不支持的浏览器: {browser_name}”) driver.implicitly_wait(5) # 设置隐式等待备用优先使用显式等待 driver.maximize_window() # 最大化窗口 # 将driver对象传递给测试用例 yield driver # 测试结束后执行的清理工作 request.addfinalizer(driver.quit) # 更推荐使用 yield 后的代码但 addfinalizer 是另一种确保退出的方式 # 实际上使用 yield 后下面的 quit 会在用例结束时执行 driver.quit() logging.info(“浏览器已关闭”) pytest.fixture(scope“function”) def login_page(browser_driver): 提供一个已初始化好的LoginPage对象 from page_objects.login_page import LoginPage return LoginPage(browser_driver) pytest.hookimpl(tryfirstTrue, hookwrapperTrue) def pytest_runtest_makereport(item, call): Hook函数在测试用例执行后获取结果用于失败时自动截图 outcome yield report outcome.get_result() # 只关注用例调用阶段setup, call, teardown中的call的失败 if report.when “call” and report.failed: # 尝试从item中获取driver对象 driver_fixture item.funcargs.get(‘browser_driver’, None) if driver_fixture is not None: # 截图并作为附件添加到HTML报告需要pytest-html支持 screenshot_path f“screenshots/{item.name}_{report.when}.png” driver_fixture.save_screenshot(screenshot_path) # 将截图路径添加到报告extra中 if hasattr(report, ‘extra’): from pytest_html import extras report.extra.append(extras.image(screenshot_path))深度解析pytest_addoption这个函数允许你定义自定义命令行参数。例如你可以通过pytest --browserfirefox --headless来指定用Firefox无头模式运行所有测试。这为测试的灵活执行如CI/CD流水线提供了入口。browser_driverfixturescope“function”这是默认值意味着每个测试函数都会获得一个全新的driver实例。这保证了测试的隔离性。你也可以设置为scope“session”让所有用例共用一个浏览器会话更快但用例间可能相互影响。yield driver这是fixture提供资源的核心语法。yield之前的代码是“设置”部分启动浏览器yield返回driver对象给测试用例使用yield之后的代码是“清理”部分关闭浏览器。无论测试用例是否通过yield后的代码都会执行确保浏览器被正确关闭避免进程残留。无头模式与选项--headless对于在服务器无GUI环境或CI/CD中运行测试至关重要。--no-sandbox和--disable-dev-shm-usage是解决Linux/Docker环境下Chrome常见崩溃问题的关键参数。login_pagefixture这是一个“业务层”的fixture。它依赖于browser_driver并直接返回一个初始化好的LoginPage对象。在测试用例中你可以直接声明def test_something(login_page):然后使用login_page.login(...)让用例更加简洁。pytest_runtest_makereportHook这是Pytest框架的一个钩子函数允许你在测试生命周期的特定时刻插入自定义逻辑。这里我们用它来监听测试失败事件并自动截图。截图会保存在screenshots/目录并且如果你使用pytest-html还可以通过extras将图片嵌入HTML报告实现“报告里直接看失败截图”的效果极大提升调试效率。5. 常见问题与排查技巧实录即使框架搭建得再完善在实际运行中依然会遇到各种“坑”。下面是我在多年实践中总结的一些高频问题和解决思路。5.1 元素定位失败自动化测试的头号敌人超过70%的自动化测试失败源于元素定位问题。错误信息通常是NoSuchElementException、TimeoutException或ElementNotInteractableException。排查清单与解决方案问题现象可能原因解决方案与技巧突然找不到之前能定位的元素1. 页面加载未完成。2. 元素在iframe或shadow DOM内。3. 页面发生了跳转或重定向。1.强化等待将BasePage中的显式等待时间调长或使用更精确的等待条件如element_to_be_clickable。2.检查iframe使用driver.switch_to.frame(frame_reference)切换到正确的iframe后再定位。3.检查Shadow DOM使用driver.execute_script执行JavaScript来穿透Shadow DOM定位元素。元素可以找到但点击或输入无效 (ElementNotInteractableException)1. 元素被遮挡如弹窗、广告。2. 元素不可见或未启用style“display: none;”。3. 需要滚动到元素可见区域。1.处理遮挡先关闭或等待遮挡物消失。可以尝试用JavaScript直接点击driver.execute_script(“arguments[0].click();”, element)。2.等待元素可交互使用EC.element_to_be_clickable进行等待。3.滚动到视图driver.execute_script(“arguments[0].scrollIntoView(true);”, element)。定位器写对了但偶尔失败1. 网络波动或前端渲染性能问题。2. 动态ID或类名每次刷新会变。1.增加重试机制使用pytest-rerunfailures插件全局重试失败用例。2.使用更稳定的定位策略优先使用id、name其次是用XPath结合相对路径和元素属性避免用绝对路径和索引。例如//button[contains(class, ‘submit-btn’) and text()‘登录’]。在Ajax动态加载的列表中定位元素元素是动态添加到DOM中的初始定位时不存在。1.等待元素出现使用EC.presence_of_all_elements_located等待一组元素出现。2.先等待加载完成等待某个加载完成的标志性元素出现再操作列表。实操心得遇到定位问题不要急着改代码。首先手动在浏览器的开发者工具F12中用Console尝试你的XPath或CSS选择器是否能选中元素。其次在测试失败时利用我们Hook函数自动截取的截图直观地看看到底是页面没加载出来还是元素状态不对。最后考虑在定位前加入一个短暂的time.sleep(2)仅用于临时调试来区分是等待问题还是定位器本身问题。5.2 测试报告优化与失败分析生成的报告不好看或者失败信息不清晰会大大增加排查成本。1. 解决Allure报告中用例标题换行问题当你使用pytest.mark.parametrize进行数据驱动测试时如果参数值很长生成的Allure报告中的用例标题可能会被挤得换行影响阅读。解决方案是自定义用例标题。import allure import pytest pytest.mark.parametrize(“username, password”, [ (“very_long_username_value_that_might_cause_line_break”, “pass1”), (“user2”, “another_long_password_value_here”), ]) def test_login_with_different_users(username, password): # 使用 allure.dynamic.title 动态设置一个简洁的标题 allure.dynamic.title(f“登录测试 - 用户: {username[:10]}...”) # 截取用户名前10位 # ... 测试步骤 ...这样在Allure报告中用例标题就会显示为你设置的简洁版本而不是完整的参数化长标题。2. 丰富HTML报告内容使用pytest-html插件时可以通过Hook函数向报告中添加更多有用信息。# 在 conftest.py 中 def pytest_html_report_title(report): report.title “我的自动化测试报告” def pytest_html_results_table_header(cells): cells.insert(2, “th测试描述/th”) cells.insert(3, “th用例ID/th”) def pytest_html_results_table_row(report, cells): # 从测试用例的特定属性中获取描述和ID description getattr(report, ‘description’, ‘’) case_id getattr(report, ‘case_id’, ‘’) cells.insert(2, f“td{description}/td”) cells.insert(3, f“td{case_id}/td”) pytest.hookimpl(hookwrapperTrue) def pytest_runtest_makereport(item, call): outcome yield report outcome.get_result() # 将测试用例函数文档字符串作为描述 report.description str(item.function.__doc__) # 假设我们用 pytest.mark.case_id(‘TC_001’) 标记了用例ID markers [mark for mark in item.iter_markers(name“case_id”)] if markers: report.case_id markers[0].args[0]然后在测试用例中你可以用文档字符串和自定义标记来提供信息pytest.mark.case_id(‘TC_LOGIN_001’) def test_admin_login(login_page): “”“验证管理员用户能够成功登录系统”“” # ... 测试步骤 ...5.3 稳定性提升与高级技巧1. 处理弹窗和浏览器通知有些网站会有登录弹窗Basic Auth、浏览器原生的alert/confirm/prompt弹窗或者请求发送通知权限。# 处理HTTP Basic认证弹窗不是JS弹窗 # 方法在URL中直接携带用户名和密码注意安全风险仅用于测试环境 driver.get(“https://username:passwordyour-site.com”) # 处理JS的Alert/Confirm/Prompt from selenium.webdriver.common.alert import Alert alert Alert(driver) print(alert.text) # 获取弹窗文本 alert.accept() # 点击“确定” # alert.dismiss() # 点击“取消” # alert.send_keys(“input text”) # 向Prompt输入文本 # 阻止浏览器通知弹窗在启动选项中添加 chrome_options.add_argument(“--disable-notifications”)2. 文件上传与下载文件上传不能使用send_keys点击上传按钮而是直接发送文件路径到input type“file”元素。# 找到文件上传的input元素 file_input driver.find_element(By.XPATH, “//input[type‘file]”) # 直接发送绝对路径 file_input.send_keys(“/Users/yourname/Downloads/test_file.pdf”)对于文件下载需要设置浏览器下载偏好并指定下载目录。prefs { “download.default_directory”: “/path/to/your/download/folder”, “download.prompt_for_download”: False, “plugins.always_open_pdf_externally”: True } chrome_options.add_experimental_option(“prefs”, prefs)3. 使用Page Factory模式可选如果你觉得在PO类里写一堆find_element调用还是有点繁琐可以考虑使用PageFactory模式源自Java的Selenium在Python中需借助selenium.webdriver.support.PageFactory。它可以让你用注解的方式声明元素并在初始化时自动查找。from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By from selenium.webdriver.support.pagefactory import PageFactory class LoginPage: # 使用PageFactory的注解方式定义元素 username_input (By.ID, “username”) password_input (By.ID, “password”) login_button (By.XPATH, “//button[type‘submit]”) def __init__(self, driver): self.driver driver self.wait WebDriverWait(driver, 10) # 初始化页面元素 PageFactory.init_elements(driver, self) def login(self, username, password): # 直接使用 self.username_input, 但实际它是一个元组需要配合find_element使用 # PageFactory模式在Python中不如Java中那么“自动”通常需要自己封装。 # 更常见的做法是结合property装饰器。 pass实际上在Python中更流行和灵活的还是我们之前介绍的显式在基类中封装find_element的方法。Page Factory可以作为了解但并非必选。构建一个健壮的SeleniumPytest自动化测试框架是一个从“能用”到“好用”再到“稳定高效”的持续迭代过程。核心在于清晰的架构分层、稳健的底层封装、灵活的数据驱动和高效的排查手段。这个框架不是一成不变的你可以根据自己项目的需求引入API测试用requests库、数据库校验、或者与Jenkins/GitLab CI等持续集成工具对接使其成为你质量保障体系中可靠的一环。记住好的自动化测试框架应该像一名不知疲倦、严谨细致的助手把你从重复的机械劳动中解放出来让你能更专注于更有价值的测试设计和问题挖掘。