Selenium 高级话题 #

Selenium Grid #

架构概览 #

text
┌─────────────────────────────────────────────────────────────┐
│                    Selenium Grid 架构                        │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│                     ┌─────────────┐                         │
│                     │    Hub      │                         │
│                     │  (调度中心)  │                         │
│                     └──────┬──────┘                         │
│                            │                                │
│         ┌──────────────────┼──────────────────┐             │
│         │                  │                  │             │
│    ┌────▼────┐        ┌────▼────┐        ┌────▼────┐       │
│    │  Node1  │        │  Node2  │        │  Node3  │       │
│    │ Chrome  │        │ Firefox │        │  Safari │       │
│    │ Windows │        │  Linux  │        │  macOS  │       │
│    └─────────┘        └─────────┘        └─────────┘       │
│                                                             │
│  优势:                                                      │
│  • 并行执行测试                                              │
│  • 跨浏览器测试                                              │
│  • 跨平台测试                                                │
│  • 资源优化利用                                              │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Grid 4 配置 #

toml
# config.toml
[server]
host = "0.0.0.0"
port = 4444

[node]
detect-drivers = false
max-sessions = 5

[[node.driver-configuration]]
display-name = "Chrome"
stereotype = '{"browserName": "chrome", "platformName": "linux"}'
max-sessions = 3

[[node.driver-configuration]]
display-name = "Firefox"
stereotype = '{"browserName": "firefox", "platformName": "linux"}'
max-sessions = 2

启动 Grid #

bash
# 启动独立服务器(Hub + Node)
java -jar selenium-server-4.x.jar standalone

# 分别启动 Hub 和 Node
java -jar selenium-server-4.x.jar hub
java -jar selenium-server-4.x.jar node

# 使用 Docker
docker run -d -p 4444:4444 --name selenium-hub selenium/hub
docker run -d --link selenium-hub:hub selenium/node-chrome
docker run -d --link selenium-hub:hub selenium/node-firefox

远程执行 #

python
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options

# 连接到 Grid
options = Options()
options.set_capability("browserName", "chrome")
options.set_capability("platformName", "linux")

driver = webdriver.Remote(
    command_executor="http://localhost:4444/wd/hub",
    options=options
)

driver.get("https://example.com")
print(driver.title)
driver.quit()

多浏览器并行测试 #

python
import pytest
from selenium import webdriver
from selenium.webdriver.chrome.options import Options as ChromeOptions
from selenium.webdriver.firefox.options import Options as FirefoxOptions

def create_remote_driver(browser, hub_url="http://localhost:4444/wd/hub"):
    """创建远程驱动"""
    if browser == "chrome":
        options = ChromeOptions()
        options.set_capability("browserName", "chrome")
    elif browser == "firefox":
        options = FirefoxOptions()
        options.set_capability("browserName", "firefox")
    else:
        raise ValueError(f"不支持的浏览器: {browser}")
    
    return webdriver.Remote(command_executor=hub_url, options=options)

@pytest.fixture(params=["chrome", "firefox"])
def driver(request):
    driver = create_remote_driver(request.param)
    yield driver
    driver.quit()

def test_cross_browser(driver):
    driver.get("https://example.com")
    assert driver.title

性能测试 #

页面加载性能 #

python
from selenium import webdriver
from selenium.webdriver.common.by import By
import time

def measure_page_load(driver, url):
    """测量页面加载时间"""
    start = time.time()
    driver.get(url)
    
    # 等待页面完全加载
    driver.execute_script("return document.readyState") == "complete"
    
    end = time.time()
    return end - start

def measure_performance_metrics(driver):
    """获取性能指标"""
    navigation_timing = driver.execute_script("""
        return JSON.stringify(window.performance.timing);
    """)
    
    import json
    timing = json.loads(navigation_timing)
    
    metrics = {
        "DNS查询": timing["domainLookupEnd"] - timing["domainLookupStart"],
        "TCP连接": timing["connectEnd"] - timing["connectStart"],
        "请求响应": timing["responseEnd"] - timing["requestStart"],
        "DOM解析": timing["domComplete"] - timing["domInteractive"],
        "页面加载": timing["loadEventEnd"] - timing["navigationStart"],
    }
    
    return metrics

# 使用示例
driver = webdriver.Chrome()
driver.get("https://example.com")

metrics = measure_performance_metrics(driver)
for name, value in metrics.items():
    print(f"{name}: {value}ms")

