news 2026/9/15 21:58:24

Freebuff CLI 端到端测试实战:基于 tmux 与 Codebuff SDK 的双层测试体系

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Freebuff CLI 端到端测试实战:基于 tmux 与 Codebuff SDK 的双层测试体系

Freebuff CLI 端到端测试实战:基于 tmux 与 Codebuff SDK 的双层测试体系

【免费下载链接】freebuffThe free coding agent项目地址: https://gitcode.com/GitHub_Trending/cod/freebuff

本文档是 Freebuff 开源仓库中 CLI 端到端(E2E)测试体系的完整技术指南。它围绕 freebuff/e2e/README.md 展开,详细讲解如何通过 tmux 驱动编译产物进行确定性断言,如何使用 Codebuff SDK 让 AI 测试代理自动验证复杂行为,以及如何对生产后端执行"真实一轮对话"的冒烟测试。读完本文,你将掌握 Freebuff E2E 测试的完整架构、核心工具类用法、构建运行流程,并能够按既有规范新增一条端到端测试。

为什么 CLI 需要端到端测试

Freebuff 是一个终端交互的编码代理 CLI(The free coding agent),其核心体验全部发生在 TUI(终端用户界面)之中:启动时的 ASCII 启动画面、模型选择器、/help等斜杠命令、流式对话输出。单元测试可以覆盖内部逻辑,却无法回答"编译出的二进制在真实终端里能不能正常启动、按键是否被正确捕获、输出是否如期渲染"这类问题。

freebuff/e2e/README.md 给出的答案是:用 tmux 模拟真实终端会话,让测试代码像真实用户一样与编译后的二进制交互——发送文本、发送按键、抓取屏幕输出、断言行为——从而验证 CLI 的端到端正确性。整个 E2E 测试目录结构如下:

freebuff/e2e/ ├── README.md ├── agent/ │ └── freebuff-tester.ts # SDK 测试代理定义 ├── tests/ # 各特性 E2E 测试 │ ├── ads-behavior.e2e.test.ts │ ├── agent-startup.e2e.test.ts │ ├── code-edit.e2e.test.ts │ ├── help-command.e2e.test.ts │ ├── knowledge-file.e2e.test.ts │ ├── live-turn.e2e.test.ts # 唯一的线上冒烟测试 │ ├── slash-commands.e2e.test.ts │ ├── startup.e2e.test.ts │ ├── terminal-command.e2e.test.ts │ └── version.e2e.test.ts └── utils/ ├── binary-helpers.ts # 二进制路径解析 ├── freebuff-session.ts # 核心会话封装类 ├── index.ts # 统一导出 ├── tmux-custom-tools.ts # SDK 自定义工具 └── tmux-helpers.ts # tmux 脚本底层封装

测试架构:三种互补的验证方式

根据 README 的定义,这套体系同时支持三种测试方式,覆盖从"快速确定性回归"到"真实后端链路"的完整梯度:

方式核心机制适用场景
1. 直接 tmux 测试FreebuffSession类在 tmux 中启动二进制、发送命令、抓取输出并直接断言快速、确定性的功能回归,如启动、版本、帮助命令
2. SDK Agent 驱动测试通过 Codebuff SDK 运行一个测试代理,代理借助自定义 tmux 工具与 CLI 交互AI 推理式验证复杂行为,输出不再是"匹配字符串"而是"理解语义"
3. 线上冒烟测试使用生产环境变量构建二进制,真实调用后端完成一轮对话并结束会话发布前与定时任务中对线上链路的冒烟验证

方式一:直接 tmux 测试(快速、确定性)

这是最常用的方式。测试代码通过FreebuffSession在 tmux 中启动二进制,随后发送命令、捕获输出、断言结果:

import { describe, test, expect, afterEach } from 'bun:test' import { FreebuffSession, requireFreebuffBinary } from '../utils' describe('My Feature', () => { let session: FreebuffSession | null = null afterEach(async () => { if (session) await session.stop() session = null }) test('works correctly', async () => { const binary = requireFreebuffBinary() session = await FreebuffSession.start(binary) await session.send('/help') const output = await session.capture(2) expect(output).toContain('Shortcuts') }, 60_000) })

