Selenium自动化测试实战:智能设备隐藏WiFi功能的端到端Web UI验证

Selenium自动化测试实战:智能设备隐藏WiFi功能的端到端Web UI验证
1. 项目概述与核心价值最近在做一个智能家居设备的测试项目其中有一个功能点让我和团队花了些心思设备的隐藏WiFi功能。简单来说就是设备在初始化或恢复出厂设置后会创建一个名称SSID不可见的WiFi热点用户需要通过特定方式比如扫描设备上的二维码才能连接并进行后续配置。这个功能在提升安全性和用户体验上很有用但手动测试起来却是个体力活——每次都要重置设备、用手机去搜那个看不见的WiFi、再手动连接验证重复几十上百次不仅效率低下还容易因为操作疲劳引入人为错误。于是我们决定用Selenium把这个过程自动化。你可能会问Selenium不是测网页的吗怎么管起WiFi来了这正是这个项目的巧妙之处。我们测试的并非WiFi协议本身而是设备配套的Web管理界面。这个隐藏WiFi功能的完整用户旅程是设备启动隐藏WiFi - 用户手机连接该WiFi - 手机浏览器自动弹出或手动输入IP进入设备Web配置页 - 在配置页完成网络设置如连接家庭WiFi - 设备切换模式。我们的自动化脚本就是要模拟用户在这个Web界面上的所有操作并验证每个环节的状态是否正确。所以核心是利用Selenium实现Web UI的自动化来间接完成对“隐藏WiFi”这一硬件功能的端到端测试。这套自动化方案的价值非常直接。首先它把测试人员从重复、枯燥的物理操作中解放出来一个脚本可以7x24小时运行覆盖各种边界情况如密码错误、信号弱、配置超时。其次它保证了测试过程的一致性避免了手动操作可能带来的偏差测试结果更可靠。最后它为持续集成/持续部署CI/CD铺平了道路每次固件更新后都能自动运行这套测试用例快速反馈质量风险。无论你是测试工程师、开发人员还是对智能硬件自动化感兴趣的朋友理解这个案例的思路都能帮你把Selenium的应用场景从纯Web扩展到“软硬结合”的物联网领域。2. 自动化测试框架设计与环境搭建2.1 技术选型与框架思路为什么用Selenium在这个场景下设备提供的配置界面是一个标准的、响应式的Web页面通过HTTP/HTTPS协议提供服务。Selenium WebDriver作为业界标准的Web自动化工具能完美地模拟人类在浏览器中的所有操作点击、输入、下拉选择、页面跳转等待等。相比于需要破解系统底层协议或直接操作网卡驱动的方法通过Web界面进行测试是非侵入式的、稳定且易于维护的。我们的自动化框架基于经典的Page Object Model (POM)设计模式。这是UI自动化的最佳实践之一核心思想是将每个Web页面抽象成一个“页面对象”类这个类封装了该页面的所有元素定位器和页面操作方法。测试用例脚本则只包含业务逻辑和断言不直接操作Web元素。这样做的好处非常明显当页面UI发生变动时我们只需要修改对应的页面对象类而不需要到处修改测试脚本大大提升了代码的可维护性。整个测试流程的自动化链条是这样的环境准备与设备控制通过脚本如使用paramikoSSH连接或HTTP API触发测试设备重启或恢复出厂设置使其进入隐藏WiFi模式。连接隐藏WiFi模拟端这一步是难点。我们无法直接用Selenium命令操作系统网络。解决方案是借助操作系统的命令行工具。在测试机上我们通过Python的subprocess模块调用系统命令Windows的netsh或Linux/macOS的nmcli/networksetup来扫描并连接指定的隐藏WiFi。Web UI自动化Selenium主场连接上设备的WiFi后Selenium WebDriver启动浏览器访问设备的默认管理IP如192.168.4.1随后按照POM模型在登录页、网络配置页等完成一系列操作。断言与结果收集在关键步骤后通过Selenium获取页面元素文本、URL或发送API请求查询设备状态进行断言验证。所有步骤和结果通过日志框架如logging记录并整合到测试报告如使用pytest-html或Allure中。2.2 测试环境搭建详解工欲善其事必先利其器。一个稳定、可复现的测试环境是自动化的基石。1. 基础编程环境我们选择Python作为主语言因为它生态丰富、编写高效。建议使用虚拟环境隔离项目依赖。# 创建并激活虚拟环境 python -m venv venv source venv/bin/activate # Linux/macOS # venv\Scripts\activate # Windows # 安装核心依赖 pip install selenium pip install webdriver-manager # 自动管理浏览器驱动强烈推荐 pip install pytest # 测试框架 pip install pytest-html # 生成HTML报告 pip install paramiko # 如需SSH控制设备2. 浏览器与驱动管理这是Selenium新手最容易踩坑的地方。浏览器版本和对应的WebDriver版本必须匹配。手动下载驱动并配置PATH是一种方式但更推荐使用webdriver-manager库它能自动检测浏览器版本并下载匹配的驱动。from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager from webdriver_manager.core.os_manager import ChromeType # 自动下载并使用匹配的ChromeDriver service Service(ChromeDriverManager(chrome_typeChromeType.GOOGLE).install()) driver webdriver.Chrome(serviceservice)对于隐藏WiFi功能测试浏览器的行为需要尽可能接近真实用户。建议禁用一些可能干扰测试的浏览器特性如密码保存提示、通知权限弹窗等。这可以通过ChromeOptions来实现。from selenium.webdriver.chrome.options import Options chrome_options Options() chrome_options.add_experimental_option(excludeSwitches, [enable-automation]) # 隐藏“正受到自动测试软件控制”提示 chrome_options.add_experimental_option(useAutomationExtension, False) prefs { credentials_enable_service: False, profile.password_manager_enabled: False } chrome_options.add_experimental_option(prefs, prefs) # 无头模式可选调试阶段建议关闭以便观察 # chrome_options.add_argument(--headless) driver webdriver.Chrome(serviceservice, optionschrome_options)3. 操作系统网络命令封装为了用代码连接隐藏WiFi我们需要封装系统命令。下面是一个跨平台处理的简单示例类import subprocess import platform import time class WiFiConnector: def __init__(self, ssid, password): self.ssid ssid self.password password self.system platform.system() def connect_to_hidden_wifi(self): 连接隐藏WiFi if self.system Windows: # Windows: 先创建配置文件再连接 # 注意需要管理员权限运行脚本 config_xml f?xml version1.0? WLANProfile xmlnshttp://www.microsoft.com/networking/WLAN/profile/v1 name{self.ssid}/name SSIDConfig SSID name{self.ssid}/name /SSID hiddentrue/hidden /SSIDConfig connectionTypeESS/connectionType connectionModemanual/connectionMode MSM security authEncryption authenticationWPA2PSK/authentication encryptionAES/encryption useOneXfalse/useOneX /authEncryption sharedKey keyTypepassPhrase/keyType protectedfalse/protected keyMaterial{self.password}/keyMaterial /sharedKey /security /MSM /WLANProfile with open(f{self.ssid}.xml, w) as f: f.write(config_xml) subprocess.run([netsh, wlan, add, profile, ffilename{self.ssid}.xml], checkTrue, capture_outputTrue) result subprocess.run([netsh, wlan, connect, fname{self.ssid}], capture_outputTrue, textTrue) elif self.system Linux: # Linux (使用nmcli)假设系统使用NetworkManager # 先删除已有连接如果存在避免冲突 subprocess.run([nmcli, connection, delete, self.ssid], capture_outputTrue) # 创建新的隐藏WiFi连接 result subprocess.run([ nmcli, device, wifi, connect, self.ssid, password, self.password, hidden, yes ], capture_outputTrue, textTrue) elif self.system Darwin: # macOS # macOS 使用 networksetup network_service Wi-Fi # 可能需要根据实际网络服务名调整 # 先移除旧配置 subprocess.run([networksetup, -removepreferredwirelessnetwork, network_service, self.ssid], capture_outputTrue) # 添加隐藏网络 subprocess.run([ networksetup, -addpreferredwirelessnetworkatindex, network_service, self.ssid, 0, WPA2, self.password ], checkTrue, capture_outputTrue) # 连接macOS添加后通常会尝试自动连接 time.sleep(5) result subprocess.run([networksetup, -getairportnetwork, network_service], capture_outputTrue, textTrue) else: raise OSError(fUnsupported operating system: {self.system}) print(fConnection result: {result.stdout}) if result.returncode ! 0: print(fConnection error: {result.stderr}) return False # 简单验证ping设备管理IP time.sleep(3) # 等待连接稳定 ping_result subprocess.run([ping, -c, 2, -W, 1, 192.168.4.1], capture_outputTrue) return ping_result.returncode 0注意执行系统网络命令通常需要较高的权限Windows管理员Linux/macOS的sudo。在CI/CD环境中需要确保运行测试的Agent或容器具有相应的权限。此外频繁切换WiFi可能导致系统网络服务不稳定建议在独立的测试机或虚拟机中运行。3. Page Object模型构建与核心页面解析3.1 登录页与初始化状态捕获设备启动隐藏WiFi后我们连接上并访问其IP第一个遇到的通常是登录页或初始化引导页。这个页面是测试的起点必须稳定定位。我们创建一个LoginPage类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: def __init__(self, driver): self.driver driver self.wait WebDriverWait(driver, 15) # 显式等待15秒 # 元素定位器 (Locators) USERNAME_INPUT (By.ID, “username”) # 假设ID PASSWORD_INPUT (By.NAME, “password”) # 假设Name LOGIN_BUTTON (By.XPATH, “//button[type‘submit’]”) PAGE_TITLE (By.TAG_NAME, “h1”) ERROR_MESSAGE (By.CLASS_NAME, “alert-error”) def navigate_to(self, base_url“http://192.168.4.1”): 访问登录页 self.driver.get(base_url) # 等待页面关键元素加载完成作为页面加载成功的标志 return self.wait.until(EC.presence_of_element_located(self.PAGE_TITLE)) def get_page_title(self): 获取页面标题用于断言当前是否在正确页面 return self.driver.find_element(*self.PAGE_TITLE).text def login(self, username“admin”, password“admin”): 执行登录操作 self.wait.until(EC.element_to_be_clickable(self.USERNAME_INPUT)).send_keys(username) self.driver.find_element(*self.PASSWORD_INPUT).send_keys(password) self.driver.find_element(*self.LOGIN_BUTTON).click() # 登录后通常跳转到主页或配置页返回下一个页面的Page Object # 例如 return HomePage(self.driver) def get_error_message(self): 获取登录错误信息用于负向测试 try: return self.driver.find_element(*self.ERROR_MESSAGE).text except: return None实操心得定位器策略优先使用ID和Name因为它们通常最稳定且解析最快。其次是CSS Selector最后是XPath。避免使用包含索引如div[3]或绝对路径的XPath它们极易因UI微调而失效。显式等待是黄金法则绝对不要使用time.sleep()进行固定等待。使用WebDriverWait配合expected_conditions只在需要的元素上等待。这能极大提升脚本运行速度和稳定性。在上面的代码中navigate_to方法等待标题出现这比盲目等待5秒要可靠得多。页面状态验证每个页面对象都应有一个方法如get_page_title来验证当前是否成功加载到了目标页面。这在测试断言和调试时非常有用。3.2 网络配置页与WiFi扫描交互登录成功后会进入核心的网络配置页。这里用户需要配置设备连接到家中的路由器WiFi。我们的自动化脚本需要模拟点击“扫描网络”按钮 - 等待扫描结果列表出现 - 从列表中选择目标家庭WiFi SSID - 输入密码 - 保存。创建NetworkConfigPage类class NetworkConfigPage: def __init__(self, driver): self.driver driver self.wait WebDriverWait(driver, 20) # 网络扫描可能较慢等待时间稍长 # 定位器 SCAN_BUTTON (By.ID, “scan-btn”) SCAN_RESULT_LIST (By.CSS_SELECTOR, “.scan-result-list”) # 动态定位根据SSID名称选择列表项 def _get_ssid_item_locator(self, ssid_name): return (By.XPATH, f“//div[class‘ssid-item’ and contains(text(), ‘{ssid_name}’)]”) PASSWORD_INPUT (By.ID, “wifi-password”) SAVE_BUTTON (By.XPATH, “//button[text()‘保存设置’]”) CONFIG_STATUS (By.ID, “config-status”) def scan_for_wifi_networks(self): 触发WiFi扫描 scan_btn self.wait.until(EC.element_to_be_clickable(self.SCAN_BUTTON)) scan_btn.click() # 等待扫描结果区域出现并包含至少一个结果项表示扫描完成 self.wait.until( EC.and_( EC.presence_of_element_located(self.SCAN_RESULT_LIST), EC.presence_of_element_located((By.CLASS_NAME, “ssid-item”)) ) ) # 可选获取扫描到的所有SSID列表用于日志或更复杂的断言 ssid_elements self.driver.find_elements(By.CLASS_NAME, “ssid-item”) return [elem.text for elem in ssid_elements] def select_target_wifi_and_configure(self, target_ssid, password): 选择目标WiFi并输入密码 # 1. 等待目标SSID出现在列表中并点击 target_item_locator self._get_ssid_item_locator(target_ssid) target_item self.wait.until(EC.element_to_be_clickable(target_item_locator)) target_item.click() # 2. 等待密码输入框激活并输入密码 # 有时点击SSID后密码输入框才动态显示或启用 password_field self.wait.until(EC.element_to_be_clickable(self.PASSWORD_INPUT)) password_field.clear() password_field.send_keys(password) # 3. 点击保存 save_btn self.driver.find_element(*self.SAVE_BUTTON) save_btn.click() # 4. 等待配置结果反馈 # 可能是一个进度条或状态文本变化 status_element self.wait.until( EC.text_to_be_present_in_element(self.CONFIG_STATUS, “成功”) # 或“配置完成”等 ) return self.driver.find_element(*self.CONFIG_STATUS).text注意事项处理动态内容WiFi扫描列表是动态加载的。脚本必须等待列表加载完成并有内容后再进行下一步操作。使用EC.presence_of_element_located结合EC.presence_of_element_located等待子元素出现是一种稳健的做法。目标SSID的选择测试环境中的家庭WiFi SSIDtarget_ssid应该作为可配置参数如从配置文件或环境变量读取而不是硬编码在脚本里。这样脚本在不同测试环境开发、测试、生产中都能运行。保存后的异步等待点击保存后设备通常会尝试连接新WiFi这个过程需要几秒到十几秒。页面可能会显示“连接中...”的状态。我们的等待条件text_to_be_present_in_element需要匹配最终的成功状态。有时设备会跳转到新页面则需要等待新页面的特定元素出现。4. 端到端测试用例实现与流程串联4.1 核心测试用例脚本编写有了封装好的页面对象和WiFi连接器我们就可以用pytest来编写清晰、可读的端到端测试用例了。一个完整的测试用例应该覆盖“正常配置流程”。创建一个测试文件test_hidden_wifi_config.pyimport pytest import logging from selenium import webdriver from wifi_connector import WiFiConnector # 前面封装的类 from pages.login_page import LoginPage from pages.network_config_page import NetworkConfigPage # 配置日志 logging.basicConfig(levellogging.INFO, format‘%(asctime)s - %(levelname)s - %(message)s’) logger logging.getLogger(__name__) # 测试配置应从配置文件读取 TEST_CONFIG { “device_hidden_ssid”: “MyDevice_AP_5G”, “device_hidden_password”: “初始化密码123”, “device_admin_ip”: “192.168.4.1”, “target_home_ssid”: “MyHomeWiFi”, “target_home_password”: “HomeWiFiPassword456”, “admin_username”: “admin”, “admin_password”: “admin” } class TestHiddenWifiConfiguration: pytest.fixture(scope“function”) def driver(self): 为每个测试用例提供独立的浏览器实例 from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.chrome.options import Options chrome_options Options() # 添加必要的options... service Service(ChromeDriverManager().install()) driver webdriver.Chrome(serviceservice, optionschrome_options) driver.implicitly_wait(5) # 设置一个全局的隐式等待作为后备 yield driver # 测试结束后清理 driver.quit() pytest.fixture(scope“function”) def wifi_setup(self): 连接设备隐藏WiFi的fixture connector WiFiConnector( ssidTEST_CONFIG[“device_hidden_ssid”], passwordTEST_CONFIG[“device_hidden_password”] ) logger.info(“尝试连接设备隐藏WiFi...”) success connector.connect_to_hidden_wifi() if not success: pytest.fail(“无法连接到设备隐藏WiFi请检查设备状态和密码。”) yield # 测试结束后可以断开连接可选 # connector.disconnect() def test_complete_wifi_configuration_flow(self, driver, wifi_setup): 测试完整的隐藏WiFi配置流程 1. 连接设备隐藏WiFi 2. 登录设备管理页 3. 扫描并选择家庭WiFi 4. 配置成功并验证 logger.info(“开始测试完整WiFi配置流程”) # 1. 访问并登录 login_page LoginPage(driver) login_page.navigate_to(TEST_CONFIG[“device_admin_ip”]) assert “登录” in login_page.get_page_title(), “未成功加载登录页面” login_page.login(TEST_CONFIG[“admin_username”], TEST_CONFIG[“admin_password”]) # 2. 进入网络配置并设置新WiFi # 假设登录后自动跳转到主页需要导航到网络设置页 # 这里简化处理实际可能需要一个HomePage对象来点击菜单 # driver.find_element(By.LINK_TEXT, “网络设置”).click() config_page NetworkConfigPage(driver) # 假设当前已是配置页 scanned_ssids config_page.scan_for_wifi_networks() logger.info(f“扫描到的WiFi网络: {scanned_ssids}”) assert TEST_CONFIG[“target_home_ssid”] in scanned_ssids, f“目标WiFi {TEST_CONFIG[‘target_home_ssid’]} 未在扫描列表中找到” status_text config_page.select_target_wifi_and_configure( TEST_CONFIG[“target_home_ssid”], TEST_CONFIG[“target_home_password”] ) logger.info(f“配置状态: {status_text}”) # 3. 关键断言配置状态包含成功关键词 assert “成功” in status_text or “完成” in status_text, f“WiFi配置失败状态: {status_text}” # 4. 进阶验证可选设备切换WiFi后原管理IP可能失效。 # 可以通过等待一段时间然后尝试ping设备新的IP如果已知或查询设备状态API来验证。 logger.info(“测试用例执行完毕。”)4.2 测试数据管理与参数化一个健壮的测试套件需要覆盖多种场景比如错误的WiFi密码、弱信号、不支持的加密方式等。pytest的pytest.mark.parametrize装饰器非常适合做数据驱动测试。import pytest class TestHiddenWifiNegativeCases: pytest.mark.parametrize(“home_wifi_password, expected_error”, [ (“wrongpassword”, “密码错误”), # 错误密码 (“”, “密码不能为空”), # 空密码 (“a”*5, “密码长度不足”), # 密码过短假设要求8位以上 ]) def test_configuration_with_invalid_password(self, driver, wifi_setup, home_wifi_password, expected_error): 测试使用无效的家庭WiFi密码进行配置 # ... 前置步骤登录进入配置页 ... config_page NetworkConfigPage(driver) config_page.scan_for_wifi_networks() config_page.select_target_ssid(TEST_CONFIG[“target_home_ssid”]) # 输入错误密码并保存 config_page.enter_wifi_password(home_wifi_password) config_page.click_save() # 验证页面出现了预期的错误提示 actual_error config_page.get_error_message() assert expected_error in actual_error, f“错误提示不符。预期包含‘{expected_error}’实际是‘{actual_error}’”通过参数化我们只需编写一个测试函数就能自动运行多个数据组合的测试极大提高了测试用例的覆盖率和维护效率。5. 常见问题排查与稳定性优化实战5.1 Selenium自动化中的典型问题与解决在实际运行中即使代码写得再严谨也会遇到各种“意外”。下面是我踩过坑后总结的一些常见问题及解决方法。问题1元素定位失败抛出NoSuchElementException或TimeoutException。这是最常见的问题。原因A页面加载太慢元素还未出现。解决优化等待策略。检查并延长WebDriverWait的超时时间。确保等待的条件是准确的例如等待一个具有特定class的加载图标消失而不是简单等待某个元素出现。原因B元素在iframe或shadow DOM内。解决Selenium不能直接定位iframe或Shadow DOM内的元素。对于iframe需要使用driver.switch_to.frame(frame_reference)切换到对应的frame后再定位。对于Shadow DOM需要使用JavaScript执行器driver.execute_script来穿透。# 处理iframe iframe driver.find_element(By.TAG_NAME, “iframe”) driver.switch_to.frame(iframe) # 现在可以定位iframe内的元素了 inner_element driver.find_element(By.ID, “inner-btn”) # 操作完成后切回主文档 driver.switch_to.default_content() # 处理Shadow DOM (示例) host_element driver.find_element(By.CSS_SELECTOR, “custom-element”) shadow_root driver.execute_script(“return arguments[0].shadowRoot”, host_element) inner_element shadow_root.find_element(By.CSS_SELECTOR, “.inner-class”)原因C元素属性动态变化定位器失效。解决使用更稳定的定位策略。避免使用包含动态ID如id‘button-1234’或绝对位置的XPath。优先使用name、稳定的class或通过文本内容结合部分属性匹配的XPath如//button[contains(class, ‘btn-primary’)]。问题2脚本在本地运行成功但在CI服务器如Jenkins上失败。原因ACI环境是无头模式或没有图形界面。解决确保ChromeOptions中正确配置了无头模式和必要的启动参数。有些操作如文件上传在无头模式下可能需要特殊处理。另外无头模式下的窗口大小可能不同可以显式设置窗口大小driver.set_window_size(1920, 1080)。chrome_options.add_argument(“--headlessnew”) # 新版Chrome推荐 chrome_options.add_argument(“--no-sandbox”) # 在CI/Docker环境中常需要 chrome_options.add_argument(“--disable-dev-shm-usage”) # 解决共享内存问题 chrome_options.add_argument(“--disable-gpu”) # 某些环境下需要 chrome_options.add_argument(“--window-size1920,1080”)原因BCI服务器上浏览器、驱动版本与本地不一致。解决使用webdriver-manager自动管理驱动版本是最佳实践。确保CI的构建脚本中也安装了此库。问题3操作过程中出现意外的弹窗如浏览器保存密码提示、证书警告。解决在浏览器启动选项中预先禁用这些提示。prefs { “profile.default_content_setting_values.notifications”: 2, # 禁用通知 “credentials_enable_service”: False, “profile.password_manager_enabled”: False, “download.prompt_for_download”: False, # 禁用下载提示 } chrome_options.add_experimental_option(“prefs”, prefs) # 忽略SSL证书错误仅测试环境使用 chrome_options.add_argument(‘--ignore-certificate-errors’)5.2 隐藏WiFi连接与网络切换的稳定性陷阱问题连接隐藏WiFi的命令执行成功但脚本后续无法访问设备IP。排查步骤检查命令返回值确保subprocess.run的returncode为0并且stdout中包含连接成功的提示信息。增加等待时间系统连接WiFi并获取IP需要时间。在连接命令后添加time.sleep(5)或更长时间或者写一个循环去ping设备IP直到ping通为止。检查IP冲突设备隐藏WiFi的IP如192.168.4.1是否与测试机现有网络冲突确保测试机在连接设备WiFi前没有使用同一网段的VPN或其他网络适配器。防火墙拦截临时关闭测试机防火墙或添加规则允许对设备IP段的访问。查看系统日志在Linux/macOS上用journalctl -f或log stream实时查看网络连接日志。在Windows上查看事件查看器中的网络相关日志。问题设备配置家庭WiFi成功后脚本失去与设备的连接无法进行后续验证。解决思路这是一个预期行为。设备连接新WiFi后其IP地址会变从192.168.4.1变为家庭路由器分配的IP如192.168.1.100。方案A被动发现在配置保存后脚本等待一段时间如30秒然后尝试在家庭WiFi网段内扫描或ping可能的IP地址范围找到设备的新IP。这比较耗时且不稳定。方案B主动上报这是更优的方案需要设备固件支持。设备在配置成功后通过UDP广播、向一个预设的HTTP端点发送请求如测试服务器或更新某个云状态告知其新的IP地址。测试脚本则监听这个信息。方案C简化验证对于自动化测试有时我们只需要验证“配置过程成功完成”即可不一定需要验证设备在新网络下的可达性。这步验证可以交由另一套在家庭网络环境下的测试套件来完成。我们的隐藏WiFi测试脚本在收到配置成功的页面提示后即可认为测试通过。5.3 提升脚本健壮性的高级技巧失败重试机制对于网络请求、元素点击等可能因瞬时波动失败的操作可以封装一个重试装饰器。import time from functools import wraps from selenium.common.exceptions import StaleElementReferenceException, ElementClickInterceptedException def retry_on_failure(max_attempts3, delay1): def decorator(func): wraps(func) def wrapper(*args, **kwargs): for attempt in range(max_attempts): try: return func(*args, **kwargs) except (StaleElementReferenceException, ElementClickInterceptedException) as e: if attempt max_attempts - 1: raise print(f”{func.__name__} 第{attempt1}次尝试失败{delay}秒后重试... 错误: {e}“) time.sleep(delay) return None return wrapper return decorator # 在页面对象方法中使用 class SomePage: retry_on_failure() def click_unstable_button(self): self.driver.find_element(*self.UNSTABLE_BUTTON).click()页面状态快照与日志在关键步骤前后如点击按钮前、等待新页面后对页面进行截图并保存HTML源码这在排查CI服务器上的失败用例时至关重要。def take_screenshot_and_log(driver, step_name): timestamp time.strftime(“%Y%m%d_%H%M%S”) screenshot_path f“./logs/screenshot_{step_name}_{timestamp}.png” html_path f“./logs/page_{step_name}_{timestamp}.html” driver.save_screenshot(screenshot_path) with open(html_path, “w”, encoding“utf-8”) as f: f.write(driver.page_source) logger.info(f”步骤 ‘{step_name}’ 截图和源码已保存至 {screenshot_path}, {html_path}“) # 在测试用例中调用 take_screenshot_and_log(driver, “before_click_save”) config_page.click_save() take_screenshot_and_log(driver, “after_click_save”)环境隔离与清理每个测试用例都应该是独立的。使用pytest.fixture的scope“function”确保每个测试用例都有全新的浏览器实例和网络连接状态。在fixture的teardown阶段务必关闭浏览器、断开测试WiFi连接避免状态残留影响下一个用例。