driver.quit()

网络性能分析 #

python
from selenium import webdriver
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
import json

def enable_network_logging():
    """启用网络日志"""
    caps = DesiredCapabilities.CHROME.copy()
    caps["goog:loggingPrefs"] = {"performance": "ALL"}
    return caps

def get_network_logs(driver):
    """获取网络日志"""
    logs = driver.get_log("performance")
    
    requests = []
    for entry in logs:
        message = json.loads(entry["message"])
        message = message.get("message", {})
        
        if message.get("method") == "Network.requestWillBeSent":
            request = message.get("params", {}).get("request", {})
            requests.append({
                "url": request.get("url"),
                "method": request.get("method"),
                "time": entry["timestamp"]
            })
    
    return requests

# 使用示例
caps = enable_network_logging()
driver = webdriver.Chrome(desired_capabilities=caps)

driver.get("https://example.com")

requests = get_network_logs(driver)
for req in requests[:10]:
    print(f"{req['method']} {req['url']}")

driver.quit()

资源监控 #

python
from selenium import webdriver
import psutil
import time

class ResourceMonitor:
    """资源监控器"""
    
    def __init__(self, driver):
        self.driver = driver
        self.chrome_pid = None
    
    def start(self):
        """开始监控"""
        for proc in psutil.process_iter(['pid', 'name']):
            if 'chrome' in proc.info['name'].lower():
                self.chrome_pid = proc.info['pid']
                break
    
    def get_memory_usage(self):
        """获取内存使用"""
        if self.chrome_pid:
            proc = psutil.Process(self.chrome_pid)
            return proc.memory_info().rss / 1024 / 1024  # MB
        return 0
    
    def get_cpu_usage(self):
        """获取 CPU 使用"""
        if self.chrome_pid:
            proc = psutil.Process(self.chrome_pid)
            return proc.cpu_percent()
        return 0

# 使用示例
driver = webdriver.Chrome()
monitor = ResourceMonitor(driver)
monitor.start()

driver.get("https://example.com")

print(f"内存使用: {monitor.get_memory_usage():.2f} MB")
print(f"CPU 使用: {monitor.get_cpu_usage():.2f}%")

driver.quit()

视觉测试 #

截图对比 #

python
from selenium import webdriver
from PIL import Image
import os

def compare_screenshots(image1_path, image2_path, diff_path=None, threshold=0.1):
    """对比两张截图"""
    img1 = Image.open(image1_path)
    img2 = Image.open(image2_path)
    
    if img1.size != img2.size:
        return False, "图片尺寸不同"
    
    diff = Image.new('RGB', img1.size)
    pixels_diff = 0
    total_pixels = img1.size[0] * img1.size[1]
    
    for x in range(img1.size[0]):
        for y in range(img1.size[1]):
            pixel1 = img1.getpixel((x, y))
            pixel2 = img2.getpixel((x, y))
            
            if pixel1 != pixel2:
                diff.putpixel((x, y), (255, 0, 0))
                pixels_diff += 1
            else:
                diff.putpixel((x, y), pixel1)
    
    diff_ratio = pixels_diff / total_pixels
    
    if diff_path and pixels_diff > 0:
        diff.save(diff_path)
    
    return diff_ratio < threshold, f"差异比例: {diff_ratio:.2%}"

# 使用示例
driver = webdriver.Chrome()
driver.get("https://example.com")

# 保存基准截图
driver.save_screenshot("baseline.png")

# 保存当前截图
driver.save_screenshot("current.png")

# 对比
is_same, message = compare_screenshots("baseline.png", "current.png")
print(message)

driver.quit()

元素截图 #

python
from selenium import webdriver
from selenium.webdriver.common.by import By
from PIL import Image
from io import BytesIO

def capture_element(driver, locator, output_path):
    """截取元素截图"""
    element = driver.find_element(*locator)
    
    # 获取元素位置和大小
    location = element.location
    size = element.size
    
    # 截取整个页面
    screenshot = driver.get_screenshot_as_png()
    
    # 裁剪元素区域
    img = Image.open(BytesIO(screenshot))
    left = location['x']
    top = location['y']
    right = left + size['width']
    bottom = top + size['height']
    
    element_img = img.crop((left, top, right, bottom))
    element_img.save(output_path)

# 使用示例
driver = webdriver.Chrome()
driver.get("https://example.com")

capture_element(driver, (By.ID, "logo"), "logo.png")

