ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Selenium Grid 4配置实战:从零搭建分布式自动化测试网格

2026/10/6 17:25:17 拓冰建站 浏览量
Selenium Grid 4配置实战:从零搭建分布式自动化测试网格 我最早接触 Selenium Grid 是在一个回归测试项目里单台机器跑 2000 多条 UI 用例串行执行一次要十几个小时每次发版前等结果等到怀疑人生。后来花了半天时间搭了一套分布式测试网格用 4 台机器把任务切分开回归时间直接压缩到 3 小时以内——从那以后Grid 就成了我自动化框架里离不开的一部分。这篇内容就围绕 Selenium Grid 的配置实战展开重点说清楚分布式测试网格到底解决了什么问题、Grid 4 和旧版有什么本质区别、从最小可用配置到生产环境可用配置一步步怎么搭以及我实际踩过的坑和排查思路。无论你是刚接触自动化测试的新人还是已经在维护一套用例量很大的框架这篇文章都能给你一套可以直接照着做的方案。1. 先搞明白Selenium Grid 到底解决什么问题1.1 单机跑自动化测试的瓶颈比你想象的更现实很多人对 Grid 的第一印象是“并发执行”这个理解没错但不够完整。单机跑 UI 自动化的时候真正卡住你的往往不是用例本身写得慢而是下面这几件事浏览器实例是串行创建的一个用例跑完释放资源下一个才能开始时间全浪费在等待上一台机器的 CPU、内存是固定的浏览器一多就互相抢资源轻则变慢重则直接崩溃操作系统和浏览器版本单一覆盖不了真实用户的多样环境跑一次回归需要几小时甚至十几个小时一旦中途失败重跑整个流程被拖得很长。用一个生活化的类比你开了一家手工作坊只有一条流水线所有订单都在这条线上慢慢做。Selenium Grid 做的事就是把流水线从一条扩成多条而且每一条流水线可以放不同的“设备参数”——Windows 上跑 Edge、macOS 上跑 Safari、Linux 容器里跑 Chrome互不干扰。1.2 Grid 4 的架构变化从 Hub-Node 到组件化如果你看过老版本的 Selenium Grid 资料很容易被它的概念带偏。Grid 2/3 时代核心就是两个角色Hub 和 Node。Hub 负责接收测试请求、分配任务Node 负责注册自己并执行用例。好处是简单坏处也很明显Hub 是单点所有请求都经过它并发一高 Hub 就变成瓶颈而且它只支持 JSON Wire Protocol协议实现重、排错也难。Grid 4 把整体拆成了五个组件各管一摊组件职责对应旧版里的哪个角色Router统一入口接收所有请求并转发相当于 Hub 的网络入口Distributor负责会话分配把任务分给合适的 Node相当于 Hub 的调度核心Session Map保存会话 ID 与 Node 的映射关系新增用于快速定位会话Node真正执行测试的地方旧版 NodeEvent Bus组件之间的异步通信总线新增让各组件解耦这个拆分的价值在于调度、路由、会话管理不再挤在一个进程里。生产环境中可以把不同组件部署到不同机器也可以只用一个进程跑全部组件——Grid 4 的 Standalone 模式就是干这个的。理解了这个架构后面配置的时候就不会被那些启动参数绕晕。1.3 什么时候不该用 GridGrid 不是银弹。我见过有些团队用例量只有一两百条也非要搭个 Grid结果基础设施维护成本比用例执行时间还高。判断是否需要 Grid可以用几个简单标准用例量是否已经多到单机回归时间超过半小时以上是否需要在多个浏览器、多个操作系统组合下执行同一批用例是否有团队协作需求比如测试用例集中管理、多环境统一调度。如果三个问题答案都是“否”那直接单机跑反而更省事。Grid 解决的是规模化问题不是“看起来更专业”的问题。2. 环境准备与选型动手前先想清楚三个选择2.1 版本与运行模式怎么选Selenium Grid 从 4.0 开始不再作为独立服务分发而是打包在 selenium-server 的 jar 文件里。所以版本选择其实约等于整个 Selenium 框架的版本选择。我的建议是直接上 Selenium 4.x 最新稳定版一来组件的兼容性修复更到位二来 Selenium Manager 会自动处理驱动的下载和匹配省掉一大半环境配置的烦恼。运行模式方面Grid 4 提供三种部署方式模式适合场景组件数复杂度Standalone学习、小型项目、调试1 个进程最低Hub-Node小型团队、少量节点2 类角色中等Fully Distributed中大规模、要求高可用5 类组件较高我实际的经验是阶段一先用 Standalone 跑通阶段二再拆成 Hub-Node真正需要上规模的时候才考虑 Fully Distributed。一步到位上全分布式往往是给自己找罪受。2.2 浏览器驱动矩阵和版本对齐一个非常容易忽略的问题就是浏览器驱动。Selenium 通过 WebDriver 驱动浏览器而 WebDriver 本质是一个可执行文件比如 chromedriver、geckodriver、msedgedriver。Driver 的版本必须和浏览器大版本匹配否则一启动就报错。具体版本对应关系并不是最近才这样的但好消息是 Grid 4 已经集成了 Selenium Manager默认情况下它会自动检查浏览器版本并下载匹配的驱动。不过这依赖网络通畅如果内网环境受限得预先下载好驱动并放在 PATH 里或者用SE_DRIVER_PATH之类的方式指定驱动位置。我建议不管有没有 Selenium Manager团队里都维护一份“浏览器版本-驱动版本-操作系统”对照表不然排查问题的时候会很难受。各浏览器在 Grid 节点上的驱动配置路径不一样。Chrome 在 Linux 上通常是/usr/bin/chromedriverFirefox 是/usr/local/bin/geckodriverWindows 上则往往需要手动加入 PATH。配置时最好统一用一个se:downloads或者se:driverPath这类自定义 capability让指定驱动位置变得更加可控。2.3 网络与硬件的规划Grid 是多进程、多机器协作的系统网络是它正常运行的生命线。核心端口默认是 4444但节点和 Hub 之间实际上还有额外的通信端口如果中间有防火墙不能只放开 4444 就以为大功告成。建议直接把 Grid 相关的网段进行白名单放通而不是逐端口磨蹭。硬件规划上我习惯按“每个并发浏览器至少 1 核 CPU 2GB 内存 至少 5GB 磁盘空间”来估算节点规格。举个例子如果每个节点预期跑 4 个并发浏览器那这台机器至少要有 4 核 CPU、8GB 内存。磁盘空间常常被忽略——浏览器缓存、截图、日志会迅速膨胀尤其是长时间跑批量的情况下几百张截图就能让磁盘告急。另外有一点容易被忽视节点机器之间的时钟偏差。Grid 4 的事件总线、会话超时都依赖时间逻辑如果某台节点系统时间和 Hub 差太多会出现会话被提前判定超时或者注册信息异常。最稳妥的办法是让所有节点用同一个 NTP 服务同步时间。3. 核心配置逐项拆解从最小可用到生产可用3.1 最省事的 Standalone 模式到底够不够用Standalone 模式是学习 Grid 的最佳入口一行命令就能起来java -jar selenium-server-4.27.0.jar standalone默认监听 4444 端口浏览器打开http://localhost:4444就能看到 Grid 控制台。这个模式相当于把所有组件塞进了一个进程里好处是零配置坏处是没法横向扩容。测试脚本连它的时候只需要把RemoteWebDriver的地址指向它即可。我在实际项目中用 Standalone 做本地开发调试比较多。比如新写一条用例想快速验证某段业务逻辑直接连本地 Standalone比让脚本在大网格里排队快得多。它还有一个隐藏用途用来验证当前目录下浏览器驱动和浏览器版本是否匹配如果 Standalone 能正常创建会话那说明环境没问题如果 Standalone 都创建不了问题一定出在“浏览器/驱动/依赖库”这一层跟 Grid 本身无关。这个排查思路能在后面省很多时间。3.2 Hub-Node 模式的完整配置解析Hub-Node 是正式环境最常用的模式。启动 Hub只需要一个命令java -jar selenium-server-4.27.0.jar hubHub 起来之后Node 机器上执行注册命令java -jar selenium-server-4.27.0.jar node \ --hub http://192.168.1.10:4444 \ --port 5555 \ --max-sessions 4 \ --detect-drivers true这里的参数值得逐个说清楚。--port 5555是 Node 自己的通信端口如果机器上有多套 Node 实例这个端口必须不同。--max-sessions 4决定该节点最多同时接收多少个会话它是资源调度的上限我一般把它设置成小于等于 CPU 核数的值这样才不会让浏览器互相抢占资源导致全部变慢。--detect-drivers true让节点自动探测本机已安装的浏览器驱动。如果想让某个节点只跑特定浏览器用--publish参数指定 capability比如java -jar selenium-server-4.27.0.jar node \ --hub http://192.168.1.10:4444 \ --max-sessions 2 \ --publish {browserName:chrome,platformName:linux,selenium:name:chrome-node}上述命令的关键在于 capability 里的 browserName 等字段它们会在测试请求创建会话时被精确匹配。写到这里提醒一句Grid 会区分大小写和无意义的空格实际写这些配置时最好直接复制官方规范的 JSON不要手敲。还有一种更推荐的配置方式把 Node 参数写进 TOML 配置文件然后通过--config加载。相比命令行配置文件更容易维护、更容易走版本管理。下面是一个真实用过的 node.toml 示例[node] port 5555 max-sessions 4 [node.driver-configuration] display-name Chrome browser-name chrome path /usr/bin/chromedriver max-sessions-per-driver 2 [node.capabilities] browserName chrome platformName linux要点是max-sessions是节点总并发max-sessions-per-driver是每个浏览器实例的并发额度二者要配合设置。我曾经在一个 8 核机器上把 total 设为 8、per-driver 设为 8结果 Chrome 一开就是 8 个进程内存直接爆掉。后来调整成 total8、per-driver2让 4 种浏览器类型共享节点额度稳定多了。3.3 Docker 化的网格配置扩容最舒服的方式如果你要在 Linux 环境里大批量运行浏览器Docker 化部署基本是主流选项因为容器天然隔离了浏览器进程不至于出现依赖库冲突破坏环境。官方提供了selenium/hub和selenium/node-chrome等镜像配合 Docker Compose 可以快速拉起一套网格。下面是我实际用过的 docker-compose.yml 精简版本services: selenium-hub: image: selenium/hub:4.27.0 container_name: selenium-hub ports: - 4444:4444 environment: - SE_GRID_MAX_SESSION16 chrome-node: image: selenium/node-chrome:4.27.0 container_name: chrome-node depends_on: - selenium-hub environment: - SE_EVENT_BUS_PUBLISH_PORT4442 - SE_EVENT_BUS_SUBSCRIBE_PORT4443 - SE_NODE_MAX_SESSIONS4 - SE_NODE_MAX_SESSIONS_PER_DRIVER1 volumes: - /dev/shm:/dev/shm - /opt/selenium/logs:/home/seluser/logs这里有个非常关键的参数/dev/shm。默认情况下容器的共享内存只有 64MB而 Chrome 重度依赖/dev/shm来存放渲染数据容量不够直接白屏崩溃。映射宿主机的/dev/shm后实际上共享内存容量等于宿主机可用的内存问题基本消失。这句话我多写一次Docker 跑浏览器遇到闪退先检查/dev/shm。Docker 化网格的另一个好处是扩容方便同一份 compose 文件里复制一个 chrome-node 服务然后换容器名和端口跑docker-compose up -d新节点就自动注册到 Hub 上了。收容、替换浏览器版本也只要重新拉镜像重建容器不用折腾宿主机环境。不过我建议不要把两种不同操作系统混在一个 compose 里比如 Linux 容器和 Windows 宿主机网络模式和文件挂载差异太大会额外添乱。3.4 测试脚本端怎么连上网格Grid 服务端配置好之后测试代码也要做出对应调整。核心变化只有一处不再是本地new ChromeDriver()而是指定一个远端地址并带上 capability。以 Java 为例ChromeOptions options new ChromeOptions(); options.setCapability(browserName, chrome); options.setPlatformName(linux); RemoteWebDriver driver new RemoteWebDriver( new URL(http://192.168.1.10:4444), options); driver.get(https://example.com);Python 版本也类似from selenium import webdriver from selenium.webdriver.chrome.options import Options options Options() options.set_capability(browserName, chrome) options.set_capability(platformName, linux) driver webdriver.Remote( command_executorhttp://192.168.1.10:4444, optionsoptions ) driver.get(https://example.com)有几个 capability 属于自定义附加字段比如selenium:name、se:downloads、se:recordVideo它们能让你在 Grid 控制台清楚地看到用例名称或者在用例失败时自动录屏对排查问题价值很高。比如options.set_capability(selenium:name, test_login_flow) options.set_capability(se:recordVideo, True)录制视频会占用节点磁盘我一般只在关键回归或冒烟测试里开不默认启用。脚本如果要并行运行建议配合线程池或者 pytest-xdist / TestNG 套件机制多线程创建多个 RemoteWebDriver。注意线程数不要超过整个网格的最大会话数否则大量请求会堆积在队列里反而把执行时间拖长。4. 实操回放两节点网格的搭建过程与经验值4.1 一次完整的搭建过程记录搭一个最基础但可用的两节点网格我的实际步骤是这样的第一步准备好三台机器一台做 Hub两台做 Node。Hub 机器可以很便宜它只做路由和调度负载不大Node 机器要关注 CPU 和内存。我实际用的是Hub 2核4GNode 各 4核8G。第二步在所有机器上装好 Java 11。Selenium Server 是纯 Java 服务没装 Java 一切免谈。我用的是 OpenJDK 17实测稳定。第三步在所有机器上放同一个版本的 selenium-server jar。版本必须一致混用版本会导致节点注册之后会话分发行为异常。下载后建议比对校验和防止文件损坏。第四步Hub 上执行启动java -jar selenium-server-4.27.0.jar hub --port 4444看到类似Selenium Grid Hub is ready的日志就说明启动成功。第五步Node 上执行注册java -jar selenium-server-4.27.0.jar node \ --hub http://192.168.1.10:4444 \ --port 5555 \ --max-sessions 4 \ --detect-drivers true启动后立刻访问http://192.168.1.10:4444/status正常情况下能看到注册节点的信息。Shows as total available slots: 4这样才算注册成功。如果没有出现不要急着改配置先执行ping 192.168.1.10确认网络再检查节点进程日志里的注册失败原因。第六步写一条最简单的脚本用 RemoteWebDriver 访问 Grid 创建一个会话并打开任意页面验证端到端链路是通的。这一步通过后再跑完整的业务用例。这套流程我反复用过很多次最快的时候半小时全部搞定。最花时间的往往不是命令本身而是排查为什么“明明启动了但控制台看不到节点”。4.2 并发会话、超时与资源估算方法网格配好之后最常遇到的问题就是并发设置不合理。具体公式我一般这样算先确定业务要求假设回归用例 1000 条每条平均耗时 2 分钟期望在 30 分钟内跑完那需要的并发数大约是 1000 × 2 ÷ 30 ≈ 67 个并发会话。整个网格的最大会话数至少要有 67。再分摊到节点如果用 4 台 Node平均每台需要约 17 个并发会话。再根据单台机器资源反推配置17 个浏览器实例至少需要 17 核 CPU 和 34GB 内存所以单台 8 核 16G 的机器根本扛不住。这里要多说一句上面只是理想值浏览器初始化、页面渲染、网络等待都会拉长单条用例耗时所以实际并发数最好留 20% 到 30% 余量。可能的情况下先把最大并发调低观察节点负载曲线再逐步往上加。超时参数也很重要。Hub 默认的session-timeout是 300 秒意味着一个会话 5 分钟没有活动就会被强制回收。要视业务情况而定如果用例里有长时间等待操作就把超时调大到 600 秒或 900 秒。太短会导致用例执行中直接断掉太长则会让坏掉的浏览器占着坑不释放。经验值是比最长用例耗时长 10%~20%。还有--session-retry-period和队列长度控制Grid 4 会把超出并发能力的请求放进队列等待。如果不希望积压太多任务可以明确设置队列最大值超出后直接拒绝新请求避免所有任务在那里干等。4.3 稳定性相关的几处关键配置一个网格跑得稳不稳配置细节影响很大。我整理了几处最值钱的配置Node 自动重启策略。部署为 systemd 服务相当常见设置 Restartalways节点进程万一挂了能自动拉起来保证下次任务分发时节点在线。浏览器空闲时间限制。在 node.toml 里设置session-timeout可以让卡死的浏览器自动释放。运行时环境变量。比如在 Linux 上给 Chrome 设置--no-sandbox --disable-dev-shm-usage。容器环境下 user namespace 权限受限不加这个参数经常直接崩溃。日志滚动。Grid 日志默认会持续增长建议用 logrotate 按天切割并压缩避免磁盘被日志灌满。健康检查脚本。每天凌晨定时访问/status接口如果节点掉线触发通知并自动重启该节点。实测这个方法比报错之后再排查高效得多。稳定性这个事急不来。我当时连续观察了两周把上面这些问题一个个收拾干净网格才进入到基本不需要人工干预的状态。5. 常见问题与排查技巧实录5.1 节点注册失败最常见的三个原因节点注册不成功九成以上逃不开这三类原因第一类是网络不通。节点访问 Hub 的 4444 端口被防火墙拦截或者节点和 Hub 不在同一个网段注册请求根本送不到。排查方法很直接在节点机器上执行curl http://hub-ip:4444/status能返回 JSON 说明网络通。第二类是版本不匹配。Node 和 Hub 用不同版本的 selenium-server jar老版本没有--publish参数或者 event bus 配置格式不同就会注册失败。解法统一版本。第三类是端口冲突。同一个机器想跑多个 Node不小心都用默认的 5555 端口后启动的进程注册不上。解法是给每个 Node 显式指定不同的--port。排查节点问题时我的习惯是看启动日志而不是直接看控制台。因为控制台信息更新有延迟日志里会有明确的异常堆栈。比如Cannot connect to the hub就是网络层问题Invalid request to a node大概率是注册参数或者 protocol 版本问题。5.2 会话长期排队或者直接超时Grid UI 里看到一堆会话卡在 Queue 状态是资源规划没做好。产生这个现象的原因通常是测试脚本并发数开得很高但节点 max-sessions 总共只有几个大量请求进队执行时间被拉长。还有一种情况某个浏览器驱动崩溃后节点上对应的 slot 迟迟不释放新会话全部堵在队列里。解决思路有两个方向。一是提高并发能力比如增加节点数量或者调大节点 max-sessions 并同步增加资源。二是做超时控制把队列最大长度调小或者设置session-timeout让异常会话不占坑。我曾经遇到过一个最隐蔽的问题脚本端使用的 RemoteWebDriver 连接池没有释放连接导致 Grid 上大量已经跑完的会话被客户端挂着节点资源一直被占用。后来在测试框架的 teardown 里强制driver.quit()现象立刻消失。5.3 浏览器或者驱动起不来的排查创建会话时报SessionNotCreatedException大概率是浏览器版本和驱动不匹配。建议先做一次经典验证直接在节点机器上写一行本地 WebDriver 脚本启动浏览器并打开一个网页。如果本地都起不来那跟 Grid 完全没有关系就是节点机器上浏览器或驱动的问题。排查顺序是浏览器能否被手动打开 → 驱动文件是否存在且版本匹配 → 是否为沙盒或共享内存限制。如果是 Linux 容器环境优先检查/dev/shm大小和--no-sandbox参数。如果是 Windows 节点注意浏览器自动更新可能导致驱动过时需要同步更新驱动。维护一个“浏览器-驱动版本-更新时间”的表格对团队排查很有帮助。5.4 僵尸进程与资源泄漏的处理运行一段时间后节点机器变慢用top一看满屏的 chrome 进程就是杀不掉这种情况多半是用例脚本异常退出Driver 进程没被正确关闭。Linux 下可以用pkill -9 chrome应急但长时间的解决方案还是从流程上控制脚本 teardown 里一定要写driver.quit()并且对所有测试做超时兜底。哪怕用例断言失败finally 块也要把 driver 关掉。我还会加一个定期清理任务每天凌晨批量删除旧进程和临时文件。这个任务本身很简单就是几行 shell但对网格稳定性的贡献非常大。另外节点上可以写一个监控脚本当进程数超过阈值或内存使用率超过 85% 时自动重启该节点的 Grid 服务实测对长周期运行的网格很有效。组合起来看网格稳定运行的本质就是一套闭环资源够、进程干净、异常能自动恢复。把这些做扎实了就不需要天天盯着控制台看了。回到我自己的经验Selenium Grid 的配置门槛并没有想象中那么高真正决定它好用不好用的往往是你愿不愿意花时间去观察节点负载、处理僵尸进程、对齐浏览器版本这些“脏活”。先把一套最小的网格跑起来再根据实际数据去调参、扩容远比你一开始就规划出一个完美架构更有价值。如果这篇内容帮你省了几个小时的排查时间那我觉得就挺值了。