Selenium 自动化测试(八):并行测试与 CI/CD 集成
本篇讲解如何使用 Selenium Grid 实现分布式测试、借助云测试平台扩展覆盖范围,以及将自动化测试集成到 CI/CD 流水线。
一、Selenium Grid 分布式测试
1.1 什么是 Selenium Grid?
Selenium Grid 允许你在多台机器、多种浏览器上并行运行测试,大幅提升测试效率。
架构示意:
┌─────────────────┐
│ 测试脚本 │
│ (Test Code) │
└────────┬────────┘
│ HTTP
┌────────▼────────┐
│ Grid Hub │
│ (路由分发器) │
└───┬─────────┬───┘
│ │
┌───▼───┐ ┌──▼────┐ ┌──────────┐
│ Node 1│ │ Node 2│ │ Node 3 │
│Chrome │ │Firefox│ │Edge/Saf. │
│ Win10 │ │ macOS │ │ Linux │
└───────┘ └───────┘ └──────────┘
1.2 Selenium 4 Grid(独立模式)
Selenium 4 内置了全新的 Grid,无需额外安装:
# 启动 Grid(单机模式,同时充当 Hub 和 Node)
java -jar selenium-server-4.x.x.jar standalone
# 指定端口
java -jar selenium-server-4.x.x.jar standalone --port 4444
# 连接到远程 Grid
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
options = Options()
options.browser_version = "120" # 指定浏览器版本
driver = webdriver.Remote(
command_executor="http://localhost:4444/wd/hub",
options=options
)
driver.get("https://www.example.com")
print(driver.title)
driver.quit()
1.3 Hub + Node 分布式模式
# 机器 A:启动 Hub
java -jar selenium-server-4.x.x.jar hub
# 机器 B:启动 Node 并注册到 Hub
java -jar selenium-server-4.x.x.jar node --hub http://machine-a:4444
# 机器 C:启动 Node(指定浏览器和最大并发数)
java -jar selenium-server-4.x.x.jar node \
--hub http://machine-a:4444 \
--max-sessions 5 \
--port 5555
1.4 Docker 部署 Selenium Grid
# 使用 docker-compose 一键启动
# docker-compose.yml
version: '3'
services:
selenium-hub:
image: selenium/hub:4.18
container_name: selenium-hub
ports:
- "4442:4442"
- "4443:4443"
- "4444:4444"
chrome:
image: selenium/node-chrome:4.18
depends_on:
- selenium-hub
environment:
- SE_EVENT_BUS_HOST=selenium-hub
- SE_EVENT_BUS_PUBLISH_PORT=4442
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443
shm_size: '2gb'
firefox:
image: selenium/node-firefox:4.18
depends_on:
- selenium-hub
environment:
- SE_EVENT_BUS_HOST=selenium-hub
- SE_EVENT_BUS_PUBLISH_PORT=4442
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443
shm_size: '2gb'
# 启动
docker-compose up -d
# 访问 Grid 控制台
# http://localhost:4444/ui
二、多浏览器并行测试
2.1 使用 pytest 并行执行
# conftest.py
import pytest
from selenium import webdriver
def pytest_generate_tests(metafunc):
"""参数化:在多个浏览器上运行同一测试"""
if "browser" in metafunc.fixturenames:
metafunc.parametrize("browser", ["chrome", "firefox"], indirect=True)
@pytest.fixture
def browser(request):
"""浏览器 fixture"""
if request.param == "chrome":
driver = webdriver.Chrome()
elif request.param == "firefox":
driver = webdriver.Firefox()
driver.implicitly_wait(10)
yield driver
driver.quit()
# test_search.py
def test_search(browser):
"""在多个浏览器上执行搜索测试"""
browser.get("https://www.baidu.com")
browser.find_element(By.ID, "kw").send_keys("Selenium")
browser.find_element(By.ID, "su").click()
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
WebDriverWait(browser, 10).until(
EC.title_contains("Selenium")
)
# 安装 pytest-xdist 实现并行
pip install pytest-xdist
# 4 个进程并行执行
pytest test_search.py -n 4
2.2 并行测试框架封装
import concurrent.futures
from selenium import webdriver
class BrowserFactory:
"""浏览器工厂"""
@staticmethod
def create(browser_name, options=None):
browsers = {
"chrome": webdriver.Chrome,
"firefox": webdriver.Firefox,
"edge": webdriver.Edge,
}
if browser_name not in browsers:
raise ValueError(f"不支持的浏览器: {browser_name}")
return browsers[browser_name](options=options)
class ParallelTestRunner:
"""并行测试运行器"""
def __init__(self, max_workers=4):
self.max_workers = max_workers
self.results = []
def run_test(self, test_func, browser_name):
"""在指定浏览器上运行测试"""
driver = BrowserFactory.create(browser_name)
try:
result = test_func(driver)
return {"browser": browser_name, "status": "passed", "result": result}
except Exception as e:
return {"browser": browser_name, "status": "failed", "error": str(e)}
finally:
driver.quit()
def run_on_all_browsers(self, test_func, browsers=None):
"""在所有浏览器上并行运行测试"""
browsers = browsers or ["chrome", "firefox", "edge"]
with concurrent.futures.ThreadPoolExecutor(max_workers=self.max_workers) as executor:
futures = {
executor.submit(self.run_test, test_func, browser): browser
for browser in browsers
}
for future in concurrent.futures.as_completed(futures):
result = future.result()
self.results.append(result)
print(f"[{result['status'].upper()}] {result['browser']}")
return self.results
# 使用示例
def test_google_search(driver):
driver.get("https://www.google.com")
driver.find_element(By.NAME, "q").send_keys("Selenium Grid")
driver.find_element(By.NAME, "q").submit()
return driver.title
runner = ParallelTestRunner()
results = runner.run_on_all_browsers(test_google_search)
三、云测试平台
3.1 BrowserStack
from selenium import webdriver
options = webdriver.ChromeOptions()
options.set_capability("browserName", "Chrome")
options.set_capability("browserVersion", "120")
options.set_capability("os", "Windows")
options.set_capability("os_version", "10")
options.set_capability("build", "Selenium 4 Python")
options.set_capability("name", "搜索功能测试")
# BrowserStack 认证
options.set_capability("bstack:options", {
"userName": "YOUR_USERNAME",
"accessKey": "YOUR_ACCESS_KEY"
})
driver = webdriver.Remote(
command_executor="https://hub-cloud.browserstack.com/wd/hub",
options=options
)
driver.get("https://www.google.com")
driver.quit()
3.2 Sauce Labs
from selenium import webdriver
options = webdriver.ChromeOptions()
options.set_capability("browserName", "chrome")
options.set_capability("browserVersion", "latest")
options.set_capability("platformName", "Windows 10")
options.set_capability("sauce:options", {
"username": "YOUR_USERNAME",
"accessKey": "YOUR_ACCESS_KEY",
"build": "CI Build #123",
"name": "登录功能测试"
})
driver = webdriver.Remote(
command_executor="https://ondemand.us-west-1.saucelabs.com/wd/hub",
options=options
)
driver.get("https://www.example.com")
driver.quit()
3.3 云平台对比
| 平台 | 特点 | 免费额度 |
|---|---|---|
| BrowserStack | 设备丰富、支持 Live 测试 | 100 分钟/月 |
| Sauce Labs | CI 集成好、有免费开源项目计划 | 有限免费 |
| LambdaTest | 性价比高、支持 Playwright | 60 分钟/月 |
| TestingBot | 简单易用 | 100 分钟/月 |
四、CI/CD 集成
4.1 GitHub Actions
# .github/workflows/selenium-tests.yml
name: Selenium Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
services:
selenium:
image: selenium/standalone-chrome:4.18
ports:
- 4444:4444
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install dependencies
run: |
pip install -r requirements.txt
pip install pytest pytest-html
- name: Run tests
run: |
pytest tests/ \
--driver Remote \
--capability browserName chrome \
--selenium-host localhost \
--selenium-port 4444 \
--html=report.html \
--self-contained-html
- name: Upload test report
uses: actions/upload-artifact@v4
if: always()
with:
name: test-report
path: report.html
4.2 GitLab CI
# .gitlab-ci.yml
selenium-test:
image: python:3.11
services:
- name: selenium/standalone-chrome:4.18
alias: chrome
before_script:
- pip install -r requirements.txt
- pip install pytest pytest-html
script:
- pytest tests/ --html=report.html --self-contained-html
artifacts:
when: always
paths:
- report.html
variables:
SELENIUM_REMOTE_URL: "http://chrome:4444/wd/hub"
4.3 Jenkins Pipeline
// Jenkinsfile
pipeline {
agent any
stages {
stage('Setup') {
steps {
sh 'pip install -r requirements.txt'
sh 'pip install pytest pytest-html'
}
}
stage('Start Selenium Grid') {
steps {
sh '''
docker network create selenium-net 2>/dev/null || true
docker run -d --net selenium-net -p 4444:4444 \
--name selenium-hub selenium/hub:4.18
docker run -d --net selenium-net \
-e SE_EVENT_BUS_HOST=selenium-hub \
--shm-size=2g selenium/node-chrome:4.18
'''
}
}
stage('Run Tests') {
steps {
sh 'pytest tests/ --html=report.html --self-contained-html'
}
}
stage('Cleanup') {
steps {
sh '''
docker stop selenium-hub || true
docker rm selenium-hub || true
docker network rm selenium-net 2>/dev/null || true
'''
}
}
}
post {
always {
archiveArtifacts artifacts: 'report.html', fingerprint: true
publishHTML target: [
reportDir: '.',
reportFiles: 'report.html',
reportName: 'Selenium Test Report'
]
}
}
}
4.4 CI 环境配置最佳实践
# conftest.py — CI 环境自动检测
import os
from selenium import webdriver
def get_driver():
"""根据环境自动选择本地或远程 driver"""
remote_url = os.getenv("SELENIUM_REMOTE_URL")
if remote_url:
# CI 环境:使用远程 Grid
options = webdriver.ChromeOptions()
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage")
options.add_argument("--headless")
return webdriver.Remote(remote_url, options=options)
else:
# 本地开发环境
options = webdriver.ChromeOptions()
return webdriver.Chrome(options=options)
# pytest conftest.py
import pytest
@pytest.fixture(scope="function")
def driver():
driver = get_driver()
driver.implicitly_wait(10)
yield driver
driver.quit()
五、总结
| 主题 | 关键点 |
|---|---|
| Selenium Grid 4 | 独立模式 / Hub+Node 模式 / Docker 部署 |
| 并行测试 | pytest-xdist / ThreadPoolExecutor / 多浏览器参数化 |
| 云测试平台 | BrowserStack / Sauce Labs / LambdaTest |
| CI/CD 集成 | GitHub Actions / GitLab CI / Jenkins Pipeline |
| 环境适配 | 自动检测 CI/本地环境,灵活切换 driver |
下一篇:问题排查与性能测试——常见问题解决方案、调试技巧和页面性能测试实践。

浙公网安备 33010602011771号