在pytest/xdist框架中存储全局Test Run ID配置数据的技术咨询
刚好我之前搭建过类似的pytest+xdist+Selenium Grid测试框架,还对接过自定义日志系统,分享下我是怎么解决Test Run ID跨用例共享这个问题的——毕竟xdist是多进程运行,普通全局变量根本没法跨进程传递,得用pytest的钩子和进程间传递机制来实现。
核心解决方案步骤
1. 在Master进程提前创建Test Run ID并传递给所有Worker
xdist的Worker进程是独立的Python解释器,所以必须在Master进程(也就是启动pytest的主进程)里先调用API生成Test Run ID,再把这个ID传递给每个Worker。我们可以用pytest的两个钩子来实现:
# conftest.py import pytest import requests def pytest_configure(config): # 只在Master进程执行这个逻辑(Worker进程会有workerinput属性) if not hasattr(config, 'workerinput'): # 调用你的创建测试运行API,替换成实际的接口地址和参数 create_run_response = requests.post( "http://your-log-system/api/test-runs", json={"run_name": "自动化测试运行", "env": "测试环境"} ) create_run_response.raise_for_status() # 确保API调用成功 config.test_run_id = create_run_response.json()["test_run_id"] def pytest_configure_node(node): # 把Master进程的Test Run ID传递给每个Worker的config对象 node.config.test_run_id = node.config.master_config.test_run_id
这样每个Worker进程的request.config里就都能拿到test_run_id了。
2. 封装日志API工具类
为了让测试用例调用日志接口更方便,我封装了一个工具类,把Test Run ID作为初始化参数,后续创建用例、记录日志/截图都直接调用类方法:
# utils/log_client.py import requests from typing import Optional class LogAPIClient: def __init__(self, test_run_id: str): self.test_run_id = test_run_id self.base_api_url = "http://your-log-system/api" def create_test_case(self, case_name: str, case_path: str) -> str: """在当前测试运行下创建测试用例记录,返回Test Case ID""" resp = requests.post( f"{self.base_api_url}/test-cases", json={ "test_run_id": self.test_run_id, "case_name": case_name, "case_path": case_path } ) resp.raise_for_status() return resp.json()["test_case_id"] def log_text(self, test_case_id: str, content: str): """给指定测试用例记录文本日志""" requests.post( f"{self.base_api_url}/test-cases/{test_case_id}/logs/text", json={"content": content} ) def log_screenshot(self, test_case_id: str, screenshot_bytes: bytes, filename: str = "screenshot.png"): """给指定测试用例上传截图""" requests.post( f"{self.base_api_url}/test-cases/{test_case_id}/logs/screenshot", files={"file": (filename, screenshot_bytes, "image/png")} )
3. 用Fixture集成日志工具到测试流程
我写了一个function级别的fixture,在每个测试用例执行前自动创建对应的用例记录,并且把日志客户端和Test Case ID传递给用例:
# conftest.py from utils.log_client import LogAPIClient import pytest @pytest.fixture(scope="function") def log_tool(request): # 从config获取Test Run ID test_run_id = request.config.test_run_id log_client = LogAPIClient(test_run_id) # 创建当前测试用例的日志记录 case_name = request.node.name case_path = request.node.nodeid test_case_id = log_client.create_test_case(case_name, case_path) # 把Test Case ID绑定到request对象,方便用例里直接用 request.test_case_id = test_case_id yield log_client # 用例执行结束后,自动记录执行状态 status = "PASSED" if request.node.rep_call.passed else "FAILED" log_client.log_text(test_case_id, f"测试用例执行完成,状态: {status}") # 这个钩子用来捕获测试用例的执行结果,供上面的fixture使用 @pytest.hookimpl(tryfirst=True, hookwrapper=True) def pytest_runtest_makereport(item, call): outcome = yield rep = outcome.get_result() # 把不同阶段的测试结果存到item对象里 setattr(item, f"rep_{rep.when}", rep)
4. 在测试用例中使用日志工具
现在测试用例里就可以直接调用日志工具来记录内容和截图了,完全不用关心Test Run ID的传递问题:
# tests/test_login.py def test_user_login(log_tool, selenium_driver): # 执行登录操作 selenium_driver.get("https://your-test-site.com/login") selenium_driver.find_element("id", "username").send_keys("test_user") selenium_driver.find_element("id", "password").send_keys("123456") selenium_driver.find_element("id", "login-btn").click() # 记录文本日志 log_tool.log_text(request.test_case_id, "用户登录操作执行完成") # 截图并上传(直接用Selenium的get_screenshot_as_bytes,不用存本地文件) screenshot_data = selenium_driver.get_screenshot_as_bytes() log_tool.log_screenshot(request.test_case_id, screenshot_data, "login_success.png") # 断言登录成功 assert "欢迎回来" in selenium_driver.page_source
几个关键注意点
- 避免重复创建Test Run ID:一定要在Master进程里创建,Worker进程只负责接收,不然每个Worker都会生成一个新的Test Run ID,日志就乱了。
- API异常处理:实际使用时记得给API调用加try-except,比如网络超时、接口报错时可以重试或者记录错误日志,别让API问题打断测试流程。
- Selenium Grid截图:如果用Grid执行,直接用
get_screenshot_as_bytes获取二进制数据传给API就行,不用存本地文件(Worker进程的本地路径在Grid节点上可能访问不到)。
内容的提问来源于stack exchange,提问作者sprad
相关产品推荐
相关产品推荐

