
本文详解 pytest 多进程(-n N)运行 Selenium 测试时,因全局 driver 变量在 pytest_sessionfinish 中被多进程争用导致的崩溃问题,并提供安全、可落地的环境信息注入 Allure 报告的完整实践方案。
本文详解 pytest 多进程(`-n n`)运行 selenium 测试时,因全局 driver 变量在 `pytest_sessionfinish` 中被多进程争用导致的崩溃问题,并提供安全、可落地的环境信息注入 allure 报告的完整实践方案。
在使用 pytest + Selenium 进行 UI 自动化测试时,为增强 Allure 报告的可追溯性,常需在测试会话结束时写入浏览器名称、版本等环境信息到 allure-results/environment.properties 文件中。然而,当启用 pytest-xdist 并行执行(如 pytest -n 5)时,每个 worker 进程都会独立调用 pytest_sessionfinish 钩子函数——而原始代码中直接引用了全局变量 driver(在 setup fixture 中初始化),该变量在多进程环境下并不存在于其他 worker 的内存空间中,导致 NameError 或 AttributeError,最终引发会话终止失败。
根本原因在于:
✅ pytest_sessionfinish 是主进程(master)或各 worker 进程各自调用的钩子,而非仅由主进程调用;
❌ 全局 driver 变量仅在 setup fixture 所在的 worker 进程中存在,无法跨进程访问;
❌ 多个 worker 同时尝试写入同一 environment.properties 文件,还可能引发文件竞争或覆盖问题。
✅ 正确做法:按 worker 粒度收集环境信息,避免全局依赖
推荐采用 “每个 worker 独立生成 environment.properties” + “Allure 主进程合并” 的策略。但更简洁稳健的方式是——利用 pytest 的 session-scoped fixture 或 xdist 的 worker_id 标识,在 fixture 中完成环境信息采集与落盘,确保线程/进程安全:
# conftest.py
import os
import pytest
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
import allure
def pytest_configure(config):
"""在 pytest 初始化阶段注册自定义配置项(可选)"""
config.addinivalue_line("markers", "env_info: mark test to include env info")
@pytest.fixture(scope="session", autouse=True)
def setup_driver_environment(request):
"""
Session-scoped fixture:仅在每个 worker 进程启动时执行一次,
安全获取 driver 实例并写入专属 environment.properties
"""
# 判断是否为 xdist worker(避免主进程重复执行)
worker_id = os.environ.get("PYTEST_XDIST_WORKER")
if not worker_id:
return # 主进程不执行 driver 初始化
# 初始化 driver(与原 setup 逻辑一致,但去除了 global 声明)
options = Options()
options.add_experimental_option("detach", True)
options.add_argument("--headless")
options.add_argument("--disable-dev-shm-usage")
driver = webdriver.Chrome(options=options)
# 收集环境信息(此时 driver 已就绪)
env_props = {
"browser": driver.name,
"browser_version": driver.capabilities.get("browserVersion", "unknown"),
"driver_version": driver.capabilities.get("chrome", {}).get("chromedriverVersion", "").split(" ")[0],
"worker_id": worker_id,
"platform": driver.capabilities.get("platformName", "unknown")
}
# 写入当前 worker 对应的 allure-results 目录(确保路径唯一)
# 注意:xdist 下各 worker 默认使用独立的 --alluredir 子目录(需配合 --allure-parallelization)
# 或手动构造隔离路径,例如:allure-results-worker-0
base_allure_dir = request.config.getoption("--alluredir", default="allure-results")
worker_allure_dir = f"{base_allure_dir}-{worker_id}"
os.makedirs(worker_allure_dir, exist_ok=True)
env_path = os.path.join(worker_allure_dir, "environment.properties")
with open(env_path, "w", encoding="utf-8") as f:
for k, v in env_props.items():
f.write(f"{k}={v}\n")
# 清理:退出 driver(确保每个 worker 独立生命周期)
yield
driver.quit()⚠️ 注意事项:
- 不再使用 global driver,所有 driver 操作严格限定在 fixture 作用域内;
- scope="session" + autouse=True 保证每个 worker 仅初始化/销毁一次 driver;
- 使用 os.environ["PYTEST_XDIST_WORKER"] 精准识别 worker 进程,规避主进程干扰;
- 环境文件写入路径按 worker_id 隔离,避免多进程文件冲突;Allure 2.21+ 支持自动合并多个 environment.properties(需确保最终报告目录结构正确);
- 若使用旧版 Allure 或需统一环境文件,可在 CI 脚本中增加合并步骤(如 cat allure-results-*/environment.properties | sort -u > allure-results/environment.properties)。
✅ 替代方案:采用成熟框架(SeleniumBase)降低维护成本
对于中大型项目,建议直接采用经过生产验证的集成框架,如 SeleniumBase,它原生支持:
- 多进程下的 driver 生命周期管理(每个 test class 自动分配独立 driver);
- 自动截图、日志、Allure 集成(含环境属性注入);
- 无需手动处理 pytest_sessionfinish 线程安全问题。
安装与使用示例:
pip install seleniumbase allure-pytest pytest --headless --alluredir=allure-results tests/
# tests/test_example.py
from seleniumbase import BaseCase
class MyTests(BaseCase):
def test_login_flow(self):
self.open("https://magento.softwaretestingboard.com/")
self.click('a[href*="login"]')
self.type("#email", "test@example.com")
self.type("#pass", "password123")
self.click("#send2")
self.assert_element("#maincontent")SeleniumBase 会在每个测试结束后自动保存截图至 ./latest_logs/,并通过 --alluredir 输出标准 Allure 结构,其 environment.properties 由框架内部安全生成,完全规避本文所述并发风险。
总结
| 方案 | 适用场景 | 关键优势 | 注意事项 |
|---|---|---|---|
| 自定义 session fixture | 需深度控制环境采集逻辑、已有代码基稳定 | 完全自主可控、零第三方依赖 | 需理解 xdist worker 生命周期,注意路径隔离 |
| SeleniumBase 框架 | 快速交付、团队协作、长期维护 | 开箱即用、高稳定性、丰富生态 | 学习成本略高,需重构部分测试代码 |
无论选择哪种路径,请始终牢记:在 pytest 分布式执行中,任何跨进程共享状态(尤其是全局变量)都是危险的。坚持“每个 worker 独立资源、独立生命周期”的设计原则,是构建健壮自动化测试体系的基石。










