
Selenium Grid 这名字听起来挺唬人但说白了它就是一套帮你把测试用例分发到多台机器、多个浏览器上同时跑的机制。做 Web UI 自动化的人早晚会撞上单机跑太慢、浏览器版本不够用、回归一次等半天这种墙。我最早接触 Selenium Grid 是在一个电商项目里用例从三百条涨到一千两百条纯靠 Jenkins 单节点跑要四五个小时每天下午都在等结果后来狠下心把 Grid 搭起来四台机器八个浏览器实例一起上时间直接压缩到四十分钟。这个东西解决的就是三件事并行提速、跨平台覆盖、资源复用。适合什么人看呢如果你的自动化脚本已经跑得比较稳但执行效率上不去或者需要覆盖 Chrome、Firefox、Edge 等多个浏览器组合那这篇文章正好对胃口。我会从环境准备、Hub/Node 架构、Docker 方案到常见坑位完整过一遍配置实战。1. 先搞清楚为什么需要分布式测试网格很多人在网上搜分布式测试网格其实内心真正的问题是我怎么让测试跑得快点。Selenium Grid 本质是一个测试代理中转层它接收你用 WebDriver 发过来的远程请求然后根据请求里的浏览器类型和版本参数把任务分配到具体的执行节点上。这个过程对你写的测试代码来说是透明的——你只需要把 WebDriver 的驱动地址指向 Grid 的入口剩下的调度逻辑都由 Grid 处理。1.1 历史演进从 Hub/Node 到 Grid 4Selenium Grid 的架构在 Grid 4 里发生了不小的变化。以前大家习惯叫 Hub 和 NodeHub 负责接收请求、查表、转发Node 负责在本地起浏览器干活。Grid 4 把 Hub 拆成了 Router、Session Map、Distributor、Node 四个组件逻辑上解耦了但本质上还是同一个思路有一个统一入口后面挂一堆真正干活的执行节点。早期版本里 Hub 挂了全部挂Grid 4 则具备更好的容错和会话恢复能力。旧版Client - Hub - Node1/Node2/Node3 新版Client - Router - Session Map - Distributor - Node1/Node2/Node3这个演进对做配置的人来说最大的直观感受是启动命令从两条变成了一条standalone 模式或者变成了一整套组件各自独立启动的分布式模式。务实地说如果你只是想让测试并行跑起来直接跑 standalone 模式就够了不用一上来就上一整套微服务架构。1.2 三个核心收益第一并行提速。每条用例在独立浏览器会话里跑互不干扰。比如你有 10 个节点每个节点开 2 个浏览器实例一次就能跑 20 条用例理论上限接近线性提升。第二多浏览器覆盖。你不用在每台机器上装齐所有浏览器只需要让不同 Node 分别承担 Chrome、Firefox、Safari 的测试任务组合出浏览器兼容矩阵。第三资源隔离。测试执行过程中经常会有脏数据、崩溃的浏览器进程一台机器挂了不影响整体任务池Grid 自动把后续任务分给其他节点。2. 环境准备与基础认知配置 Selenium Grid 没有想象中那么高的门槛核心就几样东西JDK、Selenium Server Jar 包、浏览器和对应的 WebDriver外加一台能联网的机器。Java 环境是必须的因为 Selenium Server 本身就是跑在 JVM 上的 Java 程序。2.1 Java 环境检查先确认本机 Java 版本Grid 4 对 Java 11 支持得很好JDK 17 也没问题。检查方式java -version没有的话装一个 OpenJDK 就行不用纠结用哪个发行版功能上没区别。装完记得配好JAVA_HOME环境变量这点比较基础但容易忽略因为 Selenium 脚本启动时会去找这个变量。2.2 下载 Selenium Server Jar 包去 Selenium 官方站点下载独立的学习版 jar 文件文件名一般是selenium-server-4.x.x.jar。这里有个重要提示Grid 4 开始不再需要分开下载 selenium-server-standalone 和 selenium-server 两个包直接一个 jar 包全搞定。下载后建议放到统一目录比如/opt/selenium/别乱放后面启动命令要用绝对路径。2.3 浏览器与 WebDriver 版本匹配规则这块是新手最容易踩坑的地方。Chrome 浏览器会自动升级但 chromedriver 不一定跟得上版本不匹配时启动浏览器会直接报session not created或者This version of ChromeDriver only supports Chrome version xxx。最简单的处理方式打开 Chrome 的关于页面看版本号再去 chromedriver 下载页找匹配的驱动版本严格做到大版本一致。Chrome 版本chromedriver 版本要求120.x120.0.xxxx114.x114.0.xxxx其他版本与浏览器大版本保持一致Firefox 的 geckodriver 和 Safari 的 safaridriver 同理不过 Safari 在 Mac 系统里是自带的只需要在终端执行safaridriver --enable开启远程自动化支持。3. 核心配置详解与参数说明Selenium Grid 的配置有两层一层是启动时的命令行参数或配置文件另一层是 Java 代码里设置RemoteWebDriver时的浏览器选项。很多人搞混这两层以为只在代码里写ChromeOptions就够了结果 Grid 节点的浏览器实例行为不受控制比如默认语言、无头模式、禁用沙箱这些都需要在 Node 启动时配置好代码传的参数只是其中一部分。3.1 Grid 四种运行模式对比Grid 4 有四种常用模式我按适用场景做了个对比模式启动命令示意适用场景Standalonejava -jar selenium-server.jar standalone本地快速调试单机并行Hub/Nodejava -jar selenium-server.jar hubnode传统经典架构多机分布式Fully Distributed分别启动 Router、Distributor、Session Map、Node大型集群高可用Docker 模式docker-compose up -d快速弹性伸缩推荐生产Standalone 模式适合你只有一台机器、想验证脚本能不能跑通的情况下使用。Hub/Node 是最经典的使用方式下面我主要讲这个。3.2 Hub 启动参数详解Hub 是调度中心启动命令长这样java -jar selenium-server-4.9.0.jar hub --port 4444 --config /opt/selenium/hub.toml--port指定监听端口默认 4444改掉也行但要记得后面所有 Node 注册和测试代码里都统一。如果你想对外暴露管理界面刚才那条命令启动后直接访问http://localhost:4444能看到 Grid Console实时展示节点状态、会话数量、排队任务这个 UI 对排查问题非常有用。Hub 的配置文件用 TOML 格式一个最小的示例[server] port 4444 [hub] host 0.0.0.0 [logging] level INFOhost 0.0.0.0表示监听所有网络接口这样其他机器上的 Node 才能连上来。如果只填127.0.0.1那 Node 永远注册不上这个细节我踩过坑务必注意。3.3 Node 启动参数与浏览器能力绑定Node 是实际干活的人它启动后要向 Hub 报自己的地址和能提供的浏览器型号。命令java -jar selenium-server-4.9.0.jar node \ --hub http://192.168.1.100:4444 \ --port 5555 \ --max-sessions 2 \ --max-instances 2关键参数逐个说明--hub指向 Hub 的完整地址注意是 http 协议前缀也要带。--port是 Node 自己对外提供服务的端口多个 Node 在同一台机器上必须不同端口。--max-sessions同时允许的最大 WebDriver 会话数决定了并行度。--max-instances每种浏览器最多起几个实例通常和最大会话数保持一致。--detect-drivers如果启用了Node 会自动探测本机装的浏览器和相关驱动省去手动 chrome/chromedriver 的配置。方便但不可控生产环境我建议还是手动声明。如果想精细控制浏览器选项用--browser声明一个带参数的浏览器定义java -jar selenium-server.jar node \ --hub http://192.168.1.100:4444 \ --browser browserNamechrome, maxInstances3, options[--headless,--no-sandbox,--disable-dev-shm-usage] \ --browser browserNamefirefox, maxInstances1这段配置里Chrome 会以无头模式最多跑 3 个实例Firefox 只允许 1 个实例。关于 headless 模式我多说一句很多 CI 环境没有显示器不加--headless浏览器根本起不来但也别每个用例都开无头因为无头模式偶尔会掩盖真实页面渲染问题最好在本地肉眼验证过再看要不要开。3.4 最大会话数与并发数的关系max-sessions不是浏览器实例数的别名它表示 Node 可以同时持有的会话总数可能一个会话里跑 Chrome另一个会话里跑 Firefox两者会互相抢占资源。所以通常我们设置max-sessions等于所有浏览器max-instances的总和这样就不会有会话排队或超时的问题。比如声明了 2 个 Chrome 实例加 1 个 Firefox 实例那max-sessions设 3 比较合理。4. 从单机到分布式完整实操流程理论说完了下面走一遍我实际操作的完整流程大家可以直接照着敲。我用的环境是三台 Ubuntu 22.04 虚拟机一台做 Hub两台做 Node每台 Node 上装 Chrome 和 Firefox。为了演示方便假设 Hub 的 IP 是192.168.1.100。4.1 准备 Selenium Server 与浏览器驱动在三台机器上统一执行mkdir -p /opt/selenium cd /opt/selenium wget https://github.com/SeleniumHQ/selenium/releases/download/selenium-4.9.0/selenium-server-4.9.0.jar然后在 Hub 那台机器上安装 Chrome 和 Firefox 以及对应驱动# 安装 Google Chrome 稳定版 wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | sudo apt-key add - echo deb [archamd64] http://dl.google.com/linux/chrome/deb/ stable main | sudo tee /etc/apt/sources.list.d/google-chrome.list sudo apt-get update sudo apt-get install -y google-chrome-stable # 安装 Firefox sudo apt-get install -y firefox # 安装 chromedriver须匹配版本 wget https://chromedriver.storage.googleapis.com/114.0.5735.90/chromedriver_linux64.zip unzip chromedriver_linux64.zip sudo mv chromedriver /usr/local/bin/ # 安装 geckodriver wget https://github.com/mozilla/geckodriver/releases/download/v0.33.0/geckodriver-v0.33.0-linux64.tar.gz tar -xvzf geckodriver-v0.33.0-linux64.tar.gz sudo mv geckodriver /usr/local/bin/在 Node 机器上执行同样的流程或者用 Ansible 脚本统一批量部署。这里关键是靠 PATH 变量Selenium 会到系统 PATH 里找chromedriver和geckodriver如果你把驱动放在自定义目录记得把目录加进 PATHexport PATH$PATH:/opt/selenium/drivers这条设完只是临时生效最好写进/etc/profile.d/selenium.sh让它开机自动生效。4.2 启动 Hub 节点在 Hub 那台机器上启动cd /opt/selenium nohup java -jar selenium-server-4.9.0.jar hub --port 4444 /opt/selenium/hub.log 21 然后用curl验证 Hub 是否正常curl http://localhost:4444/wd/hub/status正常会返回一段 JSON包含ready: true而且message里有nodes: []表示还没节点注册。日志写到/opt/selenium/hub.log排查问题时直接less这个文件。4.3 启动 Node 节点并注册到 Hub在 Node1 机器上执行cd /opt/selenium nohup java -jar selenium-server-4.9.0.jar node \ --hub http://192.168.1.100:4444 \ --port 5555 \ --max-sessions 2 \ --max-instances 2 \ --detect-drivers true /opt/selenium/node.log 21 启动后回到 Hub 的管理页面刷新http://192.168.1.100:4444稍等十几秒应该能在节点列表里看到 Node1而且状态显示 UP。如果列表一直没有多半是防火墙问题。Ubuntu 上执行sudo ufw allow 4444/tcp sudo ufw allow 5555/tcp另一个可能原因是 Hub 和 Node 的时钟不同步导致注册请求校验失败这类问题在虚拟机环境尤其常见最好两边都用 NTP 同步一下时间。这个不是玩笑时间戳差太多会出现诡异的握手失败。4.4 配置测试脚本使用远程 WebDriver服务端全部就绪后测试代码侧只需要把WebDriver的初始化方式从本地改成远程。我以 Java 为例ChromeOptions options new ChromeOptions(); options.setPlatformName(linux); options.setBrowserVersion(114); WebDriver driver new RemoteWebDriver( new URL(http://192.168.1.100:4444/wd/hub), options);Python 版本from selenium import webdriver from selenium.webdriver.chrome.options import Options options Options() options.browser_version 114 driver webdriver.Remote( command_executorhttp://192.168.1.100:4444/wd/hub, optionsoptions )指定browser_version时如果 Grid 上没有匹配的浏览器版本会直接报NoSuchElement无法创建会话这个错误经常让人误以为是自己代码问题。所以第一节我反复强调版本匹配不仅指驱动和浏览器匹配还指请求参数与节点能力匹配。4.5 并行执行验证用一个简单的 Pytest 工程验证并行效果。比如你有 4 条用例本地顺序执行要 4 分钟Grid 接了 2 个节点各 2 个会话理想状态下代入pytest-xdist插件pip install pytest-xdist pytest -n 4 --dist loadgroup test_cases/这样 4 条用例会同时分发到不同节点、不同会话上执行时间趋近于单条用例的最长耗时。看执行日志你会发现 Sessions 分配是动态的有的跑到 Node1有的跑到 Node2。4.6 Docker 化的快速部署方案手动装浏览器驱动这套流程做一遍觉得还好但多台机器反复搞就很烦。更优雅的方案是直接用 DockerSelenium 官方有现成的镜像比如selenium/hub和selenium/node-chrome不需要你自己处理驱动版本问题镜像里已经帮你配好了。一个最精简的docker-compose.ymlversion: 3.9 services: selenium-hub: image: selenium/hub:4.9.0 container_name: selenium-hub ports: - 4444:4444 environment: - SE_OPTS--max-sessions 10 networkMode: bridge restart: always chrome-node-1: image: selenium/node-chrome:4.9.0 container_name: chrome-node-1 depends_on: - selenium-hub environment: - SE_EVENT_BUS_PUBLISHselenium-hub:4442 - SE_EVENT_BUS_SUBSCRIBEselenium-hub:4443 - SE_NODE_MAX_SESSIONS4 volumes: - /dev/shm:/dev/shm restart: always firefox-node-1: image: selenium/node-firefox:4.9.0 container_name: firefox-node-1 depends_on: - selenium-hub environment: - SE_EVENT_BUS_PUBLISHselenium-hub:4442 - SE_EVENT_BUS_SUBSCRIBEselenium-hub:4443 - SE_NODE_MAX_SESSIONS2 volumes: - /dev/shm:/dev/shm restart: always这里面SE_EVENT_BUS_PUBLISH和SE_EVENT_BUS_SUBSCRIBE是 Grid 4 事件总线的地址默认端口是 4442 和 4443。这两个变量不设对Node 就注册不上。/dev/shm挂载非常关键容器默认的共享内存只有 64MB浏览器跑起来分分钟 OOM加到 2G 以上基本稳定。执行docker-compose up -d然后去http://localhost:4444就能看到两个 Node 注册成功。用 Docker 还有个额外优势想加节点就docker-compose scale chrome-node-14缩容同理改起来比物理机快得多。5. 常见问题排查与技巧实录这部分是真正值钱的内容。我配置和维护 Selenium Grid 大半年时间遇到的大问题基本都集中在下面几个方向列出来供大家参考。5.1 Node 注册不上或状态 DOWN先确认 Hub 和 Node 之间网络通不通telnet 192.168.1.100 4444 telnet 192.168.1.101 5555很多情况是防火墙没有放行 4442 和 4443 端口。Grid 4 的事件总线用的是这两个端口很多人只放了 4444Node 连不上 Hub 就在那干瞪眼。另外Node 启动参数里--hub的地址如果写成了localhostNode 自己解析到本地回环地址Hub 上的节点详情页虽然显示一个地址但实际会话创建时却访问不了这种问题往往表面上一点报错都没有只有跑用例才能发现。5.2 浏览器的 WebDriver 创建会话超时报错类似Unable to establish websocket connection to remote server或者Session timed out or not found。这种大概率是浏览器启动过程太慢尤其是容器环境。可以在 Node 启动命令里增加超时配置--session-timeout 90给浏览器更长的准备时间。同时检查系统资源free -h看内存是否够用df -h看/dev/shm是否被占满我遇到过一次 200 个僵尸浏览器进程把内存吃光的情况杀进程重启搞定pkill -f chrome pkill -f geckodriver5.3 版本不匹配的连环坑这个问题重复率最高。比如 Node 上 Chrome 升级到了 117但驱动还是 114你测试代码里传的browserVersion也还是 114Grid 一查节点列表发现只有 117 的 Chrome直接拒绝创建会话。报错信息是Cannot find a matching browser version一眼就能看出来。处理方式要么把代码里的版本号和驱动都升到一致要么 Grid 上启用--strict-mode关闭版本严格匹配让浏览器拉起来用哪个版本都行。我自己倾向前者严格匹配虽然麻烦但能避免测试环境与产品环境不一致导致的假通过。5.4 Grid 公网暴露的安全风险网格本身没有任何鉴权机制谁连上都能创建浏览器会话。生产环境如果要暴露公网务必在前面加一层反向代理做身份认证最简单的是用 Nginx 的 Basic Authlocation / { proxy_pass http://127.0.0.1:4444; auth_basic Selenium Grid; auth_basic_user_file /etc/nginx/.htpasswd; }另外 Selenium Grid 4 也支持自定义注册和基本的用户名密码校验通过--config配置[auth]区块不过用的时候注意不要把明文密码提交到 Git 仓库。我见过好几个团队把 Grid 端口直接暴露在公网上没过几天就被人拿去挖矿了这类事故一点都不新鲜。5.5 日志级别调整与问题定位思路Grid 的日志默认是 INFO很多关键细节在 WARN/DEBUG 级别里。定位问题时可以把 Hub 和 Node 都调到 DEBUG 再复现一次java -jar selenium-server-4.9.0.jar hub --port 4444 --log-level DEBUG注意生产环境不要长期用 DEBUG日志量会爆炸。平时 TRACE 级别的日志真的不用开除非你在排查协议层的异常。刷日志有个小技巧只看 Selenium 自己包的日志别被浏览器驱动生成的噪音干扰加个 log4j2 配置过滤掉无关包。6. 我踩过的坑与后续值得尝试的方向最后按惯例分享一点个人体会。配置 Selenium Grid 本身花不了多少时间真正费时间的是排查那些看起来一切正常但就是跑不起来的诡异问题。我个人习惯是先搭一个最小的 Hub 单 Node 环境验证基础联通性再逐步叠加浏览器类型和并行数这样定位问题范围会小很多。另外尽量把所有配置都写成配置文件和启动脚本别靠敲命令行时间一长没人记得住当时敲了哪些参数。Docker 化是我现在最推荐的方案它把环境一致性做到极致也方便迁移到 Kubernetes 调度全团队共享的测试网格。如果你做的项目还在靠单机串行跑 UI 测试我建议周末花几个小时把 Grid 搭出来跑一次并行用例那种从等一小时到等五分钟的体验变化会让你立刻明白这工具的威力。