使用pytest+Selenium并行测试时pytest_sessionfinish报错
问题:pytest并行测试时Allure环境属性写入失败
问题背景
我在conftest.py中编写了pytest+Selenium的自动化测试代码,执行并行测试命令时,pytest_sessionfinish函数在向Allure报告添加浏览器、版本等环境属性时报错。
原conftest.py代码
import os from urllib import request import pytest from selenium.webdriver.chrome.options import Options import allure from selenium import webdriver @pytest.fixture(autouse=True) def setup(request): global driver options = Options() options.add_experimental_option("detach", True) options.add_argument("--headless") # options.add_argument("--no-sandbox") options.add_argument("--disable-dev-shm-usage") # options = webdriver.ChromeOptions() driver = webdriver.Chrome(options=options) request.cls.driver = driver driver.maximize_window() driver.get("https://magento.softwaretestingboard.com/") yield driver.quit() def pytest_exception_interact(report): if report.failed: allure.attach(body=driver.get_screenshot_as_png(), name="screenshot", attachment_type=allure.attachment_type.PNG) def pytest_sessionfinish() -> None: environment_properties = { 'browser': driver.name, 'driver_version': driver.capabilities['browserVersion'] } allure_env_path = os.path.join("allure-results", 'environment.properties') with open(allure_env_path, 'w') as f: data = '\n'.join([f'{variable}={value}' for variable, value in environment_properties.items()]) f.write(data)
执行的并行测试命令
pytest -n 5 --dist loadgroup --alluredir=allure-results --reruns 2 --reruns-delay 2
报错栈
File "<frozen runpy>", line 198, in _run_module_as_main File "<frozen runpy>", line 88, in _run_code File "C:\Users\benik\PycharmProjects\pythonAutomationStore\.venv\Scripts\pytest.exe\__main__.py", line 7, in <module> File "C:\Users\benik\PycharmProjects\pythonAutomationStore\.venv\Lib\site-packages\_pytest\config\__init__.py", line 198, in console_main code = main() ^^^^^^ File "C:\Users\benik\PycharmProjects\pythonAutomationStore\.venv\Lib\site-packages\_pytest\config\__init__.py", line 175, in main ret: Union[ExitCode, int] = config.hook.pytest_cmdline_main( ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\benik\PycharmProjects\pythonAutomationStore\.venv\Lib\site-packages\pluggy\_hooks.py", line 493, in __call__ return self._hookexec(self.name, self._hookimpls, kwargs, firstresult) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\benik\PycharmProjects\pythonAutomationStore\.venv\Lib\site-packages\pluggy\_manager.py", line 115, in _hookexec return self._inner_hookexec(hook_name, methods, kwargs, firstresult) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\benik\PycharmProjects\pythonAutomationStore\.venv\Lib\site-packages\pluggy\_callers.py", line 113, in _multicall raise exception.with_traceback(exception.__traceback__) File "C:\Users\benik\PycharmProjects\pythonAutomationStore\.venv\Lib\site-packages\pluggy\_callers.py", line 77, in _multicall res = hook_impl.function(*args) ^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\benik\PycharmProjects\pythonAutomationStore\.venv\Lib\site-packages\_pytest\main.py", line 320, in pytest_cmdline_main return wrap_session(config, _main) ^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\benik\PycharmProjects\pythonAutomationStore\.venv\Lib\site-packages\_pytest\main.py", line 308, in wrap_session config.hook.pytest_sessionfinish( File "C:\Users\benik\PycharmProjects\pythonAutomationStore\.venv\Lib\site-packages\pluggy\_hooks.py", line 493, in __call__ return self._hookexec(self.name, self._hookimpls, kwargs, firstresult) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\benik\PycharmProjects\pythonAutomationStore\.venv\Lib\site-packages\pluggy\_manager.py", line 115, in _hookexec return self._inner_hookexec(hook_name, methods, kwargs, firstresult) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\benik\PycharmProjects\pythonAutomationStore\.venv\Lib\site-packages\pluggy\_callers.py", line 113, in _multicall raise exception.with_traceback(exception.__traceback__) File "C:\Users\benik\PycharmProjects\pythonAutomationStore\.venv\Lib\site-packages\pluggy\_callers.py", line 96, in _multicall teardown.throw(exception) # type: ignore[union-attr] ^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\benik\PycharmProjects\pythonAutomationStore\.venv\Lib\site-packages\_pytest\logging.py", line 861, in pytest_sessionfinish return (yield) ^^^^^ File "C:\Users\benik\PycharmProjects\pythonAutomationStore\.venv\Lib\site-packages\pluggy\_callers.py", line 96, in _multicall teardown.throw(exception) # type: ignore[union-attr] ^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\benik\PycharmProjects\pythonAutomationStore\.venv\Lib\site-packages\_pytest\terminal.py", line 854, in pytest_sessionfinish result = yield ^^^^^ File "C:\Users\benik\PycharmProjects\pythonAutomationStore\.venv\Lib\site-packages\pluggy\_callers.py", line 96, in _multicall teardown.throw(exception) # type: ignore[union-attr] ^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\benik\PycharmProjects\pythonAutomationStore\.venv\Lib\site-packages\_pytest\warnings.py", line 138, in pytest_sessionfinish return (yield) ^^^^^ File "C:\Users\benik\PycharmProjects\pythonAutomationStore\.venv\Lib\site-packages\pluggy\_callers.py", line 77, in _multicall res = hook_impl.function(*args) ^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\benik\PycharmProjects\pythonAutomationStore\tests\conftest.py", line 48, in pytest_sessionfinish driver = session.config._driver ^^^^^^^^^^^^^^^^^^^^^^
问题分析与解决方案
核心问题
- 全局变量
driver在并行环境下不可靠:pytest-xdist采用多进程模式,每个进程有独立内存空间,全局变量无法跨进程共享,pytest_sessionfinish执行时可能无法获取有效driver实例,甚至driver已被销毁。 - 多进程写入文件竞争:多个进程同时写入
environment.properties会导致内容覆盖或写入失败。
修复方案
方案1:通过pytest配置传递信息+文件锁解决写入冲突
修改conftest.py,避免全局变量,用pytest的config对象存储浏览器信息,同时通过文件锁处理多进程写入竞争:
import os import pytest from selenium.webdriver.chrome.options import Options import allure from selenium import webdriver from filelock import FileLock @pytest.fixture(autouse=True) def setup(request): options = Options() options.add_experimental_option("detach", True) options.add_argument("--headless") options.add_argument("--disable-dev-shm-usage") driver = webdriver.Chrome(options=options) request.cls.driver = driver # 将浏览器信息存入config自定义属性 if not hasattr(request.config, 'browser_info'): request.config.browser_info = { 'browser': driver.name, 'driver_version': driver.capabilities['browserVersion'] } driver.maximize_window() driver.get("https://magento.softwaretestingboard.com/") yield driver.quit() def pytest_exception_interact(report, request): if report.failed: # 通过request获取当前用例的driver,而非全局变量 driver = request.cls.driver allure.attach(body=driver.get_screenshot_as_png(), name="screenshot", attachment_type=allure.attachment_type.PNG) def pytest_sessionfinish(session) -> None: if hasattr(session.config, 'browser_info'): environment_properties = session.config.browser_info allure_env_path = os.path.join("allure-results", 'environment.properties') # 文件锁避免多进程写入冲突 with FileLock(f"{allure_env_path}.lock"): # 读取已有内容,避免覆盖 existing_data = "" if os.path.exists(allure_env_path): with open(allure_env_path, 'r') as f: existing_data = f.read() # 生成新内容并去重 new_data = '\n'.join([f'{k}={v}' for k, v in environment_properties.items()]) combined_lines = list(set(existing_data.split('\n') + new_data.split('\n'))) combined_data = '\n'.join([line for line in combined_lines if line.strip()]) # 写入文件 with open(allure_env_path, 'w') as f: f.write(combined_data)
方案2:预先生成环境文件
如果所有并行测试使用相同浏览器版本,可提前生成environment.properties文件,测试前复制到allure-results目录:
- 项目根目录创建
environment.properties:
browser=chrome driver_version=120.0.6099.109
- 执行测试命令:
copy environment.properties allure-results\environment.properties pytest -n 5 --dist loadgroup --alluredir=allure-results --reruns 2 --reruns-delay 2
额外注意事项
- 安装
filelock依赖:pip install filelock,用于处理多进程文件写入竞争。 - 并行测试中,
pytest_exception_interact需通过request对象获取当前用例的driver,避免全局变量导致的上下文错误。
内容的提问来源于stack exchange,提问作者בני קון
相关产品推荐
相关产品推荐

