
1. 项目概述为什么我们需要Docker化的Selenium Grid如果你做过Web自动化测试尤其是需要并行执行或者跨浏览器测试那你一定对Selenium Grid不陌生。传统的Grid搭建过程从下载各个浏览器的驱动到配置节点、注册中心再到处理不同浏览器版本兼容性问题每一步都可能是个坑。我记得几年前维护一个测试集群光是处理Chrome和Firefox版本升级导致的驱动不兼容就耗费了大量精力。而Docker的出现彻底改变了这个局面。docker-selenium项目将Selenium Grid的各个组件Hub、Node以及浏览器Chrome, Firefox连同其驱动全部打包成了标准的Docker镜像。这意味着环境搭建从一项繁琐的系统工程变成了几条docker run命令。版本管理变得清晰环境隔离得到保证横向扩展也异常简单。今天要聊的就是如何利用docker-selenium在几分钟内搭建起一个支持Chrome和Firefox的、稳定可用的分布式测试环境。无论你是想快速搭建一个本地调试环境还是为团队构建CI/CD中的测试基础设施这套方案都能让你事半功倍。2. 核心组件与架构解析在动手之前我们必须理解docker-selenium这套体系里有哪些“积木”以及它们是如何协同工作的。这能帮助你在出问题时快速定位在需要扩展时知道该动哪里。2.1 Selenium Grid 4 架构简述Selenium Grid 4 采用了更现代、更灵活的架构。其核心是一个Hub它现在更准确地被称为Router路由器或Distributor分发器。它不再是一个单点而是一组服务的集合包括负责接收测试请求的Router、负责分配任务的Distributor、负责会话管理的Session Map等。但对于入门使用我们可以先将其理解为一个统一的入口点。测试脚本你的WebDriver客户端将请求发送到Hub。Hub并不执行测试它只是一个调度中心。真正干活的是Node节点。一个Node就是一个能够运行浏览器实例的环境。在我们的场景中一个Node就是一个Docker容器里面预装了特定浏览器如Chrome和对应的WebDriver。docker-selenium项目为我们提供了所有这些组件的官方镜像我们只需要按需组合。2.2 Docker-Selenium 镜像家族这是整个方案的基石。主要镜像包括selenium/hub: Grid 4 的Hub/路由器组件。所有测试请求都发往它。selenium/node-chrome: 包含Chrome浏览器和ChromeDriver的节点。selenium/node-firefox: 包含Firefox浏览器和GeckoDriver的节点。selenium/node-docker: 一个特殊节点它本身内部可以运行Docker容器。这用于支持更复杂的“Docker in Docker”场景比如在容器内启动一个独立环境的浏览器实例通常用于高级隔离需求初期可以不用。selenium/standalone-chrome和selenium/standalone-firefox: 这是“All-in-One”的镜像将Hub和Node合二为一。非常适合快速启动一个单节点的本地测试环境但不适合分布式扩展。对于我们要搭建的支持Chrome和Firefox的Grid环境我们需要组合使用selenium/hub、selenium/node-chrome和selenium/node-firefox。2.3 网络与端口映射Docker容器默认运行在隔离的网络中。为了让外部你的测试脚本、你的浏览器能够访问Grid Hub和查看实时会话我们需要进行端口映射。Hub: 默认监听4442(TCP协议端口)、4443(HTTP协议端口) 和4444(HTTP协议端口用于Grid UI和客户端通信)。通常我们只需要将主机的某个端口如4444映射到容器的4444端口就能通过http://localhost:4444访问Grid控制台和提交测试。Node: 需要暴露端口供Hub通信同时为了支持VNC让我们可以实时观看测试执行过程还会映射5900端口。这些在docker-selenium的镜像中已经预设好我们启动时只需映射VNC端口即可。理解这些我们就能明白后续每一条docker run命令背后在做什么。3. 环境准备与快速启动我们跳过复杂的理论直接进入实战。假设你的机器上已经安装了Docker和Docker Compose。如果没有去Docker官网下载Docker Desktop安装这是最省事的方式。3.1 方案一使用Docker Compose推荐这是管理多容器应用的最佳实践。我们创建一个docker-compose.yml文件。version: 3.8 services: selenium-hub: image: selenium/hub:latest container_name: selenium-hub ports: - 4442:4442 - 4443:4443 - 4444:4444 environment: - SE_EVENT_BUS_HOSTselenium-hub - SE_EVENT_BUS_PUBLISH_PORT4442 - SE_EVENT_BUS_SUBSCRIBE_PORT4443 networks: - selenium-grid chrome-node: image: selenium/node-chrome:latest container_name: chrome-node depends_on: - selenium-hub environment: - SE_EVENT_BUS_HOSTselenium-hub - SE_EVENT_BUS_PUBLISH_PORT4442 - SE_EVENT_BUS_SUBSCRIBE_PORT4443 - SE_NODE_MAX_SESSIONS4 # 最大并发会话数 - SE_NODE_OVERRIDE_MAX_SESSIONStrue - SE_VNC_NO_PASSWORD1 # 启用无密码VNC仅限本地测试 volumes: - /dev/shm:/dev/shm # 共享内存提升Chrome稳定性 ports: - 5901:5900 # 将主机5901映射到容器VNC端口5900 networks: - selenium-grid firefox-node: image: selenium/node-firefox:latest container_name: firefox-node depends_on: - selenium-hub environment: - SE_EVENT_BUS_HOSTselenium-hub - SE_EVENT_BUS_PUBLISH_PORT4442 - SE_EVENT_BUS_SUBSCRIBE_PORT4443 - SE_NODE_MAX_SESSIONS4 - SE_NODE_OVERRIDE_MAX_SESSIONStrue - SE_VNC_NO_PASSWORD1 volumes: - /dev/shm:/dev/shm ports: - 5902:5900 # 将主机5902映射到容器VNC端口5900 networks: - selenium-grid networks: selenium-grid: driver: bridge关键点解析网络 (networks): 我们创建了一个名为selenium-grid的桥接网络。所有服务hub, chrome-node, firefox-node都加入这个网络。这样容器之间可以通过服务名如selenium-hub直接通信无需知道IP地址这是Docker Compose的一大便利。环境变量 (environment):SE_EVENT_BUS_*这几个变量是Grid 4节点与Hub通信所必需的必须正确指向Hub的服务名和端口。SE_NODE_MAX_SESSIONS定义了单个节点可以同时运行的最大浏览器实例数这个值需要根据你机器资源CPU、内存合理设置设置太高会导致浏览器崩溃。卷 (volumes):- /dev/shm:/dev/shm这行至关重要。Chrome和Firefox在Docker容器中运行时如果共享内存太小极易崩溃。将主机的/dev/shm挂载到容器可以避免大部分因内存不足导致浏览器意外退出的问题。VNC端口: 我们将chrome-node的VNC端口映射到主机的5901firefox-node的映射到5902。这样你可以分别用VNC客户端连接localhost:5901和localhost:5902来实时观看测试执行画面。保存文件后在终端进入该文件所在目录执行一条命令即可启动整个集群docker-compose up -d-d参数表示在后台运行。使用docker-compose logs -f可以查看实时日志观察启动过程。启动完成后打开浏览器访问http://localhost:4444你应该能看到Selenium Grid的控制台。在“Nodes”部分应该能看到一个Chrome节点和一个Firefox节点已经注册成功。3.2 方案二使用纯Docker命令如果你不想用Docker Compose或者想在脚本中动态控制可以直接使用docker run命令。第一步启动Hubdocker run -d -p 4442:4442 -p 4443:4443 -p 4444:4444 --name selenium-hub selenium/hub:latest第二步启动Chrome节点这里的关键是节点容器需要通过--link或自定义网络连接到Hub并且通过SE_EVENT_BUS_HOST环境变量指定Hub的地址。由于--link已废弃我们更推荐创建自定义网络。# 先创建一个网络 docker network create grid # 启动Hub并加入网络 docker run -d -p 4444:4444 --network grid --name selenium-hub selenium/hub:latest # 启动Chrome节点连接到同一网络并引用Hub的容器名 docker run -d --network grid --name chrome-node \ -e SE_EVENT_BUS_HOSTselenium-hub \ -e SE_EVENT_BUS_PUBLISH_PORT4442 \ -e SE_EVENT_BUS_SUBSCRIBE_PORT4443 \ -e SE_NODE_MAX_SESSIONS4 \ -e SE_VNC_NO_PASSWORD1 \ -v /dev/shm:/dev/shm \ -p 5901:5900 \ selenium/node-chrome:latest第三步启动Firefox节点docker run -d --network grid --name firefox-node \ -e SE_EVENT_BUS_HOSTselenium-hub \ -e SE_EVENT_BUS_PUBLISH_PORT4442 \ -e SE_EVENT_BUS_SUBSCRIBE_PORT4443 \ -e SE_NODE_MAX_SESSIONS4 \ -e SE_VNC_NO_PASSWORD1 \ -v /dev/shm:/dev/shm \ -p 5902:5900 \ selenium/node-firefox:latest这种方式步骤稍多但原理更清晰。无论哪种方式目标都是让Hub和Node能在网络内互通。4. 编写测试脚本并连接到Grid环境搭好了我们得验证它确实能工作。这里以Python的Selenium库为例展示如何编写测试脚本让其运行在刚搭建的Grid上。4.1 基础连接脚本首先确保安装了Selenium:pip install selenium然后创建一个Python脚本比如test_grid.pyfrom selenium import webdriver from selenium.webdriver.common.desired_capabilities import DesiredCapabilities # 1. 定义目标Grid Hub的地址 grid_url http://localhost:4444 # 2. 创建Chrome的期望能力对象 chrome_options webdriver.ChromeOptions() # 可以添加一些浏览器选项例如无头模式 # chrome_options.add_argument(--headless) # chrome_options.add_argument(--no-sandbox) # chrome_options.add_argument(--disable-dev-shm-usage) # 在容器中运行时常用 desired_caps DesiredCapabilities.CHROME.copy() # 将Chrome选项合并到期望能力中 desired_caps.update(chrome_options.to_capabilities()) # 3. 初始化Remote WebDriver指向Grid Hub driver webdriver.Remote( command_executorgrid_url, desired_capabilitiesdesired_caps ) try: # 4. 执行你的测试步骤 driver.get(https://www.baidu.com) print(f页面标题是{driver.title}) # 进行更多操作... input(按回车键结束并关闭浏览器...) except Exception as e: print(f测试执行出错{e}) finally: # 5. 务必关闭会话释放Grid节点资源 driver.quit()脚本要点webdriver.Remote: 这是关键。它告诉Selenium客户端不要启动本地浏览器而是将命令发送到指定的command_executor即我们的Grid Hub。DesiredCapabilities: 用于描述你希望节点提供什么样的浏览器环境。这里我们指定了CHROME。如果你想用Firefox就换成DesiredCapabilities.FIREFOX。driver.quit(): 在Grid环境中尤为重要。它不仅仅关闭浏览器还会通知Hub释放这个会话让节点可以接受新任务。如果忘记quit会导致会话泄漏节点资源被占用。运行这个脚本你会看到Grid控制台 (http://localhost:4444) 的会话列表中出现一个活跃的Chrome会话。同时如果你用VNC客户端连接localhost:5901就能实时看到浏览器在容器中打开百度首页的操作。4.2 实现并行与跨浏览器测试Grid的核心价值在于并行和跨浏览器。我们可以利用Python的concurrent.futures模块或pytest-xdist插件轻松实现。这里展示一个简单的多线程示例from selenium import webdriver from selenium.webdriver.common.desired_capabilities import DesiredCapabilities import threading import time grid_url http://localhost:4444 def run_test(browser_name): 一个简单的测试任务 if browser_name chrome: desired_caps DesiredCapabilities.CHROME elif browser_name firefox: desired_caps DesiredCapabilities.FIREFOX else: return print(f开始启动 {browser_name} 测试...) driver webdriver.Remote(command_executorgrid_url, desired_capabilitiesdesired_caps) try: driver.get(https://www.bing.com) title driver.title print(f[{browser_name}] 页面标题: {title}) time.sleep(2) # 模拟一些操作 finally: driver.quit() print(f{browser_name} 测试完成。) if __name__ __main__: # 定义要测试的浏览器列表 browsers [chrome, firefox, chrome] # 启动两个Chrome一个Firefox threads [] for browser in browsers: t threading.Thread(targetrun_test, args(browser,)) threads.append(t) t.start() for t in threads: t.join() print(所有并行测试执行完毕。)运行这个脚本你会在Grid控制台看到几乎同时创建了三个会话两个分配给Chrome节点一个分配给Firefox节点前提是你的节点SE_NODE_MAX_SESSIONS设置足够大。这完美演示了Grid的并行执行能力。5. 高级配置与生产环境调优快速启动只是第一步。要把这个环境用于团队协作或CI/CD流水线还需要一些优化。5.1 使用特定版本标签使用latest标签虽然方便但在生产环境是危险的因为版本更新可能导致不兼容。docker-selenium镜像提供了丰富的版本标签通常与浏览器版本号绑定。# 在docker-compose.yml中指定版本 services: selenium-hub: image: selenium/hub:4.16.0-20231219 chrome-node: image: selenium/node-chrome:4.16.0-20231219 firefox-node: image: selenium/node-firefox:4.16.0-20231219你可以去 Docker Hub Selenium 官方仓库 查看所有可用的标签。锁定版本能确保环境的一致性。5.2 配置持久化与日志收集默认情况下容器内的日志随着容器销毁而消失。我们需要将日志持久化到主机。services: selenium-hub: # ... 其他配置 ... volumes: - ./logs/hub:/opt/selenium/logs chrome-node: # ... 其他配置 ... volumes: - ./logs/chrome:/opt/selenium/logs firefox-node: # ... 其他配置 ... volumes: - ./logs/firefox:/opt/selenium/logs这样所有容器的日志都会保存在当前目录的logs子文件夹下方便排查问题。5.3 资源限制与调度优化在docker-compose.yml中可以为节点容器设置资源限制防止单个测试消耗过多资源影响宿主机或其他容器。services: chrome-node: # ... 其他配置 ... deploy: # 注意这需要Compose文件版本为3.8且通常用于Swarm模式。单机可用resources。 resources: limits: cpus: 2.0 # 限制最多使用2个CPU核心 memory: 2048M # 限制最多使用2GB内存 reservations: cpus: 0.5 memory: 512M对于单机Docker Compose可以使用resources字段chrome-node: # ... 其他配置 ... mem_limit: 2048m mem_reservation: 512m cpus: 2.0同时根据你给节点设置的内存限制合理调整SE_NODE_MAX_SESSIONS。一个Chrome实例可能就需要500MB-1GB内存所以一个内存2GB的节点设置SE_NODE_MAX_SESSIONS2是比较安全的。5.4 启用VNC密码与NoVNC之前我们设置了SE_VNC_NO_PASSWORD1是为了方便。在生产环境应该设置VNC密码。docker-selenium镜像通过SE_VNC_PASSWORD环境变量来设置密码。environment: - SE_VNC_PASSWORDyour_secure_password_here # 移除 SE_VNC_NO_PASSWORD设置了密码后连接VNC端口就需要输入密码了。此外你还可以通过SE_NO_VNC_PORT环境变量启用NoVNC这是一个基于Web的VNC客户端。environment: - SE_VNC_PASSWORDyour_password - SE_NO_VNC_PORT7900 # 启用NoVNC并映射端口 ports: - 7901:7900 # 将主机7901映射到容器NoVNC端口然后你就可以通过浏览器访问http://localhost:7901输入密码your_password来观看测试画面比安装独立的VNC客户端更方便。6. 常见问题排查与实战技巧即便用了Docker有些坑还是得自己踩过才知道。这里分享几个高频问题和解决思路。6.1 节点无法注册到Hub现象Hub启动成功节点容器也运行了但在Grid控制台看不到节点。排查步骤检查网络确保Hub和Node在同一个Docker网络中。使用docker network inspect grid查看容器是否都正确连接。检查环境变量确认Node容器中的SE_EVENT_BUS_HOST环境变量值正是Hub的容器名在Compose中或服务名在自定义网络中。在Node容器的日志里经常能看到连接错误信息。查看日志这是最直接的方式。分别查看Hub和Node的日志。docker logs selenium-hub docker logs chrome-node在Node日志中寻找类似Registered或Error的关键字。常见错误是连接不上selenium-hub:4442这基本就是网络或主机名配置问题。6.2 浏览器在容器中崩溃或无响应现象测试脚本能启动会话但执行一会儿就超时或报错VNC看到浏览器卡死或消失。解决方案挂载/dev/shm这是首要检查项。务必在Chrome和Firefox节点的配置中添加-v /dev/shm:/dev/shm。增加共享内存大小如果挂载后仍有问题可以尝试指定更大的shm大小。chrome-node: # ... shm_size: 2gb # 为容器指定2GB的共享内存添加浏览器启动参数在测试脚本的ChromeOptions中添加一些针对容器环境的参数。chrome_options.add_argument(--no-sandbox) # 禁用沙盒容器中有时需要 chrome_options.add_argument(--disable-dev-shm-usage) # 使用临时文件而非/dev/shm避免内存不足 chrome_options.add_argument(--disable-gpu) # 禁用GPU在无头或虚拟化环境中检查资源限制确保Docker容器有足够的内存和CPU分配。浏览器很吃资源。6.3 会话超时与清理Grid 4 有会话超时机制。如果一个会话长时间空闲Grid会自动清理它。有时这会导致你的长时测试意外中断。配置可以通过环境变量调整Hub的超时设置。selenium-hub: image: selenium/hub:latest environment: - SE_SESSION_REQUEST_TIMEOUT300 # 会话请求超时秒 - SE_SESSION_RETRY_INTERVAL5 # 重试间隔 - SE_RELAXED_CHECKStrue # 对一些检查放宽有助于稳定性但更根本的解决方法是确保你的测试脚本在完成操作后及时调用driver.quit()并优化测试用例避免不必要的长时间等待。6.4 如何扩展更多节点横向扩展是Grid的优势。假设流量变大需要增加一个Chrome节点。使用Docker Compose非常简单你可以直接复制一份chrome-node的配置改个容器名和主机VNC端口映射即可。chrome-node-2: image: selenium/node-chrome:latest container_name: chrome-node-2 depends_on: - selenium-hub environment: - SE_EVENT_BUS_HOSTselenium-hub - SE_EVENT_BUS_PUBLISH_PORT4442 - SE_EVENT_BUS_SUBSCRIBE_PORT4443 - SE_NODE_MAX_SESSIONS4 - SE_VNC_NO_PASSWORD1 volumes: - /dev/shm:/dev/shm ports: - 5903:5900 # 使用新的端口 networks: - selenium-grid然后运行docker-compose up -d chrome-node-2新的节点就会自动注册到现有的Hub上。Grid会自动将新的测试请求分发到空闲节点。6.5 镜像下载慢怎么办如果从Docker Hub拉取镜像速度慢可以配置国内镜像加速器。修改Docker Desktop的配置或Linux下的/etc/docker/daemon.json{ registry-mirrors: [ https://docker.mirrors.ustc.edu.cn, https://hub-mirror.c.163.com ] }修改后重启Docker服务。7. 集成到CI/CD流水线将Docker Selenium Grid集成到Jenkins、GitLab CI或GitHub Actions中可以实现自动化测试的闭环。以GitHub Actions为例一个简单的 workflow 可能如下name: Selenium Grid Test on: [push] jobs: test: runs-on: ubuntu-latest services: # 启动Selenium Grid服务 selenium-hub: image: selenium/hub:latest ports: - 4444:4444 chrome-node: image: selenium/node-chrome:latest env: SE_EVENT_BUS_HOST: selenium-hub SE_EVENT_BUS_PUBLISH_PORT: 4442 SE_EVENT_BUS_SUBSCRIBE_PORT: 4443 SE_VNC_NO_PASSWORD: 1 volumes: - /dev/shm:/dev/shm options: - --shm-size2g --link selenium-hub steps: - uses: actions/checkoutv2 - name: Set up Python uses: actions/setup-pythonv2 with: python-version: 3.9 - name: Install dependencies run: | pip install selenium pytest - name: Wait for Grid to be ready run: | # 一个简单的脚本等待Grid Hub就绪 until curl -s http://localhost:4444/wd/hub/status | grep -q ready:true; do echo Waiting for Grid... sleep 1 done - name: Run tests run: | # 运行你的测试脚本REMOTE_URL指向服务名 python -m pytest your_test_suite.py --remote-urlhttp://selenium-hub:4444/wd/hub关键点在CI环境中容器之间通过服务名通信如http://selenium-hub:4444而不是localhost。services块让GitHub Actions自动管理这些依赖容器的生命周期。通过以上七个部分的拆解从概念到实践从快速启动到生产调优你应该已经掌握了搭建和管理一个Docker化Selenium Grid环境的全套技能。这套方案的核心优势在于其声明式的配置和极佳的可复现性一份docker-compose.yml文件就是整个环境的蓝图在任何支持Docker的机器上都能一键拉起完全一致的环境这为团队协作和自动化测试带来了巨大的便利。剩下的就是根据你的具体业务测试需求去编写更健壮、更高效的测试用例了。