注意测试运行在bun:test之上(与仓库其余测试一致),每个测试都声明了超时(如60_000毫秒),因为真实的终端交互与轮询需要时间。

方式二:SDK Agent 驱动测试(AI 推理式验证)

当行为过于复杂、无法用简单的字符串包含断言时,可以使用 Codebuff SDK 运行一个测试代理。代理通过自定义 tmux 工具与 Freebuff 交互,理解CLI 输出并验证复杂行为:

import { describe, test, expect, afterEach } from 'bun:test' import { CodebuffClient } from '@codebuff/sdk' import { freebuffTesterAgent } from '../agent/freebuff-tester' import { createFreebuffTmuxTools, requireFreebuffBinary } from '../utils' describe('Agent Test', () => { let cleanup: (() => Promise<void>) | null = null afterEach(async () => { if (cleanup) await cleanup() cleanup = null }) test('verifies startup', async () => { const apiKey = process.env.CODEBUFF_API_KEY if (!apiKey) return // Skip if no API key const binary = requireFreebuffBinary() const tmuxTools = createFreebuffTmuxTools(binary) cleanup = tmuxTools.cleanup const client = new CodebuffClient({ apiKey }) const result = await client.run({ agent: freebuffTesterAgent.id, prompt: 'Start Freebuff and verify the branding is correct.', agentDefinitions: [freebuffTesterAgent], customToolDefinitions: tmuxTools.tools, handleEvent: () => {}, }) expect(result.output.type).not.toBe('error') }, 180_000) })

CODEBUFF_API_KEY时该测试直接跳过,因此本地未配置密钥也不会阻塞测试套件。

方式三:线上冒烟测试(真实后端链路)

freebuff/e2e/tests/live-turn.e2e.test.ts 是freebuff/e2e/tests/目录中唯一直接与真实后端通信的文件:它在落地页选择器上定位到 DeepSeek V4.1 Flash 模型、启动会话、发送一条提示词、等待回答,最后执行/end-session并校验后端不再存在打开的会话。它只有在设置了FREEBUFF_SMOKE_API_KEY(或CODEBUFF_API_KEY)时才运行,否则跳过;且需要一个以生产公共环境变量构建的二进制。该测试不在freebuff-e2e.yml矩阵中,而是由.github/workflows/prod-smoke.yml按计划(schedule)以及每次发布前执行(说明:当前仓库快照的 .github/workflows 目录下仅可见ci.ymlpr-hygiene.yml,README 中描述的freebuff-e2e.ymlprod-smoke.yml属于文档记载的 CI 配置)。

NEXT_PUBLIC_CB_ENVIRONMENT=prod NEXT_PUBLIC_CODEBUFF_APP_URL=https://www.codebuff.com \ NEXT_PUBLIC_FREEBUFF_APP_URL=https://freebuff.com NEXT_PUBLIC_SUPPORT_EMAIL=support@codebuff.com \ NEXT_PUBLIC_POSTHOG_API_KEY=test NEXT_PUBLIC_POSTHOG_HOST_URL=http://127.0.0.1:9 \ NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=test NEXT_PUBLIC_STRIPE_CUSTOMER_PORTAL=http://127.0.0.1:9 \ NEXT_PUBLIC_WEB_PORT=3000 bun freebuff/cli/build.ts 0.0.0-smoke CODEBUFF_API_KEY=<your token> bun test freebuff/e2e/tests/live-turn.e2e.test.ts --timeout=300000

两个关键设计细节:

  • 配置隔离:CLI 运行在自己的FREEBUFF_CONFIG_DIR下,因此不会触碰本机的真实用户配置。从源码看(freebuff/e2e/tests/live-turn.e2e.test.ts),测试用fs.mkdtempSync创建临时配置目录,写入settings.json(设置freebucksIntroSeenAt以跳过首次运行卡片),并通过FreebuffSession.startenv选项注入CODEBUFF_API_KEYFREEBUFF_CONFIG_DIR
  • 不留尾巴afterEach中若仍处于聊天界面且会话未结束,会自动发送/end-session,防止一次失败的断言在账号上留下长达一小时的开放会话(源码注释明确记录了首个 CI 运行曾因此踩坑,见 live-turn.e2e.test.ts)。

测试的提示词也刻意选择了不可靠记忆、但答案唯一的内容:"Reply with only the English word for the number 7, in lowercase.",期望输出seven(源码注释解释了为何不用算术题——Flash 曾把 4187+2359 答成 6536)。

前置条件

运行 E2E 测试前需要准备:

  • tmux必须安装:macOS 用brew install tmux,Ubuntu 用sudo apt-get install tmux
  • Freebuff 二进制必须先构建:bun freebuff/cli/build.ts 0.0.0-dev
  • SDK 构建产物(仅 Agent 测试需要):cd sdk && bun run build
  • CODEBUFF_API_KEY(仅 Agent 测试需要):设置该环境变量。

构建与运行测试

先构建二进制

bun freebuff/cli/build.ts 0.0.0-dev

二进制路径的解析规则见 freebuff/e2e/utils/binary-helpers.ts:优先读取环境变量FREEBUFF_BINARY,否则回退到cli/bin/freebuffrequireFreebuffBinary()在文件不存在时会抛出带构建提示的错误信息("Build with: bun freebuff/cli/build.ts ")。

运行全部测试

bun test freebuff/e2e/tests/

运行单个测试

bun test freebuff/e2e/tests/version.e2e.test.ts bun test freebuff/e2e/tests/startup.e2e.test.ts bun test freebuff/e2e/tests/help-command.e2e.test.ts bun test freebuff/e2e/tests/agent-startup.e2e.test.ts

使用自定义二进制路径

FREEBUFF_BINARY=/path/to/freebuff bun test freebuff/e2e/tests/

核心工具:FreebuffSession源码级详解

FreebuffSession定义于 freebuff/e2e/utils/freebuff-session.ts,是整个体系的基石。它封装了"在 tmux 中启动二进制 → 与 TUI 交互 → 抓取输出 → 清理"的全过程。

API 一览

方法说明
FreebuffSession.start(binaryPath)在 tmux 中启动二进制,返回会话对象
session.send(text)发送文本输入(默认按下回车)
session.sendKey(key)发送特殊按键(如'C-c''Escape'
session.capture(waitSec?)抓取终端输出
session.captureLabeled(label, waitSec?)抓取并保存到会话日志(带标签)
session.waitForText(pattern, timeoutMs?)轮询直到终端出现指定文本
session.stop()停止会话并清理资源

start():临时工作目录与安全的密钥注入

FreebuffSession.start的源码实现(freebuff-session.ts)值得仔细阅读:

  1. 创建临时项目目录fs.mkdtempSync在系统临时目录生成freebuff-e2e-*目录,并写入一个最小README.md# E2E Test Project),让 Freebuff 有真实项目可索引;
  2. 支持预置初始文件:通过initialFiles选项可在启动前写入任意相对路径的文件(含嵌套目录自动创建);
  3. 环境变量的安全注入env选项(如密钥、配置目录)被写入一个位于项目目录之外、权限为0600env.sh文件,再由 tmux shellsource加载(源码见 freebuff-session.ts)。这样密钥既不会落入 CLI 索引的项目目录,也不会出现在会话记录的命令行中;
  4. tmux 启动:组装cd '<tmpDir>' && '<binaryPath>'命令并调用tmuxStart,默认等待 4 秒、窗口尺寸 120×30。

会话内文件操作与轮询

除终端交互外,FreebuffSession还提供writeFile/readFile/fileExists直接在临时工作目录中读写文件,以及waitForFileContent(relativePath, pattern, timeoutMs?)轮询等待某个文件出现指定内容(默认超时 60 秒,超时后抛出含终端输出的详细错误)。这对于验证"CLI 是否在项目中实际写出了文件"的场景(如代码编辑测试)非常实用。

启动就绪判断:waitForReadywaitForBootSignal

TUI 的启动存在多种形态,源码为此提供了两套判断:

  • waitForReady(timeoutMs?, minLines?):轮询终端输出,直到非空行数达到阈值(默认 5 行),表示 TUI 已完成初始渲染;
  • waitForBootSignal(timeoutMs?):轮询直到输出命中FREEBUFF_BOOT_SIGNALS中的任意一个已知启动标志。该常量数组(freebuff-session.ts)包含:
export const FREEBUFF_BOOT_SIGNALS = [ '█████╗ ██████╔╝', // ASCII logo (full or small variant) 'Start coding for free', 'Enter a coding task', 'Pick a model to start', "Free mode isn't available", 'Press ENTER to login', 'Open this URL', 'will run commands on your behalf', ] as const

源码注释解释了原因:CI 运行环境经常落在模型选择器的 wordmark("Start coding for free")而非完整的 ASCII 大 Logo,所以相比只等待单个 Logo 行,匹配一组标志更可靠。

stop():幂等清理

stop()先调用tmuxStop(该脚本幂等,会话已不存在时静默忽略错误),再递归删除临时工作目录与环境目录。

tmux 底层封装与脚本层

FreebuffSession并不直接操作 tmux,而是通过 freebuff/e2e/utils/tmux-helpers.ts 调用仓库根目录scripts/tmux/下的 shell 脚本(tmux-start.shtmux-send.shtmux-capture.shtmux-stop.sh):

函数对应脚本说明
tmuxStart(options)tmux-start.sh启动 tmux 会话,支持--command--name--width--height--wait--plain
tmuxSend(session, text, opts?)tmux-send.sh发送文本,支持--no-enter(不回车)、--wait-idle(等待空闲)、--force
tmuxSendKey(session, key)tmux-send.sh --key发送特殊按键
tmuxCapture(session, opts?)tmux-capture.sh抓取输出,支持--wait--label(带标签保存日志)、--no-save
tmuxStop(session)tmux-stop.sh停止会话,错误被捕获(幂等)

这些脚本同时被 scripts/tmux 目录下的其他工具复用,构成一套独立的终端会话操作基础设施。

SDK 自定义工具与测试代理

createFreebuffTmuxTools(binaryPath)

定义于 freebuff/e2e/utils/tmux-custom-tools.ts,返回{ tools, cleanup }tools是四个自定义工具定义(输入模式使用zod/v4schema 声明),cleanup应在afterEach中调用以兜底停止会话:

工具名作用关键行为
start_freebuff启动 CLI先检查会话是否已存在;启动后调用waitForReady()并返回初始输出
send_to_freebuff发送文本输入如同用户输入,默认按下回车;无会话时返回错误信息
capture_freebuff_output抓取终端输出可选waitSeconds参数,等待后再抓取
stop_freebuff停止并清理幂等,返回wasRunning状态

freebuffTesterAgent:专职 QA 测试代理

freebuff/e2e/agent/freebuff-tester.ts 定义了测试代理:idfreebuff-tester,模型为anthropic/claude-sonnet-4.5,可用的工具恰好是上述四个 tmux 工具。其系统提示词(instructionsPrompt)为代理规定了标准的操作流程:

  1. 调用start_freebuff启动 CLI;
  2. capture_freebuff_output(带waitSeconds)查看终端输出;
  3. send_to_freebuff输入命令或文本;
  4. 再次抓取输出验证行为;
  5. 完成后务必调用stop_freebuff

同时要求代理重点核查:CLI 是否无错误/无崩溃启动、启动画面是否有可见内容、命令是否符合预期、错误信息是否对用户友好,并清晰汇报测试项、观察结果与通过/失败结论。这套"自定义工具 + 专用代理 + 结构化指令"的模式,正是 Freebuff 用 AI 验证 AI 产品的落地示范。

测试用例剖析

version.e2e.test.ts:版本输出的三个断言

freebuff/e2e/tests/version.e2e.test.ts 直接以execFileSync运行二进制(不经 tmux),验证三点:

  • --version输出应匹配/\d+\.\d+\.\d+/(semver 形式);
  • 退出码为 0(execFileSync遇非零退出码会抛异常,不抛即通过);
  • 忽略项目bunfig.toml的 preload:测试在临时目录写入preload = ["$config/db"]bunfig.toml后仍能正常输出版本——这验证了--version这类无界面命令不会被项目配置干扰。

startup.e2e.test.ts:启动画面与优雅退出

freebuff/e2e/tests/startup.e2e.test.ts 包含两个典型场景:

  • 启动画面渲染waitForBootSignal()等待任一启动标志出现,随后反向断言排除致命错误标记——输出不得包含Fatal error during startupInternal error: tree-sitter.wasm not foundFATALpanicSegmentation fault。这是一种"双重保险"策略:即便某个竞态导致错误与 Logo 同时出现,也会被清晰指出而非埋没在原始输出中;
  • Ctrl+C 优雅退出:启动并waitForReady()后发送C-c,等待片刻再断言输出不含UnhandledFATAL——验证 CLI 能干净地处理中断信号。

help-command.e2e.test.ts:品牌与用法校验

freebuff/e2e/tests/help-command.e2e.test.ts 检查--help输出:必须包含freebuff(忽略大小写)、匹配/usage|options|commands/i,并且不得出现codebuff字样——后者是品牌正确性的回归测试,防止帮助文案中泄露母公司品牌。

新增测试的规范流程

按 README 的指引,新增一条 E2E 测试只需三步:

  1. freebuff/e2e/tests/下新建文件,命名遵循<feature>.e2e.test.ts约定;
  2. 将测试名加入.github/workflows/freebuff-e2e.yml的矩阵(README 记载的 CI 配置,示例片段):
matrix: test: - version - startup - help-command - agent-startup - your-new-test # <-- add here
  1. 提交后,该测试将在 CI 中与其他测试并行运行。

CI 工作流设计

根据 README 对.github/workflows/freebuff-e2e.yml的描述,CI 流程为:

  1. 构建一次Freebuff 二进制(linux-x64);
  2. 通过 GitHub Actions 的 matrix 策略并行运行每个测试文件;
  3. 失败时上传 tmux 会话日志用于调试(这正是captureLabeled带标签保存日志的意义所在)。

触发时机包括每晚 PT 时间 6:00手动触发workflow_dispatch)。线上冒烟测试live-turn.e2e.test.ts则独立于矩阵之外,由prod-smoke.yml在计划任务与每次发布前执行。

最佳实践小结

  • 分层选择测试方式:确定性行为用直接 tmux 断言(快、稳、无需密钥);需要语义理解的行为交给 SDK 测试代理;线上链路用带密钥的冒烟测试且务必结束会话;
  • 善用轮询而非固定 sleepwaitForTextwaitForBootSignalwaitForFileContent都采用轮询 + 超时错误报告,失败时附上终端输出便于定位;
  • 注意密钥与配置隔离:利用env选项 + 0600env.sh+ 独立FREEBUFF_CONFIG_DIR,测试永不污染真实用户环境;
  • 每个测试声明合理超时:终端交互类测试通常以 60 秒为基准,Agent 测试与线上冒烟可达 180~300 秒;
  • 反向断言防漏网:对启动等关键路径,除了正向匹配启动标志,还应反向断言FATALpanic等致命标记。

至此,你可以基于这套框架为 Freebuff 的任意 CLI 功能编写稳定、可并行、可调试的端到端测试,并顺畅地融入其 CI 流水线。

【免费下载链接】freebuffThe free coding agent项目地址: https://gitcode.com/GitHub_Trending/cod/freebuff

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

HOG+SVM目标检测原理与手写实现指南

1. 这不是“过时技术”&#xff0c;而是你真正理解目标检测的起点HOGSVM 这个组合&#xff0c;现在一提起来&#xff0c;很多人第一反应是“老古董”“早就被YOLO和RetinaNet淘汰了”。但我在带新人做计算机视觉项目时&#xff0c;坚持让他们先手写一遍 HOG 特征提取 SVM 训练…

作者头像 李华
网站建设 2026/9/15 21:55:44

恒捷家电商城SpringBoot毕业设计项目完整拆解与避坑指南

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

作者头像 李华
网站建设 2026/9/15 21:55:03

GD32H759 + RT-Thread 以太网驱动移植实战:从 DMA 描述符到 PHY 调试

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

作者头像 李华
网站建设 2026/9/15 21:53:23

设计团队文件存储方案:三类场景下的NAS与云盘选型指南

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

作者头像 李华
网站建设 2026/9/15 21:53:22

FckSignups 项目概览:200+ 浏览器免登录工具的终极收藏

FckSignups 项目概览&#xff1a;200 浏览器免登录工具的终极收藏 【免费下载链接】FckSignups A list of tools that are open-source, in-browser, and require no-signups! 项目地址: https://gitcode.com/GitHub_Trending/fc/FckSignups FckSignups&#xff08;现已…

作者头像 李华