Python Selenium自动化测试与爬虫实战:从环境搭建到框架设计 1. 从手动到自动为什么我们需要模拟网页点击作为一名在测试和爬虫领域摸爬滚打多年的开发者我几乎每天都要和浏览器打交道。早期我的工作流程是这样的打开浏览器输入网址等待页面加载找到那个该死的按钮点击再等待再点击……日复一日重复、枯燥还容易因为手滑点错。直到我遇到了Selenium它彻底改变了我的工作方式。简单来说Selenium就是一个能让你用代码控制浏览器的工具而“模拟网页点击”则是它最基础、最核心也最实用的功能之一。你可能已经听说过Python和Selenium但或许还在犹豫这玩意儿到底能干嘛我直接告诉你它的应用场景远超你的想象。对于测试工程师它是自动化测试的基石可以模拟用户操作验证功能是否正常尤其是在回归测试中能节省海量时间。对于数据分析师或爬虫开发者当目标网站的数据需要通过点击按钮、翻页、展开下拉菜单才能获取时Selenium就是那把“万能钥匙”。甚至对于运营或市场人员一些日常的、重复的网页操作如批量上传、数据录入也能用它来解放双手。很多人一听到“自动化”、“编程”就头大觉得门槛很高。但我想说用Python操作Selenium模拟点击其核心逻辑和你手动操作几乎一模一样找到那个元素然后点击它。难点不在于“点击”这个动作而在于如何稳定、准确地“找到”那个元素以及如何处理点击前后网页的各种“反应”。这篇文章我就从一个老司机的角度带你绕过我当年踩过的所有坑手把手让你掌握这套“点石成金”的本事。我们不会空谈理论所有内容都围绕一个目标写出一段能真正跑起来、稳定工作的代码。2. 环境搭建选对武器避开版本“地狱”工欲善其事必先利其器。Selenium环境的搭建是第一步也是劝退很多新手的“第一道坎”。问题往往不是出在Selenium本身而是出在它依赖的浏览器和驱动上。下面我会给出一个当前基于2024年的常见环境最稳定、兼容性最好的方案。2.1 Python与Selenium库安装首先确保你安装了Python。我个人推荐使用Python 3.8到3.10之间的版本这是目前生态兼容性最好的区间。安装Python时务必勾选“Add Python to PATH”这是很多新手忽略导致后续命令找不到的关键一步。安装Selenium库非常简单打开你的命令行Windows的CMD或PowerShellMac/Linux的Terminal输入以下命令pip install selenium通常情况下这会安装最新稳定版的Selenium如4.x版本。但请注意有时最新版可能会引入一些不兼容的改动。如果你追求极致的稳定特别是项目需要长期运行可以指定一个稍旧的版本例如pip install selenium4.10.0这里有一个非常重要的经验永远记录下你项目所使用的Python和Selenium的具体版本号。最好是在项目根目录创建一个requirements.txt文件里面写上selenium4.10.0。这样当你换一台机器或将来重新搭建环境时可以一键复原避免版本冲突带来的莫名错误。2.2 浏览器与WebDriver版本锁死的艺术这是整个环境搭建中最核心、也最容易出错的部分。Selenium自己并不包含浏览器它需要通过一个叫做“WebDriver”的中间件来与真实的浏览器如Chrome, Firefox通信。你必须保证三者的版本匹配浏览器版本、对应的WebDriver版本、以及Selenium库的版本。核心原则先确定浏览器版本再下载对应版本的WebDriver。以最常用的Chrome为例查看Chrome版本打开Chrome浏览器点击右上角三个点 - 帮助 - 关于Google Chrome。记下完整的版本号例如114.0.5735.199。下载对应ChromeDriver访问ChromeDriver的官方下载站。这里的关键是对于较新的Chrome版本大约115之后官方推荐使用Chrome for Testing渠道的驱动。更通用的方法是在项目中通过webdriver-manager这个库来自动管理驱动这是目前最优雅的解决方案。安装webdriver-managerpip install webdriver-manager在代码中使用自动管理from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager # 自动下载、缓存并匹配当前Chrome版本的驱动 service Service(ChromeDriverManager().install()) driver webdriver.Chrome(serviceservice)这段代码会自动处理版本匹配问题极大降低了环境配置的复杂度。webdriver-manager同样支持Firefox、Edge等浏览器。为什么我强烈推荐webdriver-manager早期我们需要手动下载chromedriver.exe然后放到系统路径或者项目目录下。这带来几个问题一是每次浏览器自动升级后驱动就失效了需要重新下载二是在团队协作或部署到服务器时每台机器的环境都要手动配置非常麻烦。webdriver-manager解决了这一切它让WebDriver像Python库一样被自动管理。注意在某些严格的内网环境或无法连接外网的服务器上webdriver-manager可能无法在线下载驱动。此时你需要回归手动模式下载对应版本的驱动文件在代码中指定其绝对路径。service Service(executable_pathr‘C:\path\to\your\chromedriver.exe‘) # Windows示例 # 或 service Service(executable_path‘/usr/local/bin/chromedriver‘) # Mac/Linux示例2.3 验证环境与第一个脚本环境装好后我们写一个最简单的脚本来验证一切是否正常。这个脚本将打开百度首页在搜索框输入关键词并点击“百度一下”按钮。from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager import time # 1. 创建驱动服务自动管理ChromeDriver service Service(ChromeDriverManager().install()) # 2. 初始化浏览器驱动这里可以添加很多选项 driver webdriver.Chrome(serviceservice) try: # 3. 打开目标网页 driver.get(‘https://www.baidu.com‘) # 等待页面加载这是一个简单的强制等待实际项目中会用更智能的方式 time.sleep(2) # 4. 定位搜索框元素 # 通过元素的‘id‘属性定位这是最快最准的方式 search_box driver.find_element(By.ID, ‘kw‘) # 5. 在搜索框中输入文字 search_box.send_keys(‘Selenium自动化测试‘) # 6. 定位“百度一下”按钮并点击 # 通过元素的‘id‘属性定位 search_button driver.find_element(By.ID, ‘su‘) search_button.click() # 7. 等待搜索结果加载 time.sleep(3) # 8. 可以打印当前页面的标题或URL作为验证 print(‘当前页面标题‘, driver.title) print(‘当前页面URL‘, driver.current_url) finally: # 9. 等待一会儿方便肉眼观察结果然后关闭浏览器 time.sleep(5) driver.quit()运行这段代码你应该能看到Chrome浏览器自动打开访问百度输入文字并完成搜索然后关闭。如果成功恭喜你你的Selenium环境已经就绪。如果失败请根据错误信息通常是关于驱动或浏览器的回溯检查上述步骤。3. 元素定位精准“找到”目标的八种武器模拟点击的第一步也是最重要的一步就是告诉Selenium“你要点击页面上哪个东西”。这个过程叫做“元素定位”。Selenium提供了多达8种定位策略但并非每种都同样好用。掌握它们的优先级和适用场景是写出稳定脚本的关键。3.1 定位策略的优先级与实战选择我把定位策略按优先级排序如下ID元素的唯一身份证。如果元素有id属性且这个id不是动态生成的无条件优先使用它。它查找速度最快最精确。element driver.find_element(By.ID, ‘login-button‘)Name元素的名称。常用于表单元素input, select等。如果name是唯一且稳定的也是很好的选择。element driver.find_element(By.NAME, ‘username‘)CSS SelectorCSS选择器。这是功能最强大、最灵活的定位方式几乎可以定位任何元素。当ID和Name不可用时它是首选。语法和前端CSS选择器一致。# 通过class定位 element driver.find_element(By.CSS_SELECTOR, ‘.submit-btn‘) # 通过属性定位 element driver.find_element(By.CSS_SELECTOR, ‘input[type“submit”]‘) # 通过层级关系定位 element driver.find_element(By.CSS_SELECTOR, ‘div#container ul.list li:first-child‘)XPathXML路径语言。功能同样强大可以在整个DOM树中进行复杂查询。但通常比CSS Selector慢且语法更复杂。在CSS无法解决的极端情况下如需要根据文本内容定位使用。# 绝对路径脆弱不推荐 element driver.find_element(By.XPATH, ‘/html/body/div[1]/form/input[3]‘) # 相对路径结合属性推荐 element driver.find_element(By.XPATH, ‘//input[id“kw”]‘) # 根据文本内容定位CSS做不到 element driver.find_element(By.XPATH, ‘//button[text()“登录”]‘)Link Text / Partial Link Text专门用于定位超链接a标签。通过链接的完整或部分文本定位。# 完整文本 element driver.find_element(By.LINK_TEXT, ‘忘记密码‘) # 部分文本 element driver.find_element(By.PARTIAL_LINK_TEXT, ‘密码‘)Class Name通过CSS类名定位。谨慎使用因为一个类名通常被多个元素共享容易定位到多个元素而非一个。# 可能返回多个元素通常用find_elements elements driver.find_elements(By.CLASS_NAME, ‘btn‘)Tag Name通过标签名定位如div,input。极少单独使用因为一个页面有太多相同标签。通常与其他定位方式结合或在查找多个同类元素时使用。实战经验如何选择黄金法则优先使用IDNameCSS Selector。为什么CSS Selector优于XPath在大多数现代浏览器中CSS Selector的解析和查找速度比XPath更快且语法更简洁更易于阅读和维护。XPath的遍历机制在复杂DOM中可能更慢。如何获取这些定位信息使用浏览器的开发者工具F12。在Elements面板右键点击目标元素选择“Copy” - “Copy selector” 或 “Copy XPath”。但请注意浏览器自动生成的CSS或XPath路径往往非常长且脆弱依赖于复杂的层级结构不建议直接使用。你应该从中分析出核心的、稳定的属性如ID、独特的class组合来构造更简洁的定位器。3.2 处理动态元素与等待机制你一定会遇到这种情况代码执行find_element时浏览器却报错说“找不到元素”。十有八九是因为页面还没加载完元素还不存在。这就是“等待”要解决的问题。Selenium中有三种等待方式强制等待time.sleep(秒数)。简单粗暴但效率低下。因为你不知道网络或服务器到底需要多久设短了会失败设长了浪费时间。仅在调试或极少数确定场景下使用。隐式等待driver.implicitly_wait(秒数)。设置一个全局的等待时间。在查找任何元素时如果没立刻找到WebDriver会轮询DOM直到超时。它只对find_element系列方法有效。缺点是它会影响整个Driver生命周期并且不适用于等待元素的特定状态如可点击、可见。driver.implicitly_wait(10) # 后续查找元素最多等10秒显式等待这是生产环境中最推荐、最健壮的方式。它允许你为某个特定的元素设定等待条件条件满足则继续超时则报错。这需要用到WebDriverWait和expected_conditionsEC模块。from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC # 等待最多10秒直到ID为‘dynamic-button‘的元素可被点击 wait WebDriverWait(driver, 10) element wait.until(EC.element_to_be_clickable((By.ID, ‘dynamic-button‘))) element.click()为什么显式等待最好因为它更“智能”。它等待的是“条件”而不仅仅是“元素存在”。常见的条件有presence_of_element_located: 元素出现在DOM中不一定可见。visibility_of_element_located: 元素可见有宽高非隐藏。element_to_be_clickable: 元素可见且可点击。text_to_be_present_in_element: 元素中包含特定文本。对于动态加载的内容如AJAX请求返回后出现的列表、点击按钮后弹出的模态框显式等待是唯一可靠的解决方案。我的经验是几乎在所有需要等待的地方都使用显式等待。将隐式等待作为一个很短的全局后备例如3秒而将具体的、重要的等待逻辑用显式等待清晰地写出来。4. 点击操作进阶不仅仅是.click()你以为element.click()就是点击的全部那就太小看现代网页的复杂性了。真实的点击操作会遇到各种“妖魔鬼怪”。4.1 基础点击与JavaScript点击大多数情况下element.click()就能工作。但有些元素特别是那些由JavaScript动态生成、或者被其他元素如透明的遮罩层覆盖的元素标准的click()方法可能会失效报错ElementClickInterceptedException或ElementNotInterceptableException。这时我们可以尝试用JavaScript直接执行点击driver.execute_script(“arguments[0].click();“, element)execute_script方法允许我们在浏览器上下文中直接运行JavaScript。arguments[0]对应传入的第一个参数element。这种方式绕过了Selenium的模拟点击直接触发元素的点击事件通常能解决大部分点击失效的问题。但是请谨慎使用JS点击因为它完全模拟了JavaScript的click()事件而忽略了元素可能存在的“不可点击”状态如disabled属性。这可能导致业务逻辑错误。因此我的建议是优先使用Selenium原生的click()仅在它确认失效时再考虑使用JS点击并做好相应的状态判断。4.2 处理复杂交互悬停、双击与右键有些交互不仅仅是单击。例如下拉菜单需要先将鼠标悬停hover在父菜单上子菜单才会出现。Selenium的ActionChains类就是用来处理这类复杂键盘鼠标操作的。from selenium.webdriver.common.action_chains import ActionChains menu driver.find_element(By.ID, ‘dropdown-menu‘) submenu driver.find_element(By.ID, ‘sub-item‘) # 创建动作链 actions ActionChains(driver) # 将鼠标移动到menu元素上然后暂停再点击出现的submenu actions.move_to_element(menu).pause(1).click(submenu).perform() # perform()是执行动作链中所有动作的命令动作链可以组合多个操作move_to_element悬停、click_and_hold点击并按住、double_click双击、context_click右键点击、drag_and_drop拖放等。perform()方法必须被调用动作才会真正执行。4.3 文件上传与文件下载文件上传对于input type“file”元素不要尝试去点击它然后操作系统的文件选择框这非常复杂且不稳定。最直接的方法是使用send_keys()直接传入文件路径。upload_element driver.find_element(By.XPATH, ‘//input[type“file”]‘) upload_element.send_keys(‘/Users/yourname/Desktop/test_image.jpg‘)这样文件路径就被直接设置到了输入框相当于完成了上传操作。文件下载控制浏览器下载文件需要预先设置好下载选项。以下是一个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)设置好后点击下载链接文件就会自动保存到指定目录无需人工干预。5. 实战避坑指南让脚本稳定运行的七个关键写一个能跑通的脚本不难写一个能在各种环境下稳定运行、长期有效的脚本才是真本事。下面这些坑都是我亲身踩过用加班时间换来的经验。5.1 等待的艺术告别sleep拥抱智能等待前面提到了显式等待但实际应用中还有更深的技巧。一个常见的反模式是为了等待一个元素在代码里到处写满了time.sleep(10)。这不仅效率低而且当网络慢时可能不够网络快时又浪费生命。正确做法是组合使用等待策略from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.common.exceptions import TimeoutException def wait_for_element(driver, locator, timeout10): “”“等待元素出现并可见返回元素超时返回None”“” try: element WebDriverWait(driver, timeout).until( EC.visibility_of_element_located(locator) ) return element except TimeoutException: print(f“元素 {locator} 在 {timeout} 秒内未找到或不可见。“) # 这里可以记录日志、截图方便后续排查 driver.save_screenshot(‘timeout_error.png‘) return None # 使用示例 submit_btn_locator (By.ID, ‘submit‘) submit_button wait_for_element(driver, submit_btn_locator) if submit_button: submit_button.click() else: # 处理元素未找到的情况例如重试或终止脚本 print(“关键提交按钮未找到流程终止。“) driver.quit()将等待逻辑封装成函数可以复用代码并使主流程更清晰。同时在等待失败时进行截图这是线上问题排查的救命稻草。5.2 元素状态检测避免无效点击在点击前检查元素状态是一个好习惯。例如按钮可能是disabled状态或者被一个加载动画覆盖。# 检查元素是否可点击可见且 enabled if element.is_enabled() and element.is_displayed(): element.click() else: print(“元素不可点击当前状态“) print(f“是否启用{element.is_enabled()}“) print(f“是否显示{element.is_displayed()}“) # 可以等待一段时间再重试或执行其他逻辑5.3 处理弹窗与多窗口点击一个链接可能会打开新标签页或弹出浏览器弹窗。你需要切换Driver的焦点。# 获取当前所有窗口的句柄 main_window driver.current_window_handle all_windows driver.window_handles # 这是一个列表 # 点击某个会打开新窗口的链接 driver.find_element(By.LINK_TEXT, ‘在新窗口打开‘).click() # 等待新窗口出现 WebDriverWait(driver, 10).until(EC.number_of_windows_to_be(2)) # 切换到新窗口 for window_handle in driver.window_handles: if window_handle ! main_window: driver.switch_to.window(window_handle) break # 在新窗口操作... print(“新窗口标题“, driver.title) # 操作完毕后可以切回主窗口 driver.switch_to.window(main_window)对于JavaScript的alert,confirm,prompt弹窗使用driver.switch_to.alert来处理# 点击触发alert的按钮 driver.find_element(By.ID, ‘trigger-alert‘).click() # 切换到alert alert driver.switch_to.alert print(“弹窗文本“, alert.text) # 点击确认 alert.accept() # 或者点击取消 # alert.dismiss() # 对于prompt还可以输入文字 # alert.send_keys(‘输入的文字‘)5.4 应对页面刷新与导航点击后页面可能会刷新或跳转。此时之前获取到的元素引用WebElement对象会失效StaleElementReferenceException。你必须重新定位元素。try: old_element driver.find_element(By.ID, ‘some-button‘) old_element.click() # 点击后页面刷新 # 刷新后下面的操作会报 StaleElementReferenceException # old_element.click() except StaleElementReferenceException: # 重新定位元素 new_element driver.find_element(By.ID, ‘some-button‘) new_element.click()更稳健的做法是在每次需要操作元素前都重新进行定位尤其是在已知页面会发生变化的操作之后。5.5 浏览器选项配置让自动化更“像人”默认情况下Selenium启动的浏览器会有一些特征如状态栏显示“正由自动测试软件控制”一些网站会检测并屏蔽这类自动化浏览器。我们可以通过配置选项来让浏览器更“隐形”并优化自动化体验。from selenium import webdriver from selenium.webdriver.chrome.options import Options chrome_options Options() # 1. 无头模式不显示浏览器GUI在服务器上运行必备 # chrome_options.add_argument(‘--headless‘) # 注释掉则显示浏览器 # 2. 禁用自动化控制提示 chrome_options.add_experimental_option(“excludeSwitches“, [“enable-automation“]) chrome_options.add_experimental_option(‘useAutomationExtension‘, False) # 3. 规避检测高级 chrome_options.add_argument(‘--disable-blink-featuresAutomationControlled‘) # 4. 其他常用优化参数 chrome_options.add_argument(‘--no-sandbox‘) # Linux服务器上可能需要 chrome_options.add_argument(‘--disable-dev-shm-usage‘) # 解决共享内存问题 chrome_options.add_argument(‘--disable-gpu‘) # 某些虚拟环境需要 chrome_options.add_argument(‘--window-size1920,1080‘) # 设置初始窗口大小 # 5. 用户数据目录可选复用已有浏览器缓存、登录状态等 # chrome_options.add_argument(r‘--user-data-dirC:\Users\YourName\AppData\Local\Google\Chrome\User Data‘) # chrome_options.add_argument(‘--profile-directoryDefault‘) driver webdriver.Chrome(optionschrome_options)关于无头模式在本地调试时建议关闭无头模式这样你能直观看到浏览器的操作过程便于调试。在服务器部署执行定时任务时再开启无头模式以节省资源。5.6 日志记录与错误处理一个健壮的脚本必须有完善的日志和错误处理机制。不要让你的脚本在深夜失败后悄无声息。import logging from selenium.common.exceptions import NoSuchElementException, TimeoutException # 配置日志 logging.basicConfig(levellogging.INFO, format‘%(asctime)s - %(name)s - %(levelname)s - %(message)s‘, handlers[ logging.FileHandler(‘selenium_automation.log‘), logging.StreamHandler() ]) logger logging.getLogger(__name__) def safe_click(driver, locator, description“元素“): “”“安全的点击操作包含重试和日志记录”“” max_retries 2 for attempt in range(max_retries): try: element WebDriverWait(driver, 10).until( EC.element_to_be_clickable(locator) ) element.click() logger.info(f“成功点击 {description}: {locator}“) return True except (TimeoutException, NoSuchElementException) as e: logger.warning(f“第{attempt1}次尝试点击 {description} 失败: {e}“) if attempt max_retries - 1: logger.error(f“点击 {description} 最终失败正在截图。“) driver.save_screenshot(f‘click_failure_{description.replace(“ “, “_“)}.png‘) return False time.sleep(2) # 等待2秒后重试 return False # 使用示例 if not safe_click(driver, (By.ID, ‘next-page‘), “下一页按钮“): logger.error(“流程因无法点击下一页而中断。“) driver.quit() exit(1)这个safe_click函数增加了重试机制和详细的日志记录并在最终失败时截图。在实际项目中你应该将这类通用操作封装成工具函数。5.7 资源管理与优雅退出务必确保在脚本结束时无论成功还是异常关闭浏览器驱动释放资源。否则会导致后台残留Chrome进程。try: # 你的主要业务逻辑 do_some_automation(driver) except Exception as e: logger.error(f“脚本执行发生异常: {e}“, exc_infoTrue) # 异常时截图 driver.save_screenshot(‘unexpected_error.png‘) finally: # 无论是否异常最终都确保退出驱动 driver.quit() logger.info(“浏览器驱动已退出资源释放完毕。“)使用try...except...finally结构是保证资源清理的最佳实践。6. 从脚本到框架构建可维护的自动化项目当你掌握了单个脚本的编写后很快就会面临管理多个脚本、复用代码、处理数据等问题。这时就需要考虑项目结构了。虽然标题是“模拟点击”但任何有价值的自动化任务都不会只是一个孤立的点击动作。这里介绍两种广泛采用的模式。6.1 Page Object Model让代码远离“选择器地狱”Page Object Model是一种设计模式其核心思想是将页面抽象成一个类将页面上的元素定位器和操作这些元素的方法封装在这个类里。测试脚本或业务脚本则通过调用页面对象的方法来操作页面而无需关心元素具体是如何定位的。没有POM的代码难以维护# 脚本中充斥着各种find_element和定位字符串 driver.find_element(By.ID, ‘username‘).send_keys(‘admin‘) driver.find_element(By.ID, ‘password‘).send_keys(‘123456‘) driver.find_element(By.CSS_SELECTOR, ‘button.login-btn‘).click()使用POM后的代码# pages/login_page.py class LoginPage: def __init__(self, driver): self.driver driver self.username_input (By.ID, ‘username‘) self.password_input (By.ID, ‘password‘) self.login_button (By.CSS_SELECTOR, ‘button.login-btn‘) def enter_username(self, username): self.driver.find_element(*self.username_input).send_keys(username) def enter_password(self, password): self.driver.find_element(*self.password_input).send_keys(password) def click_login(self): self.driver.find_element(*self.login_button).click() def login(self, username, password): self.enter_username(username) self.enter_password(password) self.click_login() # 主脚本 main.py from pages.login_page import LoginPage login_page LoginPage(driver) login_page.login(‘admin‘, ‘123456‘)POM带来的好处高可维护性当页面元素的定位器如ID、CSS选择器发生变化时你只需要修改对应的Page Object类中的一处代码所有使用该页面的脚本都自动生效。高可读性业务脚本读起来就像自然语言login_page.login(...)清晰表达了“在登录页执行登录操作”。低冗余公共的页面操作被封装成方法可以在多个脚本中复用。6.2 与pytest结合组织用例与生成报告pytest是Python最主流的测试框架之一它同样非常适合用来组织和管理自动化脚本即使你的目的不是测试而是数据抓取或日常自动化。项目结构示例my_automation_project/ ├── conftest.py # pytest配置文件定义全局fixture如driver ├── pages/ # 存放所有Page Object类 │ ├── __init__.py │ ├── login_page.py │ └── home_page.py ├── tests/ # 存放测试用例/自动化任务 │ ├── __init__.py │ ├── test_login.py # 一个用例文件 │ └── test_data_crawl.py ├── utils/ # 工具函数 │ ├── __init__.py │ └── helper.py └── requirements.txt # 项目依赖conftest.py定义共享的driverimport pytest from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager pytest.fixture(scope“session“) # 整个测试会话只启动一次浏览器 def driver(): service Service(ChromeDriverManager().install()) chrome_options webdriver.ChromeOptions() # chrome_options.add_argument(‘--headless‘) _driver webdriver.Chrome(serviceservice, optionschrome_options) _driver.implicitly_wait(5) yield _driver # 将driver提供给测试用例 _driver.quit() # 所有用例执行完毕后退出 pytest.fixture def login_page(driver): # 依赖上面的driver fixture from pages.login_page import LoginPage return LoginPage(driver)tests/test_login.py编写用例def test_admin_login(login_page): “”“测试管理员登录”“” login_page.driver.get(‘https://example.com/login‘) login_page.login(‘admin‘, ‘admin123‘) # 添加断言验证登录成功 assert “Dashboard“ in login_page.driver.title # 或者验证某个成功登录后才出现的元素 # assert login_page.is_logged_in() True def test_data_crawl(driver): “”“一个数据抓取任务同样可以用pytest组织”“” driver.get(‘https://example.com/data-list‘) # ... 执行抓取逻辑 data extract_data(driver) assert len(data) 0 # 断言抓取到了数据 # 可以将数据保存到文件或数据库 save_to_csv(data, ‘output.csv‘)运行与报告在项目根目录下执行pytest tests/ -v即可运行所有用例。你还可以生成漂亮的HTML报告这得益于pytest丰富的插件生态例如pytest-htmlpytest tests/ -v --htmlreport.html --self-contained-html这会在当前目录生成一个report.html文件里面清晰记录了每个用例的执行结果、通过与否、甚至失败时的错误信息和截图需要额外配置。这对于监控自动化任务的执行情况非常有帮助。将Selenium脚本用POM和pytest组织起来虽然前期需要一些设计工作但对于任何需要长期维护、扩展或团队协作的项目来说这笔投资都是绝对值得的。它让你的代码从“一次性脚本”升级为“可维护的自动化工程”。