driver.quit()

移动端测试 #

移动端模拟 #

python
from selenium import webdriver
from selenium.webdriver.chrome.options import Options

def create_mobile_driver(device_name="iPhone 12"):
    """创建移动端模拟驱动"""
    mobile_emulation = {
        "deviceName": device_name
    }
    
    options = Options()
    options.add_experimental_option("mobileEmulation", mobile_emulation)
    
    return webdriver.Chrome(options=options)

# 使用示例
driver = create_mobile_driver("iPhone 12")
driver.get("https://example.com")

print(f"窗口大小: {driver.get_window_size()}")
print(f"User-Agent: {driver.execute_script('return navigator.userAgent')}")

driver.quit()

自定义移动设备 #

python
def create_custom_mobile_driver():
    """创建自定义移动设备模拟"""
    mobile_emulation = {
        "deviceMetrics": {
            "width": 375,
            "height": 812,
            "pixelRatio": 3.0
        },
        "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15"
    }
    
    options = Options()
    options.add_experimental_option("mobileEmulation", mobile_emulation)
    
    return webdriver.Chrome(options=options)

触摸操作模拟 #

python
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains

def simulate_touch_operations(driver):
    """模拟触摸操作"""
    actions = ActionChains(driver)
    
    element = driver.find_element(By.ID, "target")
    
    # 模拟触摸点击
    actions.click(element).perform()
    
    # 模拟长按
    actions.click_and_hold(element).pause(1).release().perform()
    
    # 模拟滑动
    actions.click_and_hold(element).move_by_offset(0, -100).release().perform()
    
    # 模拟双指缩放(需要使用 JavaScript)
    driver.execute_script("""
        var element = arguments[0];
        var rect = element.getBoundingClientRect();
        var centerX = rect.left + rect.width / 2;
        var centerY = rect.top + rect.height / 2;
        
        var touch1 = new Touch({
            identifier: 1,
            target: element,
            clientX: centerX - 50,
            clientY: centerY
        });
        
        var touch2 = new Touch({
            identifier: 2,
            target: element,
            clientX: centerX + 50,
            clientY: centerY
        });
        
        var touchEvent = new TouchEvent('touchstart', {
            touches: [touch1, touch2]
        });
        
        element.dispatchEvent(touchEvent);
    """, element)

CI/CD 集成 #

Jenkins 配置 #

groovy
// Jenkinsfile
pipeline {
    agent any
    
    stages {
        stage('Setup') {
            steps {
                sh 'pip install -r requirements.txt'
            }
        }
        
        stage('Test') {
            steps {
                sh '''
                    pytest tests/ \
                        --html=reports/report.html \
                        --self-contained-html \
                        --alluredir=reports/allure-results
                '''
            }
        }
        
        stage('Report') {
            steps {
                allure includeProperties: false, jdk: '', results: [[path: 'reports/allure-results']]
                publishHTML(target: [
                    allowMissing: false,
                    alwaysLinkToLastBuild: true,
                    keepAll: true,
                    reportDir: 'reports',
                    reportFiles: 'report.html',
                    reportName: 'Test Report'
                ])
            }
        }
    }
    
    post {
        always {
            cleanWs()
        }
    }
}

GitHub Actions #

yaml
# .github/workflows/test.yml
name: Selenium Tests

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    
    services:
      selenium:
        image: selenium/standalone-chrome
        ports:
          - 4444:4444
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Set up Python
      uses: actions/setup-python@v4
      with:
        python-version: '3.10'
    
    - name: Install dependencies
      run: |
        pip install -r requirements.txt
    
    - name: Run tests
      env:
        SELENIUM_HUB_URL: http://localhost:4444/wd/hub
      run: |
        pytest tests/ --alluredir=reports/allure-results
    
    - name: Generate Allure Report
      uses: simple-elf/allure-report-action@master
      with:
        allure_results: reports/allure-results
    
    - name: Upload report
      uses: actions/upload-artifact@v3
      with:
        name: allure-report
        path: allure-history

无头浏览器 #

Headless Chrome #

python
from selenium import webdriver
from selenium.webdriver.chrome.options import Options

def create_headless_driver():
    """创建无头浏览器驱动"""
    options = Options()
    options.add_argument('--headless')
    options.add_argument('--no-sandbox')
    options.add_argument('--disable-dev-shm-usage')
    options.add_argument('--disable-gpu')
    options.add_argument('--window-size=1920,1080')
    
    return webdriver.Chrome(options=options)

