1. 项目概述为什么选择Selenium与Python这对黄金搭档如果你正在测试领域摸索或者是一名开发想为自己的项目加上一层自动化保障那你大概率听过Selenium的大名。但面对铺天盖地的教程和概念从哪下手、怎么组合工具往往是第一个拦路虎。我干了十多年测试和开发从早期的QTP玩到现在的Selenium可以很明确地告诉你对于绝大多数Web自动化测试的入门和实战Selenium Python是目前最平滑、最务实的选择。为什么是它俩首先Selenium不是一个单一工具而是一个浏览器自动化的“套件”。它的核心WebDriver协议允许你用代码比如Python像真人一样去操作浏览器点击按钮、输入文字、跳转页面、抓取数据。而Python以其语法简洁、库生态丰富著称写自动化脚本就像写伪代码一样直观。你不用花大量时间在复杂的语法上可以更专注于测试逻辑本身。这对新手来说学习曲线平缓成就感来得快对老手而言又能利用Python强大的第三方库如Pytest做测试框架Allure出漂亮报告构建起专业级的自动化测试体系。这个组合能解决的核心痛点很明确将重复、枯燥、易出错的Web界面手动测试转化为可重复、高精度、可回归的自动化脚本。无论是每天早上的冒烟测试还是每次发版前的全流程回归自动化脚本都能7x24小时待命解放你的双手让你去处理更复杂的探索性测试或架构设计。适合谁来学测试工程师自不必说后端开发想自测前端交互、前端开发想验证页面兼容性、甚至是运营同学想自动抓取些网页数据都能从这个组合中找到切入点。2. 环境搭建与核心组件解析2.1 Python环境不止是安装那么简单万事开头难而环境配置往往是第一个“坑”。很多教程只告诉你“去官网下载Python安装包”但这远远不够。首先关于Python版本的选择。目前主流是Python 3.8至3.11。我强烈建议新手直接选择Python 3.8或3.9。为什么因为这是最稳定的版本区间几乎所有第三方库的兼容性都经过充分验证。追求最新的3.12有时会遇到某些库尚未适配的小麻烦对于学习阶段稳定压倒一切。安装过程中的关键步骤下载安装包时务必勾选“Add Python to PATH”这个选项。这是很多新手遇到的第一个大坑——不勾选系统就找不到python和pip命令后续一切操作都无法进行。勾选后安装程序会自动帮你配置好环境变量。安装完成后验证是关键。打开你的命令行Windows用CMD或PowerShellMac/Linux用Terminal输入python --version和pip --version。如果能正确显示版本号恭喜你第一步成功了。注意在Windows上如果你同时安装了多个Python版本比如系统自带的Python2和刚装的Python3可能会出现命令冲突。此时尝试使用py -3 --version或python3 --version来指定使用Python3。虚拟环境Virtual Environment是必选项不是可选项。这是我要强调的第一个核心经验。不要直接在系统全局环境里安装项目依赖。想象一下你同时做两个项目一个需要Selenium 4.0另一个老项目需要Selenium 3.14全局安装只会导致版本冲突一团糟。虚拟环境就是为每个项目创建一个独立的“沙箱”里面的包互不干扰。创建和使用虚拟环境非常简单# 安装虚拟环境工具通常Python3已内置venv模块 # 创建名为 selenium_env 的虚拟环境 python -m venv selenium_env # 激活虚拟环境 # Windows: selenium_env\Scripts\activate # Mac/Linux: source selenium_env/bin/activate激活后你的命令行提示符前会出现(selenium_env)字样表示你已经在这个沙箱里了。之后所有pip install操作都只影响当前环境。2.2 Selenium与浏览器驱动的“配对”哲学安装Selenium库本身很简单激活虚拟环境后执行pip install selenium即可。真正的难点和重点在于浏览器驱动。Selenium WebDriver的工作原理是你的Python脚本客户端通过WebDriver协议发送指令如“点击id为login的元素”给一个特定的浏览器驱动这个驱动再控制真实的浏览器去执行。所以驱动就像是一个翻译官连接你的代码和浏览器。驱动管理的核心原则版本匹配。驱动的版本必须与你的浏览器主版本号完全一致。Chrome 120的浏览器就必须用ChromeDriver 120.x.x.x。不匹配会导致各种诡异错误比如浏览器打不开或者打开后立刻闪退。最佳实践使用WebDriver Manager。手动下载驱动、配置PATH是过去的老办法既繁琐又容易出错。现在主流做法是使用webdriver-manager这个Python库让它自动处理所有事情。pip install 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)这段代码会自动完成1. 检查当前Chrome浏览器版本2. 去官方仓库下载对应版本的ChromeDriver3. 将其配置给Selenium使用。一劳永逸再也无需关心驱动版本问题。对于Firefoxgeckodriver、Edgemsedgedriver也同样有对应的Manager。2.3 IDE的选择VSCode的高效配置写Python脚本一个好用的编辑器能事半功倍。VSCode是当前的首选轻量、免费、插件生态强大。必须安装的插件Python (Microsoft)提供Python语言支持、智能提示、调试、格式化等核心功能。Pylance强大的语言服务器补全和类型提示比默认的Jedi更快更准。Test Explorer UI或Pytest如果你后续使用Pytest框架写测试用例这个插件可以可视化地发现和运行用例。关键配置在VSCode中按F1输入“Python: Select Interpreter”选择你刚才创建的虚拟环境selenium_env下的Python解释器路径类似.../selenium_env/Scripts/python.exe。这确保了VSCode运行和调试代码时使用的是你虚拟环境中的包。3. Selenium核心操作从元素定位到复杂交互环境就绪我们进入正题。Selenium自动化可以抽象为三个步骤导航、定位、操作。3.1 八大元素定位策略详解与选用指南定位元素是自动化测试的基石找不到元素一切操作都无从谈起。Selenium提供了8种核心定位方式我将其分为“首选级”和“备选/特殊级”。首选级定位器稳定、高效ID定位 (By.ID)通过HTML元素的id属性定位。id在理想情况下应该是页面内唯一的定位速度最快。这是你的第一选择。driver.find_element(By.ID, “username”).send_keys(“admin”)CSS选择器定位 (By.CSS_SELECTOR)功能极其强大语法灵活。可以通过标签名、类名、属性、层级关系等进行组合定位。当元素没有ID时CSS选择器通常是第二选择。# 通过类名定位 driver.find_element(By.CSS_SELECTOR, “.submit-btn”) # 通过属性定位 driver.find_element(By.CSS_SELECTOR, “input[type‘email’]”) # 层级组合定位 driver.find_element(By.CSS_SELECTOR, “#login-form .btn-primary”)XPath定位 (By.XPATH)功能同样强大可以遍历XML/HTML文档。在CSS选择器难以表达复杂逻辑如“查找其父元素是div的第二个span”时使用。但XPath性能通常略低于CSS选择器且语法更复杂。# 绝对路径脆弱不推荐 # 相对路径属性推荐 driver.find_element(By.XPATH, “//input[name‘username’]”) # 文本内容定位慎用文本易变 driver.find_element(By.XPATH, “//button[text()‘登录’]”)备选/特殊级定位器Name定位 (By.NAME)通过name属性定位。在表单元素中比较常见但并非唯一。Class Name定位 (By.CLASS_NAME)通过class属性定位。注意一个元素常有多个class这里需要传入完整的类名字符串。Tag Name定位 (By.TAG_NAME)通过标签名定位如input,a。通常返回多个元素需要配合其他筛选。Link Text / Partial Link Text (By.LINK_TEXT,By.PARTIAL_LINK_TEXT)专门用于定位超链接a标签通过链接的完整或部分文本来定位。实操心得定位策略优先级我的个人经验是ID CSS Selector XPath Others。 优先使用ID。没有ID时优先尝试用CSS选择器因为它更简洁、性能更好。只有当CSS选择器无法实现复杂逻辑时才动用XPath。尽量避免使用完全依赖于文本如Link Text或脆弱层级绝对XPath的定位方式因为前端UI微调就可能导致脚本失效。一个好的定位器应该尽可能语义化和唯一化。3.2 常用浏览器操作与等待机制定位到元素后就是对元素进行操作和对浏览器进行控制。基础操作click() 点击send_keys(“text”) 输入文本clear() 清空输入框get_attribute(“attribute”) 获取元素属性值text 获取元素可见文本is_displayed(),is_enabled(),is_selected() 判断元素状态浏览器控制driver.get(“https://www.example.com”) 打开网页driver.back()/driver.forward() 后退/前进driver.refresh() 刷新driver.title/driver.current_url 获取标题和当前URLdriver.quit() 关闭浏览器及驱动重要必须调用以释放资源等待机制——自动化稳定的灵魂这是新手最容易忽略也最容易导致脚本“飘忽不定”有时成功有时失败的关键点。网页加载需要时间元素出现有快有慢。硬性等待time.sleep(10)是糟糕的做法它固定等待10秒无论页面是否已就绪浪费了时间。Selenium提供了两种智能等待隐式等待 (Implicit Wait)为整个driver会话设置一个全局的等待时间在查找任何元素时如果元素没有立即出现会轮询查找直到超时。driver.implicitly_wait(10) # 单位秒它是一把“大锤”设置简单但不够精细。例如设置10秒后即使检查一个本应抛出的错误元素如“密码错误”提示也会傻等10秒。显式等待 (Explicit Wait)针对某个特定条件进行等待更灵活、更精确。这是推荐的主要等待方式。from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC # 等待“登录按钮”可被点击最多等10秒每0.5秒检查一次 wait WebDriverWait(driver, 10, poll_frequency0.5) login_button wait.until(EC.element_to_be_clickable((By.ID, “login-btn”))) login_button.click()expected_conditions模块提供了大量条件如元素可见、元素存在、标题包含某文字、弹窗出现等。显式等待能让你的脚本在元素就绪的第一时间执行操作效率最高也最稳定。我的建议是结合使用。设置一个较短的全局隐式等待如5秒作为安全网。然后在关键交互步骤如点击重要按钮、等待页面跳转后新元素出现前使用针对性的显式等待。最后对于某些特殊异步加载可以配合等待元素属性变化等更高级的条件。3.3 处理弹窗、框架与多窗口真实网页不会那么简单弹窗、iframe和多标签页是常客。处理JavaScript弹窗 (Alert, Confirm, Prompt)from selenium.webdriver.common.alert import Alert # 切换到弹窗 alert Alert(driver) # 获取弹窗文本 print(alert.text) # 点击“确认” alert.accept() # 点击“取消” alert.dismiss() # 在Prompt弹窗中输入文本 alert.send_keys(“Your input”) alert.accept()切入/切出iframe框架如果元素位于iframe标签内你必须先切换到该iframe上下文才能定位其中的元素。# 通过ID或Name切换 driver.switch_to.frame(“iframe_id_or_name”) # 通过索引切换从0开始 driver.switch_to.frame(0) # 通过WebElement切换 iframe_element driver.find_element(By.TAG_NAME, “iframe”) driver.switch_to.frame(iframe_element) # 操作iframe内的元素... driver.find_element(By.ID, “inner-element”).click() # 操作完成后切回主文档 driver.switch_to.default_content()处理多窗口/多标签页# 获取当前窗口句柄 main_window driver.current_window_handle # 执行会打开新窗口的操作例如点击一个target“_blank”的链接 driver.find_element(By.LINK_TEXT, “Open New Window”).click() # 获取所有窗口句柄 all_windows driver.window_handles # 切换到新窗口 for window in all_windows: if window ! main_window: driver.switch_to.window(window) break # 在新窗口操作... print(driver.title) # 关闭新窗口切回主窗口 driver.close() driver.switch_to.window(main_window)4. 构建可维护的自动化测试框架写几个零散的脚本不难但要想让自动化测试可持续、可协作、易维护就需要引入框架和最佳实践。4.1 测试框架Pytest不仅仅是运行器unittest是Python标准库但pytest因其简洁、灵活、功能强大而成为事实上的标准。它让测试写起来更像写普通函数。安装与基础使用pip install pytest创建一个测试文件test_login.pyimport pytest from selenium import webdriver # 测试类 class TestLogin: # 每个测试函数开始前执行 pytest.fixture(scope“function”) def setup(self): self.driver webdriver.Chrome() self.driver.implicitly_wait(5) yield self.driver # 测试函数使用此driver # 每个测试函数结束后执行 self.driver.quit() # 一个测试用例 def test_login_success(self, setup): driver setup driver.get(“https://example.com/login”) driver.find_element(By.ID, “user”).send_keys(“correct_user”) driver.find_element(By.ID, “pwd”).send_keys(“correct_pwd”) driver.find_element(By.ID, “submit”).click() # 断言登录成功后页面应跳转到dashboard assert “dashboard” in driver.current_url def test_login_failed(self, setup): driver setup driver.get(“https://example.com/login”) driver.find_element(By.ID, “user”).send_keys(“wrong_user”) driver.find_element(By.ID, “pwd”).send_keys(“wrong_pwd”) driver.find_element(By.ID, “submit”).click() # 断言登录失败后应出现错误提示 error_msg driver.find_element(By.CLASS_NAME, “error”).text assert “Invalid” in error_msg运行测试只需在命令行输入pytest test_login.py。pytest会自动发现以test_开头的函数并执行。Pytest的强大特性Fixture夹具如上例中的setup用于提供测试依赖如driver管理生命周期打开/关闭浏览器实现数据复用。参数化测试用一组数据驱动同一个测试逻辑。pytest.mark.parametrize(“username, password, expected”, [ (“admin”, “admin123”, True), (“user”, “wrong”, False), (“”, “”, False), ]) def test_login_param(self, setup, username, password, expected): # ... 使用参数执行测试丰富的插件生态如pytest-html生成报告pytest-xdist并行测试pytest-rerunfailures失败重试。4.2 Page Object Model (POM)让代码远离“意大利面条”如果所有定位器和操作都堆在测试用例里代码很快就会变得难以阅读和维护。POM设计模式是解决这个问题的银弹。核心思想将每个页面或页面中的重要组件封装成一个类。这个类包含元素定位器以类变量的形式存放。页面操作方法封装对页面元素的操作如输入用户名、点击登录。一个简单的登录页面对象示例# 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 class LoginPage: # 定位器 USERNAME_INPUT (By.ID, “username”) PASSWORD_INPUT (By.ID, “password”) LOGIN_BUTTON (By.ID, “login-btn”) ERROR_MSG (By.CLASS_NAME, “alert-error”) def __init__(self, driver): self.driver driver self.wait WebDriverWait(driver, 10) # 页面操作方法 def enter_username(self, username): element self.wait.until(EC.visibility_of_element_located(self.USERNAME_INPUT)) element.clear() element.send_keys(username) return self # 支持链式调用 def enter_password(self, password): self.driver.find_element(*self.PASSWORD_INPUT).send_keys(password) return self def click_login(self): self.wait.until(EC.element_to_be_clickable(self.LOGIN_BUTTON)).click() def get_error_message(self): try: return self.driver.find_element(*self.ERROR_MSG).text except: return None # 一个完整的业务流方法 def login(self, username, password): self.enter_username(username).enter_password(password).click_login()在测试用例中使用# tests/test_login.py from pages.login_page import LoginPage def test_login_with_pom(setup): driver setup driver.get(“https://example.com/login”) login_page LoginPage(driver) # 测试失败场景 login_page.login(“wrong”, “wrong”) assert “Invalid credentials” in login_page.get_error_message() # 测试成功场景 login_page.login(“admin”, “admin123”) assert “dashboard” in driver.current_urlPOM带来的好处高可维护性当页面元素ID变化时你只需要修改LoginPage类中的定位器所有测试用例无需改动。高可读性测试用例读起来就像业务文档login_page.login(...)清晰易懂。低冗余页面操作逻辑被复用避免了代码重复。4.3 数据驱动与配置文件管理测试数据如用户名、密码、URL不应该硬编码在脚本里。常用的做法是使用外部文件管理。使用JSON或YAML文件管理测试数据// configs/test_data.json { “login”: { “valid_user”: { “username”: “standard_user”, “password”: “secret_sauce” }, “invalid_user”: { “username”: “locked_out_user”, “password”: “wrong” } }, “urls”: { “base_url”: “https://www.saucedemo.com” } }# utils/data_loader.py import json import os def load_test_data(file_name): file_path os.path.join(os.path.dirname(__file__), ‘..’, ‘configs’, file_name) with open(file_path, ‘r’, encoding‘utf-8’) as f: return json.load(f) # 在测试中使用 test_data load_test_data(“test_data.json”) base_url test_data[“urls”][“base_url”] valid_user test_data[“login”][“valid_user”]使用.env文件管理环境配置敏感信息或环境相关的配置如数据库连接串、密钥可以放在.env文件中并使用python-dotenv读取。# .env BASE_URLhttps://staging.example.com ADMIN_EMAILadminexample.com# conftest.py (pytest的全局配置文件) from dotenv import load_dotenv import os load_dotenv() # 加载.env文件中的变量到环境变量 pytest.fixture(scope“session”) def base_url(): return os.getenv(“BASE_URL”, “https://default.example.com”) # 提供默认值4.4 测试报告与日志让结果一目了然自动化测试跑完了结果呢一份清晰的报告至关重要。使用Allure生成炫酷的测试报告Allure能生成非常直观、详细的HTML报告包含用例层级、步骤、截图、附件等。安装pip install allure-pytest运行测试时添加参数pytest --alluredir./allure-results test_login.py生成并查看报告allure serve ./allure-results(需要先安装Allure命令行工具)在代码中为测试添加步骤和截图import allure from selenium import webdriver class TestLogin: allure.title(“测试用户成功登录”) allure.feature(“登录功能”) def test_login_success(self, setup): driver setup with allure.step(“1. 打开登录页面”): driver.get(“https://example.com/login”) allure.attach(driver.get_screenshot_as_png(), name“登录页面”, attachment_typeallure.attachment_type.PNG) with allure.step(“2. 输入正确凭据”): driver.find_element(By.ID, “user”).send_keys(“admin”) with allure.step(“3. 点击登录按钮”): driver.find_element(By.ID, “submit”).click() with allure.step(“4. 验证登录成功”): assert “welcome” in driver.current_url allure.attach(driver.get_screenshot_as_png(), name“登录成功页面”, attachment_typeallure.attachment_type.PNG)这样生成的Allure报告会清晰地展示每个测试步骤和对应的页面截图排查失败原因时一目了然。集成日志记录使用Python内置的logging模块记录脚本运行过程方便调试。import logging logging.basicConfig(levellogging.INFO, format‘%(asctime)s - %(name)s - %(levelname)s - %(message)s’) logger logging.getLogger(__name__) def click_element(driver, locator): try: element driver.find_element(*locator) element.click() logger.info(f“成功点击元素: {locator}”) except Exception as e: logger.error(f“点击元素失败 {locator}: {e}”) raise5. 进阶技巧与实战避坑指南掌握了基础框架我们来看看如何让脚本更健壮、更智能以及如何绕过那些常见的“坑”。5.1 处理动态元素与复杂等待现代前端大量使用Ajax、Vue/React等框架元素动态加载是常态。等待元素“稳定”再操作等待元素可见 (EC.visibility_of_element_located)等待元素可点击 (EC.element_to_be_clickable)等待元素存在 (EC.presence_of_element_located)等待元素消失 (EC.invisibility_of_element_located)等待页面标题包含特定文字 (EC.title_contains)自定义等待条件这是高级技巧。例如等待一个进度条加载到100%。def wait_for_progress_complete(driver, locator): def _predicate(drv): element drv.find_element(*locator) # 假设进度条的值在 ‘value’ 属性里 return element.get_attribute(“value”) “100” WebDriverWait(driver, 30).until(_predicate, message“进度条未在30秒内完成”)处理StaleElementReferenceException元素过时引用这是Selenium中最常见的异常之一。当你找到一个元素后页面发生了刷新或重绘如Ajax更新之前获取的元素引用就“过时”了再操作它就会抛出此异常。解决方案使用“重找策略”。不要将找到的元素对象长期存储而是在每次需要操作前重新定位。或者在try-catch块中捕获此异常然后重新定位元素。def safe_click(driver, locator, retries3): for i in range(retries): try: element driver.find_element(*locator) element.click() return True except StaleElementReferenceException: if i retries - 1: logger.warning(f“元素过时第{i1}次重试...”) time.sleep(0.5) else: raise return False5.2 文件上传、下载与Cookie处理文件上传对于input type“file”元素直接使用send_keys()传入文件的绝对路径即可无需模拟点击“浏览”按钮。upload_element driver.find_element(By.XPATH, “//input[type‘file’]”) upload_element.send_keys(“/Users/yourname/Desktop/test_image.png”)文件下载需要预先设置浏览器的下载选项。以下以Chrome为例from selenium import webdriver from selenium.webdriver.chrome.options import Options 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) driver webdriver.Chrome(optionschrome_options)下载后可以使用os模块检查文件是否已存在。Cookie处理自动化测试中有时需要绕过登录直接使用已登录的状态。Cookie是关键。# 1. 先手动登录一次获取Cookie可在浏览器开发者工具Application标签页复制 cookies [{‘name’: ‘sessionid’, ‘value’: ‘abc123...’, ‘domain’: ‘.example.com’}] # 2. 在自动化脚本中先访问网站然后添加Cookie driver.get(“https://www.example.com”) # 必须先访问域名才能设置其Cookie for cookie in cookies: driver.add_cookie(cookie) # 3. 再次访问此时已处于登录状态 driver.get(“https://www.example.com/dashboard”)5.3 常见问题排查与调试技巧浏览器打不开或秒退99%的原因是驱动版本不匹配。请用webdriver-manager或手动检查Chrome版本与ChromeDriver版本是否一致。检查是否有残留的浏览器进程或驱动进程任务管理器中结束它们。元素找不到 (NoSuchElementException)首先检查定位器用浏览器的开发者工具F12的Console标签输入$x(‘your_xpath’)或$$(‘your_css_selector’)验证定位器是否正确。检查是否在iframe里如果是需要先switch_to.frame。检查是否有足够的等待时间添加显式等待。检查元素是否在新窗口需要切换窗口句柄。脚本在本地运行成功在服务器/CI上失败无头模式(Headless)差异在服务器上通常以无头模式运行。有些网站在无头模式下行为不同。可以在无头模式下启用--window-size参数并考虑添加--disable-blink-featuresAutomationControlled来避免被检测为自动化工具。chrome_options.add_argument(“--headless”) # 无头模式 chrome_options.add_argument(“--window-size1920,1080”) # 设置窗口大小 chrome_options.add_argument(“--disable-blink-featuresAutomationControlled”)环境差异确保服务器上的Python、包版本与本地一致。使用requirements.txt文件固化依赖。路径问题文件上传、下载的路径在服务器上需要使用绝对路径。如何调试使用time.sleep()临时暂停在疑似有问题的地方插入sleep然后手动观察浏览器状态。截图在关键步骤或失败时截图driver.save_screenshot(‘debug.png’)。打印页面源码print(driver.page_source)查看当前DOM结构。使用PDB或VSCode调试器在代码中设置断点单步调试这是最强大的调试手段。5.4 持续集成与Headless运行自动化测试最终要融入开发流程在代码提交后自动运行。这就需要用到持续集成/持续部署工具如Jenkins、GitHub Actions、GitLab CI等。核心思路将你的测试代码、requirements.txt、配置文件提交到代码仓库。在CI服务器上配置一个任务Job。任务触发时如每次git pushCI服务器会自动拉取最新代码。创建一个干净的Python虚拟环境。根据requirements.txt安装依赖。以Headless模式运行测试因为CI服务器通常没有图形界面。收集测试结果和报告如Allure报告、JUnit XML。将报告发布到指定位置或发送通知。一个简单的GitHub Actions配置示例 (.github/workflows/test.yml)name: Selenium UI Tests on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkoutv2 - name: Set up Python uses: actions/setup-pythonv2 with: python-version: ‘3.9’ - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt - name: Install Chrome and ChromeDriver run: | sudo apt-get update sudo apt-get install -y google-chrome-stable - name: Run tests with pytest run: | pytest --headless --alluredirallure-results env: BASE_URL: ${{ secrets.BASE_URL }} # 使用GitHub Secrets存储敏感配置 - name: Upload Allure report uses: actions/upload-artifactv2 with: name: allure-report path: allure-results这份配置会在每次代码推送时在一个Ubuntu虚拟机中自动安装环境并运行测试。--headless是一个自定义的pytest标记你可以在conftest.py中定义它让fixture返回一个Headless模式的driver。走到这一步你已经从一个Selenium的初学者成长为能够搭建和维护一个企业级自动化测试框架的实践者了。记住自动化测试不是一蹴而就的从最关键、最稳定的冒烟用例开始逐步积累持续重构让它真正成为你研发流程中可靠的安全网。
Selenium与Python自动化测试:从环境搭建到POM框架实战
1. 项目概述为什么选择Selenium与Python这对黄金搭档如果你正在测试领域摸索或者是一名开发想为自己的项目加上一层自动化保障那你大概率听过Selenium的大名。但面对铺天盖地的教程和概念从哪下手、怎么组合工具往往是第一个拦路虎。我干了十多年测试和开发从早期的QTP玩到现在的Selenium可以很明确地告诉你对于绝大多数Web自动化测试的入门和实战Selenium Python是目前最平滑、最务实的选择。为什么是它俩首先Selenium不是一个单一工具而是一个浏览器自动化的“套件”。它的核心WebDriver协议允许你用代码比如Python像真人一样去操作浏览器点击按钮、输入文字、跳转页面、抓取数据。而Python以其语法简洁、库生态丰富著称写自动化脚本就像写伪代码一样直观。你不用花大量时间在复杂的语法上可以更专注于测试逻辑本身。这对新手来说学习曲线平缓成就感来得快对老手而言又能利用Python强大的第三方库如Pytest做测试框架Allure出漂亮报告构建起专业级的自动化测试体系。这个组合能解决的核心痛点很明确将重复、枯燥、易出错的Web界面手动测试转化为可重复、高精度、可回归的自动化脚本。无论是每天早上的冒烟测试还是每次发版前的全流程回归自动化脚本都能7x24小时待命解放你的双手让你去处理更复杂的探索性测试或架构设计。适合谁来学测试工程师自不必说后端开发想自测前端交互、前端开发想验证页面兼容性、甚至是运营同学想自动抓取些网页数据都能从这个组合中找到切入点。2. 环境搭建与核心组件解析2.1 Python环境不止是安装那么简单万事开头难而环境配置往往是第一个“坑”。很多教程只告诉你“去官网下载Python安装包”但这远远不够。首先关于Python版本的选择。目前主流是Python 3.8至3.11。我强烈建议新手直接选择Python 3.8或3.9。为什么因为这是最稳定的版本区间几乎所有第三方库的兼容性都经过充分验证。追求最新的3.12有时会遇到某些库尚未适配的小麻烦对于学习阶段稳定压倒一切。安装过程中的关键步骤下载安装包时务必勾选“Add Python to PATH”这个选项。这是很多新手遇到的第一个大坑——不勾选系统就找不到python和pip命令后续一切操作都无法进行。勾选后安装程序会自动帮你配置好环境变量。安装完成后验证是关键。打开你的命令行Windows用CMD或PowerShellMac/Linux用Terminal输入python --version和pip --version。如果能正确显示版本号恭喜你第一步成功了。注意在Windows上如果你同时安装了多个Python版本比如系统自带的Python2和刚装的Python3可能会出现命令冲突。此时尝试使用py -3 --version或python3 --version来指定使用Python3。虚拟环境Virtual Environment是必选项不是可选项。这是我要强调的第一个核心经验。不要直接在系统全局环境里安装项目依赖。想象一下你同时做两个项目一个需要Selenium 4.0另一个老项目需要Selenium 3.14全局安装只会导致版本冲突一团糟。虚拟环境就是为每个项目创建一个独立的“沙箱”里面的包互不干扰。创建和使用虚拟环境非常简单# 安装虚拟环境工具通常Python3已内置venv模块 # 创建名为 selenium_env 的虚拟环境 python -m venv selenium_env # 激活虚拟环境 # Windows: selenium_env\Scripts\activate # Mac/Linux: source selenium_env/bin/activate激活后你的命令行提示符前会出现(selenium_env)字样表示你已经在这个沙箱里了。之后所有pip install操作都只影响当前环境。2.2 Selenium与浏览器驱动的“配对”哲学安装Selenium库本身很简单激活虚拟环境后执行pip install selenium即可。真正的难点和重点在于浏览器驱动。Selenium WebDriver的工作原理是你的Python脚本客户端通过WebDriver协议发送指令如“点击id为login的元素”给一个特定的浏览器驱动这个驱动再控制真实的浏览器去执行。所以驱动就像是一个翻译官连接你的代码和浏览器。驱动管理的核心原则版本匹配。驱动的版本必须与你的浏览器主版本号完全一致。Chrome 120的浏览器就必须用ChromeDriver 120.x.x.x。不匹配会导致各种诡异错误比如浏览器打不开或者打开后立刻闪退。最佳实践使用WebDriver Manager。手动下载驱动、配置PATH是过去的老办法既繁琐又容易出错。现在主流做法是使用webdriver-manager这个Python库让它自动处理所有事情。pip install 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)这段代码会自动完成1. 检查当前Chrome浏览器版本2. 去官方仓库下载对应版本的ChromeDriver3. 将其配置给Selenium使用。一劳永逸再也无需关心驱动版本问题。对于Firefoxgeckodriver、Edgemsedgedriver也同样有对应的Manager。2.3 IDE的选择VSCode的高效配置写Python脚本一个好用的编辑器能事半功倍。VSCode是当前的首选轻量、免费、插件生态强大。必须安装的插件Python (Microsoft)提供Python语言支持、智能提示、调试、格式化等核心功能。Pylance强大的语言服务器补全和类型提示比默认的Jedi更快更准。Test Explorer UI或Pytest如果你后续使用Pytest框架写测试用例这个插件可以可视化地发现和运行用例。关键配置在VSCode中按F1输入“Python: Select Interpreter”选择你刚才创建的虚拟环境selenium_env下的Python解释器路径类似.../selenium_env/Scripts/python.exe。这确保了VSCode运行和调试代码时使用的是你虚拟环境中的包。3. Selenium核心操作从元素定位到复杂交互环境就绪我们进入正题。Selenium自动化可以抽象为三个步骤导航、定位、操作。3.1 八大元素定位策略详解与选用指南定位元素是自动化测试的基石找不到元素一切操作都无从谈起。Selenium提供了8种核心定位方式我将其分为“首选级”和“备选/特殊级”。首选级定位器稳定、高效ID定位 (By.ID)通过HTML元素的id属性定位。id在理想情况下应该是页面内唯一的定位速度最快。这是你的第一选择。driver.find_element(By.ID, “username”).send_keys(“admin”)CSS选择器定位 (By.CSS_SELECTOR)功能极其强大语法灵活。可以通过标签名、类名、属性、层级关系等进行组合定位。当元素没有ID时CSS选择器通常是第二选择。# 通过类名定位 driver.find_element(By.CSS_SELECTOR, “.submit-btn”) # 通过属性定位 driver.find_element(By.CSS_SELECTOR, “input[type‘email’]”) # 层级组合定位 driver.find_element(By.CSS_SELECTOR, “#login-form .btn-primary”)XPath定位 (By.XPATH)功能同样强大可以遍历XML/HTML文档。在CSS选择器难以表达复杂逻辑如“查找其父元素是div的第二个span”时使用。但XPath性能通常略低于CSS选择器且语法更复杂。# 绝对路径脆弱不推荐 # 相对路径属性推荐 driver.find_element(By.XPATH, “//input[name‘username’]”) # 文本内容定位慎用文本易变 driver.find_element(By.XPATH, “//button[text()‘登录’]”)备选/特殊级定位器Name定位 (By.NAME)通过name属性定位。在表单元素中比较常见但并非唯一。Class Name定位 (By.CLASS_NAME)通过class属性定位。注意一个元素常有多个class这里需要传入完整的类名字符串。Tag Name定位 (By.TAG_NAME)通过标签名定位如input,a。通常返回多个元素需要配合其他筛选。Link Text / Partial Link Text (By.LINK_TEXT,By.PARTIAL_LINK_TEXT)专门用于定位超链接a标签通过链接的完整或部分文本来定位。实操心得定位策略优先级我的个人经验是ID CSS Selector XPath Others。 优先使用ID。没有ID时优先尝试用CSS选择器因为它更简洁、性能更好。只有当CSS选择器无法实现复杂逻辑时才动用XPath。尽量避免使用完全依赖于文本如Link Text或脆弱层级绝对XPath的定位方式因为前端UI微调就可能导致脚本失效。一个好的定位器应该尽可能语义化和唯一化。3.2 常用浏览器操作与等待机制定位到元素后就是对元素进行操作和对浏览器进行控制。基础操作click() 点击send_keys(“text”) 输入文本clear() 清空输入框get_attribute(“attribute”) 获取元素属性值text 获取元素可见文本is_displayed(),is_enabled(),is_selected() 判断元素状态浏览器控制driver.get(“https://www.example.com”) 打开网页driver.back()/driver.forward() 后退/前进driver.refresh() 刷新driver.title/driver.current_url 获取标题和当前URLdriver.quit() 关闭浏览器及驱动重要必须调用以释放资源等待机制——自动化稳定的灵魂这是新手最容易忽略也最容易导致脚本“飘忽不定”有时成功有时失败的关键点。网页加载需要时间元素出现有快有慢。硬性等待time.sleep(10)是糟糕的做法它固定等待10秒无论页面是否已就绪浪费了时间。Selenium提供了两种智能等待隐式等待 (Implicit Wait)为整个driver会话设置一个全局的等待时间在查找任何元素时如果元素没有立即出现会轮询查找直到超时。driver.implicitly_wait(10) # 单位秒它是一把“大锤”设置简单但不够精细。例如设置10秒后即使检查一个本应抛出的错误元素如“密码错误”提示也会傻等10秒。显式等待 (Explicit Wait)针对某个特定条件进行等待更灵活、更精确。这是推荐的主要等待方式。from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC # 等待“登录按钮”可被点击最多等10秒每0.5秒检查一次 wait WebDriverWait(driver, 10, poll_frequency0.5) login_button wait.until(EC.element_to_be_clickable((By.ID, “login-btn”))) login_button.click()expected_conditions模块提供了大量条件如元素可见、元素存在、标题包含某文字、弹窗出现等。显式等待能让你的脚本在元素就绪的第一时间执行操作效率最高也最稳定。我的建议是结合使用。设置一个较短的全局隐式等待如5秒作为安全网。然后在关键交互步骤如点击重要按钮、等待页面跳转后新元素出现前使用针对性的显式等待。最后对于某些特殊异步加载可以配合等待元素属性变化等更高级的条件。3.3 处理弹窗、框架与多窗口真实网页不会那么简单弹窗、iframe和多标签页是常客。处理JavaScript弹窗 (Alert, Confirm, Prompt)from selenium.webdriver.common.alert import Alert # 切换到弹窗 alert Alert(driver) # 获取弹窗文本 print(alert.text) # 点击“确认” alert.accept() # 点击“取消” alert.dismiss() # 在Prompt弹窗中输入文本 alert.send_keys(“Your input”) alert.accept()切入/切出iframe框架如果元素位于iframe标签内你必须先切换到该iframe上下文才能定位其中的元素。# 通过ID或Name切换 driver.switch_to.frame(“iframe_id_or_name”) # 通过索引切换从0开始 driver.switch_to.frame(0) # 通过WebElement切换 iframe_element driver.find_element(By.TAG_NAME, “iframe”) driver.switch_to.frame(iframe_element) # 操作iframe内的元素... driver.find_element(By.ID, “inner-element”).click() # 操作完成后切回主文档 driver.switch_to.default_content()处理多窗口/多标签页# 获取当前窗口句柄 main_window driver.current_window_handle # 执行会打开新窗口的操作例如点击一个target“_blank”的链接 driver.find_element(By.LINK_TEXT, “Open New Window”).click() # 获取所有窗口句柄 all_windows driver.window_handles # 切换到新窗口 for window in all_windows: if window ! main_window: driver.switch_to.window(window) break # 在新窗口操作... print(driver.title) # 关闭新窗口切回主窗口 driver.close() driver.switch_to.window(main_window)4. 构建可维护的自动化测试框架写几个零散的脚本不难但要想让自动化测试可持续、可协作、易维护就需要引入框架和最佳实践。4.1 测试框架Pytest不仅仅是运行器unittest是Python标准库但pytest因其简洁、灵活、功能强大而成为事实上的标准。它让测试写起来更像写普通函数。安装与基础使用pip install pytest创建一个测试文件test_login.pyimport pytest from selenium import webdriver # 测试类 class TestLogin: # 每个测试函数开始前执行 pytest.fixture(scope“function”) def setup(self): self.driver webdriver.Chrome() self.driver.implicitly_wait(5) yield self.driver # 测试函数使用此driver # 每个测试函数结束后执行 self.driver.quit() # 一个测试用例 def test_login_success(self, setup): driver setup driver.get(“https://example.com/login”) driver.find_element(By.ID, “user”).send_keys(“correct_user”) driver.find_element(By.ID, “pwd”).send_keys(“correct_pwd”) driver.find_element(By.ID, “submit”).click() # 断言登录成功后页面应跳转到dashboard assert “dashboard” in driver.current_url def test_login_failed(self, setup): driver setup driver.get(“https://example.com/login”) driver.find_element(By.ID, “user”).send_keys(“wrong_user”) driver.find_element(By.ID, “pwd”).send_keys(“wrong_pwd”) driver.find_element(By.ID, “submit”).click() # 断言登录失败后应出现错误提示 error_msg driver.find_element(By.CLASS_NAME, “error”).text assert “Invalid” in error_msg运行测试只需在命令行输入pytest test_login.py。pytest会自动发现以test_开头的函数并执行。Pytest的强大特性Fixture夹具如上例中的setup用于提供测试依赖如driver管理生命周期打开/关闭浏览器实现数据复用。参数化测试用一组数据驱动同一个测试逻辑。pytest.mark.parametrize(“username, password, expected”, [ (“admin”, “admin123”, True), (“user”, “wrong”, False), (“”, “”, False), ]) def test_login_param(self, setup, username, password, expected): # ... 使用参数执行测试丰富的插件生态如pytest-html生成报告pytest-xdist并行测试pytest-rerunfailures失败重试。4.2 Page Object Model (POM)让代码远离“意大利面条”如果所有定位器和操作都堆在测试用例里代码很快就会变得难以阅读和维护。POM设计模式是解决这个问题的银弹。核心思想将每个页面或页面中的重要组件封装成一个类。这个类包含元素定位器以类变量的形式存放。页面操作方法封装对页面元素的操作如输入用户名、点击登录。一个简单的登录页面对象示例# 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 class LoginPage: # 定位器 USERNAME_INPUT (By.ID, “username”) PASSWORD_INPUT (By.ID, “password”) LOGIN_BUTTON (By.ID, “login-btn”) ERROR_MSG (By.CLASS_NAME, “alert-error”) def __init__(self, driver): self.driver driver self.wait WebDriverWait(driver, 10) # 页面操作方法 def enter_username(self, username): element self.wait.until(EC.visibility_of_element_located(self.USERNAME_INPUT)) element.clear() element.send_keys(username) return self # 支持链式调用 def enter_password(self, password): self.driver.find_element(*self.PASSWORD_INPUT).send_keys(password) return self def click_login(self): self.wait.until(EC.element_to_be_clickable(self.LOGIN_BUTTON)).click() def get_error_message(self): try: return self.driver.find_element(*self.ERROR_MSG).text except: return None # 一个完整的业务流方法 def login(self, username, password): self.enter_username(username).enter_password(password).click_login()在测试用例中使用# tests/test_login.py from pages.login_page import LoginPage def test_login_with_pom(setup): driver setup driver.get(“https://example.com/login”) login_page LoginPage(driver) # 测试失败场景 login_page.login(“wrong”, “wrong”) assert “Invalid credentials” in login_page.get_error_message() # 测试成功场景 login_page.login(“admin”, “admin123”) assert “dashboard” in driver.current_urlPOM带来的好处高可维护性当页面元素ID变化时你只需要修改LoginPage类中的定位器所有测试用例无需改动。高可读性测试用例读起来就像业务文档login_page.login(...)清晰易懂。低冗余页面操作逻辑被复用避免了代码重复。4.3 数据驱动与配置文件管理测试数据如用户名、密码、URL不应该硬编码在脚本里。常用的做法是使用外部文件管理。使用JSON或YAML文件管理测试数据// configs/test_data.json { “login”: { “valid_user”: { “username”: “standard_user”, “password”: “secret_sauce” }, “invalid_user”: { “username”: “locked_out_user”, “password”: “wrong” } }, “urls”: { “base_url”: “https://www.saucedemo.com” } }# utils/data_loader.py import json import os def load_test_data(file_name): file_path os.path.join(os.path.dirname(__file__), ‘..’, ‘configs’, file_name) with open(file_path, ‘r’, encoding‘utf-8’) as f: return json.load(f) # 在测试中使用 test_data load_test_data(“test_data.json”) base_url test_data[“urls”][“base_url”] valid_user test_data[“login”][“valid_user”]使用.env文件管理环境配置敏感信息或环境相关的配置如数据库连接串、密钥可以放在.env文件中并使用python-dotenv读取。# .env BASE_URLhttps://staging.example.com ADMIN_EMAILadminexample.com# conftest.py (pytest的全局配置文件) from dotenv import load_dotenv import os load_dotenv() # 加载.env文件中的变量到环境变量 pytest.fixture(scope“session”) def base_url(): return os.getenv(“BASE_URL”, “https://default.example.com”) # 提供默认值4.4 测试报告与日志让结果一目了然自动化测试跑完了结果呢一份清晰的报告至关重要。使用Allure生成炫酷的测试报告Allure能生成非常直观、详细的HTML报告包含用例层级、步骤、截图、附件等。安装pip install allure-pytest运行测试时添加参数pytest --alluredir./allure-results test_login.py生成并查看报告allure serve ./allure-results(需要先安装Allure命令行工具)在代码中为测试添加步骤和截图import allure from selenium import webdriver class TestLogin: allure.title(“测试用户成功登录”) allure.feature(“登录功能”) def test_login_success(self, setup): driver setup with allure.step(“1. 打开登录页面”): driver.get(“https://example.com/login”) allure.attach(driver.get_screenshot_as_png(), name“登录页面”, attachment_typeallure.attachment_type.PNG) with allure.step(“2. 输入正确凭据”): driver.find_element(By.ID, “user”).send_keys(“admin”) with allure.step(“3. 点击登录按钮”): driver.find_element(By.ID, “submit”).click() with allure.step(“4. 验证登录成功”): assert “welcome” in driver.current_url allure.attach(driver.get_screenshot_as_png(), name“登录成功页面”, attachment_typeallure.attachment_type.PNG)这样生成的Allure报告会清晰地展示每个测试步骤和对应的页面截图排查失败原因时一目了然。集成日志记录使用Python内置的logging模块记录脚本运行过程方便调试。import logging logging.basicConfig(levellogging.INFO, format‘%(asctime)s - %(name)s - %(levelname)s - %(message)s’) logger logging.getLogger(__name__) def click_element(driver, locator): try: element driver.find_element(*locator) element.click() logger.info(f“成功点击元素: {locator}”) except Exception as e: logger.error(f“点击元素失败 {locator}: {e}”) raise5. 进阶技巧与实战避坑指南掌握了基础框架我们来看看如何让脚本更健壮、更智能以及如何绕过那些常见的“坑”。5.1 处理动态元素与复杂等待现代前端大量使用Ajax、Vue/React等框架元素动态加载是常态。等待元素“稳定”再操作等待元素可见 (EC.visibility_of_element_located)等待元素可点击 (EC.element_to_be_clickable)等待元素存在 (EC.presence_of_element_located)等待元素消失 (EC.invisibility_of_element_located)等待页面标题包含特定文字 (EC.title_contains)自定义等待条件这是高级技巧。例如等待一个进度条加载到100%。def wait_for_progress_complete(driver, locator): def _predicate(drv): element drv.find_element(*locator) # 假设进度条的值在 ‘value’ 属性里 return element.get_attribute(“value”) “100” WebDriverWait(driver, 30).until(_predicate, message“进度条未在30秒内完成”)处理StaleElementReferenceException元素过时引用这是Selenium中最常见的异常之一。当你找到一个元素后页面发生了刷新或重绘如Ajax更新之前获取的元素引用就“过时”了再操作它就会抛出此异常。解决方案使用“重找策略”。不要将找到的元素对象长期存储而是在每次需要操作前重新定位。或者在try-catch块中捕获此异常然后重新定位元素。def safe_click(driver, locator, retries3): for i in range(retries): try: element driver.find_element(*locator) element.click() return True except StaleElementReferenceException: if i retries - 1: logger.warning(f“元素过时第{i1}次重试...”) time.sleep(0.5) else: raise return False5.2 文件上传、下载与Cookie处理文件上传对于input type“file”元素直接使用send_keys()传入文件的绝对路径即可无需模拟点击“浏览”按钮。upload_element driver.find_element(By.XPATH, “//input[type‘file’]”) upload_element.send_keys(“/Users/yourname/Desktop/test_image.png”)文件下载需要预先设置浏览器的下载选项。以下以Chrome为例from selenium import webdriver from selenium.webdriver.chrome.options import Options 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) driver webdriver.Chrome(optionschrome_options)下载后可以使用os模块检查文件是否已存在。Cookie处理自动化测试中有时需要绕过登录直接使用已登录的状态。Cookie是关键。# 1. 先手动登录一次获取Cookie可在浏览器开发者工具Application标签页复制 cookies [{‘name’: ‘sessionid’, ‘value’: ‘abc123...’, ‘domain’: ‘.example.com’}] # 2. 在自动化脚本中先访问网站然后添加Cookie driver.get(“https://www.example.com”) # 必须先访问域名才能设置其Cookie for cookie in cookies: driver.add_cookie(cookie) # 3. 再次访问此时已处于登录状态 driver.get(“https://www.example.com/dashboard”)5.3 常见问题排查与调试技巧浏览器打不开或秒退99%的原因是驱动版本不匹配。请用webdriver-manager或手动检查Chrome版本与ChromeDriver版本是否一致。检查是否有残留的浏览器进程或驱动进程任务管理器中结束它们。元素找不到 (NoSuchElementException)首先检查定位器用浏览器的开发者工具F12的Console标签输入$x(‘your_xpath’)或$$(‘your_css_selector’)验证定位器是否正确。检查是否在iframe里如果是需要先switch_to.frame。检查是否有足够的等待时间添加显式等待。检查元素是否在新窗口需要切换窗口句柄。脚本在本地运行成功在服务器/CI上失败无头模式(Headless)差异在服务器上通常以无头模式运行。有些网站在无头模式下行为不同。可以在无头模式下启用--window-size参数并考虑添加--disable-blink-featuresAutomationControlled来避免被检测为自动化工具。chrome_options.add_argument(“--headless”) # 无头模式 chrome_options.add_argument(“--window-size1920,1080”) # 设置窗口大小 chrome_options.add_argument(“--disable-blink-featuresAutomationControlled”)环境差异确保服务器上的Python、包版本与本地一致。使用requirements.txt文件固化依赖。路径问题文件上传、下载的路径在服务器上需要使用绝对路径。如何调试使用time.sleep()临时暂停在疑似有问题的地方插入sleep然后手动观察浏览器状态。截图在关键步骤或失败时截图driver.save_screenshot(‘debug.png’)。打印页面源码print(driver.page_source)查看当前DOM结构。使用PDB或VSCode调试器在代码中设置断点单步调试这是最强大的调试手段。5.4 持续集成与Headless运行自动化测试最终要融入开发流程在代码提交后自动运行。这就需要用到持续集成/持续部署工具如Jenkins、GitHub Actions、GitLab CI等。核心思路将你的测试代码、requirements.txt、配置文件提交到代码仓库。在CI服务器上配置一个任务Job。任务触发时如每次git pushCI服务器会自动拉取最新代码。创建一个干净的Python虚拟环境。根据requirements.txt安装依赖。以Headless模式运行测试因为CI服务器通常没有图形界面。收集测试结果和报告如Allure报告、JUnit XML。将报告发布到指定位置或发送通知。一个简单的GitHub Actions配置示例 (.github/workflows/test.yml)name: Selenium UI Tests on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkoutv2 - name: Set up Python uses: actions/setup-pythonv2 with: python-version: ‘3.9’ - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt - name: Install Chrome and ChromeDriver run: | sudo apt-get update sudo apt-get install -y google-chrome-stable - name: Run tests with pytest run: | pytest --headless --alluredirallure-results env: BASE_URL: ${{ secrets.BASE_URL }} # 使用GitHub Secrets存储敏感配置 - name: Upload Allure report uses: actions/upload-artifactv2 with: name: allure-report path: allure-results这份配置会在每次代码推送时在一个Ubuntu虚拟机中自动安装环境并运行测试。--headless是一个自定义的pytest标记你可以在conftest.py中定义它让fixture返回一个Headless模式的driver。走到这一步你已经从一个Selenium的初学者成长为能够搭建和维护一个企业级自动化测试框架的实践者了。记住自动化测试不是一蹴而就的从最关键、最稳定的冒烟用例开始逐步积累持续重构让它真正成为你研发流程中可靠的安全网。