Browser Use Demo 测试套件实战指南:从 pytest 配置、Mock 策略到边缘用例全覆盖
【免费下载链接】claude-quickstartsA collection of projects designed to help developers quickly get started with building deployable applications using the Claude API项目地址: https://gitcode.com/GitHub_Trending/an/claude-quickstarts
本指南以 browser-use-demo/tests/README.md 为核心骨架,深入剖析 Browser Use Demo(基于 Claude + Playwright 的浏览器自动化参考实现)重构后的测试体系:如何安装依赖、运行单测与覆盖率报告、按 marker 筛选用例,以及MessageRenderer、Streamlit 辅助函数和端到端集成测试分别覆盖了哪些行为。结合仓库中的 pytest.ini、conftest.py 与各测试文件源码,你将掌握一套面向 Streamlit + 异步事件循环 + 浏览器工具的三层 Mock 测试方法论,并能够直接复用到自己的 Agent 应用中。
测试套件概览
这套测试套件服务于重构后的 Browser Use Demo(入口与架构见 browser-use-demo/README.md)。它的设计目标有两个:
- 无需真实浏览器即可测试:通过 Mock 掉 Streamlit 组件、
BrowserTool(避免引入 Playwright 依赖)和 asyncio 事件循环,使测试可以在纯 Python 环境快速执行; - 边缘用例全覆盖:针对消息渲染、会话状态初始化、事件循环管理等高风险逻辑,构造空值、类型错误、状态不一致、并发修改、超长输入等异常输入,验证系统的健壮性。
从测试文件规模看(详见"测试结构"一节),test_message_renderer.py约 300 个用例、test_streamlit_helpers.py约 150 个用例、test_integration.py约 50 个用例,共同构成了对该 demo 消息展示层与 Streamlit 交互层的高密度回归保障。
安装测试依赖
有两种等价方式安装测试依赖:
# 方式一:直接安装 test-requirements.txt pip install -r test-requirements.txt # 方式二:通过 setup.py 的 extras 安装(推荐,与 CI 一致) pip install -e ".[test]"两种方式引入的依赖完全一致,见 test-requirements.txt 与 setup.py 中extras_require["test"]的定义:
| 依赖 | 版本 | 用途 |
|---|---|---|
| pytest | 8.3.3 | 测试框架核心 |
| pytest-cov | 4.1.0 | 覆盖率统计(--cov) |
| pytest-mock | 3.11.1 | 基于monkeypatch的 Mock 工具 |
| pytest-asyncio | 0.23.6 | asyncio 测试支持(对应asynciomarker) |
需要说明的是,pip install -e ".[test]"同时会安装 setup.py 中的运行时依赖(streamlit、anthropic、playwright、boto3、google-auth 等)。由于本项目要求 Python 版本 >= 3.11(python_requires=">=3.11"),请确保解释器版本满足要求。
运行测试
运行全部测试
pytest tests/pytest.ini中的testpaths = tests已经限定了测试目录,因此直接执行pytest也能达到同样效果。
带覆盖率报告运行
pytest tests/ --cov=browser_tools_api_demo --cov-report=html # 打开 htmlcov/index.html 查看覆盖率报告--cov=browser_tools_api_demo指定统计目标为被测包。关于这个包名需要留意:文章撰写时 pytest.ini 与tests/README.md中仍写的是重构前的旧包名browser_tools_api_demo,而当前源码中实际被测模块位于 browser_use_demo/ 下(例如 message_renderer.py),因此若要统计真实覆盖率,应使用:
pytest tests/ --cov=browser_use_demo --cov-report=html这一点属于仓库演进过程中的文档滞后,请以当前源码为准。
运行指定测试文件
pytest tests/test_message_renderer.py -v-v输出每个用例的详细结果。pytest.ini的addopts中已经默认附加了-v,所以即使不加也能看到详细输出。
运行指定测试类或测试方法
# 运行整个测试类 pytest tests/test_message_renderer.py::TestMessageRenderer -v # 运行单个测试方法(用 :: 逐级定位) pytest tests/test_message_renderer.py::TestRenderMethod::test_render_string_message -v按 marker 筛选测试
pytest.ini中声明了三个自定义 marker:
markers = integration: Integration tests that test multiple components slow: Tests that take longer than usual to run asyncio: Tests that use asyncio对应的筛选命令:
# 只运行集成测试 pytest -m integration # 排除集成测试(只跑单元测试) pytest -m "not integration" # 运行 asyncio 相关测试 pytest -m asyncio注意addopts中带有--strict-markers,意味着未在pytest.ini注册的 marker 会直接报错,这也是项目要求测试分类必须显式声明的原因。此外,pytest.ini还做了如下全局配置:
python_files = test_*.py/python_classes = Test*/python_functions = test_*:限定测试发现规则;asyncio_mode = auto:所有async def测试自动以 asyncio 模式运行,无需逐个标注;asyncio_default_fixture_loop_scope = function:asyncio fixture 的 loop 作用域默认为函数级;--tb=short、--disable-warnings:控制失败回溯与警告输出;minversion = 3.11:强制最低 Python 版本;filterwarnings:忽略DeprecationWarning与PendingDeprecationWarning,避免第三方库弃用警告干扰输出。
测试结构
browser-use-demo/tests/ ├── conftest.py # Shared fixtures and mocks ├── test_message_renderer.py # MessageRenderer class tests (~300 test cases) ├── test_streamlit_helpers.py # Helper function tests (~150 test cases) └── test_integration.py # End-to-end integration tests (~50 test cases)各文件职责如下:
| 文件 | 被测对象 | 侧重点 |
|---|---|---|
| conftest.py | 无(测试基建) | 共享 fixture、Mock 环境、路径注入 |
| test_message_renderer.py | MessageRenderer(message_renderer.py) | 消息渲染的输入分支与异常处理 |
| test_streamlit_helpers.py | setup_state/get_or_create_event_loop/authenticate(streamlit.py) | 会话状态、事件循环、鉴权逻辑 |
| test_integration.py | 渲染管线 + 状态 + 事件循环组合 | 跨组件端到端行为 |
conftest.py通过sys.path.insert(0, str(Path(__file__).parent.parent))将被测包根目录注入sys.path,使测试可以from browser_use_demo.tools import ToolResult直接导入。
测试覆盖详解
MessageRenderer(test_message_renderer.py)
MessageRenderer 是 Streamlit 聊天界面的消息渲染器,其核心职责是把三种来源(用户输入、assistant 回复、工具结果)统一渲染为界面组件。Sender类定义了三种发送者:USER = "user"、BOT = "assistant"、TOOL = "tool"。
测试覆盖的行为包括:
- 初始化:以各种状态配置创建
MessageRenderer,包括Nonesession state、空 state(对应TestMessageRenderer的三个用例); - 渲染所有消息类型:字符串消息、字典消息(
text/tool_use/ 未知type)、ToolResult对象。例如test_render_dict_message_tool_use_type验证tool_use消息会被格式化为"Tool Use: <name>\nInput: <input>"并交给st.code展示; - 会话历史渲染:
render_conversation_history对多消息、未知 role、缺失 content 字段、Nonecontent、列表 content、嵌套结构、tool_result关联等场景的处理。其中test_skip_image_blocks_in_history验证历史渲染会跳过 image 块(避免重复展示截图),而test_tool_result_in_assistant_message验证tool_result会从session_state.tools中按tool_use_id查找对应的ToolResult并渲染; - 边缘用例:空消息(跳过渲染)、
None消息(跳过渲染)、循环引用(不死循环)、畸形ToolResult(优雅降级)、渲染异常(按预期传播)、Base64 解码失败等; - Unicode 与特殊字符:如
"Hello 世界 🌍 \n\t\r ñáéíóú"可被完整渲染(对应test_render_unicode_special_chars); - 大消息性能:10 万字符长消息可正常渲染(
test_render_very_long_message)。
值得注意的细节:test_render_tool_result_with_hidden_screenshots验证了session_state.hide_screenshots开关——开启时只渲染文本、不调用st.image,这是聊天界面中"隐藏截图"功能的直接回归保障。
Streamlit Helpers(test_streamlit_helpers.py)
该文件针对 streamlit.py 中的三个关键函数:
setup_state():初始化st.session_state。测试覆盖全新初始化(所有默认键被写入)、部分初始化(已存在的键不覆盖)、环境变量缺失、lambda 惰性求值(model依据 provider 动态确定)、BrowserTool初始化失败时异常传播、状态损坏时异常传播、并发调用setup_state的线程安全(5 个线程并发无崩溃)、只读状态下的AttributeError;get_or_create_event_loop():管理 asyncio 事件循环。测试覆盖无 loop 时新建、已有 closed loop 时重建、已有 open loop 时复用、创建/设置 loop 失败时的异常传播、存在 running loop 时仍新建独立 loop 等场景;authenticate():鉴权。测试覆盖有效 key 通过、缺失/Nonekey 时调用st.error并st.stop、非 Anthropic provider(如 BEDROCK)在空 key 情况下也放行。
集成测试 test_integration.py 中的TestCompleteStateInitialization还给出了setup_state初始化键的完整清单:messages、api_key、provider、model、max_tokens、system_prompt、hide_screenshots、tools、browser_tool、event_loop、rendered_message_count、is_agent_running、active_messages、active_response_container,可作为理解该函数行为的权威参考。
集成测试(test_integration.py)
所有集成测试类都标注了@pytest.mark.integration,因此可用pytest -m integration单独执行。覆盖范围:
- 完整消息渲染管线:
TestFullMessageRenderingPipeline构造包含用户文本、assistanttool_use、tool_result(分别指向成功与失败的ToolResult)的混合会话,验证st.markdown/st.write/st.error的调用次数符合预期; - 状态初始化与持久化:
TestStateInitializationAndPersistence验证全新状态初始化出全部必需键,且多次渲染间状态保持一致; - 事件循环与异步操作:
TestEventLoopManagementWithAsync验证 loop 的创建/复用、异步 Agent 执行与asyncio.gather并发任务处理; - 错误传播:
TestErrorPropagationAndHandling验证缺失工具的tool_result被优雅处理(不调用st.error)、初始化失败后二次调用可恢复(side_effect依次抛异常/成功); - 完整用户交互工作流:
TestCompleteWorkflow从setup_state→ 用户输入 → 事件循环 →run_agent全链路模拟; - 性能与可扩展性:
TestPerformanceAndScalability验证 1000 条消息的历史渲染(断言st.markdown恰好被调用 1000 次)以及 100 层嵌套内容不栈溢出。
覆盖的边界用例
1. 边界条件
- 空字符串、空列表、空字典;
- 单元素集合;
- 最大尺寸输入:10 万字符单条消息、100 万字符 content 块(见
conftest.py中edge_case_messages["huge_message"]); Null/None值(消息 content 为None、key 为None)。
2. 类型不匹配
- 期望字段类型错误(如
role为整数123); - 缺少必填字段(
{"role": "user"}无 content、{"content": "No role"}无 role); - 多余意外字段;
- 非法消息结构(
malformed_dict)。
3. 状态不一致
- 引用了
session_state.tools中不存在的tool_use_id(missing_toolfixture 专门构造此场景); - 部分初始化的状态;
- 渲染过程中的并发修改(
test_concurrent_modification在渲染期间清空 tools); - 损坏的状态(访问即抛异常)。
4. 错误条件
- 导入错误;
- asyncio 异常(新建/设置 loop 失败);
- 环境变量错误(
clean_environment删除ANTHROPIC_API_KEY); - Lambda 求值失败;
- Base64 解码错误(
test_base64_decode_error中 patchbase64.b64decode抛出异常)。
5. 性能边界
- 1000+ 条消息的历史记录;
- 100+ 层深层嵌套(
test_deeply_nested_content/test_deeply_nested_content_performance均构造 100 层 wrapper); - 循环引用(
_create_circular_reference让 content 列表包含自身); - Unicode 与特殊字符。
Mock 策略
Streamlit 组件
所有 Streamlit 组件被整体 Mock,使测试无需运行真实的 Streamlit 服务器即可验证渲染行为。Mock 清单与文档一致,且在conftest.py的mock_streamlitfixture 中可以看到具体实现方式:使用patch("streamlit.session_state")初始化默认状态(hide_screenshots=False、tools={}、messages=[]、api_key="test-key"等),再用嵌套的with patch(...)依次 Mockst.chat_message、st.markdown、st.write、st.error、st.code、st.image,并通过字典把全部 mock 对象返回给测试用例使用。
特别要注意st.chat_message是上下文管理器(with st.chat_message(...)形式),因此 fixture 中为mock_chat.return_value显式设置了__enter__/__exit__。
外部依赖
BrowserTool:通过mock_browser_toolfixture 整体 Mock,避免引入 Playwright 浏览器依赖。注意当前重构后的版本BrowserTool()已不再从环境变量读取尺寸参数(conftest.py中相关注释和test_streamlit_helpers.py中被移除的测试均印证了这一点);- asyncio 事件循环:
mock_asyncio_loop使用Mock(spec=asyncio.AbstractEventLoop)构造受控 loop,run_until_complete直接委托给asyncio.run; - 环境变量:
mock_environment/clean_environment基于 pytest 的monkeypatch设置或删除ANTHROPIC_API_KEY,从而无副作用地测试环境变量存在/缺失两种场景。
Fixtures 一览
conftest.py提供的共享 fixtures 及其职责:
| Fixture | 职责 |
|---|---|
mock_streamlit | 完整的 Streamlit 组件 Mock 集,含默认 session_state |
mock_browser_tool | BrowserTool的 MagicMock 实例 |
sample_tool_result | 5 种ToolResult形态:成功、错误、含截图(真实 1x1 PNG 的 Base64)、空、全字段 |
sample_messages | 覆盖普通 / 复杂 / 边缘 / Unicode / 超长 / 嵌套结构的消息样本 |
edge_case_messages | 专门触发边界与错误的输入:空列表、None、畸形字典、循环引用、缺失工具、非法类型、100 万字符巨块 |
mock_asyncio_loop | 受控 asyncio 事件循环 |
mock_environment | 设置测试环境变量 |
clean_environment | 删除环境变量(模拟缺失场景) |
mock_provider | 模拟APIProvider枚举(anthropic / bedrock / vertex) |
mock_api_response_with_text_and_tools | 模拟同时含文本与多个tool_use的 API 响应 |
mock_tool_collection | 模拟ToolCollection(含tool_map与to_params) |
sample_mixed_content_messages | 文本与工具调用混合的完整对话流 |
sample_tool_result中真实 PNG 的 Base64(iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJ...)保证了"带截图渲染"路径能够走完实际的 Base64 解码逻辑。
持续集成(CI)实践
将测试接入 CI 的标准流程(与tests/README.md一致):
# 1. 安装依赖 pip install -e ".[test]" # 2. 运行测试并输出 XML 与终端覆盖率 pytest tests/ --cov=browser_tools_api_demo --cov-report=xml --cov-report=term # 3. 生成覆盖率徽章 coverage-badge -o coverage.svg与前文同理,--cov的目标包名建议按当前源码改为browser_use_demo。coverage-badge需要单独安装(pip install coverage-badge),它读取上一步生成的覆盖率数据产出 SVG 徽章,用于在仓库 README 中展示。
贡献测试代码的规范
tests/README.md对新增功能与重构提出了明确的测试要求,这也是本项目持续保持高测试密度的机制保障:
- 新增功能必须配套相应测试;
- 确保所有边缘用例被覆盖;
- 提交前运行完整测试套件;
- 维持 >95% 的代码覆盖率;
- 若测试结构发生变化,同步更新本 README。
配套的静态检查依赖(ruff、pyright、pre-commit)定义在 setup.py 的devextras 中,可作为 CI 流水线的质量关卡。
关键要点回顾
- 三种运行粒度:全量(
pytest tests/)、按文件/类/方法(::定位)、按 marker(-m integration/-m "not integration"/-m asyncio)自由组合; - 两层 Mock 体系:Streamlit 组件层(
mock_streamlit)与外部依赖层(BrowserTool、asyncio、环境变量),让测试完全不依赖真实浏览器与网络; - 覆盖率统计:结合
pytest-cov输出 HTML/XML/终端报告,但需注意仓库文档中旧包名browser_tools_api_demo与当前源码browser_use_demo的差异; - 高密度边界覆盖:从 100 万字符消息、100 层嵌套、循环引用到并发修改、Base64 解码失败,测试套件系统地验证了渲染层与状态层的健壮性;
- 可复用价值:
conftest.py的 fixture 设计(尤其是 Streamlit 上下文管理器 Mock 与事件循环 Mock)可直接迁移到其他基于 Streamlit + Anthropic SDK 的 Agent 项目中。
如需深入底层实现,建议对照阅读 message_renderer.py(渲染逻辑)、streamlit.py(setup_state/authenticate/ 事件循环)以及 pytest.ini(测试发现与 marker 配置),三者与测试文件互为印证,能帮助你完整理解这套测试体系的每个断言背后的真实行为。
【免费下载链接】claude-quickstartsA collection of projects designed to help developers quickly get started with building deployable applications using the Claude API项目地址: https://gitcode.com/GitHub_Trending/an/claude-quickstarts
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考