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

下一篇:问题排查与性能测试——常见问题解决方案、调试技巧和页面性能测试实践。

posted @ 2026-04-07 16:02  小小阿狸。  阅读(5)  评论(0)    收藏  举报