
1. 项目概述从手工到自动化的必然跨越干了这么多年软件测试我最大的感受就是手工测试就像用勺子舀干一个游泳池而自动化测试则是打开了排水阀。今天要聊的“Selenium”就是那个最经典、最可靠的“排水阀”之一。无论你是刚入行的测试新人还是想从功能测试转向自动化的老手这个系列都将带你从零开始亲手搭建起一套可用的自动化测试框架。我们不会空谈理论而是聚焦于“怎么做”和“为什么这么做”把我在实际项目中踩过的坑、总结的技巧都揉碎了讲给你听。Selenium的核心价值在于模拟真实用户操作浏览器实现Web应用的自动化测试它能帮你把那些重复、枯燥的回归测试任务交给机器从而解放人力去进行更有价值的探索性测试和复杂场景设计。2. Selenium生态与核心组件拆解在动手写第一行代码之前我们必须先理清Selenium到底是什么以及它由哪些部分组成。很多人一上来就pip install selenium然后对着报错一头雾水问题往往就出在对整体架构的理解上。2.1 Selenium工具集不止一个“Selenium”Selenium其实是一个项目集合包含了多个工具针对不同的需求和场景。Selenium IDE这是一个浏览器插件支持Chrome和Firefox主要用于录制和回放测试脚本。它非常适合测试新手快速上手或者用于快速生成一些基础的操作脚本。但它的局限性也很明显录制的脚本可维护性差难以处理复杂逻辑如条件判断、数据驱动且通常绑定在特定浏览器上。在真正的项目自动化中它更多是作为一个辅助工具用于快速探索或生成部分操作代码。Selenium WebDriver这才是我们常说的“Selenium”的核心也是本系列重点讲解的对象。WebDriver提供了一套面向各种编程语言Java, Python, C#, JavaScript, Ruby等的API。这些API允许你通过代码直接向浏览器发送指令如点击、输入、获取元素并获取浏览器的响应。它的强大之处在于它调用的是浏览器原生支持的控制接口因此能实现最接近真实用户的操作。我们后续的所有自动化脚本都将基于WebDriver来编写。Selenium Grid这是一个用于分布式测试的工具。想象一下你需要同时在不同操作系统Windows, macOS, Linux的不同浏览器Chrome, Firefox, Safari, Edge上运行同一套测试用例以进行兼容性测试。手动一台台机器去跑显然不现实。Selenium Grid允许你将测试命令分发到网络中的多个节点即不同的机器和浏览器环境上并行执行极大地提升了测试效率尤其适合大型项目和持续集成流水线。对于绝大多数从零开始的个人学习或中小项目我们的起点和核心就是Selenium WebDriver。2.2 WebDriver的工作原理与浏览器对话的桥梁理解WebDriver如何工作有助于你在遇到诡异问题时进行排查。它的架构遵循W3C标准可以概括为“客户端-服务器”模式。客户端Client就是你用Python或Java等写的测试脚本。你调用Selenium提供的客户端库如from selenium import webdriver。浏览器驱动Browser Driver这是一个独立的可执行文件如chromedriver用于Chrome/Edge、geckodriver用于Firefox。它充当了服务器。你的客户端脚本通过HTTP请求使用JSON Wire Protocol或W3C协议向这个驱动发送命令。浏览器Browser浏览器驱动接收到命令后通过浏览器提供的原生自动化接口如Chrome DevTools Protocol来控制真实的浏览器实例执行相应操作并将结果返回给驱动驱动再返回给你的脚本。所以一个完整的Selenium自动化环境必须包含三部分你的测试脚本 对应语言的Selenium客户端库 对应浏览器的驱动。最常见的错误就是只安装了Python的selenium包却忘了下载或正确配置chromedriver。2.3 与其他工具的对比为什么是Selenium市面上自动化测试工具很多比如Playwright、Cypress、Puppeteer等。Selenium的优势和定位是什么Selenium vs. Playwright/CypressPlaywright和Cypress是较新的工具它们在架构上更现代默认支持无头模式、自动等待、网络拦截等高级特性开箱即用体验更好。Selenium的优势则在于其历史最久、社区最庞大、生态最成熟。几乎所有浏览器都官方支持Selenium有海量的资料、问答和第三方库如Page Object模式框架、报告工具。对于需要支持老旧浏览器如IE或深度定制化框架的企业级项目Selenium的灵活性和可控性更强。选择Selenium意味着你站在了一个巨人的肩膀上有整个开源社区作为后盾。Selenium for Web vs. AppiumSelenium专注于Web应用。如果你需要测试手机原生App或混合应用那么应该使用Appium。有趣的是Appium在底层复用了一部分Selenium的协议和思想所以学会Selenium对学习Appium有很大帮助。功能自动化 vs. 接口自动化这是两个不同层面。Selenium属于UI层功能自动化模拟用户操作界面。而接口自动化常用Requests, Postman等工具是直接测试后端API。一个完整的自动化测试体系通常两者结合接口自动化保证业务逻辑正确、快速反馈UI自动化保证前端交互和集成无误。在项目后期UI自动化由于执行慢、稳定性受前端影响大更适合作为冒烟测试或核心流程的回归保障。3. 环境搭建与核心配置实战理论清楚了我们立刻动手搭建环境。这里以最流行的组合Python 3.x Chrome浏览器 Selenium为例。3.1 基础环境安装安装Python确保你的系统已安装Python 3.6或更高版本。在命令行输入python --version或python3 --version检查。安装Selenium客户端库通过pip安装这是最简单的一步。pip install selenium我建议总是使用虚拟环境如venv或conda来管理项目依赖避免不同项目间的包版本冲突。下载浏览器驱动这是最关键也最容易出错的一步。ChromeDriver访问 ChromeDriver官网 或国内镜像站。驱动的版本必须与你的Chrome浏览器主版本号完全一致。查看Chrome版本浏览器设置 - 关于Chrome。下载对应版本的chromedriver压缩包。GeckoDriver (for Firefox)访问 GeckoDriver GitHub发布页 。Microsoft Edge Driver如果你的Edge是基于Chromium的那么使用edgedriver其版本也需与Edge浏览器版本匹配。3.2 驱动的配置与“找不到驱动”问题解决下载的驱动是一个可执行文件Windows是.exemacOS/Linux无后缀。有三种常用配置方法方法一放入系统PATH推荐将下载的chromedriver或geckodriver文件放在系统环境变量PATH包含的任意目录下例如Windows: 放在C:\Windows\或C:\Windows\System32\。macOS/Linux: 放在/usr/local/bin/。这样做之后你在代码中初始化驱动时Selenium会自动在PATH中查找。方法二指定驱动文件路径在代码中显式指定驱动文件的绝对路径。from selenium import webdriver # 指定驱动路径 driver webdriver.Chrome(executable_path/path/to/your/chromedriver) # 注意新版本Selenium中executable_path参数已弃用推荐使用Service类 # 新版本推荐写法 from selenium.webdriver.chrome.service import Service service Service(executable_path/path/to/your/chromedriver) driver webdriver.Chrome(serviceservice)方法三使用WebDriver Manager强烈推荐给新手这是一个第三方库能自动下载、匹配并管理浏览器驱动彻底解决版本匹配的烦恼。pip install webdriver-manager使用起来极其简单from selenium import webdriver from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.chrome.service import Service service Service(ChromeDriverManager().install()) driver webdriver.Chrome(serviceservice)WebDriverManager会自动检查你的浏览器版本下载对应的驱动并返回路径。这是我目前最推荐的方式尤其在持续集成环境中。注意如果遇到驱动无法打开或秒退的问题除了检查版本还要注意驱动文件是否有可执行权限Linux/macOS系统chmod x chromedriver。是否杀毒软件或系统安全策略拦截了驱动运行。尝试以管理员/root权限运行你的脚本。3.3 编写并运行第一个脚本打开百度搜索环境就绪我们来写一个最简单的脚本感受一下Selenium的威力。from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys import time # 1. 创建浏览器驱动实例这里假设驱动已在PATH中或使用WebDriver Manager driver webdriver.Chrome() # 这将打开一个全新的Chrome窗口 # 2. 访问目标网址 driver.get(https://www.baidu.com) # 3. 定位搜索框并输入搜索词 # 通过元素的ID属性定位这是最快最准的方式 search_box driver.find_element(By.ID, kw) search_box.send_keys(Selenium自动化测试) # 输入文字 # 4. 模拟按下回车键进行搜索 search_box.send_keys(Keys.RETURN) # 5. 等待一下观察结果 time.sleep(3) # 强制等待3秒这是一种简单的等待方式但不推荐在生产脚本中使用 # 6. 可以获取当前页面的标题或URL并打印 print(当前页面标题, driver.title) print(当前页面URL, driver.current_url) # 7. 关闭浏览器窗口 driver.quit() # 使用quit()会关闭所有窗口并结束驱动进程。close()只关闭当前窗口。将上面的代码保存为first_test.py然后在命令行运行python first_test.py。你应该能看到一个Chrome窗口自动打开访问百度输入关键词并搜索然后打印出信息后关闭。实操心得driver.quit()和driver.close()有区别。quit()会退出整个WebDriver会话释放资源是结束测试时的标准操作。close()仅关闭当前标签页如果只有一个标签页则关闭浏览器但驱动进程可能还在。养成用driver.quit()收尾的习惯。上面的脚本使用了time.sleep(3)这是一种“强制等待”。在实际项目中应尽量避免使用因为它会造成不必要的时间浪费且无法智能判断页面是否就绪。我们马上会讲到更优的“智能等待”。4. 元素定位自动化测试的基石自动化测试的本质是“找到元素操作元素”。因此元素定位的准确性和稳定性是脚本能否成功运行的关键。Selenium提供了8种主要的定位方式通过By类。4.1 八大定位策略详解与选用原则ID(By.ID)通过元素的id属性定位。id在HTML中应该是唯一的因此这是优先级最高、最可靠的定位方式。如果元素有稳定且唯一的ID请毫不犹豫地使用它。element driver.find_element(By.ID, “username”)Name(By.NAME)通过元素的name属性定位。name也可能不唯一但在表单元素中很常见。element driver.find_element(By.NAME, “password”)Class Name(By.CLASS_NAME)通过元素的class属性定位。一个元素可以有多个class空格分隔而一个class也可以被多个元素使用所以稳定性较差。通常用于辅助定位或定位一组元素。elements driver.find_elements(By.CLASS_NAME, “btn-primary”) # 注意是 find_elements返回列表Tag Name(By.TAG_NAME)通过标签名定位如input,div,a。一个页面中同类型标签极多几乎从不单独用于精确定位多用于查找元素集合。all_links driver.find_elements(By.TAG_NAME, “a”)Link Text(By.LINK_TEXT)专门用于定位超链接 (a标签)通过链接的完整可见文本定位。element driver.find_element(By.LINK_TEXT, “登录”)Partial Link Text(By.PARTIAL_LINK_TEXT)通过链接的部分可见文本定位。比Link Text更灵活但也要注意文本的唯一性。element driver.find_element(By.PARTIAL_LINK_TEXT, “忘记密”)CSS Selector(By.CSS_SELECTOR)使用CSS选择器语法定位。功能非常强大可以组合ID、Class、属性、层级关系等进行复杂定位。是仅次于ID的推荐定位方式尤其当元素没有ID时。# 通过ID element driver.find_element(By.CSS_SELECTOR, “#username”) # 通过Class element driver.find_element(By.CSS_SELECTOR, “.submit-btn”) # 组合定位具有classmenu的ul下的第一个li子元素 element driver.find_element(By.CSS_SELECTOR, “ul.menu li:first-child”)XPath(By.XPATH)使用XML路径语言定位。功能最强大可以遍历整个HTML文档树实现非常灵活的定位。但表达式可能较复杂且性能略低于CSS Selector。# 绝对路径脆弱不推荐 element driver.find_element(By.XPATH, “/html/body/div[1]/form/input[1]”) # 相对路径 属性定位推荐 element driver.find_element(By.XPATH, “//input[id‘username’]”) # 文本内容定位 element driver.find_element(By.XPATH, “//button[text()‘提交’]”) # 包含文本 element driver.find_element(By.XPATH, “//a[contains(text(), ‘下一页’)]”)定位策略选用优先级个人经验ID Name唯一且稳定首选。CSS Selector灵活、性能好、语法简洁。对于现代Web应用前端框架生成的ID可能动态变化此时利用稳定的Class和属性组合的CSS选择器是更好的选择。XPath当CSS选择器无法满足复杂逻辑时使用例如需要根据兄弟节点、父节点文本等关系定位。尽量避免使用长的绝对路径XPath。Link Text / Partial Link Text专门用于链接简单直接。Class Name / Tag Name通常不用于精确查找单个元素多用于查找列表或结合其他方法。4.2find_element与find_elements的区别find_element(By.XX, “value”)返回第一个匹配到的元素WebElement对象。如果没找到会抛出NoSuchElementException。find_elements(By.XX, “value”)返回一个列表包含所有匹配到的元素。如果没找到返回一个空列表[]不会抛出异常。根据你的需求选择。例如要操作一个特定的按钮用find_element要获取一个商品列表中的所有项用find_elements。4.3 定位失败常见原因与排查技巧即使你写对了定位器脚本也可能因为以下原因失败页面未加载完成这是最常见的原因。你执行定位代码时元素可能还没被渲染出来。解决方案是使用“等待”我们下一章详细讲。元素在iframe/frame内如果目标元素位于iframe或frame标签内你必须先切换到对应的frame中才能定位其中的元素。# 通过ID或索引切换到frame driver.switch_to.frame(“frame_id”) # 或者 driver.switch_to.frame(0) # 第一个frame # 操作frame内的元素... # 操作完成后切回主文档 driver.switch_to.default_content()元素在Shadow DOM内一些现代前端框架如Vue, React的某些组件会使用Shadow DOM普通定位方法无法直接穿透。需要使用driver.execute_script()执行JavaScript来访问。动态ID/Class前端框架如React, Angular可能会生成随机的ID或Class。此时应寻找其他不变的属性或者使用XPath/CSS Selector通过相对关系、文本内容等定位。弹窗/新窗口操作后打开了新窗口或弹窗需要切换句柄。# 获取当前所有窗口句柄 all_handles driver.window_handles # 切换到新窗口通常是最后一个 driver.switch_to.window(all_handles[-1])元素不可见或不可交互元素可能被其他元素遮挡如弹层或者设置了display: none、disabled属性。需要先确保元素可见且可操作。排查技巧当定位失败时不要盲目修改代码。首先在浏览器的开发者工具F12中使用Console标签尝试你的定位表达式。对于CSS Selector:document.querySelector(“你的选择器”)对于XPath:$x(“你的XPath表达式”)如果能在Console中找到元素说明定位器本身没问题问题可能出在等待、frame或窗口切换上。5. 等待机制让脚本稳定运行的关键自动化测试脚本不稳定十有八九是“等”得不对。Selenium提供了三种等待方式理解并正确使用它们是写出健壮脚本的核心。5.1 强制等待 (time.sleep)最简单粗暴的方式让线程暂停指定的时间。import time time.sleep(5) # 无条件等待5秒缺点无论页面是否已经就绪都必须等待固定时间。如果元素提前加载好就浪费了时间如果元素加载比预期慢依然会失败。仅在调试或不得已的情况下使用。5.2 隐式等待 (implicitly_wait)在WebDriver对象的整个生命周期或直到被改变内设置一个全局的等待时间。当使用find_element或find_elements定位元素时如果元素没有立即找到WebDriver会轮询DOM一段时间默认0.5秒检查一次直到找到元素或超时。driver webdriver.Chrome() driver.implicitly_wait(10) # 设置为10秒特点与注意事项全局性设置一次对所有后续的find_element操作都生效。只对元素定位有效它只作用于“查找元素”这个动作。如果元素找到了但不可点击例如被遮挡、未启用它不会继续等待。与显式等待混用当同时设置了隐式等待和显式等待时实际等待时间取两者最大值。通常建议在项目中只使用一种推荐显式等待以避免不可预期的长时间等待。很多团队的最佳实践是禁用隐式等待设为0完全使用显式等待来获得更精确的控制。5.3 显式等待 (WebDriverWaitexpected_conditions)这是最强大、最推荐的等待方式。它允许你为某个特定的条件设置等待条件成立则立即继续执行否则在超时后抛出异常。from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By # 创建一个WebDriverWait对象设置最大等待时间10秒轮询间隔0.5秒默认 wait WebDriverWait(driver, 10) # 等待直到某个元素出现并可被定位 element wait.until(EC.presence_of_element_located((By.ID, “dynamic_element”))) # 等待直到某个元素可见不仅存在而且display不为nonevisibility不为hidden element wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, “.loading-message”))) # 等待直到某个元素可被点击可见且enabled button wait.until(EC.element_to_be_clickable((By.NAME, “submit”))) button.click() # 等待直到某个元素从DOM中消失例如等待加载动画消失 wait.until(EC.invisibility_of_element_located((By.ID, “spinner”))) # 等待直到页面标题包含特定文字 wait.until(EC.title_contains(“订单提交成功”)) # 等待直到某个元素包含特定文本 wait.until(EC.text_to_be_present_in_element((By.TAG_NAME, “h1”), “欢迎回来”))为什么显式等待最好精准只为必要的条件等待条件满足立即执行效率高。灵活丰富的预期条件expected_conditions模块覆盖了各种场景。清晰代码明确表达了“在做什么之前需要等待什么状态”可读性强。稳定能有效应对网络延迟、动态加载等导致的元素状态变化。实操心得与避坑指南超时时间设置根据网络和应用的实际情况设置通常5-15秒。太短容易失败太长会拖慢测试套件整体速度。选择正确的预期条件presence_of_element_located元素存在于DOM中即可哪怕它不可见。适用于你后续需要操作隐藏元素的情况。visibility_of_element_located元素不仅存在还要可见。这是最常用的条件因为用户通常只能与可见元素交互。element_to_be_clickable在“可见”的基础上还要求元素是enabled状态。用于点击操作前的最佳检查。自定义等待条件如果内置条件不满足需求你可以用lambda函数自定义。# 等待直到元素的某个属性值变为特定值 wait.until(lambda d: d.find_element(By.ID, “progress”).get_attribute(“value”) “100”)处理超时异常until方法在超时后会抛出TimeoutException。你应该在测试框架中妥善捕获并处理这个异常将其转化为测试失败并输出有意义的日志而不是让整个脚本崩溃。避免“等待链”不要在循环或连续操作中频繁使用短时间的显式等待这可能导致累积等待时间很长。尽量用一个等待条件覆盖一个完整的交互阶段。6. 常用浏览器操作与高级交互掌握了定位和等待我们就可以组合它们来完成各种复杂的用户操作了。6.1 基础操作点击、输入、清空、提交from selenium.webdriver.common.keys import Keys from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC # 假设driver已初始化 wait WebDriverWait(driver, 10) # 1. 点击操作 login_button wait.until(EC.element_to_be_clickable((By.ID, “loginBtn”))) login_button.click() # 2. 输入文本 username_input driver.find_element(By.ID, “username”) username_input.send_keys(“my_username”) # 输入 username_input.clear() # 清空输入框 username_input.send_keys(“new_username”) # 重新输入 # 3. 模拟特殊键 search_input driver.find_element(By.NAME, “q”) search_input.send_keys(“Selenium”) search_input.send_keys(Keys.ENTER) # 模拟回车键等同于 .send_keys(Keys.RETURN) # 也可以组合键如CtrlA全选 search_input.send_keys(Keys.CONTROL, ‘a’) # Windows/Linux # 对于Mac通常是 COMMAND 键但WebDriver可能仍映射为CONTROL需注意系统差异 # 4. 提交表单 # 如果输入框在一个form里可以通过submit()提交 form_element driver.find_element(By.TAG_NAME, “form”) form_element.submit() # 或者直接找到提交按钮点击6.2 获取元素信息与状态判断自动化测试不仅是操作也需要验证。我们需要从页面上获取信息来进行断言。element driver.find_element(By.ID, “someElement”) # 获取文本内容可见文本 text element.text print(f”元素文本{text}”) # 获取属性值 attr_value element.get_attribute(“class”) print(f”class属性{attr_value}”) # 对于自定义属性或标准属性都适用 data_id element.get_attribute(“data-testid”) # 获取CSS属性值 css_color element.value_of_css_property(“color”) print(f”字体颜色{css_color}”) # 判断元素状态 is_displayed element.is_displayed() # 是否可见 is_enabled element.is_enabled() # 是否可用未被disabled is_selected element.is_selected() # 是否被选中用于复选框、单选框 # 获取元素位置和大小 location element.location # {‘x’: 100, ‘y’: 200} size element.size # {‘height’: 30, ‘width’: 120}6.3 高级交互鼠标动作与键盘动作链对于拖放、悬停、右键菜单等复杂操作需要使用ActionChains类。from selenium.webdriver.common.action_chains import ActionChains actions ActionChains(driver) # 鼠标悬停 menu_element driver.find_element(By.CSS_SELECTOR, “.dropdown-menu”) actions.move_to_element(menu_element).perform() # 注意.perform() 是执行动作链的必须调用 # 拖放操作 source_element driver.find_element(By.ID, “draggable”) target_element driver.find_element(By.ID, “droppable”) actions.drag_and_drop(source_element, target_element).perform() # 或者更精确的控制 # actions.click_and_hold(source_element).move_to_element(target_element).release().perform() # 右键点击上下文菜单 actions.context_click(element).perform() # 双击 actions.double_click(element).perform() # 组合动作点击并按住移动到某点然后释放 actions.click_and_hold(element).move_by_offset(100, 50).release().perform()6.4 执行JavaScript当Selenium API无法直接实现某些操作时如滚动到元素、修改元素属性、处理Shadow DOM可以借助执行JavaScript的能力。# 执行简单的JS driver.execute_script(“alert(‘Hello Selenium!’);”) # 带参数的JS并获取返回值 title driver.execute_script(“return document.title;”) print(title) # 滚动到元素可见非常实用的操作 element driver.find_element(By.ID, “footer”) driver.execute_script(“arguments[0].scrollIntoView(true);”, element) # true表示与窗口顶部对齐 # 修改元素属性常用于调试或处理特殊场景 driver.execute_script(“arguments[0].setAttribute(‘style’, ‘border: 2px solid red;’);”, element) # 处理Shadow DOM示例 # 假设有一个自定义组件 my-component内部有Shadow Root shadow_host driver.find_element(By.TAG_NAME, “my-component”) shadow_root driver.execute_script(“return arguments[0].shadowRoot”, shadow_host) # 现在可以通过shadow_root来查找内部元素需要再次执行JS或使用其他方法Selenium 4有改进支持 inner_element driver.execute_script(“return arguments[0].querySelector(‘.inner-button’)”, shadow_root) inner_element.click()注意事项虽然execute_script很强大但过度使用会使测试脚本与前端实现细节JS耦合过紧降低可维护性。应优先使用标准的WebDriver API。7. 实战封装一个简单的页面操作类将上述知识结合起来我们尝试为一个简单的登录页面封装一个操作类。这是迈向Page Object模式后续文章会详述的第一步。假设我们有一个登录页面包含用户名输入框、密码输入框、登录按钮和错误提示区域。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: “”“登录页面操作封装”“” # 页面元素定位器Locators集中管理便于维护 USERNAME_INPUT (By.ID, “username”) PASSWORD_INPUT (By.ID, “password”) LOGIN_BUTTON (By.CSS_SELECTOR, “button[type‘submit’]”) ERROR_MESSAGE (By.CLASS_NAME, “alert-error”) def __init__(self, driver): self.driver driver self.wait WebDriverWait(driver, 10) def navigate_to(self, url): “”“导航到登录页面”“” self.driver.get(url) # 可以在这里增加一个等待确保页面关键元素加载完成 self.wait.until(EC.presence_of_element_located(self.USERNAME_INPUT)) return self def enter_username(self, username): “”“输入用户名”“” username_elem self.wait.until(EC.visibility_of_element_located(self.USERNAME_INPUT)) username_elem.clear() username_elem.send_keys(username) return self # 支持链式调用 def enter_password(self, password): “”“输入密码”“” password_elem self.driver.find_element(*self.PASSWORD_INPUT) # 因为密码框通常就在用户名下面可以不用再等待 password_elem.clear() password_elem.send_keys(password) return self def click_login(self): “”“点击登录按钮”“” login_btn self.wait.until(EC.element_to_be_clickable(self.LOGIN_BUTTON)) login_btn.click() return self def get_error_message(self): “”“获取错误提示信息如果存在的话”“” try: # 错误信息可能不会立即出现等待一小会儿 error_elem WebDriverWait(self.driver, 3).until( EC.visibility_of_element_located(self.ERROR_MESSAGE) ) return error_elem.text except: # 如果没有找到错误信息元素返回None或空字符串 return None def login(self, username, password): “”“完整的登录流程”“” self.enter_username(username) self.enter_password(password) self.click_login() # 登录后可以返回下一个页面的对象这里简单返回自身 # 实际项目中这里可能会 return HomePage(self.driver) return self # 使用示例 if __name__ “__main__”: from selenium import webdriver driver webdriver.Chrome() try: login_page LoginPage(driver) login_page.navigate_to(“http://example.com/login”) # 测试登录失败场景 login_page.login(“wrong_user”, “wrong_pass”) error_msg login_page.get_error_message() if error_msg: print(f”登录失败错误信息{error_msg}”) else: print(“未捕获到预期的错误信息”) # 可以继续测试登录成功场景... # login_page.login(“correct_user”, “correct_pass”) # 验证是否跳转到首页... finally: driver.quit()这个简单的封装带来了几个好处代码复用登录操作被封装成一个方法多个测试用例都可以调用。易于维护所有元素定位器集中在类顶部如果页面元素ID变了只需修改一处。可读性高测试用例读起来像自然语言login_page.login(“user”, “pass”)。减少重复等待逻辑被封装在方法内部调用者无需关心。这就是Page Object Model (POM) 设计模式的雏形。在后续文章中我们会深入探讨如何构建更健壮、可扩展的POM框架并集成单元测试框架如pytest来组织和管理测试用例。