这几年经常被同行问同一个问题:AI写代码都快把自己卷下岗了,Selenium这套跑了快二十年的自动化框架,是不是也该被“新型AI驱动工具”拍死在沙滩上了?
我理解这种焦虑。GitHub Copilot、ChatGPT帮人写脚本,Testim、Mabl这类带AI能力的测试平台又不断在社区刷存在感,好像测试开发这个岗位不赶紧拥抱AI就会成为恐龙。但先别急着站队——我在不同类型项目里把Selenium和AI驱动测试工具都反复折腾过,一个比较扎心的结论是:它们根本不是同一维度的东西,硬拿Selenium对标AI测试工具,就像拿手动挡汽车和自动驾驶对比舒适度,结论当然一边倒,可你让自动驾驶跑一趟泥泞工地试试?
我这篇不是来劝退谁的,而是想把两套工具的真实边界拆开聊透:Selenium在什么场景下依然无法被替代,AI驱动工具真正帮你省力的是哪几个环节,以及最关键的——一个普通测试开发/测试工程师,在2025年该怎么把两者组合起来用,而不是非此即彼。文章会比较长,涉及安装、Python实操、上传本地文件、处理异步下载等大家搜索最多的点,也会把我实际踩过的坑一并讲清楚。
1. 这个话题为什么值得重新聊一次
1.1 从搜索数据里能看到的真实焦虑
我平时会关注一些技术社区的搜索风向。最近“selenium安装”“selenium python”“selenium自动化测试框架”这些词的搜索热度一直没降,同时“AI测试框架”的讨论量也在涨。这说明什么?说明大量新人还在学Selenium、老手还在用Selenium,而另一拨人则被各种AI测试概念撩得心痒。
这种双轨并行的现象非常真实:一边是存量系统的Selenium脚本每天都在CI流水线里跑,动它们就是动钱,没人敢轻易换;另一边是新的项目、新的Web应用越来越复杂,光靠Selenium硬写定位器,维护成本高到让人想离职。
但真正让我觉得有意思的是,很多人对“AI测试框架”的理解停留在营销话术层面。有人以为买了某个AI测试平台就能“对着页面说几句话就自动生成用例”,有人以为像Selenium这种传统框架已经“过时”。这两种看法都偏离了现实。我要做的,就是把它们拉回到工程实操的地面上来比较。
1.2 AI测试框架到底是什么,不是什么
先说清楚概念,否则后面全是在打空气。
Selenium是一个浏览器自动化协议的实现:你用Python/Java/JS等语言写代码,通过WebDriver协议把指令发给浏览器,让它点击、输入、断言。它本质上是“模拟人操作浏览器”的底层通道,非常通用,也非常“笨”——你必须告诉它每一步怎么做,它没有推理能力。
新型“AI驱动测试工具”大致分成三类:
- AI辅助定位与自动修复:代表有Testim、Mabl、Functionize。它们在你录制/编写脚本时记录额外的DOM快照、视觉特征,当元素定位失败时,用算法猜测你要找的其实是哪个元素,从而自动修复脚本。
- 视觉回归与智能断言:代表有Applitools、Percy。用截图对比代替传统属性断言,AI负责处理“看起来哪里不一样”的像素级判断。
- 代码级AI助手:类似Copilot这样的工具集成到IDE里,帮你生成Selenium代码,或者用聊天方式生成测试脚本。它不替代框架,而是替代一部分编程体力劳动。
这个分类很重要。你会发现,市面上大部分所谓AI测试框架,并没有脱离浏览器的自动化控制层——底层很多还是用Selenium或Playwright跟浏览器通信的。它们加了AI层,主要作用是在元素定位失败、脚本维护、结果分析这些环节做智能化。所以“Selenium vs. AI驱动工具”这个命题,严谨地说更像是“自己用手写脚本 vs. 让平台带AI帮你写帮你修”之间的选择。
2. Selenium的底牌:稳了十多年的东西到底强在哪
2.1 它解决的不是“测试”问题,而是“协议”问题
很多人低估Selenium的成色,是因为距离太近,天天在跟它的xpath搏斗,反而忘了它最核心的贡献:WebDriver协议已经成了事实上的浏览器自动化标准。所有主流浏览器(Chrome、Firefox、Edge、Safari)都原生或间接支持这个协议,这玩意儿的好处是稳。
什么叫稳?就是你一套Selenium脚本,理论上可以不改逻辑地在不同浏览器上跑兼容性测试。我维护过一个跑了快五年的核心交易链路回归套件,中间Web应用大改版好几次,但核心脚本只要小修小补就能继续用。这种长周期稳定性,在软件行业是个极其稀缺的品质。
AI驱动测试平台虽然听起来智能,但有一个很现实的问题:它们大多绑定在自己的云基础设施上。数据要上传到人家的服务器,或者至少跑在人家封装好的运行环境里。遇到严格合规要求的企业内部系统,这一步就直接卡死了——数据出不去,AI再牛也用不了。这时候Selenium这种本地化、开源的方案就成了没得选的方案。
2.2 从安装到运行的现实体验
说到大家搜索最多的“selenium安装”,这事本身就值得展开讲讲。很多人以为装个pip包就行,但实际动手时会碰到一堆环境问题。我给出一个经过很多项目验证的安装路径,尤其是国内网络环境下比较顺利的版本:
# 1. 创建虚拟环境,避免把包装进系统Python python -m venv .venv source .venv/bin/activate # Windows用 .venv\Scripts\activate # 2. 安装Selenium,我建议固定版本,比如4.x pip install selenium==4.21.0 # 3. 安装浏览器驱动 # Selenium Manager在4.6+版本会自动管理驱动,但企业内网环境经常失效Selenium 4.6以后的版本内置了Selenium Manager,可以自动下载对应版本的chromedriver。听起来很方便,但在公司内网、有代理限制的环境里,它经常连不上Google的更新服务器。所以我在真实项目里更推荐手动管理驱动:
# 下载与本地Chrome主版本号一致的chromedriver # 放到一个不用配PATH的目录,然后用Service指向它写代码时用Service特性:
from selenium import webdriver from selenium.webdriver.chrome.service import Service service = Service("/opt/drivers/chromedriver") driver = webdriver.Chrome(service=service)这里有个特别容易踩的坑:Chrome每次自动升级,chromedriver就失灵,脚本报session not created。很多新手以为是代码问题,重装了三遍selenium都没用。实际上就是版本没对齐。我现在都是直接锁死浏览器版本,在CI配置文件里固定Chrome的安装版本,不和自动更新竞争。
2.3 上传本地文件和等待下载完成的硬核操作
热搜词里“selenium上传本地文件”出现得很高频,说明这是实际业务里的刚需。上传这个操作,分两种情况:
情况一:原生input框上传
这是最简单的,直接send_keys把本地文件路径发进去,根本不用模拟弹窗:
file_input = driver.find_element(By.CSS_SELECTOR, "input[type='file']") file_input.send_keys("/path/to/local/file.pdf")为什么这样做可以?因为浏览器安全机制虽然禁止JS自动操作文件选择框,但input元素本身接受直接赋值路径,Selenium通过协议层把这个值传进去,绕开了用户手工操作的步骤。
情况二:自定义弹窗上传
现在很多前端框架(比如Element UI、Ant Design)的文件上传组件,input被隐藏了,或者需要点一个按钮弹窗。这时就得先点击上传按钮,然后在弹出的系统文件对话框里操作。Selenium本身处理不了系统级对话框,但可以借助AutoIt或pywinauto(Windows环境)。不过更优雅的解法是:用JS把隐藏的input变成可见,然后照常send_keys。
# 如果input是隐藏的,先通过JS把它的样式改出来 driver.execute_script(""" let input = document.querySelector("input[type='file']"); input.style.display = 'block'; """)这个方法在日常工作中实用性极高。我估计很多搜上传问题的人,大概率就是遇到了自定义组件。
再说“selenium怎样使文件下载完成之后才进行下一步”。这个搜索词背后是一个经典难题:WebDriver的get()和click()都是同步的,但文件下载的完成时机是浏览器底层决定的,Selenium感知不到。你点完下载按钮,马上检查文件,很可能没下完。
我的常规做法是写一个显式等待工具函数,轮询文件系统直到文件出现且大小稳定:
import time from pathlib import Path def wait_for_download(download_dir, expected_name=None, timeout=30): """ 等待文件下载完成的轮询函数。 核心逻辑:文件存在 -> 大小不再变化 -> 认为下载完成。 """ end_time = time.time() + timeout file_path = Path(download_dir) / expected_name if expected_name else None last_size = -1 stable_count = 0 while time.time() < end_time: if not download_dir: continue candidates = list(Path(download_dir).iterdir()) # 浏览器下载中的文件常以 .crdownload / .tmp 结尾 real_files = [f for f in candidates if not f.suffix.lower() in (".crdownload", ".tmp")] if not real_files: time.sleep(0.5) continue # 如果指定了文件名,精准匹配 target = file_path if file_path else real_files[0] if not target.exists(): time.sleep(0.5) continue current_size = target.stat().st_size if current_size == last_size: stable_count += 1 if stable_count >= 2: return str(target) else: stable_count = 0 last_size = current_size time.sleep(0.5) raise TimeoutError(f"等待下载完成超时: {expected_name or download_dir}")注意,不同浏览器下载中文件的命名规则不一样。Chrome是.crdownload,Firefox是.part,Edge类似Chrome。把这些临时后缀排除掉后,还要看文件大小是否在一段时间内保持稳定,因为网络抖动可能导致文件暂时不变但实际没下完。我一般取“连续两次轮询大小一致”作为完成条件,虽然多等几百毫秒,但可靠性高很多。
这个轮询函数在多个项目里被复用,是我接触Selenium以来写得最值的工具函数之一。后面讲AI工具时你会看到,这类逻辑AI平台也很少原生解决,最终还是得自己写。
3. 新型AI驱动工具:换了个思路,还是换了个马甲
3.1 不同AI工具的实际能力拆解
不具体说某个平台的好坏,而是按能力维度拆。
录制回放 + AI自动修复
这类平台一般提供浏览器插件或者云录制环境。你在页面上点一圈,它就生成一个测试步骤序列。听起来很像早年的Selenium IDE,但区别在于它不只是记录元素的xpath/css,而是会记录整个页面元素的上下文特征,包括周围的文本、位置关系、DOM结构等。当页面改版导致原定位方式失效时,AI决策引擎会基于这些特征去猜测“用户本来要点的可能是现在页面上这个同名的按钮”。
这个能力在实践中有多靠得住?我的体感是,对于常见的按钮文案变化、页面局部布局调整,自动修复成功率还不错,能省下不少维护时间。但遇到整体架构性改版——比如从jQuery项目重构成Vue项目,DOM整个重写——AI修复基本帮不上忙,该重写还是得重写。
视觉比对与智能断言
Applitools这类工具解决了一个Selenium时代的痛点:传统断言只能检查属性值、文本、元素数量等结构化数据,界面上哪里多了个像素级别的错位,脚本是感知不到的。视觉回归工具的思路是让AI学习“正常页面长什么样”,然后在每次版本迭代后判断“这幅截图的差异是可接受的小变化,还是会导致用户困惑的重大bug”。
这个方向很性感,但也会产生大量误报。尤其是前端经常调字体、改间距、换主题色,AI认为“需要人类确认”,于是每次跑完给你堆一堆review任务。真实项目里需要配置视觉忽略区域,或者把断言粒度调低,否则review工作量反而比手写断言更大。
大模型生成/维护脚本
这一两年用大模型(GPT-4级别)辅助写Selenium脚本的实践开始变多。典型用法是:把页面HTML片段或者图贴给模型,让它生成定位器和操作步骤。这确实把写脚本的门槛降了一大截——过去需要熟练的CSS、XPath技能,现在直接描述业务即可。
但同样有边界。模型训练数据是滞后的,大型企业应用里各种内部系统、自定义控件,模型根本没见过,生成的脚本大概率跑不通。这时候你把模型当成一个“会写代码的实习生”而不是“领域专家”,让它在你能审查的前提下工作,价值才能显现。
3.2 AI到底在哪个环节真正起作用
我给AI驱动的软件做一个简化抽象:自动测试 = 生成 + 维护 + 分析。
- 生成环节,AI可以通过录制转脚本、自然语言生成脚本,帮你把“从0到1”的过程提速。
- 维护环节,AI可以在元素定位失败时自动修复、在UI变化时更新快照,降低“每次发版后脚本红一片”的挫败感。
- 分析环节,AI可以对失败结果分类,把“环境问题”“元素变更”“真实缺陷”区分开,减少人工翻日志的时间。
这三个环节,恰好是传统Selenium时代最耗精力的地方。所以新型AI工具的价值,主要在于降低自动化的使用门槛和维护成本,而不是彻底取代浏览器自动化本身。
理解了这一点,就能解释为什么Selenium还能活:它作为底层控制协议,是任何一个AI测试平台都不能绕过的基础设施。那些打着“不需要代码”旗号的工具,背后依然得用某种方式驱动浏览器,只是把这一层封装掉了。
3.3 AI工具目前最大的软肋
我会直接说几个还没被解决的问题。
不稳定,而且不确定为什么不稳
传统Selenium脚本挂掉,定位器报错,你打开DevTools去查元素,通常能快速定位问题。但AI自动修复偶尔会出现“这次跑过了,下次又挂了”的玄学情况——因为AI的决策包含概率因素,同一次失败可能在两次重跑中做出不同判断,导致脚本行为不一致。
在测试领域,确定性比“看起来智能”重要得多。测试失败必须能稳定复现,否则你根本无法判断是真bug还是工具抽风。AI测试平台在动态容错上做了大量优化,但在关键路径上,我还是会刻意关闭自动修复功能,让脚本严格失败,而不是由机器猜测着改我的步骤。
黑盒化带来排查困难
Selenium脚本是白盒的,每行代码都有明确含义。AI平台把脚本抽象成一套内部表示,出问题时,你需要理解“为什么AI会这么判断”,很多时候平台只给你一句“已自动修复定位器”。这对排查深层次问题来说远远不够。
成本模型不透明
按执行次数、按并发数、按项目数,AI测试平台的计费方式五花八门。一个小团队用到后面,月费可能比请一个初级测试开发还贵。而且数据都放在别人平台上,迁移出去又有一大笔隐性成本。开源方案(如Selenium、Playwright)虽然要自己写代码,但成本是可控的。
4. 同场景硬碰硬:五组对比拆开看
把这轮对比单独拿出来讲,是因为我觉得光谈概念没意义,得把同一个问题抛给两套方案看它们的表现。这样选型时才能心里有底。
4.1 定位稳定性:XPath兵来将挡 vs. 自适应特征匹配
Selenium面对的经典问题:前端把<button>重构成<div>,然后把原来的id弄丢了,于是定位失败。解决办法是写更健壮的定位策略:优先用>from selenium import webdriver from selenium.webdriver.chrome.service import Service from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC import time from pathlib import Path def create_driver(): """创建Chrome浏览器实例。""" options = webdriver.ChromeOptions() # 规避webdriver识别特征,部分测试环境需要 options.add_experimental_option("excludeSwitches", ["enable-automation"]) # 下载目录固定到项目下downloads文件夹 options.add_argument("--download.default_directory=/path/to/downloads") service = Service("/opt/drivers/chromedriver") driver = webdriver.Chrome(service=service, options=options) driver.implicitly_wait(5) return driver
这里有个容易忽略的细节:implicitly_wait设置的是全局隐式等待,而WebDriverWait是显式等待。在实际代码中我倾向于把隐式等待时间设得很短(比如3-5秒),在关键步骤用显式等待,避免全局等待过长导致每个操作都慢吞吞的。
6.2 文件上传的两种实现
第一种:原生input直接传路径
file_path = "/path/to/local/data.csv" # 等input出现并可见 input_el = WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.CSS_SELECTOR, "input[type='file']")) ) input_el.send_keys(file_path)第二种:隐藏input + 自定义上传组件
很多企业级前端组件会隐藏原生input,点上传按钮弹出文件选择框。在测试环境里,最稳妥的方式是强行将隐藏input暴露出来:
driver.execute_script(""" const input = document.querySelector("input[type='file']"); input.style.display = 'block'; input.style.opacity = '1'; """) file_input = WebDriverWait(driver, 10).until( EC.element_to_be_clickable((By.CSS_SELECTOR, "input[type='file']")) ) file_input.send_keys(file_path)注意,execute_script修改的是运行时样式,不会影响页面本身。但这个方案对React/Vue内部状态管理也有一定兼容性,因为文件选择后组件都能监听到change事件,后续逻辑照常触发。
如果以上方案在某些极其特殊的组件上失效,还有第三招:用pywinauto操作系统级文件对话框。但这种方法只适用于Windows,且对对话框的自动化权限有要求,遇到杀毒软件或UAC弹窗会失败。非必要不用。
6.3 下载完成后再执行下一步的完整写法
回到搜索热词“selenium怎样使文件下载完成之后才进行下一步”,其实最核心的点就是上传后的提交、下载、轮询三步。我直接给出一个相对完整的流程示例:
def upload_and_submit(): driver = create_driver() driver.get("https://example.com/tool") # 上传文件 upload_input = WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.CSS_SELECTOR, "input[type='file']")) ) upload_input.send_keys("/path/to/data.csv") # 点击提交按钮 driver.find_element(By.CSS_SELECTOR, "button#submit").click() # 等待“下载”链接出现,并点击 download_link = WebDriverWait(driver, 20).until( EC.element_to_be_clickable((By.CSS_SELECTOR, "a.download")) ) download_link.click() # 等待下载完成,拿到文件路径 download_dir = "/path/to/downloads" downloaded_file = wait_for_download(download_dir, timeout=60) print("下载完成:", downloaded_file) # 后续步骤:读取文件做校验或进一步操作 driver.quit() if __name__ == "__main__": upload_and_submit()这里的wait_for_download就是第2.3小节给的那个轮询函数。我实际使用时遇到过一种情况:文件下载完成后,页面先后弹出两个下载(浏览器多点了几次),导致目录里出现多个文件。这时轮询函数需要增加一个“锁定预期文件名”的逻辑,更稳的做法是在下载前清空目录,或者根据接口响应拿到真实文件名。但后端接口一般不会暴露文件名,所以清空目录是很多团队最终采用的办法:
def clear_download_dir(directory): for f in Path(directory).glob("*"): if f.is_file(): f.unlink()在执行下载动作前先调用这个函数,保证后续轮询到的文件一定是本次下载产生的。
6.4 AI辅助定位:什么时候有用、什么时候想砸键盘
在Selenium脚本里引入AI辅助,最常见的接入点就是封装find_element。比如写一个“先常规定位,失败后调用AI元描述定位”的中间层:
class AIFindElement: """ 一个简化的AI辅助定位中间层: 常规定位失败后,调用mock的AI接口,结合页面文本上下文返回候选定位符。 生产环境可接入大模型,或商业平台的智能定位API。 """ def __init__(self, driver): self.driver = driver def find(self, by, value, timeout=10, fallback_desc=""): try: return WebDriverWait(self.driver, timeout).until( EC.element_to_be_clickable((by, value)) ) except Exception: if not fallback_desc: raise # 这里调用AI工具:把页面关键信息包装为描述,请求模型推荐候选定位器 candidates = mock_ai_locate(self.driver, fallback_desc) for cand in candidates: try: return self.driver.find_element(By.CSS_SELECTOR, cand) except Exception: continue raise RuntimeError("AI辅助定位失败")这个封装在真实项目里其实很有价值。当UI文案没变但DOM结构调整时,AI可以根据“按钮文案是提交”这一语义线索给出候选定位器,避免脚本直接红掉。我也要提醒:不要在点击删除、提交订单等高风险动作上启用AI自动修复。因为一旦AI猜错元素,后果比脚本失败严重得多。这类交互我会强制定位失败就报错,让人来处理。
另外,AI提示词工程在这里也有效。给模型的上下文越多,定位准确率越高。实践里有个不错的小技巧:把页面上所有按钮的文本、附近标签、元素可见性都记录下来,作为描述上下文输入给模型(直接贴原始HTML太浪费token,也没必要,模型容易把注意力分散到无关属性上)。结构化地描述“页面上有多个按钮,分别是什么文本,哪个在表单区域”,模型推荐的定位器准确率会高很多。
7. 几个我踩了很多次才绕开的坑
说实话,这篇文章写到这儿,已经把两套方案的对比和实操都覆盖了。但作为补充,我想把几个不亲身踩一遍很难绕开的坑单独列出来,尤其适合关注“selenium安装”“selenium上传本地文件”“selenium下载完成”这些特定问题的新手。
坑一:把等待条件写成了死睡
很多刚入门的同学一遇到加载问题就time.sleep(5)。短了会偶发失败,长了会把整套用例拖慢。正确思路是用WebDriverWait结合expected_conditions,让脚本在元素就绪的第一时间继续执行。
# 错误示范 time.sleep(5) driver.find_element(By.ID, "login").click() # 正确做法 WebDriverWait(driver, 10).until( EC.element_to_be_clickable((By.ID, "login")) ).click()坑二:上传文件时路径用了相对路径
Selenium的send_keys在部分浏览器上对相对路径的处理并不一致,尤其是远程执行环境或者Docker容器里。我的惯例是绝对路径一票到底,有条件的话先os.path.abspath统一转换一下。
坑三:下载等待只判断文件存在,不判断文件是否完整
只判断文件存在,下载到一半脚本就继续跑了,后面读取文件时大概率拿到一个损坏的临时文件。这就是为什么前面那个轮询函数要同时判断“临时后缀排除”和“大小稳定”。这一点无论用Selenium还是AI测试平台,逻辑都一样,AI平台同样解决不了。
坑四:AI工具的自动修复覆盖了核心业务步骤
我见过一个团队买了一套AI测试平台,把下单流程的每个点击都配上自动修复。结果前端一次改版,AI把一个“加入购物车”按钮自动修复成了“立即购买”按钮,脚本虽然跑过了,但业务路径完全是错的,差点带着生产环境出问题。自动修复这个功能,用在低风险的UI步骤上可以,核心业务步骤一定要关掉或者设为“只报告,不自动执行”。
坑五:驱动版本和浏览器版本严格对齐
这个坑太经典了。你本地Chrome升级到134,chromedriver还是133,脚本必然报错。CI环境更痛苦,镜像里固定了驱动版本,结果宿主浏览器自动升级了。解决办法有两个:要么锁死浏览器版本,要么每次拉取最新的驱动版本。大型团队建议维护一个驱动版本管理服务,小型脚本项目就老老实实手动指定版本。
坑六:AI生成脚本后没有做代码评审
大模型写出来的Selenium代码看着像模像样,但往往有几类通病:多余的等待、过长的选择器、缺少异常处理。如果不评审,这些脚本上线后会变成维护噩梦。我用Copilot或ChatGPT写自动化脚本时,一定会再过一遍,重点看定位器是否精简、等待条件是否合理、有没有硬编码运算数据。
这些坑,每一条都是真金白银换来的教训。尤其是坑四,本质上是AI工具的能力边界问题:它可以帮你减少维护成本,但永远替代不了你对业务正确性的判断。测试这个行业,最核心的价值始终是“用确定性对抗不确定性”。工具怎么变,这一点不会变。
如果你正站在Selenium和AI测试工具的十字路口犹豫,我的建议是:先别急着把老框架扔掉,也先别对AI测试框架抱有“全自动生成、零维护”的幻想。用我第5章说的混合路线,从你最痛的维护环节入手,小范围试点,用数据说话。等跑通了一个业务模块,你自然会清楚接下来该怎么选,也能更有底气地拿着方案和团队、老板去沟通。这条路,我走过,稳的。