最近遇到 Selenium 项目突然崩掉,控制台打出一行我之前没太见过的错误:selenium_manager- error trying to connect: 远程主机强迫关闭了一个现有的连接。第一反应是 chromedriver 和浏览器版本不匹配了,结果换了几个驱动版本,问题照旧,折腾了快一个小时才意识到方向完全错了。
这个报错里的selenium_manager是 Selenium 4.6 之后内置的自动驱动程序管理器,而“远程主机强迫关闭了一个现有的连接”这句话,翻译过来更像是“对方已经把我的 TCP 连接掐断”,根本不是说驱动文件版本有问题。为了不让后来的同事再走弯路,我把这次排查的过程、原理和几个解决办法完整整理出来。无论你用的是 Python、Java 还是 JS 版本 Selenium,只要报错里出现这一句,下面的排查思路基本通用。
1. 先看这个报错出现在链路的哪一环
1.1 这个错误说的根本不是“浏览器连不上驱动”
早期用 Selenium,步骤很繁琐:先打开浏览器看版本号,再去第三方站点下载匹配的 chromedriver,手动指定路径,还要处理各种权限问题。从 Selenium 4.6 开始,官方把这件事收编了,内置了 Selenium Manager 来自动识别浏览器版本、下载对应的 WebDriver 文件、放到本地缓存并启动,省去了手工下载的麻烦。
我遇到的这个报错,恰恰就出在“自动识别和下载”这个环节上。看日志时记住一点:error trying to connect是 Selenium Manager 在尝试向外发请求时抛出来的,不是 WebDriver 服务连接失败。也就是说,驱动和浏览器之间的那层没问题,出问题的是 Selenium Manager 和外部网络之间。
1.2 自动驱动的完整流程里,哪一步最容易断
拆开看整个调用链,逻辑是这样的:
- 你的测试代码执行
webdriver.Chrome()。 - Selenium Manager 先读取本机安装的浏览器版本。
- 它检查本地缓存里有没有匹配版本的 WebDriver 文件。
- 如果没有,去访问官方元数据接口,拿版本号与下载地址。
- 下载压缩包,校验、解压、放入缓存。
- 启动本地的 WebDriver 服务,浏览器开始执行自动化操作。
报错出现在第 4 步和第 5 步之间的概率最大。尤其当你看到日志里同时出现一个https://开头的地址和这句连接错误,基本上就锁定了范围:不是代码逻辑问题,也不是浏览器问题,而是这台机器访问外部的通道出了问题。
1.3 搜索selenium_manager这个关键词的人,通常差在环境上
我在本地复现了几次之后,发现凡是跑来搜这个报错的人,大概率符合下面一种情况:刚从 Selenium 3 升到 4,第一次接触 Selenium Manager;或者测试代码没动,只是换了一台新机器、换了个网络环境。这两种情况都有个共同点,代码本身没变,变量变了。所以遇到这个报错,我的第一建议永远是别急着改代码,先查环境和网络。
2. 为什么偏偏是“现有连接”被强迫关闭
2.1 HTTP 连接池里的复用陷阱
“远程主机强迫关闭了一个现有的连接”这句话,严格说不是 HTTP 错误,而是操作系统网络层面的一段描述。很多语言的 HTTP 客户端在第一次请求成功后,不会立刻断开 TCP 连接,而是把它放进连接池,供后续请求复用。这样可以减少握手的次数,性能更高,但也带来一个隐含问题:连接池里的连接不会永久存活。
Selenium Manager 在一次执行过程中可能要先后访问好几个接口。比如先读取已知驱动源的清单,再根据返回结果去拼接下载地址,再下载压缩包。这个过程中,如果第一次请求结束了,连接留在池子里,第二次请求复用了同一连接,但这条连接已经被服务端或中间设备超时回收,客户端不知道,直接把新请求丢进去,结果就是“远程主机强迫关闭了一个现有的连接”。
2.2 中间网关设备主动发 RST 包
第二种情况,也是出现频率最高的情况,机器在办公网络、机房或者容器环境里跑。Selenium Manager 访问的域名基本都是公开网络上的服务,比如 Chrome for Testing 的清单、Mozilla 的驱动发布页、Edge 的更新接口。企业出口网关、防火墙、内容过滤设备会在某些条件下主动向连接发起重置。一旦中间设备发出 TCP RST 包,客户端这边看到的就是“连接被强制关闭”。
这种环境下,有时候是访问完全不通,有时候是时好时坏。如果你发现这个报错在某个网络下从来没有出现,换到另一个网络就频繁出现,不用怀疑,八成就是网关层的问题。
2.3 本机安全软件劫持连接
还有一个很隐蔽的原因,是我排查到后面才发现的。部分安全软件会做流量扫描,它会劫持应用程序与外部的连接,把 TLS 流量截下来做检查。如果安全软件发现自己不认识的进程,或者发现 Selenium Manager 在下载压缩包后立刻执行解压,有一定概率直接中断连接。更麻烦的是,有些安全软件还会监控127.0.0.1上的回环连接,WebDriver 启动后会在本机随机端口监听,安全模块可能误判为恶意行为。
我遇到过一次,关掉杀毒软件的实时防护后,报错立即消失,重新开启后又复现。这种情况只靠看报错内容是很难定位的,必须要动手做排除实验。
2.4 版本差异导致时好时坏
不同时期的 Selenium 版本,Selenium Manager 的行为差异很大。早一点的版本,每次脚本执行都会向外查询一次,网络稍微抖动就失败;后来的版本加强了缓存命中逻辑,本地有匹配版本就不再外联。所以你可能会看到两种截然相反的反馈:有人升级 Selenium 后报错消失,有人升级后反而开始报错。这取决于你原来处在哪个版本区间,也取决于你的网络环境够不够稳定。
3. 一条完整的排查链路,从日志到网络逐层锁死
别一上来就在代码里绕开 Selenium Manager,那样治标不治本。我建议按下面的顺序排查,每步都有明确目的,基本能覆盖九成以上的原因。
3.1 第一步:打开 Selenium Manager 的调试日志
Selenium Manager 支持通过环境变量控制日志级别。在运行测试脚本前设置:
# Windows PowerShell $env:SE_MANAGER_LOG="debug" # macOS / Linux export SE_MANAGER_LOG=debugWindows 的 CMD 写法是:
set SE_MANAGER_LOG=debug设置后再次运行脚本,你会看到类似下面的输出:
[DEBUG] Running command: chromedriver --version [INFO] cache not found: 114.0.5735.90 [DEBUG] Checking versions from https://googlechromelabs.github.io/chrome-for-testing/ [INFO] error trying to connect: 远程主机强迫关闭了一个现有的连接日志里cache not found说明本地没有可用的驱动缓存,紧接着出现一长串网址,说明它正在尝试访问外部的元数据接口。你要重点记下这个完整的网址,它是下一步验证的对象。
3.2 第二步:用 curl 验证目标地址是否真的能访问
根据日志里给出的地址,在当前机器上做一个最小化验证:
curl -v -I --connect-timeout 5 https://googlechromelabs.github.io/chrome-for-testing/known-sources.json重点看输出结果:
- 返回
Recv failure: Connection was reset,说明连接被中间设备重置。 - 一直卡在
Connected to ... port 443后没反应,超时才断开,说明出口可能对特定目标做了拦截。 - 如果返回正常,能看到
HTTP/2 200,那说明问题不在外网可达性,可能出在 Selenium Manager 自身或本地安全软件上。
这一步成本很低,却能把“网络不通”和“库有问题”彻底分开。
3.3 第三步:检查系统环境变量里有没有残留的网络转发设置
很多测试机曾经因为各种原因,配置过系统级别的网络转发变量。这类变量会被子进程继承,Selenium Manager 也不例外。如果变量指向的网关通道已经失效,它发出的请求就会在中途被切断,正好产生“远程主机强迫关闭了一个现有的连接”。
检查的方法在 macOS 和 Linux 上很简单:
env | grep -iE "http|all|no_"Windows PowerShell 里这样查:
Get-ChildItem Env: | Where-Object { $_.Name -match "HTTP|HTTPS|ALL|NO_" }看到相关变量后,先不要急着删,确认它指向的服务是否还存在。如果是临时换网络环境导致的,可以把变量清掉再重试。完成后重跑脚本,如果报错消失,就是这些残留配置的问题。
注意:这类环境变量不一定只存在于终端会话里,也可能写在系统用户配置或 CI 运行器配置中。改完本地后,CI 环境也要同步检查。
3.4 第四步:临时关闭安全软件,快速定位本机冲突
这一步虽然看起来不太优雅,但很高效。把 Windows Defender 实时防护、第三方安全软件的“流量扫描”“网页防护”之类开关临时关闭,然后重新运行测试。报错消失,基本就是安全软件的问题;报错依旧,再把它打开,继续往后排查。
确认是安全软件导致的后,不一定非得关闭整套防护。更合理的方式是把项目目录、Selenium Manager 缓存目录加入信任列表;如果安全软件支持排除特定进程,把selenium-manager进程和浏览器驱动进程加进去即可。
3.5 第五步:在 CI 和容器环境里额外检查网络模式
容器环境里还多两个坑:DNS 配置和网络模式。容器如果用的是默认桥接网络,容器内部访问公网需要出口允许;如果容器用宿主机网络,宿主机上的网络变量和防火墙规则也会直接影响容器内进程。排查时先确认你用的哪种网络模式,再针对性地查。
CI 里我推荐加一个预处理步骤,在跑 UI 测试前先做一个连通性探活:
curl -fsSL --connect-timeout 5 https://googlechromelabs.github.io/chrome-for-testing/known-sources.json || exit 1探活失败就立刻把任务标记为环境问题,而不是让脚本跑到一半才抛出一个很迷茫的连接错误。这样后续的人一看日志就知道问题出在哪里。
4. 不同场景下的几个解法,按环境分类取用
4.1 场景 A:公网可访问,但连接不稳定
如果你在普通家用网络或者测试机网络下,公网可以访问,只是偶尔抖动,最省事的办法是减少 Selenium Manager 的外联频率,或者干脆让它在已确定的环境里不再外联。
方法一,升级 Selenium 到较新版本,新版本对缓存命中的逻辑做了不少优化,本地有匹配版本时不会再每次请求远端的元数据。方法二,手动把常用驱动放到缓存目录,详见第 5 章。方法三,直接在代码里指定驱动路径:
from selenium import webdriver from selenium.webdriver.chrome.service import Service service = Service(executable_path="/your_path/chromedriver") options = webdriver.ChromeOptions() driver = webdriver.Chrome(service=service, options=options)给Service传了显式路径以后,Selenium 会直接用这个驱动,不再经过 Selenium Manager 的下载链路。对于版本已经确定的机器,这是最稳定的选择。
4.2 场景 B:隔离环境中无法触达官方源
在隔离网、内网测试机房这类环境里,不要奢望 Selenium Manager 能自动从公网下载驱动。哪怕你把代码改得再漂亮,只要它向外网发请求,最终都会被通道限制掐断。正确做法是把驱动文件的获取动作放到环境初始化阶段去解决。
实际操作中,我会把 chrome 驱动包放到内部的制品仓库里,然后在机器初始化脚本中根据浏览器版本去拉取:
#!/bin/bash CHROME_VERSION=$(google-chrome --version | awk '{print $3}') echo "Chrome version: $CHROME_VERSION" curl -fsSL "http://artifactory.internal/chromedriver/${CHROME_VERSION}/linux64/chromedriver" -o /usr/local/bin/chromedriver chmod +x /usr/local/bin/chromedriver脚本里的版本映射需要留意:浏览器的完整版本字符串和 driver 压缩包里的版本号有时不完全一致,需要维护一张对应表。此类脚本跑完,后续测试全部用本地驱动,不依赖公网,稳定性会大幅提升。
4.3 场景 C:CI 流水线里反复出现连接错误
CI 环境还有一个相对隐蔽的问题:网络转发配置没有显式同步。跑步机器上配置了某种网关转发,但 CI 容器的环境变量和宿主机不是自动继承的,运行器配置里没写,容器内就会直接访问公网;而容器外又有更严格的出网限制,两条链路一交叉,连接被切断是常态。
另外,CI 跑批时经常并发启动多个浏览器。每新建一个 WebDriver,Selenium Manager 如果发现缓存不匹配,就会发起新请求。几十个并发线程同时向外请求,网关很容易判定为可疑流量,强制关闭若干连接。这种场景下不需要修改 Selenium 源码,而是应该在并发任务前做一轮“预处理”,先把驱动和缓存准备好,或者直接通过独立驱动服务来复用连接,而不是让每个线程各自触发下载流程。
4.4 场景 D:办公网络下的合理妥协
办公网里最常见的是“并非完全不通,而是特定域名不通”。面对这种情况,反复在代码层面死磕没有意义。务实的做法是,让测试环境的驱动来源走内部系统,而不是每一台电脑都直连公网。企业内的工程系统基本都有内部仓库,把不同平台的驱动文件同步进去,在安装阶段分发,既解决了版本统一问题,也绕开了出口网关的不确定因素。
5. 几个特别容易踩中的细节点
5.1 本地缓存的目录结构,别凭感觉猜
Selenium Manager 的本地缓存目录,在 Windows 上通常在%LOCALAPPDATA%\Selenium,在 macOS 和 Linux 上是~/.cache/selenium。但真正容易踩坑的是它内部的布局,目录会按浏览器厂商、平台、版本号一层层嵌套:
~/.cache/selenium/chrome/linux64/114.0.5735.90/chromedriver如果某个层级不对,路径对不上,Selenium Manager 会认为缓存不存在,继续发起外部请求。手动放驱动文件之前,建议先在能联网的机器上正常跑一次,再照着它生成的目录结构放置,成功率最高。Selenium 不同版本调整过几次路径规则,千万别凭旧经验猜。
5.2 版本字符串要完全一致,别只看主版本
浏览器主版本相同,不代表驱动版本完全匹配。比如 Chrome 是 114.0.5735.110,而驱动压缩包可能叫 114.0.5735.90。二者主版本号一样,但 Selenium Manager 比对时用的是完整版本字符串,不匹配就会重新外联,然后再次触发连接问题。手动预置驱动时,版本号必须和缓存目录里的目录名一致,不能偷懒截断。
5.3 并发启动多个浏览器时的瞬时冲击
我之前遇到过一个很奇怪的规律:并发跑 20 个实例时,偶尔有 3 个报这个连接错误,其余 17 个正常。排查后发现是对外请求被短时间集中触发,网关对突发连接数做限制,把后面的连接给重置了。这提醒我一个关键经验:驱动初始化这种操作应该集中做,最好不要在几个线程里同时抢着下载。可以在任务开始前单独执行一次驱动准备流程,后续线程只使用已经就绪的驱动服务。
5.4 日志变量与代码里的调试参数不要混着设
调试 Selenium Manager 时,有人既设置环境变量SE_MANAGER_LOG,又会在代码里加上传给管理器的参数。两个方式混用,版本之间优先级不统一,你会看到日志有时候打开、有时候关闭,导致排查效率下降。建议调试时只用环境变量,代码里不要同时传一堆调试参数,减少干扰。
5.5 “清理缓存”要谨慎操作
有一种常见建议是“把缓存删掉试试”,这个方法在普通连接失败时没问题,但在网络受限的环境里,删缓存只会逼着 Selenium Manager 再次外联,反而让报错重新出现。只有在确认外网完全通畅、或者手动驱动已经就绪的前提下,清理缓存才是一个有效的修复动作。
6. 这几次排错之后,我的确定顺序
现在遇到这个报错,我的处理顺序基本固定了,不会一上来就改代码:
- 先查看本地缓存目录里有没有匹配版本的驱动。有,直接检查驱动与浏览器版本对应关系;没有,继续往下。
- 打开 Selenium Manager 的调试日志,拿到它正在访问的地址。
- 用
curl验证该地址在当前网络下是否可访问。 - 检查系统环境变量和 CI 运行器配置,确认是否有遗留的转发设置。
- 临时关闭安全软件的流量扫描,重试一次。
- 如果依旧失败,停手,把前三步的输出汇总起来,对比正常机器与问题机器的差异,再选择对应的解法。
这套流程看起来简单,但它有明确的依据:它把问题限定在“本地缓存、网络、环境变量、安全软件”四个维度里,绕开了业务代码和浏览器本身的干扰。我维护自动化测试体系的这几年,遇到 Selenium Manager 连接报错,用这套顺序解决率很高,基本十次里有八次是靠日志和连通性测试定位的,剩下两次靠的是检查本机安全设置。
最后说一句我的心里话:Selenium Manager 的初衷是让使用者不再关心驱动下载,这本来是个好事。但“自动拉取”这件事天然依赖网络环境,而测试环境往往恰恰是最复杂的地方。真正上了规模之后,别再依赖自动下载器来解决每台机器的驱动问题,把驱动文件纳入内部制品管理,让版本可控、分发可预期,才是长期稳定的做法。这个报错本身并不难处理,难得是搞清楚它到底发生在哪一层。希望这篇文章能帮你少浪费一两个小时的无谓排查。