T3 Code 非官方中文翻译不是官方产品,也不是 T3 Tools 官方文档。源提交 d2c25950b4d9英文原文GitHub 源文件
Skip to content

场景 ​

场景是一种 e2e 测试,在真实应用里展示一种行为:一台跑在自己的 T3 主目录上的真实服务器,真实的 HTTP 和 WebSocket,以及 Web 客户端(client)上的真实浏览器,或一台池里的 iPhone 模拟器,上面跑着移动应用。开发回放智能体代替模型,因此一次运行不花钱,也可以重复。用它来证明一次缺陷修复:在基线提交上失败,在修复上通过。

场景放在 scripts/scenarios,文件是 *.e2e.ts。e2e 是运行器;文档随 scripts/node_modules/e2e/docs 提供,e2e guide 会把它们打印出来。

编写一个 ​

ts
import { expect } from "e2e";

import { test, WEB } from "./t3.ts";

test(
  "Tab moves from the add-project Git URL field to its submit button",
  WEB,
  async ({ t3, app, screen, browser }) => {
    await t3.project("demo");
    await t3.open();

    await screen.getByRole("button", "Add project").tap();
    await screen.getByText("Git URL").tap();
    await screen.getByRole("combobox").fill("https://github.com/pingdotgg/t3code.git");
    const submit = screen.getByRole("button", "Continue (Enter)");
    await expect(submit).toBeEnabled();

    await browser.keyboard.press("Tab");
    await app.screenshot("after-tab");
    await expect(submit).toBeFocused();
  },
);

screen、browser、app 和 expect 是 e2e 自带的。t3.ts再补一个夹具,用来覆盖 e2e 无从知道的 T3 Code 细节:

  • t3.project(title, files) 创建一个 git 仓库,并把它加为项目(project)。
  • t3.thread({ project, title, messages }) 启动一条对话线程(thread),返回之前按每条消息播放一个回放的回合(turn),这样对话线程里就有真实历史。model 用来选定录制。
  • t3.open(path) 打开应用,此时已经登录。

状态是通过应用自己的 CLI 创建的,所以它们是真实的事件(event),在数据模式并不相同的基线提交上也能用。新对话线程默认播放回放智能体的 showcase 录制:输入一条消息,然后发送。要播放别的内容,请往回放智能体里再加一段录制。

每个文件只写一个测试:证明会运行这个文件,并且文件里必须恰好有一个测试。

在缺陷显现的位置取一次 app.screenshot("label")。证明按标签把基线与头部的捕获配成对。e2e 还会在失败点本身再加一次捕获。

只针对缺陷所破坏的那一点编写 expect 断言。证明把断言失败计为该缺陷。找不到控件,或应用没有启动,则计为场景未能运行。

场景里不要使用 agent.act 和 agent.assert:它们会调用模型,而这里没有配置模型。

运行 ​

sh
vp run scenarios                                                  # all of them
cd scripts && pnpm exec e2e run scenarios/replay-turn-streams.e2e.ts --headed

e2e 通过 start-app.ts 在一个空闲端口上启动开发栈,运行测试,然后停掉它。结果落在 scripts/.e2e:report.json、summary.md,以及每个测试各自的 trace.md、它的截图和一段视频。vp run scenarios --target web 只跑一个目标;标志紧跟在脚本名后面,不要加 --。

第一次运行会下载 e2e 的浏览器(大约 120 MB)。若要提前取好,例如做进机器镜像,请在仓库根目录、于 vp i 之后运行:

sh
node scripts/node_modules/playwright-core/cli.js install --only-shell chromium ffmpeg

e2e 在 127.0.0.1 打开 Web 应用,启动脚本会让 Web 开发服务器绑定这个地址。它不用 localhost,因为有些机器会先把它解析成 ::1。

e2e run --help 列出其余项:--grep、--watch、--last-failed、--changed-since。

每个检出一次只跑一个:一次运行会重建该检出的场景 T3 主目录,以及它的 .e2e 输出。要同时再跑一次,请换一个工作树(worktree)。

证明修复 ​

