news 2026/9/13 15:35:28

Browser Use Demo 测试套件实战指南:从 pytest 配置、Mock 策略到边缘用例全覆盖

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Browser Use Demo 测试套件实战指南:从 pytest 配置、Mock 策略到边缘用例全覆盖

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)。它的设计目标有两个:

  1. 无需真实浏览器即可测试:通过 Mock 掉 Streamlit 组件、BrowserTool(避免引入 Playwright 依赖)和 asyncio 事件循环,使测试可以在纯 Python 环境快速执行;
  2. 边缘用例全覆盖:针对消息渲染、会话状态初始化、事件循环管理等高风险逻辑,构造空值、类型错误、状态不一致、并发修改、超长输入等异常输入,验证系统的健壮性。

从测试文件规模看(详见"测试结构"一节),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"]的定义:

依赖版本用途
pytest8.3.3测试框架核心
pytest-cov4.1.0覆盖率统计(--cov
pytest-mock3.11.1基于monkeypatch的 Mock 工具
pytest-asyncio0.23.6asyncio 测试支持(对应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.iniaddopts中已经默认附加了-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:忽略DeprecationWarningPendingDeprecationWarning,避免第三方库弃用警告干扰输出。

测试结构

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.pyMessageRenderer(message_renderer.py)消息渲染的输入分支与异常处理
test_streamlit_helpers.pysetup_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.errorst.stop、非 Anthropic provider(如 BEDROCK)在空 key 情况下也放行。

集成测试 test_integration.py 中的TestCompleteStateInitialization还给出了setup_state初始化键的完整清单:messagesapi_keyprovidermodelmax_tokenssystem_prompthide_screenshotstoolsbrowser_toolevent_looprendered_message_countis_agent_runningactive_messagesactive_response_container,可作为理解该函数行为的权威参考。

集成测试(test_integration.py)

所有集成测试类都标注了@pytest.mark.integration,因此可用pytest -m integration单独执行。覆盖范围:

  • 完整消息渲染管线TestFullMessageRenderingPipeline构造包含用户文本、assistanttool_usetool_result(分别指向成功与失败的ToolResult)的混合会话,验证st.markdown/st.write/st.error的调用次数符合预期;
  • 状态初始化与持久化TestStateInitializationAndPersistence验证全新状态初始化出全部必需键,且多次渲染间状态保持一致;
  • 事件循环与异步操作TestEventLoopManagementWithAsync验证 loop 的创建/复用、异步 Agent 执行与asyncio.gather并发任务处理;
  • 错误传播TestErrorPropagationAndHandling验证缺失工具的tool_result被优雅处理(不调用st.error)、初始化失败后二次调用可恢复(side_effect依次抛异常/成功);
  • 完整用户交互工作流TestCompleteWorkflowsetup_state→ 用户输入 → 事件循环 →run_agent全链路模拟;
  • 性能与可扩展性TestPerformanceAndScalability验证 1000 条消息的历史渲染(断言st.markdown恰好被调用 1000 次)以及 100 层嵌套内容不栈溢出。

覆盖的边界用例

1. 边界条件

  • 空字符串、空列表、空字典;
  • 单元素集合;
  • 最大尺寸输入:10 万字符单条消息、100 万字符 content 块(见conftest.pyedge_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_idmissing_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.pymock_streamlitfixture 中可以看到具体实现方式:使用patch("streamlit.session_state")初始化默认状态(hide_screenshots=Falsetools={}messages=[]api_key="test-key"等),再用嵌套的with patch(...)依次 Mockst.chat_messagest.markdownst.writest.errorst.codest.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_toolBrowserTool的 MagicMock 实例
sample_tool_result5 种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_mapto_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_democoverage-badge需要单独安装(pip install coverage-badge),它读取上一步生成的覆盖率数据产出 SVG 徽章,用于在仓库 README 中展示。

贡献测试代码的规范

tests/README.md对新增功能与重构提出了明确的测试要求,这也是本项目持续保持高测试密度的机制保障:

  1. 新增功能必须配套相应测试;
  2. 确保所有边缘用例被覆盖;
  3. 提交前运行完整测试套件;
  4. 维持 >95% 的代码覆盖率;
  5. 若测试结构发生变化,同步更新本 README。

配套的静态检查依赖(ruffpyrightpre-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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 15:34:03

VMware安装macOS虚拟机全教程:从解锁到性能优化

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 15:32:29

DMA跨平台失效根因:CPU缓存与内存一致性模型差异

1. 项目概述&#xff1a;一段DMA代码跨平台失效&#xff0c;真相藏在CPU缓存与内存一致性协议里“同一段DMA代码&#xff0c;x86上跑得稳如老狗&#xff0c;换到ARM平台&#xff08;比如RK3588&#xff09;或者RISC-V开发板上&#xff0c;数据就随机错乱——有时第3次传输出错&…

作者头像 李华
网站建设 2026/9/13 15:30:41

YOLO推理迁移Java:ONNX Runtime CPU部署半年省10万实战

如果你抱着“Java要干翻Python”的心态来看这篇文章&#xff0c;可能会失望。因为我到今天仍然认为&#xff0c;YOLO的训练和算法迭代&#xff0c;Python生态就是最顺手的&#xff0c;没有之一。真正想说的是另外一件事&#xff1a;当YOLO要从实验室原型变成工厂里一条24小时不…

作者头像 李华
网站建设 2026/9/13 15:29:13

如何把一堆 PDF 整理成规范文档库?PDF编辑工具 PDF补丁丁免费指南

如何把一堆 PDF 整理成规范文档库&#xff1f;PDF编辑工具 PDF补丁丁免费指南 【免费下载链接】PDFPatcher PDF补丁丁——PDF工具箱&#xff0c;可以编辑书签、剪裁旋转页面、解除限制、提取或合并文档&#xff0c;探查文档结构&#xff0c;提取图片、转成图片等等 项目地址:…

作者头像 李华