Selenium自动化测试异常处理实战:从核心异常到健壮框架设计

📅 2026/6/24 4:48:07
Selenium自动化测试异常处理实战:从核心异常到健壮框架设计
1. 项目概述为什么异常处理是Selenium测试的“安全带”干了这么多年自动化测试我见过太多因为异常处理没做好而“翻车”的案例。一个精心编写的Selenium脚本可能在开发环境跑得风生水起一到测试环境就各种报错浏览器打不开、元素找不到、页面卡死……最后测试工程师花在调试和排查上的时间比写脚本的时间还长。这背后的核心问题往往不是业务逻辑有多复杂而是对自动化测试中无处不在的“异常”缺乏系统的应对策略。Selenium自动化测试本质上是在模拟一个真实用户与Web应用进行交互。但测试环境远比用户环境复杂和不稳定网络波动、服务器响应慢、前端元素动态加载、浏览器版本差异、甚至是测试数据本身的问题都会导致脚本执行中断。异常处理就是给我们的自动化脚本系上“安全带”和“安全气囊”。它的目标不是消灭所有异常那不可能而是让脚本具备“弹性”——遇到预期内的问题时能优雅处理、记录日志并继续执行或合理退出遇到意外问题时能提供足够清晰的错误信息帮助我们快速定位根因。很多人把Selenium异常处理简单理解为try...except包一下这远远不够。一个健壮的异常处理机制需要我们从等待策略、元素定位、浏览器交互、断言验证等多个维度进行设计。接下来我将结合大量实战踩坑经验为你拆解Selenium中那些高频、棘手却又必须掌握的异常场景及其处理心法。2. 核心异常类型与发生场景深度解析要处理异常首先得知道会遇到哪些“妖魔鬼怪”。Selenium的异常主要来源于其底层组件如WebDriver与浏览器、操作系统以及被测应用本身的交互过程。2.1 元素定位相关异常测试脚本的“迷路”时刻这是最常见的一类异常脚本试图与一个元素交互但这个元素在页面上“不见了”或者“还没来”。NoSuchElementException这是Selenium的“招牌”异常。当find_element方法无法在DOM中找到与指定定位器匹配的任何元素时抛出。from selenium.common.exceptions import NoSuchElementException try: element driver.find_element(By.ID, “non-existent-id”) except NoSuchElementException: print(“目标元素未找到可能页面未加载完成或定位器已失效。”) # 常见处理截图、记录日志、尝试备用定位器或标记测试用例为失败发生场景深度剖析页面加载未完成脚本执行速度远快于网络和浏览器渲染。你定位的元素可能还在后台加载。元素位于iframe/frame内未先切换driver.switch_to.frame直接在外层DOM查找。动态ID或类名前端框架如React, Vue生成的元素属性可能每次刷新都变化使用固定值定位必然失败。页面结构变更前端开发修改了HTML结构或属性但测试脚本未同步更新。StaleElementReferenceException“元素过时”异常这个异常比“找不到”更微妙。它发生在你已经找到了一个元素并存储在了变量中但随后页面的DOM发生了更新如Ajax刷新、部分重绘之前获取的元素引用就“过时”了再对这个引用进行操作就会抛出此异常。# 错误示例 search_box driver.find_element(By.NAME, “q”) driver.refresh() # 页面刷新search_box引用失效 search_box.send_keys(“test”) # 这里会抛出StaleElementReferenceException # 正确做法重新定位 driver.refresh() search_box driver.find_element(By.NAME, “q”) # 必须重新获取引用 search_box.send_keys(“test”)注意在循环列表元素如商品列表、搜索结果并进行操作时极易遇到此异常。因为每操作一个项页面状态都可能微调。安全的做法是在循环内部、每次操作前重新定位当前要操作的那个项。2.2 浏览器与会话相关异常环境不稳的“信号灯”这类异常通常指向测试环境本身的问题。WebDriverException这是一个比较宽泛的异常基类很多底层问题都会归到此类比如浏览器驱动ChromeDriver, GeckoDriver与浏览器版本不匹配、驱动服务启动失败等。# 常见原因Chrome浏览器自动升级后未更新对应的ChromeDriver from selenium import webdriver from selenium.common.exceptions import WebDriverException try: driver webdriver.Chrome() except WebDriverException as e: print(f“驱动启动失败: {e.msg}”) # 处理提示用户检查浏览器和驱动版本或自动下载匹配的驱动TimeoutException在等待某个条件成立时超时。这通常与显式等待WebDriverWait结合使用。它本身不是错误而是等待策略的预期结果之一用于判断页面状态是否如预期。from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.common.exceptions import TimeoutException try: # 等待最多10秒直到登录按钮可点击 button WebDriverWait(driver, 10).until( EC.element_to_be_clickable((By.ID, “login-btn”)) ) button.click() except TimeoutException: print(“登录按钮在10秒内未变为可点击状态登录页面可能加载异常。”) # 处理截图、记录错误、执行清理操作或抛出更具体的业务异常InvalidSelectorException当提供的定位器表达式语法错误时抛出。例如XPath写错了、CSS选择器格式不对。try: # 错误的XPath缺少括号 driver.find_element(By.XPATH, “//button[id‘submit’”) except InvalidSelectorException: print(“定位器语法错误请检查XPath或CSS选择器格式。”)这类异常通常在脚本开发阶段就能发现但如果是动态拼接定位器字符串则可能在运行时触发。2.3 用户交互与状态异常模拟行为的“意外”ElementNotInteractableException元素存在于DOM中但当前状态不可交互。常见原因元素不可见被其他元素遮挡、display: none、visibility: hidden、opacity: 0。元素被禁用具有disabled属性。元素位于视窗外需要滚动才能看到并点击。from selenium.common.exceptions import ElementNotInteractableException from selenium.webdriver.common.action_chains import ActionChains try: button driver.find_element(By.ID, “hidden-button”) button.click() except ElementNotInteractableException: print(“元素不可交互尝试滚动到元素位置或等待其可见。”) # 方案一滚动到元素位置 driver.execute_script(“arguments[0].scrollIntoView(true);”, button) # 方案二使用ActionChains进行更复杂的交互适用于部分遮挡 actions ActionChains(driver) actions.move_to_element(button).click().perform() # 方案三检查并等待其变为可交互状态推荐 # WebDriverWait(driver, 5).until(EC.element_to_be_clickable((By.ID, “hidden-button”)))ElementClickInterceptedException这是ElementNotInteractableException的一个子类特指点击操作被其他元素如弹窗、蒙层、浮动广告拦截。 处理方式通常是先关闭或移除拦截物或者使用JavaScript直接执行点击driver.execute_script(“arguments[0].click();”, element)但这可能绕过前端的一些事件监听需谨慎使用。3. 构建健壮异常处理的核心策略等待、定位与断言知道了异常类型我们就要构建防御体系。这套体系的核心是三个理念智能等待、弹性定位、安全断言。3.1 等待策略给页面加载足够的“耐心”盲目使用time.sleep()是初级错误它固定等待效率低下且不可靠。Selenium提供了两种智能等待。隐式等待 (Implicit Wait)设置一个全局的等待时间在查找任何元素时如果元素没有立即出现WebDriver会轮询DOM一段时间。driver.implicitly_wait(10) # 单位秒优点设置简单一劳永逸。致命缺点只对find_element和find_elements方法生效。无法处理更复杂的条件如“元素可点击”、“元素包含特定文本”。与显式等待混用时可能导致最大等待时间超出预期两者会叠加。实操建议不建议作为主要等待机制尤其在大中型项目中。可以设置一个较短的全局隐式等待如2-3秒作为基础保障但复杂同步必须依赖显式等待。显式等待 (Explicit Wait)针对某个特定条件进行等待条件满足则立即继续超时则抛出TimeoutException。这是生产级自动化测试的黄金标准。from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC wait WebDriverWait(driver, timeout10, poll_frequency0.5) # 超时10秒每0.5秒检查一次 # 等待元素可见 element wait.until(EC.visibility_of_element_located((By.ID, “dynamic-content”))) # 等待元素可点击 clickable_element wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, “.submit-btn”))) # 等待URL包含特定字符串 wait.until(EC.url_contains(“success”)) # 等待旧元素从DOM中消失用于判断加载完成 wait.until(EC.staleness_of(old_element))expected_conditions(EC) 常用条件一览条件方法说明presence_of_element_located元素存在于DOM中不一定可见visibility_of_element_located元素存在且可见宽高大于0element_to_be_clickable元素存在、可见且可点击text_to_be_present_in_element元素文本包含特定文字invisibility_of_element_located元素不可见或不存在alert_is_present出现JavaScript弹窗自定义等待条件当EC提供的条件不满足时可以自定义函数。def element_has_css_class(locator, css_class): “”“自定义条件等待元素拥有特定的CSS类”“” def _predicate(driver): element driver.find_element(*locator) if css_class in element.get_attribute(“class”).split(): return element else: return False return _predicate # 使用 wait.until(element_has_css_class((By.ID, “status”), “active”))3.2 弹性定位策略多套“备选方案”不要将元素的“生死”寄托在单一定位器上。一个健壮的元素查找应该像侦探破案有多条线索。1. 使用组合定位器或备用定位器def find_element_with_fallback(driver, primary_locator, fallback_locator): “”“优先使用主定位器失败则尝试备用定位器”“” try: return driver.find_element(*primary_locator) except NoSuchElementException: print(f“主定位器 {primary_locator} 未找到尝试备用定位器 {fallback_locator}.”) return driver.find_element(*fallback_locator) # 示例优先用ID找不到再用CSS login_button find_element_with_fallback( driver, (By.ID, “loginButton”), # 主定位器 (By.CSS_SELECTOR, “button.btn-primary”) # 备用定位器 )2. 使用相对定位或更稳定的属性避免使用绝对XPath如/html/body/div[3]/div[2]/button它极其脆弱。优先使用id、name。其次使用有语义的class、>class SoftAssert: def __init__(self): self.errors [] def assert_true(self, condition, message): if not condition: self.errors.append(message) def assert_equal(self, actual, expected, message): if actual ! expected: self.errors.append(f“{message}。预期: {expected}, 实际: {actual}”) def assert_all(self): if self.errors: error_report “\n”.join(self.errors) raise AssertionError(f“测试验证失败:\n{error_report}”) # 在测试用例中使用 soft_assert SoftAssert() soft_assert.assert_equal(page.get_title(), “预期标题”, “检查页面标题”) soft_assert.assert_true(element.is_displayed(), “检查元素是否显示”) # ... 执行其他操作和断言 soft_assert.assert_all() # 所有操作完成后统一检查对断言本身进行异常包装即使使用硬断言也要确保断言失败时有足够的上下文信息如截图被记录下来而不是一个干巴巴的错误信息。def safe_assert(assert_func, *args, **kwargs): try: assert_func(*args, **kwargs) except AssertionError as e: # 在断言失败时自动截图 timestamp datetime.now().strftime(“%Y%m%d_%H%M%S”) screenshot_path f“./screenshots/assert_fail_{timestamp}.png” driver.save_screenshot(screenshot_path) print(f“断言失败截图已保存至: {screenshot_path}”) # 可以选择将原始异常重新抛出或者记录后标记测试为失败 raise # 重新抛出让测试框架处理 # 或者 return False # 不抛出仅记录4. 实战编写一个带完整异常处理的页面操作函数理论说得再多不如看一个综合案例。假设我们要实现一个登录函数它需要处理各种异常并具备重试机制。import logging from datetime import datetime from selenium.common.exceptions import (TimeoutException, NoSuchElementException, ElementNotInteractableException, StaleElementReferenceException) from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By logging.basicConfig(levellogging.INFO, format‘%(asctime)s - %(levelname)s - %(message)s’) def login_with_retry(driver, username, password, max_retries2): “”“ 带异常处理和重试机制的登录函数。 参数: driver: WebDriver实例 username: 用户名 password: 密码 max_retries: 最大重试次数针对某些特定异常 返回: bool: 登录成功返回True否则返回False “”“ retry_count 0 while retry_count max_retries: try: logging.info(f“尝试登录第 {retry_count 1} 次。”) # 1. 等待登录页面加载完成通过判断用户名输入框出现 wait WebDriverWait(driver, 15) username_input wait.until( EC.visibility_of_element_located((By.ID, “username”)) ) password_input driver.find_element(By.ID, “password”) login_button driver.find_element(By.XPATH, “//button[type‘submit’]”) # 2. 执行登录操作 username_input.clear() username_input.send_keys(username) password_input.clear() password_input.send_keys(password) # 点击前再次确认按钮可点击防止在输入过程中页面有变化 WebDriverWait(driver, 5).until(EC.element_to_be_clickable((By.XPATH, “//button[type‘submit’]”))) login_button.click() # 3. 验证登录成功等待登录后跳转或成功元素出现 # 假设成功后会跳转到dashboard页面或者出现用户头像元素 success_indicator wait.until( EC.any_of( # any_of 表示满足任意一个条件即可 EC.url_contains(“dashboard”), EC.presence_of_element_located((By.CLASS_NAME, “user-avatar”)) ) ) logging.info(“登录成功”) return True except TimeoutException as e: logging.error(f“登录超时: {e}”) # 超时可能是页面加载极慢或成功条件始终不满足即登录失败 # 检查是否有登录失败的提示信息如‘用户名或密码错误’ try: error_msg_element driver.find_element(By.CLASS_NAME, “error-message”) logging.error(f“登录失败服务端返回错误: {error_msg_element.text}”) # 如果是凭证错误重试无意义直接退出 if “密码” in error_msg_element.text or “用户名” in error_msg_element.text: break except NoSuchElementException: # 没有明确错误信息可能是网络或服务器问题可以重试 pass except (NoSuchElementException, ElementNotInteractableException) as e: logging.error(f“页面元素异常: {e}”) # 可能是页面结构变了或者有弹窗遮挡 # 尝试查找并关闭可能的弹窗例如Cookie通知 try: close_btn driver.find_element(By.CSS_SELECTOR, “.cookie-close-btn”) close_btn.click() logging.info(“检测并关闭了干扰弹窗。”) # 关闭弹窗后不增加重试计数直接进入下一次循环尝试登录 continue except NoSuchElementException: # 没有弹窗是真正的元素缺失 pass except StaleElementReferenceException as e: logging.warning(f“遇到元素过时引用可能页面动态更新: {e}”) # 这是重试的主要场景之一页面在操作期间刷新了 except Exception as e: # 捕获其他所有未预见的异常 logging.critical(f“登录过程中发生未预期异常: {type(e).__name__}: {e}”, exc_infoTrue) # 如果执行到这里说明登录尝试失败 retry_count 1 if retry_count max_retries: logging.info(f“准备第 {retry_count 1} 次重试...”) # 重试前可以做一些清理或刷新操作 driver.refresh() # 刷新页面回到初始状态 import time time.sleep(2) # 刷新后简单等待一下 else: logging.error(f“登录失败已达最大重试次数 {max_retries}。”) # 失败时截图便于后续分析 take_screenshot(driver, “login_failed”) break return False def take_screenshot(driver, name_prefix): “”“失败时截图”“” timestamp datetime.now().strftime(“%Y%m%d_%H%M%S”) filename f“./test_failures/{name_prefix}_{timestamp}.png” driver.save_screenshot(filename) logging.info(f“截图已保存: {filename}”)这个函数体现了几个关键思想分层捕获异常针对不同类型的异常TimeoutException,NoSuchElementException等采取不同的处理策略。智能重试对于因页面瞬时状态如Stale元素或网络抖动导致的失败自动重试。但对于明确的业务错误如密码错误则立即停止重试。丰富的日志每个步骤和异常都有清晰的日志输出便于事后排查。失败取证在最终失败时自动截图保留现场。使用显式等待在关键步骤如等待输入框、等待登录成功使用WebDriverWait避免盲目等待。5. 框架级集成将异常处理融入测试生命周期在大型自动化项目中我们不会在每个函数里都写满try...except。更好的做法是利用测试框架如pytest, unittest的钩子hook机制进行全局的异常捕获和处理。使用pytest的钩子进行全局异常处理与报告增强# conftest.py import pytest from selenium import webdriver from selenium.common.exceptions import WebDriverException import logging pytest.fixture(scope“function”) def driver(request): “”“为每个测试用例提供driver并在失败时自动截图”“” # 初始化driver这里以Chrome为例 options webdriver.ChromeOptions() options.add_argument(“--headless”) # 无头模式适合CI环境 driver_instance webdriver.Chrome(optionsoptions) driver_instance.implicitly_wait(3) # 设置一个较短的全局隐式等待 yield driver_instance # 测试用例执行后的清理工作 # 如果测试失败检查是否是Selenium相关异常并截图 if request.node.rep_call.failed: # 检查异常类型 if hasattr(request.node, ‘_exception’) and request.node._exception: exc_type request.node._exception[0] exc_value request.node._exception[1] # 如果是Selenium相关异常或任何异常都截图 if issubclass(exc_type, WebDriverException) or True: # 这里示例对所有失败截图 take_screenshot_for_test(driver_instance, request.node.name) driver_instance.quit() def take_screenshot_for_test(driver, test_name): “”“为失败的测试用例截图”“” import os screenshot_dir “./test_results/screenshots/” os.makedirs(screenshot_dir, exist_okTrue) safe_test_name “”.join(c for c in test_name if c.isalnum() or c in (‘-’, ‘_’)).rstrip() filepath os.path.join(screenshot_dir, f“{safe_test_name}.png”) driver.save_screenshot(filepath) logging.error(f“测试失败截图已保存至: {filepath}”) pytest.hookimpl(tryfirstTrue, hookwrapperTrue) def pytest_runtest_makereport(item, call): “”“获取测试用例的执行结果用于判断是否失败”“” outcome yield rep outcome.get_result() # 将结果存储到item中供fixture使用 setattr(item, “rep_” rep.when, rep) # 特别存储异常信息 if rep.failed: item._exception (rep.longrepr.chain[0][0]._excrepr, rep.longrepr.chain[0][0]._excrepr)创建自定义的Selenium操作基类将所有常用的、需要异常处理的操作封装起来让页面对象Page Object类继承它。# 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, StaleElementReferenceException import logging class BasePage: def __init__(self, driver): self.driver driver self.logger logging.getLogger(__name__) self.wait WebDriverWait(self.driver, 10) def safe_find_element(self, locator, timeout10): “”“安全查找元素带重试机制”“” wait WebDriverWait(self.driver, timeout) try: element wait.until(EC.presence_of_element_located(locator)) return element except TimeoutException: self.logger.error(f“元素定位超时: {locator}”) # 可以在这里加入截图逻辑 raise # 或者返回None取决于你的错误处理策略 def safe_click(self, element_or_locator, max_retries2): “”“安全点击处理StaleElementReferenceException”“” retries 0 while retries max_retries: try: if isinstance(element_or_locator, tuple): # 传入的是定位器 element self.safe_find_element(element_or_locator) else: # 传入的是WebElement对象 element element_or_locator # 确保元素可点击 self.wait.until(EC.element_to_be_clickable(element)) element.click() return True except StaleElementReferenceException: self.logger.warning(f“点击时元素过时第{retries1}次重试...”) retries 1 if retries max_retries: self.logger.error(f“点击元素失败超过最大重试次数。”) raise continue # 重试循环 except Exception as e: self.logger.error(f“点击元素时发生未知错误: {e}”) raise def is_element_present(self, locator, timeout5): “”“检查元素是否存在不抛出异常”“” try: WebDriverWait(self.driver, timeout).until( EC.presence_of_element_located(locator) ) return True except TimeoutException: return False这样在你的页面对象中操作就变得非常简洁和健壮# login_page.py from base_page import BasePage from selenium.webdriver.common.by import By class LoginPage(BasePage): USERNAME_INPUT (By.ID, “username”) PASSWORD_INPUT (By.ID, “password”) LOGIN_BUTTON (By.XPATH, “//button[type‘submit’]”) ERROR_MSG (By.CLASS_NAME, “error-message”) def enter_username(self, username): elem self.safe_find_element(self.USERNAME_INPUT) elem.clear() elem.send_keys(username) def enter_password(self, password): elem self.safe_find_element(self.PASSWORD_INPUT) elem.clear() elem.send_keys(password) def click_login(self): self.safe_click(self.LOGIN_BUTTON) def get_error_message(self): if self.is_element_present(self.ERROR_MSG, timeout3): return self.safe_find_element(self.ERROR_MSG).text return None6. 高级场景与疑难杂症处理即使掌握了上述策略在实际项目中仍会遇到一些棘手的“坑”。这里分享几个高级场景的处理技巧。处理动态内容与无限滚动对于需要滚动加载内容的页面如社交媒体信息流简单的presence_of_element_located可能不够。def wait_for_dynamic_content(driver, locator, max_scroll_attempts5, timeout30): “”“等待动态加载的内容出现支持滚动”“” from selenium.webdriver.common.keys import Keys wait WebDriverWait(driver, timeout) attempts 0 while attempts max_scroll_attempts: try: # 尝试查找元素 element wait.until(EC.presence_of_element_located(locator)) return element except TimeoutException: # 没找到尝试向下滚动一屏 driver.find_element(By.TAG_NAME, ‘body’).send_keys(Keys.END) import time time.sleep(2) # 等待新内容加载 attempts 1 logging.info(f“未找到元素第{attempts}次滚动屏幕...”) raise TimeoutException(f“在滚动{max_scroll_attempts}次后仍未找到元素: {locator}”)处理文件上传弹窗文件上传弹窗是操作系统级别的窗口Selenium无法直接操作。标准做法是不要尝试点击弹窗而是直接通过send_keys将文件路径发送到input type“file”元素。# 假设有一个文件上传按钮点击后会弹出系统窗口 upload_input driver.find_element(By.ID, “file-upload”) # 这是一个type“file”的input元素 # 直接发送文件绝对路径绕过弹窗 upload_input.send_keys(“/Users/yourname/Desktop/test_file.pdf”)如果页面使用JavaScript模拟的上传组件隐藏了真正的input元素则需要通过JavaScript来操作。处理浏览器弹窗 (Alert, Confirm, Prompt)from selenium.common.exceptions import NoAlertPresentException try: # 等待弹窗出现最多5秒 WebDriverWait(driver, 5).until(EC.alert_is_present()) alert driver.switch_to.alert print(f“弹窗文本: {alert.text}”) # 根据业务逻辑处理 if “确认删除” in alert.text: alert.accept() # 点击“确定” else: alert.dismiss() # 点击“取消” # 对于Prompt还可以发送文本 # alert.send_keys(“输入的文字”) # alert.accept() except TimeoutException: print(“预期中的弹窗未出现。”) except NoAlertPresentException: print(“当前没有弹窗。”)处理多窗口/标签页# 获取当前窗口句柄 main_window driver.current_window_handle # 点击一个会打开新窗口的链接 driver.find_element(By.LINK_TEXT, “在新窗口打开”).click() # 获取所有窗口句柄 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)7. 调试技巧与日志分析当异常发生时如何快速定位异常发生了错误信息却模棱两可怎么办除了截图还有更强大的调试手段。启用Chrome DevTools日志在初始化Driver时开启性能日志和浏览器日志可以捕获到网络请求错误、JavaScript错误等宝贵信息。from selenium.webdriver import Chrome from selenium.webdriver.chrome.service import Service from selenium.webdriver.common.desired_capabilities import DesiredCapabilities caps DesiredCapabilities.CHROME # 开启性能日志 caps[‘goog:loggingPrefs’] { ‘performance’: ‘ALL’, ‘browser’: ‘ALL’ } service Service(‘/path/to/chromedriver’) driver Chrome(serviceservice, desired_capabilitiescaps) # 在测试结束后获取日志 for entry in driver.get_log(‘browser’): if entry[‘level’] ‘SEVERE’: # 只打印严重错误 print(f“[Browser Error] {entry[‘message’]}”) for entry in driver.get_log(‘performance’): # 分析网络请求查找失败的API调用 print(entry)使用page_source和get_screenshot_as_base64进行即时分析当测试在远程CI服务器上失败时获取截图和页面源码进行分析至关重要。def save_debug_info_on_failure(driver, test_case_name): “”“保存失败时的页面源码和截图”“” import base64 import os debug_dir “./debug_info/” os.makedirs(debug_dir, exist_okTrue) # 1. 保存页面HTML源码 page_source driver.page_source with open(os.path.join(debug_dir, f“{test_case_name}_source.html”), “w”, encoding“utf-8”) as f: f.write(page_source) # 2. 保存截图Base64格式方便嵌入报告 screenshot_base64 driver.get_screenshot_as_base64() # 也可以直接保存为图片文件 driver.save_screenshot(os.path.join(debug_dir, f“{test_case_name}_screenshot.png”)) # 3. 保存当前URL和浏览器日志 debug_info { “url”: driver.current_url, “title”: driver.title, “window_size”: driver.get_window_size(), “screenshot_base64”: screenshot_base64[:100] “...” # 只存一部分示例 } import json with open(os.path.join(debug_dir, f“{test_case_name}_info.json”), “w”) as f: json.dump(debug_info, f, indent2) logging.info(f“调试信息已保存至: {debug_dir}”)在异常信息中添加上下文自定义异常类在抛出时携带更多上下文信息。class AutomationException(Exception): “”“自定义自动化测试异常”“” def __init__(self, message, driverNone, elementNone): super().__init__(message) self.driver driver self.element element self.context { “current_url”: driver.current_url if driver else None, “page_title”: driver.title if driver else None, “element_info”: element.get_attribute(“outerHTML”)[:200] if element else None # 截取部分HTML } def __str__(self): base_msg super().__str__() context_msg f“\n上下文: {self.context}” return base_msg context_msg # 使用示例 try: button driver.find_element(By.ID, “critical-btn”) if not button.is_enabled(): raise AutomationException(“关键按钮不可用”, driverdriver, elementbutton) except AutomationException as e: logging.error(e) # 这会打印出异常信息和丰富的上下文异常处理不是Selenium自动化测试的选修课而是决定脚本能否在复杂真实环境中稳定运行的必修课。它考验的不仅是编程技巧更是对Web应用行为、测试环境不稳定性的深刻理解。从被动的try...except包裹到主动的等待策略设计、弹性定位、框架集成再到主动的调试信息收集这是一个测试工程师从脚本编写者向质量保障架构师演进的关键路径。记住一个好的异常处理机制不会让错误消失但会让错误的代价排查时间降到最低让你的自动化测试真正成为可靠的质量守护者而不是团队中那个“动不动就报错”的麻烦制造者。