sh
vp run scenarios:prove scripts/scenarios/add-project-tab-reaches-submit.e2e.ts \
  --base e509db3a2f^ --head e509db3a2f --upload-command "ghdrop upload"

prove-scenario.ts在两个提交上为该场景运行 e2e run,并比较两份报告。它会打印,并写入 scripts/.e2e/prove/proof.json、failsOnBase、passesOnHead、baseSha、headSha、verdict、每一侧的 steps、captures、videos,以及 markdown:一张基线与头部捕获并排的表,每一侧都标着对应的提交,可以直接放进拉取请求(pull request)描述。--upload-command 指定一条命令(command),这条命令接收各个捕获路径,并每行打印一个 URL。--head 的默认值是 HEAD。

只有场景在基线上失败、在头部上通过时,退出码才是 0;跑完了但没有证明修复,是 1;没能跑起来,是 2。在基线上就通过的场景并没有复现这个缺陷,结论里会写明。

场景和 e2e 始终来自你的检出。只有应用来自被测提交:在检出旁边建一个临时工作树(worktree),用 vp i 安装,用完即删。如果被测提交就是你干净的 HEAD,就地运行。因此,一条新场景可以拿一个比它更早的基线来证明修复。

iOS ​

iOS 场景从 t3-mobile.ts 导入,并传入 IOS,也就是 Web 场景传入 WEB 的那个位置:

ts
import { expect } from "e2e";

import { IOS, test } from "./t3-mobile.ts";

test(
  "The iOS app lists a seeded thread and opens its history",
  IOS,
  async ({ t3, app, screen }) => {
    const project = await t3.project("demo");
    await t3.thread({ project, title: "Earlier work", messages: ["first", "second"] });
    await t3.open();

    const thread = screen.getByText("Earlier work", { exact: false }).first();
    await expect(thread).toBeVisible({ timeout: 120_000 });
    await thread.tap();
    await expect(screen.getByText("second fixture turn complete", { exact: false })).toBeVisible();
  },
);

t3.project 和 t3.thread 是一样的。t3.open() 按该检出的原生指纹安装开发客户端,从 Metro 加载,并把它和场景的服务器配成对。device 是 e2e 用来做系统操作的设备夹具。

这次运行会从 Simlock 池租用一台 iPhone 18 Pro,因此需要 SIMLOCK_AGENT_DEVICE_URL 和 AGENT_DEVICE_DAEMON_AUTH_TOKEN,跟 mobile:e2e 一样,还需要 Simlock 0.7.0 或更高版本上的网关。只跑 Web 时什么都不租。设备经 tailnet 访问这台机器的 13990(服务器)和 8190(Metro)端口;本机上另一次运行占着这些端口时,设置 T3_SCENARIO_SERVER_PORT 和 T3_SCENARIO_METRO_PORT。一次 iOS 运行要两到三分钟,大部分花在设备和首个包上。e2e run --target web 或 --target ios 只跑一侧。

scenarios:prove 对 iOS 场景同样适用。每个提交都会得到按它自己的原生代码构建出来的开发客户端。

其他层级 ​

  • Android。 用 mobile({ platform: "android", device: simlock("Pixel 9") }) 添加一个目标,并用 Expo 链接的 Android 形式打开开发客户端。尚未实现。
  • 在线提供方。 把 start-app.ts 里的默认模型指向某个真实提供方(provider)最便宜的模型。针对回复原文的断言站不住。

在 CI 中 ​

目前没有任何作业运行场景,也不应该在每个拉取请求(pull request)上把它们全部跑一遍。e2e 可以用 e2e run --changed-since origin/main 选出分支触及的那些场景,只要测试过滤器已经配好,再由 @e2e-dev/github 把结果发成拉取请求评论。

场景无法覆盖的内容 ​

  • 桌面壳的行为(菜单、窗口、IPC),以及 Android。
  • 特定于提供方(provider)的行为。回放智能体使用的是通用 ACP。
  • 任何命令都创建不出来的状态,例如一次运行因用量限额而停下。
  • 时序。回放的流式传输是模拟出来的,所以场景展示不了性能回归。

非官方译文 · 源提交 d2c25950b4d9 · 不代表 T3 Tools