# 使用示例
driver = create_headless_driver()
driver.get("https://example.com")
print(driver.title)
driver.quit()

性能对比 #

python
import time
from selenium import webdriver
from selenium.webdriver.chrome.options import Options

def compare_headless_performance(url, iterations=5):
    """对比有头和无头模式性能"""
    
    # 有头模式
    options = Options()
    driver = webdriver.Chrome(options=options)
    
    headful_times = []
    for _ in range(iterations):
        start = time.time()
        driver.get(url)
        headful_times.append(time.time() - start)
    
    driver.quit()
    
    # 无头模式
    options.add_argument('--headless')
    driver = webdriver.Chrome(options=options)
    
    headless_times = []
    for _ in range(iterations):
        start = time.time()
        driver.get(url)
        headless_times.append(time.time() - start)
    
    driver.quit()
    
    print(f"有头模式平均: {sum(headful_times)/len(headful_times):.2f}s")
    print(f"无头模式平均: {sum(headless_times)/len(headless_times):.2f}s")

扩展工具 #

Selenium Wire(网络拦截) #

python
# pip install selenium-wire
from seleniumwire import webdriver

def intercept_network():
    """拦截网络请求"""
    driver = webdriver.Chrome()
    
    # 拦截请求
    def interceptor(request):
        if request.url.endswith('.js'):
            request.abort()
    
    driver.request_interceptor = interceptor
    
    driver.get("https://example.com")
    
    # 查看请求
    for request in driver.requests:
        if request.response:
            print(f"{request.method} {request.url} -> {request.response.status_code}")
    
    driver.quit()

BrowserMob Proxy #

python
from browsermobproxy import Server
from selenium import webdriver
from selenium.webdriver.chrome.options import Options

def use_proxy():
    """使用代理"""
    server = Server("/path/to/browsermob-proxy")
    server.start()
    
    proxy = server.create_proxy()
    
    options = Options()
    options.add_argument(f'--proxy-server={proxy.proxy}')
    
    driver = webdriver.Chrome(options=options)
    
    proxy.new_har("example")
    driver.get("https://example.com")
    
    har = proxy.har
    for entry in har['log']['entries']:
        print(f"{entry['request']['method']} {entry['request']['url']}")
    
    driver.quit()
    server.stop()

调试技巧 #

远程调试 #

python
from selenium import webdriver
from selenium.webdriver.chrome.options import Options

def enable_remote_debugging():
    """启用远程调试"""
    options = Options()
    options.add_argument('--remote-debugging-port=9222')
    
    driver = webdriver.Chrome(options=options)
    return driver

# 访问 chrome://inspect 进行调试

VNC 远程查看 #

python
# 使用 Docker 启动带 VNC 的 Selenium
# docker run -d -p 4444:4444 -p 5900:5900 selenium/standalone-chrome-debug

# 使用 VNC 客户端连接 localhost:5900 查看测试执行

学习路径总结 #

text
┌─────────────────────────────────────────────────────────────┐
│                    Selenium 学习路径                         │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  入门阶段                                                    │
│  ├── Selenium 简介                                          │
│  ├── 安装与配置                                              │
│  ├── 基础使用                                                │
│  └── 元素定位                                                │
│                                                             │
│  进阶阶段                                                    │
│  ├── 用户交互                                                │
│  ├── 等待机制                                                │
│  ├── 高级处理                                                │
│  └── Actions 链操作                                          │
│                                                             │
│  高级阶段                                                    │
│  ├── Pytest 集成                                             │
│  ├── 页面对象模式                                            │
│  ├── 最佳实践                                                │
│  └── 高级话题                                                │
│                                                             │
│  专家阶段                                                    │
│  ├── Selenium Grid                                          │
│  ├── 性能测试                                                │
│  ├── CI/CD 集成                                              │
│  └── 测试架构设计                                            │
│                                                             │
└─────────────────────────────────────────────────────────────┘

恭喜完成! #

你已经完成了 Selenium Web 自动化测试的完整学习路径。现在你已经具备了:

  • 扎实的 Selenium 基础知识
  • 熟练的元素定位和操作技能
  • 完善的测试框架设计能力
  • 专业的最佳实践经验
  • 高级的测试扩展技能

继续实践和探索,成为一名优秀的自动化测试工程师!

最后更新:2026-03-28