一、为啥要把Selenium和Docker放在一起
很多搞测试的朋友都头疼一件事:每次搭一套 Selenium 测试环境,光装浏览器、驱动、环境变量就能折腾半天。更别提换台电脑或者要跑不同浏览器版本,全得重来一遍。时间都花在配置上了,真正测试的时间反而没多少。
这时候 Docker 就站出来说 “让我来”。它能把整个 Selenium 运行环境打包成一个轻量级容器,你只需要一条命令就能拉起来一个带 Chrome 或者 Firefox 的测试环境。而且 Docker 的环境隔离能力让不同版本互不打架,想切版本就跟换个容器一样简单。
1.1 测试环境的老大难问题
先说说我们以前怎么干的。在一台开发机上装 Chrome,再装对应版本的 ChromeDriver,然后设置 PATH,搞不好还要装个 Java 或者 Python 的库。等换到另一台机器,又要重复一遍,万一操作系统不一样,还得调各种依赖。团队里每个人环境不同,测出来的结果也经常不一致,你明明能跑过的脚本,到别人机器上就报错。
更头疼的是,我们有时候需要同时测 Chrome 80 和 Chrome 90,或者同时跑 Firefox 和 Edge。平常手动切换真的很费劲,而且你要是想用 Selenium Grid 玩并发,还得自己搭 Hub 和 Node,那配置量又大了好几倍。
1.2 Docker 能帮上什么忙
Docker 最大的好处就是 “一次构建,到处运行”。你只需要拉一个官方提供的 Selenium 镜像,里面已经装好了浏览器、驱动和 Selenium Server,不管你的电脑是 Windows、Mac 还是 Linux,跑起来的测试环境都一模一样。
而且 Docker 容器启动很快,几秒钟就能就绪。测试完直接删除容器,不留下任何乱七八糟的残留。要是想跑多个浏览器版本,开几个不同标签的容器就行,不会互相干扰。配合 Docker Compose 或者 Selenium Grid 官方镜像,搭建并发测试环境变得轻而易举。
二、准备工作:选对 Docker 镜像
2.1 官方镜像推荐
Selenium 官方在 Docker Hub 上提供了好几个镜像,我用得最多的是这几个:
- selenium/standalone-chrome – 单个 Chrome 节点的独立镜像,开箱即用。
- selenium/standalone-firefox – 类似,不过用的是 Firefox。
- selenium/standalone-edge – 针对 Edge 浏览器。
- selenium/hub – 配合后面的 Node 镜像,组成 Grid 集群。
这些镜像都是官方维护的,更新很及时,而且会跟着浏览器最新版本走。你不用担心驱动版本不匹配的问题,因为它们都已经帮你调好了。
2.2 镜像版本怎么挑
镜像的标签通常像 selenium/standalone-chrome:4.15.0-20231227,前面是 Selenium 版本号,后面是镜像构建日期。我一般直接用 latest 标签来获取最新版,但如果项目有特殊要求,比如必须用 Chrome 某个旧版,就需要去查对应的镜像标签。
另外还有几个带 debug 的镜像,比如 selenium/standalone-chrome-debug,里面加了 VNC 服务,你可以远程连进去看浏览器到底在干啥,调试非常方便。刚开始学的时候我特别爱用这个,因为能看到脚本操作浏览器的过程。
三、启动容器,跑起 Selenium
3.1 最简单的启动命令
要启动一个 Selenium 独立 Chrome 容器,只需要一行命令:
# 拉取最新版 standalone-chrome 镜像
docker pull selenium/standalone-chrome:latest
# 启动容器,映射 4444 端口到宿主机
docker run -d -p 4444:4444 --name selenium-chrome selenium/standalone-chrome:latest
解释一下:
-d表示后台运行。-p 4444:4444把容器的 4444 端口映射到宿主机的 4444 端口,这样就能通过http://localhost:4444访问 Selenium Server。--name给容器起个名字,方便后面管理。
启动后,你可以用 Python 写个脚本连上去试试:
# 技术栈:Python + Selenium
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
# 配置远程连接的选项
chrome_options = Options()
chrome_options.add_argument("--no-sandbox")
chrome_options.add_argument("--disable-dev-shm-usage")
chrome_options.add_argument("--headless") # 无头模式,不显示浏览器窗口
# 连接到 Docker 中的 Selenium Server
driver = webdriver.Remote(
command_executor="http://localhost:4444/wd/hub", # Docker 映射的地址
options=chrome_options
)
try:
# 打开百度
driver.get("https://www.baidu.com")
print(f"页面标题是:{driver.title}") # 打印标题确认环境正常
finally:
driver.quit() # 关闭会话
如果你用的是 Firefox,只要把镜像换成 selenium/standalone-firefox,然后 chrome_options 换成 firefox_options 就行。
3.2 挂载卷让测试脚本飞进去
上面那种方式,测试脚本必须从宿主机连接容器,脚本文件放在宿主机。但有时候我们想把脚本直接放到容器里运行,或者需要容器访问宿主机上的某个文件夹(比如下载目录)。这时候就要用挂载卷。
# 启动时挂载本地文件夹到容器内的 /home/seluser/scripts
docker run -d -p 4444:4444 \
-v /path/on/host/scripts:/home/seluser/scripts \
--name selenium-mount selenium/standalone-chrome:latest
挂载之后,如果你在容器里运行 shell,就能直接看到宿主机的文件。但 Selenium 容器通常不是拿来跑脚本的,而是作为远程服务。所以挂载更常见的用途是让容器里保存截图、日志或者下载文件到宿主机。
比如你想让测试脚本生成的截图保存到宿主机:
# 技术栈:Python + Selenium
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
import time
chrome_options = Options()
chrome_options.add_argument("--no-sandbox")
driver = webdriver.Remote(
command_executor="http://localhost:4444/wd/hub",
options=chrome_options
)
driver.get("https://www.example.com")
time.sleep(2)
# 截图保存在容器内的 /home/seluser/screenshots,因为挂载了卷,所以实际会出现在宿主机目录
driver.save_screenshot("/home/seluser/screenshots/test.png")
driver.quit()
然后在宿主机上就能在 /path/on/host/scripts 里找到截图(因为挂载的路径映射了)。注意容器里的用户是 seluser,默认对挂载目录有写权限,但有时会因为权限问题报错,可以加 :Z 或者用 --user root 来解决。
3.3 端口映射和网络设置
如果你一台机器上要跑多个 Selenium 容器(比如一个 Chrome,一个 Firefox),就不能都用 4444 端口,需要映射不同的端口:
docker run -d -p 4444:4444 --name chrome-v1 selenium/standalone-chrome:latest
docker run -d -p 4445:4444 --name firefox-v1 selenium/standalone-firefox:latest
这样 Chrome 的 Selenium 服务在 localhost:4444,Firefox 在 localhost:4445。脚本里只要把 command_executor 的端口改一下就能连到对应的浏览器。
另外,如果你想在多个容器之间进行网络通信(比如后面说到 Grid 场景),可以创建一个自定义网络:
docker network create selenium-net
docker run -d --network selenium-net --name hub selenium/hub:latest
docker run -d --network selenium-net -e SE_EVENT_BUS_HOST=hub -e SE_EVENT_BUS_PUBLISH_PORT=4442 -e SE_EVENT_BUS_SUBSCRIBE_PORT=4443 --name node-chrome selenium/node-chrome:latest
这里不展开 Grid 细节,后面会讲。
四、并发管理:跑多份测试
4.1 用 Docker Compose 组织多个容器
当你需要同时启动多个容器(比如一个 Hub+ 多个 Node),用手打命令太容易搞错。Docker Compose 可以帮你用 YAML 文件描述整个环境,一条命令全部搞定。
下面是一个简单的示例,同时启动一个 Chrome 节点和一个 Firefox 节点,都注册到同一个 Hub 上:
# 技术栈:docker-compose.yml
version: "3"
services:
selenium-hub:
image: selenium/hub:latest
container_name: selenium-hub
ports:
- "4442:4442"
- "4443:4443"
- "4444:4444"
environment:
- SE_SESSION_REQUEST_TIMEOUT=300
- SE_NODE_MAX_SESSIONS=5
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=2
volumes:
- /dev/shm:/dev/shm
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=2
volumes:
- /dev/shm:/dev/shm
然后执行:
# 在 docker-compose.yml 所在目录启动
docker-compose up -d
这样三个容器就都起来了。Hub 的 4444 端口映射到宿主机,所有测试脚本连到 http://localhost:4444/wd/hub,Hub 会自动把任务分配给 Chrome 或者 Firefox 节点。
4.2 让 Selenium Grid 派上用场
刚才的 Compose 其实已经搭建了一个 Selenium Grid。Grid 的最大优势是支持并发测试。比如你有 5 个测试用例,可以同时发给 Hub,Hub 根据节点配置(SE_NODE_MAX_SESSIONS)决定能同时跑多少个。在脚本里,你只需要创建一个线程池,每个线程创建一个 Remote WebDriver 连到同一个 Hub 地址就行。
# 技术栈:Python + Selenium + 多线程
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
import threading
import time
def run_test(browser_name):
"""
模拟一个测试任务,browser_name 可以是 'chrome' 或 'firefox'
"""
opts = Options()
opts.add_argument("--headless")
opts.add_argument("--no-sandbox")
opts.add_argument("--disable-dev-shm-usage")
if browser_name == 'firefox':
from selenium.webdriver.firefox.options import Options as FirefoxOptions
opts = FirefoxOptions()
opts.add_argument("--headless")
# 连接到 Hub(端口 4444)
driver = webdriver.Remote(
command_executor="http://localhost:4444/wd/hub",
options=opts
)
try:
driver.get("https://www.baidu.com")
print(f"[{browser_name}] 页面标题: {driver.title}")
time.sleep(2) # 模拟一点操作
finally:
driver.quit()
# 启动 5 个线程并发测试
threads = []
for i in range(5):
browser = 'chrome' if i % 2 == 0 else 'firefox'
t = threading.Thread(target=run_test, args=(browser,))
threads.append(t)
t.start()
for t in threads:
t.join()
print("全部测试完成")
运行这个脚本,你就会看到 Hub 把任务分发给节点。如果节点限制最多 2 个并发会话,那么会排队执行。
4.3 动态伸缩容器数量
测试高峰期你可能需要更多节点,低峰期想省资源。Docker 可以手动启动或停止容器,但更优雅的方式是用 Docker Compose 的 --scale 参数:
# 把 chrome-node 扩展到 3 个
docker-compose up -d --scale chrome-node=3 --scale firefox-node=1
Compose 会自动创建多个同名容器(后面会加序号),它们都会注册到 Hub。这样并发能力就翻倍了。测试完了再缩回去:
docker-compose up -d --scale chrome-node=1 --scale firefox-node=1
不过要注意,--scale 需要服务名在 Compose 里定义,而且不能有端口映射冲突(这里节点没有暴露端口到宿主机,所以安全)。
五、实际应用场景和注意事项
5.1 哪些项目最适合
这种方式特别适合以下场景:
- 持续集成(CI):比如 Jenkins、GitLab CI 里,每次构建都启动新的容器跑测试,跑完就销毁,保证环境清洁。
- 多浏览器兼容测试:你需要测 Chrome、Firefox、Edge,甚至不同版本,开几个容器就能覆盖。
- 快速原型验证:临时想测一个新特性,或者帮同事复现 bug,拉一个容器几秒搞定。
- 团队协作:大家用同一个镜像,确保环境一致,减少 "我这边能跑" 的问题。
5.2 资源占用和性能优化
Docker 虽然轻量,但一个 Selenium 容器也要占几百 MB 内存(浏览器的开销)。并发跑多了,机器会扛不住。有几个优化建议:
- 使用
--shm-size=256m或者挂载/dev/shm(像上面 Compose 里那样),避免 Chrome 因共享内存不足崩溃。 - 尽量用无头模式(
--headless),减少图形渲染消耗。 - 限制每个节点的最大会话数(
SE_NODE_MAX_SESSIONS),一般设为 2-5,太多会让浏览器抢资源反而变慢。 - 如果只是做简单的页面截图或 API 测试,可以考虑用更轻的替代方案,比如 Playwright 的无头浏览器。
5.3 常见坑和解决办法
- 容器启动后无法连接:检查端口映射是否正确,防火墙有没有放行,另外 Hub 或 Node 可能需要几秒钟启动,脚本要等一等。
- 权限问题:挂载卷时容器内用户
seluser可能没有写权限,可以在 Dockerfile 里修改,或者挂载时加:Z参数(Linux SELinux)。 - 版本不匹配:Selenium 客户端库版本要和镜像里的 Server 版本一致,否则会出现无法解析新功能的错误。
- 网络不通:容器网络模式下,如果要跨容器通信(比如脚本也在容器里),建议使用同一个网络。
六、总结
把 Selenium 测试环境用 Docker 容器化,相当于给测试流程装上了快速部署的翅膀。你不再需要为环境配置头疼,不再担心多人环境不一致,更不用手动管理浏览器版本。一条命令就能拉起一个浏览器节点,配合 Compose 或 Grid 可以轻松扩展并发能力。
虽然 Docker 容器也有自己的资源开销,但相比省下来的维护时间和提升的稳定性,这点投入绝对值得。只要注意上面提到的几个要点,比如挂载权限、共享内存、版本匹配,你就能享受 “一键部署,随时销毁” 的清爽测试体验。
以后无论是本地开发调试,还是 CI 流水线自动跑用例,都可以把这套方案用起来,让测试工作更舒心。
Comments