Selenium自动化测试:配置文件管理与参数化实战指南
1. 项目概述为什么我们需要读取用户配置参数做自动化测试或者数据采集的朋友对Selenium肯定不陌生。我们经常用它来模拟浏览器操作登录网站、点击按钮、填写表单。但不知道你有没有遇到过这样的场景每次跑脚本都要手动去改代码里的用户名、密码、代理地址或者换个测试环境就得重新调整一堆等待时间和元素定位方式。脚本写得很溜但用起来却特别“脆”换个地方就跑不起来或者需要频繁修改源码。这就是我们今天要深入探讨的核心问题如何让Selenium脚本与具体的用户配置参数解耦。简单说就是写一个“聪明”的脚本它能自动读取外部的配置文件根据不同的用户、不同的环境、不同的任务动态地调整自己的行为。比如开发用一套账号密码在测试环境跑运维用另一套在生产环境跑脚本本身不需要做任何改动。这不仅仅是写几行config.get(‘username’)那么简单。背后涉及到配置管理的哲学、代码健壮性的设计以及如何应对各种复杂场景。一个配置良好的脚本能显著提升团队协作效率和自动化流程的可靠性。接下来我会结合我多年的实战经验从设计思路到具体实现再到避坑指南为你完整拆解Selenium访问网站时读取用户配置参数的系统性方法。2. 配置参数的核心分类与设计思路在动手写代码之前我们得先想清楚到底有哪些参数是需要被“配置化”的。盲目地把所有变量都丢进配置文件只会让管理变得更混乱。根据参数的性质和使用场景我通常把它们分为四大类。2.1 环境依赖型参数这类参数直接决定了脚本在哪个“世界”里运行。它们通常是静态的一次配置长期使用除非基础设施发生变化。浏览器驱动路径chromedriver、geckodriver的绝对或相对路径。特别是在Windows服务器或Docker容器中运行时路径问题是个大坑。目标网站基础URL测试环境、预发布环境、生产环境的域名完全不同。例如测试环境可能是http://test.example.com而生产环境是https://www.example.com。资源文件路径脚本运行需要读取的上传文件如图片、Excel、需要保存的下载文件或日志的存放目录。设计思路这类参数适合放在一个全局的、与环境强相关的配置文件中例如config.ini或config.yaml。可以通过环境变量来指定加载哪个配置文件实现“一键切换环境”。2.2 用户身份型参数这是最常见也最敏感的一类。涉及到账号、密码、令牌等认证信息。登录凭证用户名、密码。重要提示绝对不要将明文密码硬编码在脚本或配置文件中API密钥/令牌一些网站采用OAuth、JWT等认证方式需要配置access_token、api_key等。会话信息有时为了绕过复杂的登录流程或验证码我们会直接使用已登录状态的Cookies或Session。设计思路安全性是首要考虑。建议采用分级配置将用户名等非核心标识放在普通配置文件中。将密码、密钥等敏感信息存储在环境变量中或在运行时从加密文件或密钥管理服务如Vault动态读取。Cookies可以序列化后保存到本地文件下次启动时直接加载实现“免登录”。2.3 行为控制型参数这类参数像脚本的“调节旋钮”控制着Selenium如何与网页交互直接影响脚本的稳定性和执行效率。等待时间隐式等待implicitly_wait的全局超时以及用于显式等待WebDriverWait的超时时间和轮询间隔。处理慢速网络或动态加载页面时至关重要。元素定位策略与表达式虽然定位表达式通常写在页面对象里但某些动态变化的元素前缀或ID也可以作为参数提取出来。操作延迟在两次点击或输入之间插入的短暂time.sleep虽然不推荐无脑sleep但某些反爬严格的场景需要可以用参数控制延迟时长。浏览器选项是否启用无头模式、禁用GPU、设置用户代理、忽略SSL错误等。这些ChromeOptions或FirefoxOptions的配置项非常适合参数化。设计思路将这些参数集中管理便于做A/B测试比如调整等待时间来看哪种策略成功率最高。它们通常放在与核心业务逻辑分离的配置模块中。2.4 业务数据型参数这是与具体自动化任务相关的输入数据。搜索关键词用于测试搜索功能或进行数据采集。表单测试数据注册、下单时需要填写的姓名、地址、电话号码等注意使用测试数据避免真实信息。流程控制标志例如一个脚本既能跑冒烟测试用例也能跑全量用例可以通过一个test_scope参数来控制。设计思路这类参数可以放在独立的数据文件中如JSON、CSV或Excel。脚本读取这些文件作为输入。这样做的好处是测试数据与脚本逻辑分离非技术人员也能准备和维护测试数据。实操心得不要追求一个配置文件解决所有问题。我推荐采用“分层配置”策略一个env_config.ini管理环境依赖一个behavior_config.yaml管理行为控制敏感信息由环境变量注入业务数据则来自test_data.csv。这样结构清晰维护方便。3. 主流配置文件的读取方法与实战明确了要配置什么接下来就是怎么配、怎么读。下面我会对比几种主流方案并给出详细的代码示例和选型建议。3.1 使用INI文件简单直接的经典之选INI文件格式简单通过节和键值对来组织Python标准库configparser即可解析无需额外依赖。配置示例 (config.ini):[Browser] driver_path ./drivers/chromedriver headless True window_size 1920,1080 [Environment] base_url https://test.example.com timeout 10 [Credentials] ; 密码不应明文存储这里仅为示例实际应使用环境变量 username test_userPython读取代码import configparser from selenium import webdriver from selenium.webdriver.chrome.options import Options config configparser.ConfigParser() config.read(config.ini, encodingutf-8) # 读取浏览器配置 chrome_options Options() if config.getboolean(Browser, headless): chrome_options.add_argument(--headless) driver_path config.get(Browser, driver_path) # 读取环境配置 base_url config.get(Environment, base_url) timeout config.getint(Environment, timeout) # 初始化Driver driver webdriver.Chrome(executable_pathdriver_path, optionschrome_options) driver.implicitly_wait(timeout) driver.get(base_url)优点内置支持简单易懂适合扁平化的配置。缺点不支持复杂的数据结构如列表、嵌套字典功能相对较弱。适用场景中小型项目配置项不多且结构简单。3.2 使用YAML文件层次分明的人性化之选YAML通过缩进来表示层级支持列表、字典等复杂结构可读性极强。需要安装PyYAML库。配置示例 (config.yaml):browser: driver_path: ./drivers/chromedriver options: headless: true window_size: [1920, 1080] arguments: - --disable-gpu - --no-sandbox user_agent: Mozilla/5.0 ... environment: base_url: https://test.example.com timeouts: implicit: 10 explicit: 20 poll_frequency: 0.5 credentials: username: test_user # 密码建议通过环境变量传递 password: !ENV ${LOGIN_PASSWORD} test_data: search_keywords: - selenium - automation testing - web scrapingPython读取代码import yaml import os from selenium import webdriver # 定义一个构造器来处理自定义标签如 !ENV def env_constructor(loader, node): value loader.construct_scalar(node) return os.getenv(value) # 从环境变量读取 yaml.add_constructor(!ENV, env_constructor) with open(config.yaml, r, encodingutf-8) as f: config yaml.safe_load(f) # 读取复杂配置变得非常直观 options_config config[browser][options] chrome_options webdriver.ChromeOptions() if options_config[headless]: chrome_options.add_argument(--headless) for arg in options_config.get(arguments, []): chrome_options.add_argument(arg) if user_agent in options_config: chrome_options.add_argument(fuser-agent{options_config[user_agent]}) driver webdriver.Chrome( executable_pathconfig[browser][driver_path], optionschrome_options ) driver.implicitly_wait(config[environment][timeouts][implicit]) # 读取测试数据列表 for keyword in config[test_data][search_keywords]: print(fTesting search for: {keyword}) # ... 执行搜索操作优点表达能力强支持复杂结构可读性好与Python数据结构转换自然。缺点需要第三方库缩进错误会导致解析失败。适用场景中大型项目配置项之间有明显的层次关系或者需要配置列表类型数据。3.3 使用JSON文件通用标准的程序化之选JSON是Web开发中的通用数据交换格式Python标准库json即可解析。配置示例 (config.json):{ browser: { driver_path: ./drivers/chromedriver, headless: true, arguments: [--disable-gpu, --no-sandbox] }, environment: { base_url: https://test.example.com, timeout: 10 } }Python读取代码import json from selenium import webdriver with open(config.json, r, encodingutf-8) as f: config json.load(f) chrome_options webdriver.ChromeOptions() if config[browser][headless]: chrome_options.add_argument(--headless) for arg in config[browser].get(arguments, []): chrome_options.add_argument(arg) driver webdriver.Chrome( executable_pathconfig[browser][driver_path], optionschrome_options )优点标准、通用几乎所有编程语言都支持适合作为不同系统间传递的配置格式。缺点不允许注释虽然有些解析器扩展支持对于人类阅读和编辑不如YAML友好。适用场景配置需要被其他非Python程序读取或者项目本身是前后端分离希望统一配置格式。3.4 使用环境变量管理敏感信息的首选对于密码、API密钥等绝对敏感的信息环境变量是最简单、最安全的方式之一。它不落盘与系统用户会话绑定。设置环境变量Linux/macOS命令行示例export LOGIN_USERNAMEtest_user export LOGIN_PASSWORDyour_secure_password_here export API_KEYsk_live_xxxPython读取代码import os username os.environ.get(LOGIN_USERNAME) password os.environ.get(LOGIN_PASSWORD) # 安全地获取密码 api_key os.environ.get(API_KEY) if not all([username, password]): raise ValueError(关键登录凭证未在环境变量中设置)最佳实践在项目根目录创建一个.env文件切记加入.gitignore使用python-dotenv库在应用启动时加载。# .env 文件 LOGIN_USERNAMEtest_user LOGIN_PASSWORDsecure_password BASE_URLhttps://test.example.comfrom dotenv import load_dotenv load_dotenv() # 从 .env 文件加载环境变量到 os.environ # 之后就可以用 os.environ.get() 读取了优点安全性高便于在CI/CD流水线、Docker容器、云服务器等不同部署环境中灵活设置。缺点不适合存储大量或结构复杂的配置。适用场景存储所有敏感信息和可能因环境而异的核心参数如数据库连接字符串。选型建议对于大多数Selenium自动化项目我推荐YAML 环境变量的组合。YAML管理大部分行为和非敏感配置环境变量管理密钥和密码。这样既保证了可读性和灵活性又兼顾了安全性。4. 构建可维护的配置管理模块直接在每个脚本里写open(‘config.yaml’)是初学者的做法。一个可维护的项目需要一个统一的配置管理模块提供清晰的接口和必要的验证。4.1 设计一个配置管理类下面是一个设计范例它集成了YAML读取、环境变量覆盖、类型转换和缺省值功能。# config_manager.py import os import yaml from typing import Any, Dict, Optional from pathlib import Path class ConfigManager: 统一的配置管理器 def __init__(self, config_path: str config.yaml, env_prefix: str SELENIUM_): self.config_path Path(config_path) self.env_prefix env_prefix self._config: Dict[str, Any] {} self._load_config() def _load_config(self): 加载YAML配置文件并用环境变量覆盖 if not self.config_path.exists(): raise FileNotFoundError(f配置文件未找到: {self.config_path}) with open(self.config_path, r, encodingutf-8) as f: self._config yaml.safe_load(f) or {} # 递归地用环境变量覆盖配置值 self._override_with_env(self._config) def _override_with_env(self, config_node: Dict[str, Any], prefix: str ): 递归遍历配置字典查找对应的环境变量进行覆盖。 规则将配置键转换为大写加上前缀作为环境变量名。 例如browser.headless - SELENIUM_BROWSER_HEADLESS for key, value in config_node.items(): full_key f{prefix}{key.upper()} if prefix else key.upper() env_key f{self.env_prefix}{full_key} if isinstance(value, dict): # 如果是字典继续递归 self._override_with_env(value, prefixf{full_key}_) else: # 如果是值检查环境变量 env_value os.environ.get(env_key) if env_value is not None: # 简单类型转换可根据需要扩展 if isinstance(value, bool): config_node[key] env_value.lower() in (true, 1, yes) elif isinstance(value, int): config_node[key] int(env_value) elif isinstance(value, float): config_node[key] float(env_value) else: config_node[key] env_value def get(self, key_path: str, default: Any None) - Any: 通过点分路径获取配置值如 browser.options.headless keys key_path.split(.) value self._config try: for key in keys: value value[key] return value except (KeyError, TypeError): return default def get_browser_options(self) - Dict[str, Any]: 获取浏览器配置返回一个字典 return self.get(browser.options, {}) def get_timeout(self, timeout_type: str implicit) - int: 获取超时时间 return self.get(fenvironment.timeouts.{timeout_type}, 10) property def base_url(self) - str: 获取基础URL url self.get(environment.base_url) if not url: raise ValueError(配置中未找到 environment.base_url) return url.rstrip(/)4.2 在Selenium项目中的集成应用有了这个配置管理器你的Selenium脚本会变得非常简洁和健壮。# main.py from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from config_manager import ConfigManager def create_driver(config: ConfigManager): 根据配置创建并返回WebDriver实例 browser_config config.get_browser_options() options webdriver.ChromeOptions() # 设置无头模式 if browser_config.get(headless): options.add_argument(--headless) # 添加其他参数 for arg in browser_config.get(arguments, []): options.add_argument(arg) # 设置用户代理 if user_agent in browser_config: options.add_argument(fuser-agent{browser_config[user_agent]}) # 初始化Driver driver_path config.get(browser.driver_path, chromedriver) # 提供默认值 driver webdriver.Chrome(executable_pathdriver_path, optionsoptions) # 设置隐式等待 driver.implicitly_wait(config.get_timeout(implicit)) return driver def main(): # 初始化配置管理器 config ConfigManager(config.yaml) # 创建驱动 driver create_driver(config) try: # 访问配置中指定的基础URL driver.get(config.base_url) # 使用显式等待超时时间也从配置中读取 wait WebDriverWait(driver, config.get_timeout(explicit)) # 示例登录操作密码从环境变量来 username config.get(credentials.username) # 注意密码不应存储在yaml中这里假设是通过环境变量覆盖进来的 password config.get(credentials.password) if username and password: username_elem wait.until(EC.presence_of_element_located((By.ID, username))) password_elem driver.find_element(By.ID, password) username_elem.send_keys(username) password_elem.send_keys(password) driver.find_element(By.ID, login-btn).click() print(登录成功) else: print(未配置登录凭证跳过登录步骤。) # ... 后续业务逻辑 finally: driver.quit() if __name__ __main__: main()通过这种方式所有配置都集中在一处管理。要切换环境你只需要修改config.yaml中的base_url。或者更优雅地设置一个环境变量export SELENIUM_ENVIRONMENT_BASE_URLhttps://prod.example.com它会自动覆盖配置文件中的值。5. 高级技巧与实战避坑指南掌握了基础方法我们再来看看一些能让你事半功倍的高级技巧和那些容易踩坑的地方。5.1 动态配置与多环境支持大型项目通常有开发、测试、预发布、生产等多个环境。手动改配置太低效。解决方案使用配置文件模板和变量替换。创建一个config.template.yaml其中使用占位符。environment: base_url: ${BASE_URL} name: ${ENV_NAME}在运行时或部署阶段通过脚本如使用Jinja2模板或CI/CD工具如Jenkins, GitLab CI将占位符替换为实际值。更简单的方法利用我们上面ConfigManager中环境变量覆盖的功能。为不同环境设置不同的环境变量前缀或值即可。5.2 敏感信息的安全处理这是重中之重。除了使用环境变量还有更多策略加密配置文件将包含敏感信息的配置文件整体加密脚本运行时使用密钥解密。密钥本身通过环境变量或硬件安全模块传递。使用密钥管理服务在云环境中直接使用AWS Secrets Manager、Azure Key Vault或HashiCorp Vault等服务。脚本启动时调用API获取密钥。临时令牌对于OAuth等流程获取到的access_token通常有过期时间。不要将其硬编码而是实现一个自动刷新令牌的逻辑将刷新的令牌保存在内存或短暂的缓存中。5.3 配置验证与错误处理配置错误是自动化脚本失败的常见原因。增加验证逻辑能快速定位问题。class ValidatedConfigManager(ConfigManager): def __init__(self, config_path: str config.yaml): super().__init__(config_path) self._validate() def _validate(self): 验证关键配置项是否存在且有效 required_paths [ browser.driver_path, environment.base_url, ] for path in required_paths: if self.get(path) is None: raise ValueError(f关键配置项缺失: {path}) # 验证URL格式 import re url self.base_url url_regex re.compile( r^(?:http|ftp)s?:// # http:// or https:// r(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)| # domain... rlocalhost| # localhost... r\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) # ...or ip r(?::\d)? # optional port r(?:/?|[/?]\S)$, re.IGNORECASE) if not re.match(url_regex, url): print(f警告配置的base_url格式可能不正确: {url})5.4 常见问题排查与解决即使配置得当运行时也可能出现问题。这里记录几个我踩过的坑和解决方案。问题现象可能原因排查步骤与解决方案KeyError或配置值为None1. 配置文件中键名拼写错误。2. 配置路径层级错误。3. 环境变量覆盖未生效。1. 使用ConfigManager.get()方法并提供默认值避免程序崩溃。2. 在初始化后打印整个self._config字典检查实际加载的配置结构。3. 检查环境变量名是否符合命名规则大写、下划线、指定前缀。驱动路径错误WebDriverException1.driver_path配置的是相对路径当前工作目录不对。2. 驱动版本与浏览器不匹配。1. 使用os.path.abspath()将配置中的相对路径转换为绝对路径。2. 将驱动路径设置为环境变量PATH的一部分然后在配置中只写驱动文件名如chromedriver或者使用webdriver-manager库自动管理驱动。环境变量覆盖不生效1. 环境变量未正确设置或导出。2.ConfigManager中覆盖逻辑的键名转换规则有误。1. 在Python脚本中print(os.environ)查看所有环境变量确认你的变量是否存在。2. 调试_override_with_env方法打印出它尝试查找的每一个env_key。无头模式下元素找不到无头模式可能与普通模式的渲染稍有差异或者窗口大小影响布局。1. 在配置中为无头模式设置一个明确的窗口大小window_size: [1920, 1080]。2. 增加显式等待的时间给动态内容更多加载时间。3. 在关键步骤前添加截图功能保存无头模式下的页面状态以便排查。配置更改后脚本行为未变脚本缓存了配置没有重新读取。确保你的ConfigManager是单例模式或者在脚本主入口每次重新实例化。对于长时间运行的脚本可以实现一个热重载机制监听配置文件变化。关于webdriver-manager的特别推荐这是一个能极大简化驱动管理的Python库。你不再需要手动下载和配置驱动路径。# 安装 pip install webdriver-manager from selenium import webdriver from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.chrome.service import Service # 自动下载、缓存并使用正确版本的ChromeDriver service Service(ChromeDriverManager().install()) driver webdriver.Chrome(serviceservice)这样browser.driver_path这个配置项就可以彻底省略了完美解决了驱动版本兼容性问题。6. 一个完整的实战案例可配置的自动化登录脚本让我们将所有知识点串联起来编写一个从配置读取参数实现自动化登录并处理常见异常的完整脚本。假设我们要登录一个需要验证码的网站通过配置决定是否处理验证码。项目结构my_selenium_project/ ├── config/ │ ├── config.yaml # 主配置文件 │ └── .env # 敏感信息gitignore ├── utils/ │ └── config_manager.py # 配置管理类 ├── pages/ │ └── login_page.py # 登录页面对象 ├── main.py # 主脚本 └── requirements.txt1. 配置文件 (config/config.yaml)project: name: 网站自动化登录Demo log_level: INFO browser: options: headless: false # 开发时设为false方便调试 window_size: [1920, 1080] arguments: - --disable-blink-featuresAutomationControlled # 尝试绕过基础反爬 - --disable-dev-shm-usage experimental_options: excludeSwitches: [enable-automation] useAutomationExtension: false environment: base_url: https://example.com/login timeouts: implicit: 5 explicit: 15 poll: 0.5 login: username: your_username # 可被环境变量覆盖 # password 通过 .env 文件设置 element_selectors: username_input: #username password_input: #password captcha_image: #captchaImage captcha_input: #captcha submit_button: button[typesubmit] features: handle_captcha: false # 是否处理验证码复杂验证码建议关闭手动处理2. 环境变量文件 (config/.env)# 密码等敏感信息放在这里 SELENIUM_LOGIN_PASSWORDyour_secure_password_here # 可以覆盖yaml中的配置 SELENIUM_BROWSER_OPTIONS_HEADLESStrue # 在服务器上运行时设为true3. 增强的配置管理器 (utils/config_manager.py)在之前的基础上增加日志配置和更健壮的获取方法。4. 页面对象 (pages/login_page.py)from selenium.webdriver.common.by import By 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 LoginPage: def __init__(self, driver, config): self.driver driver self.config config self.timeout config.get_timeout(explicit) self.logger logging.getLogger(__name__) def load(self): 访问登录页面 url self.config.base_url self.logger.info(f正在访问登录页面: {url}) self.driver.get(url) return self def enter_credentials(self, username: str, password: str): 输入用户名和密码 selectors self.config.get(login.element_selectors, {}) try: username_elem WebDriverWait(self.driver, self.timeout).until( EC.presence_of_element_located((By.CSS_SELECTOR, selectors[username_input])) ) password_elem self.driver.find_element(By.CSS_SELECTOR, selectors[password_input]) username_elem.clear() username_elem.send_keys(username) password_elem.clear() password_elem.send_keys(password) self.logger.debug(用户名和密码已输入。) except (TimeoutException, KeyError) as e: self.logger.error(f定位或输入登录凭证时出错: {e}) raise def handle_captcha_if_needed(self): 根据配置决定是否处理验证码此处为简单示例仅做识别提示 if not self.config.get(login.features.handle_captcha): self.logger.info(配置为跳过验证码处理。) return selectors self.config.get(login.element_selectors, {}) if captcha_image in selectors: try: captcha_img self.driver.find_element(By.CSS_SELECTOR, selectors[captcha_image]) # 在实际项目中这里可以集成第三方OCR服务识别验证码 # captcha_text ocr_service.recognize(captcha_img.screenshot_as_base64) # self.driver.find_element(By.CSS_SELECTOR, selectors[captcha_input]).send_keys(captcha_text) self.logger.warning(检测到验证码但自动处理功能未实现。请手动处理或配置 handle_captcha: false。) input(请手动输入验证码后在控制台按回车继续...) except NoSuchElementException: self.logger.info(未找到验证码元素可能不需要验证码。) def submit(self): 点击登录按钮 selector self.config.get(login.element_selectors.submit_button, button[typesubmit]) try: submit_btn WebDriverWait(self.driver, self.timeout).until( EC.element_to_be_clickable((By.CSS_SELECTOR, selector)) ) submit_btn.click() self.logger.info(已提交登录表单。) except TimeoutException: self.logger.error(f登录按钮不可点击或未找到选择器: {selector}) raise def is_login_successful(self, success_indicator: str div.user-info) - bool: 检查登录是否成功通过查找成功后的页面元素判断 try: WebDriverWait(self.driver, 10).until( EC.presence_of_element_located((By.CSS_SELECTOR, success_indicator)) ) self.logger.info(登录成功) return True except TimeoutException: self.logger.warning(未检测到登录成功后的页面元素登录可能失败。) # 可以在这里保存错误截图 self.driver.save_screenshot(login_failed.png) return False5. 主脚本 (main.py)import logging import sys from utils.config_manager import ValidatedConfigManager from pages.login_page import LoginPage from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager def setup_logging(config): 根据配置设置日志 log_level getattr(logging, config.get(project.log_level, INFO).upper()) logging.basicConfig( levellog_level, format%(asctime)s - %(name)s - %(levelname)s - %(message)s, handlers[ logging.FileHandler(automation.log), logging.StreamHandler(sys.stdout) ] ) def create_configured_driver(config): 使用配置创建WebDriver options webdriver.ChromeOptions() browser_options config.get_browser_options() # 应用配置中的选项 if browser_options.get(headless): options.add_argument(--headless) if window_size in browser_options: w, h browser_options[window_size] options.add_argument(f--window-size{w},{h}) for arg in browser_options.get(arguments, []): options.add_argument(arg) for key, value in browser_options.get(experimental_options, {}).items(): options.add_experimental_option(key, value) # 使用webdriver-manager自动管理驱动 service Service(ChromeDriverManager().install()) driver webdriver.Chrome(serviceservice, optionsoptions) # 设置等待时间 driver.implicitly_wait(config.get_timeout(implicit)) return driver def main(): # 1. 加载配置 try: config ValidatedConfigManager(config/config.yaml) except (FileNotFoundError, ValueError) as e: logging.error(f配置加载失败: {e}) sys.exit(1) setup_logging(config) logger logging.getLogger(__name__) logger.info(f开始执行项目: {config.get(project.name)}) # 2. 创建驱动 driver None try: driver create_configured_driver(config) logger.info(浏览器驱动初始化成功。) # 3. 执行登录流程 login_page LoginPage(driver, config) login_page.load() username config.get(login.username) password config.get(login.password) # 从环境变量读取 if not username or not password: logger.error(登录用户名或密码未配置。) raise ValueError(缺少登录凭证) login_page.enter_credentials(username, password) login_page.handle_captcha_if_needed() login_page.submit() # 4. 验证结果 if login_page.is_login_successful(): logger.info(自动化登录流程执行完毕。) # 这里可以继续后续的业务操作... # driver.get(f{config.base_url}/dashboard) else: logger.error(登录流程执行失败请检查网络、凭证或页面结构。) except Exception as e: logger.exception(f执行过程中发生未预期错误: {e}) if driver: driver.save_screenshot(error_screenshot.png) logger.info(错误截图已保存为 error_screenshot.png) finally: if driver: driver.quit() logger.info(浏览器已关闭。) if __name__ __main__: main()这个案例展示了如何将一个完整的Selenium自动化任务高度参数化。通过修改YAML配置文件和.env文件你可以轻松地切换运行环境测试/生产。开启或关闭无头模式。调整超时策略。更换登录账号。启用或禁用验证码处理功能。甚至调整页面元素的CSS选择器而无需修改任何一行Python源代码。这种设计极大地提升了代码的复用性、可维护性和团队协作效率。新同事接手项目时只需关注配置文件就能理解整个脚本的运行逻辑和可调参数。