Docker+Selenium Grid构建分布式UI自动化测试集群实战指南
1. 项目概述与核心价值
如果你正在为UI自动化测试执行缓慢、环境配置繁琐、多浏览器兼容性测试头疼,那么今天分享的这个“Docker + Selenium Grid”组合方案,绝对是你工具箱里不可或缺的利器。我经历过太多项目,从单机脚本跑几个小时,到后来团队协作时环境不一致导致的“在我机器上好好的”窘境,最终这套方案成了我们团队的测试基础设施标配。它本质上解决的,是通过容器化技术将Selenium Grid的分布式测试能力标准化、轻量化,让你能像搭积木一样,快速构建起一个支持并行、跨浏览器、且环境一致的自动化测试集群。
简单来说, Docker负责封装和交付标准化的测试环境 ,而 Selenium Grid负责调度和分发测试任务 。你不再需要在一台台机器上手动安装Java、浏览器、驱动,也不用担心不同测试人员环境差异导致的结果不一致。通过几个Docker命令,你就能在几分钟内拉起一个包含Chrome、Firefox、Edge等多种浏览器的测试网格,然后你的自动化脚本只需要告诉Grid“我要一个Chrome”,Grid就会自动把任务分配到空闲的、装有Chrome的容器节点上去执行。这对于需要快速反馈的持续集成流水线,或者需要进行大规模回归测试的场景,效率提升是数量级的。
接下来,我会从一个实践者的角度,带你从零开始,一步步拆解如何搭建、配置、使用这个分布式测试环境,并分享我们在实际项目中踩过的坑和总结出的最佳实践。无论你是测试开发工程师、DevOps,还是对自动化测试感兴趣的后端开发,这篇内容都能让你获得一套可直接复现的完整方案。
2. 环境整体设计与架构解析
在动手敲命令之前,理解整个架构的设计思路至关重要。这能帮助你在遇到问题时,知道该从哪个环节去排查,也能让你根据自己团队的实际需求进行灵活的调整。
2.1 为什么是Docker + Selenium Grid?
传统的UI自动化测试,尤其是需要多浏览器并行时,通常面临几个痛点:
- 环境配置地狱 :每台测试机都需要安装特定版本的JDK、浏览器、浏览器驱动(如chromedriver),版本匹配是个精细活,一旦出错,脚本就无法运行。
- 资源利用率低 :单台机器通常只能串行执行测试用例,或者通过多线程模拟并行,但受限于单机硬件资源(CPU、内存、网络),并发数有上限,且浏览器实例间可能相互干扰。
- 维护成本高 :浏览器版本升级,需要同步更新所有测试机上的驱动;测试机操作系统差异也会带来额外适配工作。
- 难以集成CI/CD :在Jenkins、GitLab CI等工具中动态准备和清理测试环境比较麻烦。
Docker 的引入,完美解决了环境一致性与便携性问题。Selenium官方维护的 docker-selenium 镜像,已经将Selenium Server、浏览器、驱动以及必要的依赖(如字体、显示服务器)打包好。你拉取一个镜像,运行一个容器,就是一个立即可用的、隔离的浏览器测试环境。
Selenium Grid 则解决了任务调度与资源池化的问题。它采用经典的Hub-Node(中心-节点)架构:
- Hub : 作为大脑和调度中心。它不执行任何测试,只负责接收来自自动化测试脚本的请求,并根据请求中描述的“能力”(Capabilities,如浏览器类型、版本、平台等),将请求转发给注册到它这里、且符合要求的Node。
- Node : 作为执行单元。每个Node都是一个独立的测试执行环境,上面运行着Selenium Server以及一种或多种浏览器。Node启动后会向Hub注册,告知Hub自己具备哪些“能力”(例如:“我能提供Chrome 125.0”)。
当你的测试脚本通过 RemoteWebDriver 连接到Hub时,Hub会根据脚本的需求,从注册的Node中挑选一个合适的,建立连接,后续的所有浏览器操作指令都会由Hub转发给该Node执行。
将两者结合, Docker负责快速、批量地生产出标准化的Node(甚至Hub) ,而 Grid负责高效地管理和利用这些Node资源 。你可以轻松地在一台性能强劲的服务器上,运行多个浏览器容器作为Node,实现单机多并发;也可以将Node分布在多台物理机上,实现真正的分布式并发,突破单机资源限制。
2.2 核心组件与网络规划
在部署前,我们需要规划好各个组件的角色、位置以及它们之间的网络通信。
- Hub容器 : 通常一个集群只需要一个Hub。你需要将它运行在一个所有Node和测试执行机都能访问到的主机上。Hub默认监听4444端口(用于WebDriver协议通信)和4442/4443端口(用于内部事件总线通信)。
- Node容器 : 你可以运行任意多个Node。每个Node容器需要知道Hub的地址(
SE_EVENT_BUS_HOST)以便注册。Node会暴露5555端口用于与Hub通信,以及5900端口用于VNC远程查看(调试用)。 - 测试执行机 : 运行你自动化测试脚本(Python pytest、Java TestNG等)的机器。它只需要能通过网络访问到Hub的4444端口即可,完全不需要安装任何浏览器或驱动。
- 网络模式选择 : 这是实践中的一个关键决策点。
- 场景A:所有容器跑在同一台宿主机(本地开发/快速验证) 。
- 方案 :使用Docker默认的
bridge网络,或者更简单的,使用docker-compose。容器间通过容器名或自定义网络互通非常方便。此时,SE_EVENT_BUS_HOST可以设置为Hub的容器名(如selenium-hub)。
- 方案 :使用Docker默认的
- 场景B:Node分布在多台不同的物理机/虚拟机(生产级分布式) 。
- 方案 :Hub运行在一台主机上,并 将端口映射到主机网络 (
-p 4442-4444:4442-4444)。Node运行在其他主机上,启动时,SE_EVENT_BUS_HOST必须设置为Hub宿主机的 真实IP地址 ,确保跨主机网络可达。 - 重要提醒 :务必确保宿主机防火墙开放了相关端口(4442-4444, 5555, 5900),否则容器间无法通信。
- 方案 :Hub运行在一台主机上,并 将端口映射到主机网络 (
- 场景A:所有容器跑在同一台宿主机(本地开发/快速验证) 。
实操心得 :在规划初期,我强烈建议先从“单机多容器”模式开始,使用
docker-compose来管理,这能极大简化网络配置和生命周期管理。等整个流程跑通后,再扩展到多机部署。一开始就搞多机,网络问题可能会让你寸步难行。
3. 实战搭建:从零构建分布式测试集群
理论清晰后,我们进入实战环节。我会以最常用的“单机部署Hub和多个Node”为例,因为这是学习和中小项目中最实用的场景。多机部署的原理完全相同,只是IP地址需要替换为实际的主机IP。
3.1 基础环境准备
首先,确保你的机器上已经安装了Docker和Docker Compose。这是所有操作的前提。
- 对于Linux(Ubuntu/CentOS) : 通过官方脚本或包管理器安装即可。安装后记得将当前用户加入
docker用户组,避免每次命令都要sudo。# 以Ubuntu为例,安装Docker Engine sudo apt-get update sudo apt-get install docker.io sudo systemctl start docker sudo systemctl enable docker # 安装Docker Compose插件(Docker新版本已集成) sudo apt-get install docker-compose-plugin # 将当前用户加入docker组 sudo usermod -aG docker $USER # 退出终端重新登录生效 - 对于Windows/macOS : 直接下载并安装 Docker Desktop 。它自带了Docker Engine、CLI和Compose。安装完成后,通常需要重启电脑。
验证安装:
docker --version
docker-compose --version # 或 docker compose version
3.2 使用Docker Compose一键部署
手动 docker run 每个容器虽然直观,但参数多,管理麻烦。 docker-compose.yml 文件能让我们用声明式的方式定义和运行多容器应用,是管理Selenium Grid集群的绝佳工具。
创建一个名为 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_PUBLISH_PORT=4442
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443
- SE_OPTS=--log-level FINE # 可选,设置Hub日志级别,调试时有用
networks:
- selenium-grid
chrome-node:
image: selenium/node-chrome:latest
container_name: chrome-node
depends_on:
- selenium-hub
environment:
- SE_EVENT_BUS_HOST=selenium-hub
- SE_EVENT_BUS_PUBLISH_PORT=4442
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443
- SE_NODE_MAX_SESSIONS=4 # 单个Node最大并发会话数
- SE_NODE_OVERRIDE_MAX_SESSIONS=true
- SE_VNC_NO_PASSWORD=1
- SE_START_VNC=true
volumes:
- /dev/shm:/dev/shm # 挂载宿主机共享内存,解决浏览器崩溃问题
shm_size: 2g # 另一种设置共享内存大小的方式,与上面volumes二选一即可
ports:
- "5901:5900" # 将容器5900映射到主机5901,避免端口冲突
- "5555:5555"
networks:
- selenium-grid
firefox-node:
image: selenium/node-firefox:latest
container_name: firefox-node
depends_on:
- selenium-hub
environment:
- SE_EVENT_BUS_HOST=selenium-hub
- SE_EVENT_BUS_PUBLISH_PORT=4442
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443
- SE_NODE_MAX_SESSIONS=4
- SE_NODE_OVERRIDE_MAX_SESSIONS=true
- SE_VNC_NO_PASSWORD=1
- SE_START_VNC=true
volumes:
- /dev/shm:/dev/shm
shm_size: 2g
ports:
- "5902:5900" # 映射到主机5902
- "5556:5555" # 映射到主机5556,避免与chrome-node冲突
networks:
- selenium-grid
networks:
selenium-grid:
driver: bridge
关键配置解析:
- 网络(Networks) : 我们创建了一个名为
selenium-grid的自定义桥接网络。所有服务(hub, chrome-node, firefox-node)都加入这个网络。在这个网络里,容器间可以通过 服务名 (如selenium-hub)直接通信,这是最简洁的方式。 - Hub配置 : 暴露了4442-4444端口到宿主机。环境变量
SE_EVENT_BUS_PUBLISH_PORT和SE_EVENT_BUS_SUBSCRIBE_PORT明确指定了事件总线端口,与端口映射保持一致。 - Node配置 :
-
SE_EVENT_BUS_HOST=selenium-hub: Node通过这个主机名在Docker网络内找到Hub。 这是单机部署的关键 ,你不需要知道宿主机的IP。 -
SE_NODE_MAX_SESSIONS: 这个参数决定了该Node容器能同时运行多少个浏览器实例。设置它需要综合考虑容器分配的内存、CPU以及测试用例的资源消耗。对于Chrome/Firefox,4是一个比较保守且通用的起步值。我们的经验是,一个分配了2核4G内存的容器,跑4个Chrome会话比较稳定。 -
SE_NODE_OVERRIDE_MAX_SESSIONS=true: 必须设置为true,上述最大会话数的配置才会生效。 -
shm_size: 2g和volumes: - /dev/shm:/dev/shm: 这是解决Docker容器内浏览器崩溃的最重要配置! Chrome和Firefox会使用/dev/shm(共享内存)进行进程间通信。Docker容器默认的64MB共享内存通常不够,会导致浏览器崩溃。这里我们通过shm_size参数将容器内的/dev/shm大小设置为2GB。挂载宿主机的/dev/shm是另一种等效做法,通常选其一即可。 - 端口映射 : 我们将chrome-node的VNC端口(5900)映射到宿主机的5901,firefox-node的映射到5902,这样可以通过不同的端口分别访问两个节点的VNC界面。5555端口也做了区分映射,避免冲突。
-
在包含 docker-compose.yml 文件的目录下,执行以下命令启动整个集群:
docker-compose up -d
-d 参数表示后台运行。执行后,Docker会拉取镜像(如果本地没有)并启动所有容器。
使用 docker-compose ps 查看服务状态,确保所有容器都是 Up 状态。访问 http://localhost:4444 ,你应该能看到Selenium Grid的控制台页面。刚开始可能看不到Node,等待十几秒刷新一下,如果配置正确,chrome-node和firefox-node就会注册上来。
3.3 验证与调试:使用Grid控制台和VNC
Grid控制台(http://localhost:4444) 是你的管理面板。在这里你可以:
- 查看所有已注册的Node及其能力(浏览器、版本、平台)。
- 查看当前正在执行的会话(Sessions)。
- 配置队列等高级参数(通常默认即可)。
VNC远程查看 : 这是调试UI自动化测试的“上帝视角”。当测试脚本在Node容器中操作浏览器时,你可以实时看到浏览器画面。
- 下载一个VNC Viewer客户端,如RealVNC VNC Viewer。
- 在VNC Viewer中连接地址。对于我们的配置:
- Chrome节点:
localhost:5901 - Firefox节点:
localhost:5902
- Chrome节点:
- 连接时,因为我们设置了
SE_VNC_NO_PASSWORD=1,所以密码留空即可。 - 连接成功后,你会看到一个干净的桌面,当有测试任务在该节点执行时,浏览器窗口就会在这里出现。
注意事项 : VNC功能虽然强大,但会消耗额外资源。在生产环境的CI/CD流水线中,通常不需要开启VNC。可以通过设置
SE_START_VNC=false来禁用它,以节省资源。仅在调试或需要录制测试视频时开启。
4. 编写并执行分布式自动化测试
环境就绪后,我们需要编写能够连接到Grid的测试脚本。这里以Python(pytest)和Java(TestNG)两种最流行的语言为例。
4.1 Python + pytest 实现并行测试
Python生态中, pytest 是测试框架的事实标准,结合 pytest-xdist 插件可以轻松实现测试用例的并行执行。
第一步:安装依赖
pip install pytest selenium pytest-xdist
第二步:编写测试脚本( test_grid_demo.py )
这里的关键是使用 webdriver.Remote 来连接Grid Hub,并通过 DesiredCapabilities (或新版 Options )来指定浏览器能力。
import pytest
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
import time
# 定义Hub的地址
HUB_URL = "http://localhost:4444/wd/hub"
@pytest.fixture(scope="function")
def driver(request):
"""
为每个测试函数提供一个远程WebDriver实例。
使用request.param来参数化浏览器类型。
"""
browser = request.param
if browser == "chrome":
# 方法1:使用DesiredCapabilities(传统方式,仍可用)
# capabilities = DesiredCapabilities.CHROME.copy()
# 方法2:使用Options(推荐,更现代,支持更多配置)
options = webdriver.ChromeOptions()
# 可以添加浏览器选项,例如无头模式
# options.add_argument('--headless')
# options.add_argument('--no-sandbox')
# options.add_argument('--disable-dev-shm-usage') # 在Docker中建议添加
driver = webdriver.Remote(command_executor=HUB_URL, options=options)
elif browser == "firefox":
options = webdriver.FirefoxOptions()
# options.add_argument('-headless')
driver = webdriver.Remote(command_executor=HUB_URL, options=options)
else:
raise ValueError(f"Unsupported browser: {browser}")
yield driver
# 测试结束后,退出浏览器,释放Node上的会话资源
driver.quit()
# 使用pytest的parametrize装饰器,让测试函数对'chrome'和'firefox'两个参数各运行一次
@pytest.mark.parametrize("driver", ["chrome", "firefox"], indirect=True)
def test_search_on_baidu(driver):
"""一个简单的测试用例:访问百度并搜索关键词。"""
try:
driver.get("https://www.baidu.com")
# 显式等待通常比time.sleep更好,这里为了示例简单使用sleep
time.sleep(2)
search_box = driver.find_element(By.ID, "kw")
search_box.send_keys("Docker Selenium Grid")
search_box.submit()
time.sleep(3)
# 简单的断言:检查页面标题是否包含搜索词
assert "Docker Selenium Grid" in driver.title
print(f"Test passed for {driver.capabilities['browserName']}")
except Exception as e:
print(f"Test failed for {driver.capabilities['browserName']}: {e}")
raise
第三步:使用pytest-xdist并行执行
在终端中,进入脚本所在目录,运行:
pytest test_grid_demo.py -v -n 2
-
-v: 显示详细输出。 -
-n 2: 指定使用2个worker进程并行执行。pytest-xdist会收集到两个测试项(因为参数化),然后用两个worker同时执行它们。
执行过程解析 :
- pytest启动2个worker进程。
- 第一个worker执行
test_search_on_baidu(driver="chrome")。它向http://localhost:4444/wd/hub发起请求,要求一个Chrome浏览器。 - Hub收到请求,查看注册的Node,发现
chrome-node有能力提供Chrome,于是将请求转发给它。 -
chrome-node容器内启动一个Chrome实例,并返回一个会话ID给Hub,再传回给worker1的脚本。后续所有driver.xxx的操作,都会通过Hub转发到chrome-node的这个Chrome实例上。 - 与此同时,第二个worker执行
test_search_on_baidu(driver="firefox"),过程类似,但请求会被Hub分配给firefox-node。 - 这样,两个测试用例就在不同的浏览器、不同的容器中 真正并行 执行了。你可以在Grid控制台看到两个活跃的会话,也可以用VNC分别连接到5901和5902端口观察两个浏览器的实时操作。
4.2 Java + TestNG 实现并行测试
Java生态中,TestNG是支持并发测试非常成熟的框架。
第一步:项目依赖(Maven pom.xml )
<dependencies>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>4.15.0</version> <!-- 使用较新版本 -->
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.8.0</version>
<scope>test</scope>
</dependency>
</dependencies>
第二步:编写测试基类与测试用例
// BaseTest.java
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Parameters;
import java.net.MalformedURLException;
import java.net.URL;
public class BaseTest {
protected WebDriver driver;
protected static final String HUB_URL = "http://localhost:4444/wd/hub";
@BeforeMethod
@Parameters("browser")
public void setUp(String browser) throws MalformedURLException {
DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.setBrowserName(browser); // 从testng.xml接收浏览器参数
// 可以设置版本、平台等更多能力
// capabilities.setVersion("125.0");
// capabilities.setPlatform(Platform.LINUX);
driver = new RemoteWebDriver(new URL(HUB_URL), capabilities);
driver.manage().window().maximize();
}
@AfterMethod
public void tearDown() {
if (driver != null) {
driver.quit();
}
}
}
// GridDemoTest.java
import org.openqa.selenium.By;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.testng.Assert;
import org.testng.annotations.Test;
import java.time.Duration;
public class GridDemoTest extends BaseTest {
@Test
public void testBaiduSearch() {
driver.get("https://www.baidu.com");
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
wait.until(ExpectedConditions.presenceOfElementLocated(By.id("kw")));
driver.findElement(By.id("kw")).sendKeys("TestNG Selenium Grid");
driver.findElement(By.id("su")).click();
wait.until(ExpectedConditions.titleContains("TestNG Selenium Grid"));
Assert.assertTrue(driver.getTitle().contains("TestNG Selenium Grid"));
System.out.println("Test passed on browser: " + ((RemoteWebDriver) driver).getCapabilities().getBrowserName());
}
}
第三步:配置TestNG XML文件以实现并行 创建 testng.xml 文件,配置并行执行策略。
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Selenium Grid Suite" parallel="tests" thread-count="2">
<!-- parallel="tests" 表示以<test>标签为单位并行 -->
<!-- thread-count="2" 表示最大并发线程数为2 -->
<test name="Chrome Test">
<parameter name="browser" value="chrome"/>
<classes>
<class name="GridDemoTest"/>
</classes>
</test>
<test name="Firefox Test">
<parameter name="browser" value="firefox"/>
<classes>
<class name="GridDemoTest"/>
</classes>
</test>
</suite>
第四步:执行测试 你可以使用IDE(如IntelliJ IDEA)直接运行 testng.xml ,或者通过Maven命令执行:
mvn test -DsuiteXmlFile=testng.xml
TestNG会启动两个线程,分别执行两个 <test> ,从而同时向Grid请求Chrome和Firefox浏览器,实现并行测试。
5. 高级配置、优化与故障排查
基础搭建和脚本编写只是第一步,要让这套系统在生产环境中稳定、高效地运行,还需要一些进阶的配置和优化技巧。
5.1 指定浏览器版本与规模化部署
使用特定版本的浏览器镜像 : 测试常常需要锁定特定的浏览器版本。 selenium/node-chrome:latest 标签可能随时变化。你应该使用固定版本的镜像。
- 去 Docker Hub 查找需要的标签,例如
selenium/node-chrome:120.0。 - 在
docker-compose.yml中修改image字段:chrome-node: image: selenium/node-chrome:120.0 # ... 其他配置保持不变
使用 docker-compose 扩展Node数量 : 如果你想快速增加同类型浏览器的节点数量以提升并发能力, docker-compose 的 scale 命令非常方便。
# 将chrome-node扩展到3个实例
docker-compose up -d --scale chrome-node=3
Compose会自动为新的容器实例生成唯一的名称(如 projectname_chrome-node_1 , projectname_chrome-node_2 ),它们都会注册到同一个Hub上。你可以在Grid控制台看到多个提供相同能力的Chrome节点。
5.2 性能调优与稳定性保障
-
SE_NODE_MAX_SESSIONS设置 : 这个值不是越大越好。它受限于容器分配的资源(CPU、内存)。一个经验法则是:- 观察单个浏览器实例在执行测试时的内存占用(可以通过
docker stats命令观察容器内存)。 - 为容器分配的内存 / 单个实例内存占用 ≈ 最大会话数。并预留一部分内存给操作系统和Selenium Server本身。
- 例如,容器分配了4G内存,一个Chrome会话峰值占用800MB,那么
SE_NODE_MAX_SESSIONS设置为4比较安全。盲目设置成10,会导致容器内存耗尽(OOM)被系统杀死。
- 观察单个浏览器实例在执行测试时的内存占用(可以通过
-
会话超时与清理 : 测试脚本异常退出可能导致浏览器会话没有正常
quit(),这些“僵尸会话”会一直占用Node资源。Grid有自动清理机制,但你可以通过环境变量调整:-
SE_NODE_SESSION_TIMEOUT: 设置会话超时时间(秒),默认300。超时后Grid会强制清理该会话。 - 在你的测试框架中,务必在
tearDown或@AfterMethod、@AfterTest等钩子中确保driver.quit()被调用。
-
-
使用无头模式(Headless) : 在CI/CD流水线中,通常不需要图形界面。使用无头模式可以显著减少资源消耗,提高执行速度。
- 在Python的
Options中添加add_argument('--headless')。 - 在Java的
DesiredCapabilities或Options中设置。 - 注意 : 无头模式下无法使用VNC查看,调试时可以先关闭此选项。
- 在Python的
5.3 常见问题与排查实录
即使按照步骤操作,你也可能会遇到一些问题。这里记录了几个我们团队高频遇到的坑和解决方法。
问题1:Node启动成功,但在Grid控制台看不到(状态为 Unreachable )
- 现象 : Node容器日志显示已启动并尝试注册,但Hub控制台显示Node不可用。
- 排查 :
- 网络不通 : 这是最常见的原因。检查Node容器内是否能ping通Hub的地址。在单机
docker-compose部署中,确保SE_EVENT_BUS_HOST设置的是服务名(如selenium-hub),并且所有服务在同一个自定义网络中。在多机部署中,检查防火墙是否放行了4442、4443、5555端口。 - 端口映射错误 : 确保Hub的4442和4443端口正确映射到了宿主机,并且Node配置的
SE_EVENT_BUS_PUBLISH_PORT和SE_EVENT_BUS_SUBSCRIBE_PORT与Hub映射出来的端口一致。 - 查看Hub和Node日志 : 使用
docker logs -f selenium-hub和docker logs -f chrome-node查看实时日志,通常会有明确的错误信息。
- 网络不通 : 这是最常见的原因。检查Node容器内是否能ping通Hub的地址。在单机
问题2:测试脚本能连接到Hub,但无法启动浏览器,报 SessionNotCreatedException
- 现象 : 脚本抛出异常,提示无法创建新会话。
- 排查 :
- Node资源不足 : 检查Node的
SE_NODE_MAX_SESSIONS是否已满。Grid控制台会显示每个Node的“最大会话数”和“当前会话数”。 - 浏览器驱动不匹配 : 如果你使用了非
latest标签的镜像,或者自行构建了镜像,确保容器内的浏览器版本和驱动版本匹配。官方镜像通常已经匹配好。 - 共享内存(/dev/shm)不足 : 这是导致浏览器在容器内崩溃的元凶。 务必确保 在
docker run命令或docker-compose.yml中设置了足够的shm-size(如2g)或挂载了宿主机的/dev/shm。
- Node资源不足 : 检查Node的
问题3:测试执行速度慢,或者偶尔超时
- 现象 : 测试用例执行时间远长于本地,或出现
TimeoutException。 - 排查 :
- 网络延迟 : 如果Hub、Node和测试执行机分布在不同的机器甚至不同的机房,网络延迟会显著影响指令传输速度。尽量让它们处于同一个低延迟的网络内。
- Hub成为瓶颈 : 在超高并发(如上百个会话)下,单Hub可能成为瓶颈。可以考虑Selenium Grid的“完全分布式”模式,或者使用更强大的机器运行Hub。
- 优化测试脚本 : 避免在远程执行中使用大量的
time.sleep(),改用显式等待(WebDriverWait)。减少不必要的页面截图、日志输出等操作。
问题4:如何查看实时运行的浏览器画面?
- 方案 : 使用VNC。确保Node容器启动时设置了
SE_START_VNC=true和SE_VNC_NO_PASSWORD=1(或设置密码),并将容器的5900端口映射到宿主机。使用VNC Viewer连接宿主机IP:映射端口即可。这在调试元素定位、验证操作流程时非常有用。
问题5:想录制测试视频怎么办?
- 方案 : Selenium Grid官方镜像的Node本身就支持视频录制。通过环境变量
SE_RECORD_VIDEO=true可以开启。录制好的视频文件会保存在容器内的/videos目录,你可以通过挂载卷(volumes)的方式将其持久化到宿主机。
测试结束后,视频文件会自动生成在宿主机的chrome-node: image: selenium/node-chrome:latest environment: - SE_RECORD_VIDEO=true volumes: - ./videos:/videos # 将宿主机当前目录下的videos文件夹挂载到容器/videos./videos目录下。
6. 集成到CI/CD流水线
将Docker Selenium Grid集成到Jenkins、GitLab CI、GitHub Actions等CI/CD工具中,可以实现自动化测试的常态化运行。
核心思路 :
- 动态创建测试环境 : 在Pipeline的某个阶段(例如
post { always { ... } }或专门的测试阶段),使用docker-compose up -d命令启动Selenium Grid集群。 - 等待服务就绪 : 启动后,需要添加一个健康检查步骤,确保Hub和Node完全启动并可以接受请求。一个简单的方法是循环调用Hub的
/status端点(http://hub-host:4444/status),直到返回成功。 - 执行测试 : 在环境就绪后,运行你的pytest或TestNG测试套件。测试脚本中Hub的地址应配置为CI环境中的服务名或IP(例如,在Docker Compose网络中,就是
selenium-hub)。 - 收集结果与清理 : 测试完成后,收集测试报告和日志(如pytest-html报告、Allure报告)。最后,无论测试成功与否,在Pipeline的最后阶段使用
docker-compose down命令清理所有容器,释放资源。
一个简化的GitLab CI .gitlab-ci.yml 示例 :
stages:
- test
ui-automation:
stage: test
image: python:3.9-slim # 使用一个包含Docker客户端的镜像,或自定义镜像
services:
- docker:dind # 使用Docker-in-Docker服务,允许在CI作业中运行Docker命令
variables:
DOCKER_HOST: tcp://docker:2375
DOCKER_DRIVER: overlay2
before_script:
- apk add --no-cache docker-compose
- pip install pytest selenium pytest-xdist pytest-html
script:
# 1. 启动Selenium Grid集群
- docker-compose up -d
# 2. 等待Hub就绪(简单轮询)
- |
echo "Waiting for Selenium Hub to be ready..."
for i in `seq 1 10`; do
curl -s http://selenium-hub:4444/status | grep -q "ready" && break
sleep 3
done
# 3. 执行测试,生成HTML报告
- pytest test_grid_demo.py -v -n 2 --html=report.html --self-contained-html
# 4. 测试完成后,清理环境
after_script:
- docker-compose down
artifacts:
when: always
paths:
- report.html
reports:
junit: report.xml # 如果生成了JUnit格式报告
这套流程确保了每次代码提交都能在一个全新的、一致的环境中运行UI自动化测试,极大地提高了测试的可靠性和可信度。
更多推荐


所有评论(0)