如果只让我从Selenium 的各种特性里挑一部分讲给新人听我会毫不犹豫地选常用函数。接触过的自动化项目越多越发现一个规律真正拉开项目成败差距的,不是谁的框架搭得花哨而是谁把最基础的那十几个操作函数用得更稳。这篇文章想做的就是把 Selenium 自动化测试里那些高频使用、几乎每个自动化脚本都会碰到的函数一次讲透——从元素定位到文件上传覆盖日常实战中 99% 的场景。适合刚入门自动化测试的测试工程师、准备搭建 UI 自动化框架的开发者也适合那些已经能跑通脚本但总在稳定性上翻车的人。我不会堆一个又一个孤立 API而是把函数放到真实业务场景里拆解讲清楚每个功能背后的运行机制和适用边界。毕竟网上文档很多为什么这样用什么时候不该用才是真正值钱的部分。1. 环境准备与驱动匹配自动化还没开始就废掉的一步很多人以为写完 Selenium 脚本的第一步是打开编辑器敲代码其实不是。正确的第一步是搞定浏览器驱动这一步没做好后面所有函数都是空中楼阁。最常见的报错就是WebDriverException: Message: session not created或者chromedriver executable needs to be in PATH这类问题引起的排查耗时往往比写脚本还长。1.1 Selenium 安装与依赖选择安装 Selenium 本身很简单一条命令就能完成pip install selenium但真正容易踩坑的是版本匹配。Selenium 4 之后的架构变化很大如果你还在看老教程会发现很多find_element_by_id、find_element_by_xpath这类写法已经不能用了。Selenium 4 推荐统一使用By类加定位策略的写法这一点在后面的定位章节会详细展开。另一个容易忽略的点是Selenium 脚本只负责通过 WebDriver 协议和浏览器通信它本身不包含浏览器内核。所以你的电脑上必须装好对应版本的浏览器同时拿到配套的浏览器驱动。Chrome 对应 chromedriverFirefox 对应 geckodriverEdge 对应 msedgedriver。1.2 驱动版本匹配的硬规则与 Selenium Manager浏览器驱动的版本必须和浏览器的大版本一致。比如 Chrome 版本升到了 126那么 chromedriver 也必须是 126.x差一个号都可能直接启动失败。这在很多团队里是个大痛点因为 Chrome 会自动更新而驱动不会于是每隔几周就出现一次昨天还能跑今天全挂了的情况。针对这个问题Selenium 4.6 以上版本内置了一个叫 Selenium Manager 的组件可以自动检测浏览器版本并下载对应驱动省去了手动维护驱动的麻烦。实操中很多人还是习惯手动准备驱动原因通常是测试环境是内网访问不了公共下载地址。这时候就需要在代码里手动指定驱动路径from selenium import webdriver from selenium.webdriver.chrome.service import Service service Service(/usr/local/bin/chromedriver) driver webdriver.Chrome(serviceservice)如果你用的是 Selenium Manager这行代码都不需要webdriver.Chrome()会自己搞定。但我的建议是脚本和驱动分离部署、在项目文档里明确记录驱动版本仍然是团队协作里最稳妥的做法至少能减少成员各自调试环境的成本。2. 元素定位策略ID、XPath 与 CSS Selector 到底怎么选元素定位是整个 Selenium 自动化测试的核心技术点。如果定位不到元素后面的点击、输入、等待全都是空谈。很多新手学 Selenium 上来就抄 XPath导致脚本里全是又长又脆的绝对路径稍微改版就崩。这里我会把定位策略的选择顺序、XPath 与 CSS Selector 的适用场景、以及常见的定位失败原因一次讲清楚。2.1 定位器的优先顺序与 By 类用法Selenium 4 中统一通过By类来指定定位策略常用的有这些定位策略写法示例适用场景IDBy.ID元素有稳定 id 时优先使用NameBy.NAME表单类页面常用Class NameBy.CLASS_NAME多个元素共享样式类时Tag NameBy.TAG_NAME批量获取同类标签CSS SelectorBy.CSS_SELECTOR结构清晰、性能好XPathBy.XPATH需要按文本内容或复杂关系定位时Link TextBy.LINK_TEXT精确定位超链接文本Partial Link TextBy.PARTIAL_LINK_TEXT只知道超链接部分文本时实际写代码时它们长得是这样的from selenium.webdriver.common.by import By driver.find_element(By.ID, username) driver.find_element(By.NAME, password) driver.find_element(By.CSS_SELECTOR, button[data-testidsubmit]) driver.find_element(By.XPATH, //span[contains(text(),登录)])我的定位策略选择顺序是ID 优先其次 CSS Selector再次 XPath。ID 是页面上唯一的标识定位速度最快CSS Selector 的语法简洁且浏览器原生支持好XPath 最灵活但相对较慢而且容易写出脆弱的长路径只在必须按文本内容定位时才用。2.2 相对 XPath 与 contains/text 的组合技巧XPath 最大的价值在于它能直接通过文本内容定位这在很多没有 id 和 class 的动态页面里几乎是救命稻草。但关键是写出相对、稳定、短的 XPath而不是从 html 根节点开始一长串的绝对路径。单条绝对 XPath 长这样/html/body/div[2]/div[3]/form/div[1]/input这样的写法只要页面加一层 div 就全部失效。推荐写法是基于元素的属性或层级关系//input[placeholder请输入用户名] //button[contains(text(),提交)] //div[classuser-panel]//span[text()设置]contains(text(), 提交)是处理动态文本的利器。比如按钮文字是提交订单和提交审核你要找的是包含提交的按钮就可以用它。还有starts-with、normalize-space这类函数也能处理带空格或换行的文本定位问题。2.3 CSS Selector 的优势与性能考量XPath 虽然灵活但 CSS Selector 在性能、可读性上更胜一筹。尤其是当页面元素属性明确、结构嵌套又不太深时CSS Selector 是最舒服的选择。#username - id 为 username 的元素 .form-item .username - class 为 form-item 下 class 为 username 的元素 input[typetext] - type 属性为 text 的 input 元素 button[data-idsubmit] - 使用任意 data 属性定位CSS Selector 为什么性能好因为浏览器里 CSS 原本就是高效的匹配机制而 XPath 是基于遍历文档树的方式在复杂页面里会更慢。不过在真实项目中性能差异只有在定位几十个元素时才会显现单个脚本通常感觉不出来。我选择 CSS Selector 更多是因为它更简洁、更稳定少了一层复杂的 XPath 函数套用。2.4 定位不到元素的常见原因与排查链路这个场景我见过太多次了脚本清楚、元素清楚、代码也没报错但就是NoSuchElementException。排查的时候别急着改定位器先按下面这个链路走一遍确认页面是否真的加载完成有些元素是动态渲染出来的页面标题出现了但表格数据还没加载。此时需要配合等待机制后面专门展开。确认元素是否在 iframe 或新窗口里这是新手最容易犯的错。元素明明在页面里用浏览器 F12 也能查到但脚本定位不到八成是因为它在 iframe 里需要先切换进去。确认元素是否可见、可交互有些元素存在于 DOM 中但被隐藏或遮挡。find_element能找到不代表click能成功。确认定位器写的路径是否准确多层级或者用错了属性值可以用浏览器控制台验证比如$(#username)或$x(//input[nameuser])。排查的时候把浏览器开发者工具打开在 Console 里直接测试定位表达式比盲改代码效率高一倍。这也算是我踩了无数次坑之后总结出来的经验。3. 高频操作函数点击、输入、下拉这些动作别重复写工具类定位解决了之后我们对元素做的最多的操作无非是点击、输入、清空、选择、读取文本和属性值。这一章我们逐一展开把它们的底层行为讲透。很多人把这些函数用得很将就能跑就不优化实际上它们之间细节差异很大。3.1 点击与提交click 和 submit 不要混用click()是最基础的操作函数它模拟鼠标左键单击。绝大部分按钮、链接、复选框的触发都用它。对应地表单里还有一个submit()方法直接提交表单。两者的区别在于submit()会忽略按钮上的事件绑定只走表单的 submit 流程。如果页面按钮绑定了自定义校验、AJAX 提交逻辑用submit()跳过按钮点击反而是错的。所以我的建议是统一先用click()。只有一种情况用submit()有意义那个按钮本身样式或定位有问题但表单结构完整的时候。实际上我在项目里用submit()的次数屈指可数日常全用click()就够了。3.2 输入与清空send_keys 不只是填文本输入函数send_keys()是另一个高频函数。它不仅可以输入文本还能模拟键盘按键比如回车、Tab、CtrlA 等等。input_box driver.find_element(By.ID, search) input_box.clear() input_box.send_keys(自动化测试) input_box.send_keys(Keys.ENTER)我在项目里经常用到的组合是clear()之后立刻send_keys()。clear()用来清空输入框原有内容比手动 CtrlA 再删除要靠谱得多。send_keys(Keys.ENTER)常用于搜索场景省去再定位一次搜索按钮的时间。send_keys还有一个隐藏用法传递文件路径这个在文件上传章节会重点展开。此外如果输入的内容很长从文本文件里读取出来再塞进去也是常见做法with open(test_data.txt, r, encodingutf-8) as f: content f.read() input_box.send_keys(content)3.3 下拉框、单选与复选Select 类的正确打开方式下拉框是 Web 页面里最常见的组件但很多人处理下拉框时还在用点击展开再点击选项的土办法。实际上 Selenium 提供了专门的Select类专门处理原生select标签。from selenium.webdriver.support.ui import Select select_element Select(driver.find_element(By.ID, province)) select_element.select_by_index(1) select_element.select_by_value(guangdong) select_element.select_by_visible_text(广东省)三个方法分别按选项序号、选项 value 属性、选项可见文本选择。项目里我用得最多的是select_by_visible_text因为文本往往比 value 更直观稳定。select_by_index适合选项顺序固定、且不需要关心文本内容的场景。单选框和复选框的处理则比较简单先定位到目标元素再判断是否需要先取消选中状态最后click()。比如一个默认选中的复选框你想取消它checkbox driver.find_element(By.CSS_SELECTOR, input[typecheckbox]) if checkbox.is_selected(): checkbox.click()3.4 读取属性、文本与页面信息断言的基础自动化测试里光有操作没有校验算不上测试。校验就需要从元素身上读取信息。最常用的是这几个函数element.text # 获取可见文本 element.get_attribute(value) # 获取 value 属性 element.get_attribute(href) # 获取链接 element.is_displayed() # 是否可见 element.is_enabled() # 是否可用我通常在断言场景里这样组合先等待元素出现再读取文本或属性最后用 Python 的assert判断。比如验证登录成功后右上角显示的用户名就可以user_span wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, .navbar-user))) assert 测试用户 in user_span.text有时候需要读取整个页面的状态比如断言跳转后的 URL或者比页面标题是否符合预期。这些信息直接在driver上就能获取根本不需要定位元素比定位元素再读属性更稳定。4. 文件上传突破系统弹窗的三种实用路径文件上传是 Selenium 自动化测试里的一块硬骨头。很多人的第一反应是这玩意儿肯定得借助第三方工具吧实际上分场景。我把日常遇到的上传场景分成三类input标签上传、非input标签上传、拖拽上传。三种处理方式完全不同适用场景也不同。4.1 input 标签上传send_keys 直接塞路径如果页面里的上传控件是一个input typefile标签那就简单了。Selenium 的send_keys可以直接把本地文件路径传给这个元素不需要点击任何按钮更不需要处理系统弹窗。file_input driver.find_element(By.CSS_SELECTOR, input[typefile]) file_input.send_keys(/Users/me/test_files/report.pdf)这个操作背后的原理是input[typefile]接受文件路径作为 value。浏览器出于安全考虑禁止脚本直接赋值文件路径但send_keys模拟了用户输入行为所以合法。这条路是最简单也最稳的遇到这种场景完全不用搞什么自动化上传工具。这里有三个细节路径必须是绝对路径相对路径在多数浏览器里都不可靠。如果有多个文件需要上传send_keys也可以一次传多个路径用换行符分隔。路径里的斜杠建议统一转成当前操作系统识别的格式用os.path.join生成更保险。4.2 非 input 标签上传系统弹窗用键盘操作绕过如果页面上传控件是自定义的div或button点击后才弹出系统级文件选择窗口这时候 Selenium 就没法直接操作了因为那个窗口不在浏览器的 DOM 里。最实用的处理方式是借助 Python 的第三方库模拟键盘操作。Windows 环境用pyautogui或者win32api都行Linux 下可以用pyautoguimacOS 上也可以跑。思路都是点击上传按钮 - 等待系统弹窗出现 - 模拟键盘输入完整文件路径 - 回车确认。import pyautogui import time upload_btn.click() time.sleep(2) pyautogui.typewrite(C:\\Users\\me\\test_files\\report.pdf, interval0.05) pyautogui.press(enter)interval参数很关键如果一次性输入太快部分系统会丢字符。尤其当路径很长、包含多个目录层级时一定要保留这个参数。这里要提醒一句系统弹窗的标题、输入框位置会随操作系统版本和语言环境变化所以这类方案天生不太稳定。我的建议是只要开发愿意把上传控件改成input[typefile]就让开发改这是一本万利的事。如果实在改不了再考虑用键盘模拟或者第三方工具兜底。4.3 拖拽上传与上传后的等待校验现代 Web 应用有很多拖拽上传区域这类场景 Selenium 的ActionChains可以派上用场。from selenium.webdriver.common.action_chains import ActionChains drag_area driver.find_element(By.CSS_SELECTOR, .upload-drop-zone) ActionChains(driver).move_to_element(drag_area).pause(1).send_keys(/tmp/report.pdf).perform()这个方案本质上还是通过键盘输入路径只是先把焦点移动到目标区域。不过说实话拖拽上传我建议优先验证一下是否走的是底层input上传如果是回到第一种方案反而是最稳的。上传完成后的等待与校验同样值得重视。别一上传完就立刻断言文件列表很多文件需要走上传接口、服务端转码、列表刷新这一整套流程短则一两秒长则十几秒。此时应该用显式等待等待目标文件出现在列表里file_name report.pdf wait.until(EC.visibility_of_element_located((By.XPATH, f//span[contains(text(),{file_name})])))这种操作后等待结果出现的写法是整个文件上传测试稳定性的关键。很多人以为上传这步过了就完事了实际上一大半的 flaky 用例都是栽在上传成功但页面还没刷新出来这个时间差上。5. 三种等待方式的取舍稳定性的关键不在函数在等待进入这一章前先问一个问题你写的 Selenium 脚本是不是经常随机失败有时跑十遍有八遍过有时连一遍都过不去如果是那八成和等待有关。我见过很多测试工程师在脚本里无脑加time.sleep(5)结果要么白等浪费时间要么还是不稳定。掌握三种等待方式的取舍比多记住十个函数都重要。5.1 强制等待time.sleep 为什么不建议瞎用强制等待就是让脚本停下来休息固定时间import time time.sleep(3)在 Selenium 自动化测试里time.sleep属于无差别阻塞。元素 1 秒就出现了你让脚本白等 2 秒元素 5 秒才出现你 sleep 3 秒就报错。所以强制等待本质上是在用固定时间去赌一个不固定的加载时间赌赢的概率完全看运气。那我是不是说time.sleep就完全不能用也不是。比如前面提到的文件上传系统弹窗场景点击按钮后要等弹窗真正弹出这种等待操作系统响应的场景用短 sleep 是合理的。关键是短睡眠、有目的、有边界。5.2 隐式等待设置一次全局生效的兜底策略隐式等待是 WebDriver 实例的一个属性设置了之后每次定位元素时 WebDriver 会在指定的时间内轮询查找。它作用于所有查找操作所以代码里只需要设置一次driver.implicitly_wait(10)它的优点是省心缺点是均匀作用于所有元素。如果一个页面有 20 个需要定位的元素每个都要轮询几秒才能找到那总耗时就会非常难看。还有一个容易让人困惑的点隐式等待只针对元素的查找不保证元素一定可见或者可点击。很多隐性 bug 就出在这里——元素找到了但被遮挡点击时报ElementClickInterceptedException。我的建议是把implicitly_wait(10)作为全局兜底设置但关键操作一定要叠加显式等待。5.3 显式等待WebDriverWait 是稳定性之王显式等待是最符合自动化测试需求的等待方式。它的逻辑是等到某个条件成立才继续不成立就持续轮询超时再抛异常。from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC wait WebDriverWait(driver, 10) username_input wait.until(EC.presence_of_element_located((By.ID, username)))expected_conditions提供了一堆现成的条件常用的有这几个条件适用场景presence_of_element_located元素出现在 DOM 中visibility_of_element_located元素可见element_to_be_clickable元素可见且可点击text_to_be_present_in_element元素文本包含指定内容alert_is_present弹窗出现url_containsURL 包含指定字符串这里面我使用频率最高的是element_to_be_clickable。因为很多场景下元素不仅需要存在还需要真正可以点击比如按钮被遮罩层盖住时前一个条件过了点下去照样报错。更进阶的用法是自定义轮询频率比如每 0.5 秒查一次wait WebDriverWait(driver, 10, poll_frequency0.5)在排队接口响应比较慢的上传、提交场景里这个参数可以把耗时从每 0.5 秒试一次改成每 2 秒试一次降低无用轮询造成的资源浪费。5.4 三种等待的组合策略与超时排查我自己项目的组合策略是这样的全局设置implicitly_wait(10)作为兜底代码中涉及关键链路的地方全部使用WebDriverWait显式等待time.sleep只出现在系统弹窗、非浏览器进程交互这几类场景里。选择等什么有一个很实用的原则优先等待你真正关心的结果。比如提交订单后等待订单成功文案出现而不是傻等一个按钮重新变为可点击。前者校验了业务结果后者只校验了 UI 状态。如果显式等待超时了先别急着加时间。查看是不是条件选错了比如你要的是visibility_of_element_located但元素其实在页面里但被遮挡。此时换成element_to_be_clickable反而能正常通过。这个细节我记忆犹新曾经一个用例动不动就超时排查半天发现是元素在一个半透明的 loading 遮罩下刚好可见属性为真、但点击被 intercept两个条件的差异直接决定了用例的稳定性。6. 滚动、多窗口与 iframe这几个必踩场景的处理方式标题里说的覆盖 99% 实战场景除了前面这些基础操作函数之外还有很多测试工程师会突然卡住的地方——页面滚动了但 element 还是找不到、点击一个链接后新窗口打开了但脚本还在旧页面里、元素明明在 DOM 里却定位不到。这些场景我们逐一拆开讲。6.1 页面滚动scrollIntoView 与滚动条控制有些页面内容很长需要滚动到目标元素所在位置才能继续操作。很多人一遇到滚动就想到driver.execute_script(window.scrollTo(0, document.body.scrollHeight))直接滚到底。但更精准的做法是让元素自己滚到视野中央element driver.find_element(By.CSS_SELECTOR, .load-more-btn) driver.execute_script(arguments[0].scrollIntoView(true);, element)这和人眼看到元素再点击是同一个道理能有效减少元素处于页面可视区域外导致的 click 失败。另一个常用场景是测试无限下拉列表时需要滚动到页面底部来触发加载此时结合循环来做for _ in range(5): driver.execute_script(window.scrollTo(0, document.body.scrollHeight)) time.sleep(1)另外还有个技巧页面上的横向滚动条可以通过设置 scrollLeft 来控制左右滑动常见于一些数据大屏场景driver.execute_script(document.querySelector(.table-container).scrollLeft 500)6.2 多窗口切换window_handles 的正确打开方式点击一个链接在新标签页打开是再常见不过的场景。但很多初学者会踩这个坑click()之后马上定位新页面的元素结果定位不到因为 WebDriver 的上下文还停留在旧窗口。这时候需要手动切换。driver.find_element(By.LINK_TEXT, 查看详情).click() # 切换到最新打开的窗口 new_window driver.window_handles[-1] driver.switch_to.window(new_window) # 用完记得切回来 driver.close() driver.switch_to.window(driver.window_handles[0])注意window_handles用索引取值依赖于窗口打开的先后顺序虽然多数情况下最新窗口在列表末尾但严谨的做法是记录下来旧窗口的句柄然后遍历所有句柄判断哪些是新增的。6.3 iframe 切换定位不到元素的隐藏元凶iframe 是多窗口之外另一个元素明明在页面上但就是定位不到的常见原因。iframe 里的元素和主文档不是一个 DOM 树必须先进去才能定位。# 切换到 iframe可以用 id、name 或 WebElement driver.switch_to.frame(mainFrame) # 定位 iframe 内部的元素 input_box driver.find_element(By.ID, iframe_username) # 操作完成后回到主文档 driver.switch_to.default_content()还有嵌套 iframe 的情况需要一层层往里切。如果发现定位不到可以先检查一下元素是否藏在 iframe 里。一个常见的排查方法是看浏览器 F12 里元素对应 html 结构上方是否有iframe包裹。每次遇到定位失败先问自己一句这个元素是不是在 iframe 中这个问题解决了大概 30% 的定位失败问题。处理完 iframe 内的操作切回主文档这一步特别容易漏。漏了之后下一个定位操作会继续在 iframe 上下文里找错误信息还特别让人摸不着头脑这也是为什么很多自动化脚本越写越乱的原因之一。6.4 弹窗处理alert 的确定、取消与文本获取Web 里有两种弹窗。一种是浏览器原生的alert、confirm、prompt这个不在 DOM 里另一种是页面自定义的 Modal 弹窗本质上是普通 HTML 元素。后者直接定位一般没问题重点是前者。原生弹窗的处理方式很固定alert driver.switch_to.alert alert_text alert.text # 获取弹窗文本 alert.accept() # 点击确定 alert.dismiss() # 点击取消 prompt_alert driver.switch_to.alert prompt_alert.send_keys(输入内容) # 如果有输入框实际项目里confirm和prompt比较少alert相对多见。碰到弹窗一定要记得先处理再继续否则后面的脚本全被阻塞。如果用了显式等待可以用之前提到的alert_is_present条件确认弹窗确实出现防止脚本在弹窗前就挂起。6.5 ActionChains 的进阶应用拖拽、悬停与组合操作ActionChains是处理一连串鼠标和键盘操作的类。对于复杂的交互场景比如拖拽滑块、鼠标悬停展开菜单、右键菜单、双击等都比逐个操作可靠。from selenium.webdriver.common.action_chains import ActionChains target driver.find_element(By.CSS_SELECTOR, .drop-target) source driver.find_element(By.CSS_SELECTOR, .drag-source) ActionChains(driver).drag_and_drop(source, target).perform()悬停下拉菜单是电商后台和 CMS 系统的常见操作。比如鼠标悬停在用户管理上才会显示用户列表子菜单menu driver.find_element(By.LINK_TEXT, 用户管理) sub_menu driver.find_element(By.LINK_TEXT, 用户列表) ActionChains(driver).move_to_element(menu).click(sub_menu).perform()很多人直接用click()点子菜单会提示元素不可点击就是因为子菜单需要先悬停才出现。这个坑在后台管理类系统里太常见了。ActionChains 的高级用法还包括组合键、上下文菜单等但在实际自动化测试中遇到最多的还是拖拽和悬停这两类。把这两个吃透你已经能覆盖绝大多数后台管理页面的交互逻辑。写在最后的一个小习惯做 Selenium 自动化测试这几年我最大的体会就是稳定比速度重要封装比重复重要。每次遇到脚本不稳定先考虑是不是等待方式不对、是不是没处理 iframe 或新窗口而不是盲目加 sleep 或者换定位器。我自己的项目里有一个习惯所有元素定位统一放到一个locators.py模块里所有操作封装成可复用的方法这样页面改版的时候只需要改一处测试用例本身改动很小。这个习惯一开始养成会有点费时间但两个月以后你会发现改脚本的效率提升了不止一倍。这篇文章涵盖的内容基本覆盖了我在日常工作中最常用的那些函数和场景。如果你正准备搭建一套 Selenium 自动化测试体系建议不要急着去写复杂的框架先把这些基础函数用熟练把每个函数背后的适用边界搞清楚遇到问题时排查的思路才会清晰。后面有机会再单独聊聊数据驱动、报告集成和 CI 联动那又是另一块大内容了。