VS Code Agent Sessions 会话导航与历史加载 E2E 场景剖析:从自然语言场景到确定性回归验证
本文围绕 VS Code 仓库中 Agent Sessions(代理会话)工作台的端到端测试场景
04-navigate-sessions,讲解"会话之间来回切换并验证历史消息正确加载"这一核心交互的测试目标、底层 UI 行为,以及它如何被 "compile-and-replay"(编译—回放)测试架构翻译成可确定性执行的playwright-cli命令。读者将掌握该场景的逐步语义、.commands.json生成物结构、测试运行方式,以及如何书写与维护同类会话场景。
场景文档说了什么
仓库中 src/vs/sessions/test/e2e/scenarios/04-navigate-sessions.scenario.md 是一个人类可读的 E2E 场景(scenario),全文以 H2 ## Steps 下的自然语言编号列表组织,共 9 步:
- 在聊天输入框输入
explain the code - 按 Enter 提交
- 校验聊天区出现响应
- 点击 New Session 按钮
- 在聊天输入框输入
build the project - 按 Enter 提交
- 校验聊天区出现响应
- 在会话列表中点击另一个会话
- 校验当前会话已切换,且聊天内容与之前不同
它的测试意图集中且清晰:一个 Agent 工作台需要支持创建多个会话(Session)、在会话间导航,并且每次切换都必须把对应会话的历史聊天记录正确恢复出来。这是一个典型的 "状态隔离 + 历史加载" 回归验证场景——如果会话模型把多条会话的上下文串了,或切换时没有正确按会话加载历史,第 8、9 步就会失败。
该场景不是孤立的,它隶属于 src/vs/sessions/test/e2e/ 下的场景族(编号 01–05),场景文件按文件名排序后顺序执行。兄弟场景分别覆盖:01-chat-response(发送消息并收到响应)、02-chat-with-changes(聊天产出真实文件 diff)、03-session-in-sidebar(发送消息后侧边栏出现会话)、05-full-workflow(把会话与终端标签、Changes 视图串成完整工作流)。
前置理解:被测试的"会话"是什么
该场景针对的是 src/vs/sessions 目录实现的 Agent Sessions 工作台——一个以"会话"为中心组织聊天、变更与文件操作的环境(内部文档见 src/vs/sessions/README.md、src/vs/sessions/SESSIONS.md)。从目录结构可以推断其 UI 构成:
- browser/parts/sessionsPart.ts 与 browser/parts/sessionView.ts 负责承载"会话列表 / 会话视图"——即场景第 8 步点击的目标;
- contrib/sessions/browser 下有 27 个
.ts文件实现会话列表、会话条目的管理与渲染; - contrib/chat/browser/newSession.ts 定义了
NewSessionChangeType(repoUri、isolationMode、branch、options、disabled、agent),对应"新建会话"时要变更的属性维度; - contrib/chat/browser/sessionsChatHistory.ts 则暗示了"会话 → 聊天历史"的加载机制。
因此场景中的 "New Session" 按钮、会话列表(sessions list)、会话条目,都有对应的真实 UI 部件;切换会话时被验证的"内容不同",本质上是工作台按照当前激活会话重新渲染其专属的历史消息序列(05-full-workflow 场景还进一步验证:终端标签会随会话切换在 session-1 / session-2 之间来回改变,佐证会话切换会带动一组相关联的会话级 UI 状态)。
逐步拆解:每一步在测什么
结合场景族与 Mock 架构(详见 test/e2e/README.md),9 个步骤可归纳为三个测试片段:
| 步骤 | 用户动作 | 真实 UI 行为 | 验证点 / Mock 行为 |
|---|---|---|---|
| 1–3 | explain the code → Enter |
消息经 Chat Widget → ChatService 提交给 Chat agent | 出现聊天响应。生成物中该断言具体化为 ASSERT_VISIBLE: This project has a simple structure with a main entry point and utility functions. |
| 4 | 点击 New Session | 创建并激活一个全新的、空的会话(保留旧会话在列表中) | 步骤 5–7 中旧会话历史不再显示,新输入进入新会话上下文 |
| 5–7 | build the project → Enter |
同上提交路径;该消息关键字命中带 textEdit 的 canned 响应 |
出现新响应。生成物中断言 ASSERT_VISIBLE: I'll help you build the project. Here are the changes: |
| 8 | 点击会话列表中的另一会话 | 工作台把激活态切回第一个会话 | 生成物中以 click listitem "explain the code" 定位会话条目——可见会话条目以首条用户消息文本作为可访问性标签 |
| 9 | — | 视图重新加载目标会话的历史 | 生成物中两条断言:ASSERT_VISIBLE: explain the code(条目仍可见)与 ASSERT_VISIBLE: This project has a simple structure...(第一条会话的历史响应被正确恢复) |
可以看到,第 9 步的真正杀手锏是:断言恢复出来的内容是第一段会话特有的 canned 响应文本,而不是第二段会话的 build the project 响应。这样只要历史加载错位(例如两个会话共享同一份上下文),断言立刻失败。
需要说明的是:这些 "canned response" 来自测试 Mock agent 的关键字匹配响应,具体定义在 test/web.test.ts 的 getMockResponseWithEdits(message)(该函数针对 build the project 这类消息返回 I'll help you build the project. Here are the changes: 文本并附带编辑进度项)。真实 LLM 与真实 git 均被 Mock,测试验证的是除外部后端之外的整条真实代码路径。
场景如何变成可回放的命令:compile-and-replay 架构
.scenario.md 只是"剧本",真正被测试运行器执行的是配套的 .commands.json。这套 E2E 采用 compile-and-replay 架构,由 @playwright/cli 与 Copilot CLI 驱动(README 对此有完整说明),包含两个阶段:
Phase 1 — 生成(npm run generate,需要 LLM,运行一次)
生成脚本 generate.cjs 对每个 .scenario.md:启动 Sessions Web 服务并打开页面 → 抓取当前页面的可访问性树(accessibility tree)快照 → 把每个自然语言步骤连同快照交给 Copilot CLI,由其给出精确的 playwright-cli 命令(如 click e143、type "hello")→ 执行命令推进 UI → 将编译结果写入 scenarios/generated/ 下的同名 .commands.json。这些 .commands.json 会被提交到 git,作为人人可复现的确定性测试计划。
Phase 2 — 回放(npm test,无需 LLM,快且确定)
测试运行器 test.cjs 读取每个 .commands.json,机械地逐条回放 playwright-cli 命令并执行断言,全程没有 LLM 调用、没有正则猜 UI。
本场景的编译产物
场景 04-navigate-sessions 的生成产物为 src/vs/sessions/test/e2e/scenarios/generated/04-navigate-sessions.commands.json。其头部记录场景名与生成时间,steps 数组把 9 个人类步骤映射为原子命令:
{
"scenario": "Scenario: Navigate between sessions and verify history loads",
"generatedAt": "2026-03-06T04:56:01.957Z",
"steps": [
{ "description": "Type \"explain the code\"",
"commands": ["click textbox \"Chat input\"", "type \"explain the code\""] },
{ "description": "Press Enter to submit",
"commands": ["click textbox \"Chat input\"", "press Enter"] },
{ "description": "Verify there is a chat response",
"commands": ["# ASSERT_VISIBLE: This project has a simple structure with a main entry point and utility functions."] },
{ "description": "Click button \"New Session\"",
"commands": ["click button \"New Session\""] },
{ "description": "Click on a different session in the sessions list",
"commands": ["click listitem \"explain the code\""] },
{ "description": "Verify the session changed and the content in the chat is different",
"commands": ["# ASSERT_VISIBLE: explain the code",
"# ASSERT_VISIBLE: This project has a simple structure with a main entry point and utility functions."] }
]
}
这个文件很好地展示了两个阶段的交接方式:
- 场景描述(
description)保留人类可读步骤,用于失败时输出清晰的错误上下文; - 动作型步骤编译成语义化命令(如
click button "New Session"、click textbox "Chat input"、press Enter),回放时运行器先从实时快照解析出对应控件引用再执行(见test.cjs的resolveSemanticCommand); - 校验型步骤编译成
snapshot+ 注释式断言,即# ASSERT_VISIBLE: <text>,运行器执行快照后检查文本是否可见。
test.cjs 支持的注释断言格式(README 与源码均可确认)包括:ASSERT_VISIBLE(检查快照含该文本)、ASSERT_DISABLED(检查按钮带 [disabled])、ASSERT_ENABLED(检查按钮不带 [disabled])。
场景文件如何被解析
场景解析逻辑在 common.cjs 的 parseScenario() 中:以 # 开头行作为场景名,/^## steps?$/i 之后的 - 或 1. 有序列表项全部视为步骤,遇到其他 ## 标题即结束解析;discoverScenarios() 会扫描 scenarios/ 目录下所有 *.scenario.md 并按文件名排序。这解释了场景文件的硬性格式约定:首行为 # Scenario: ...,随后是 ## Steps 及其下的编号列表,任何其他 Markdown 章节都不会被当作执行步骤。
复现与运行
按 test/e2e/README.md 的说明,本场景的运行前提与命令如下:
- 前置条件:仓库根目录已编译(
npm install && npm run compile,产物out/);在src/vs/sessions/test/e2e下执行npm install安装依赖;只有运行npm run generate才需要本地有 Copilot CLI(copilot --version校验)。 - 启动服务的等价脚本为
npm run serve,即运行仓库根目录的 scripts/code-sessions-web.js 并以--mock模式启动(该标志会启用 Mock 后端)。 - 回放执行:
cd src/vs/sessions/test/e2e
npm test
- 需要重新编译场景(UI 变化导致控件引用过期、新增或修改场景步骤)时执行
npm run generate,也可按前缀选择性重编:npm run generate -- 04-navigate。
Mock 服务的最小集合(可参会话列表)来自 README:IChatEntitlementService 返回 ChatEntitlement.Free、IDefaultAccountService 返回假登录账号、IGitService 立即解析、chat agent 提供 canned 响应、mock-fs:// 由 InMemoryFileSystemProvider 在工作台内直接注册、GitHub 认证为始终登录的 Mock 扩展、PR 命令为空操作。其余服务(ChatEditingService、ChatModel、ChangesViewPane、diff 编辑器、context keys 等)全部走真实实现——Mock 是数据唯一的注入点。
与相邻场景的协同关系
04 场景不是凭空出现的,它与场景族互为补充,共同逼近"多会话完整工作流":
- 03-session-in-sidebar.scenario.md 先验证"发送一条消息后侧边栏出现对应会话"——这是
04第 8 步能够点击列表条目的前置能力; - 02-chat-with-changes.scenario.md 验证会话内聊天会驱动 Changes 视图产生真实 diff;
- 05-full-workflow.scenario.md 把会话与终端标签联动:新建
session-2后终端标签从session-1变为session-2,点击回第一个会话后又变回session-1——从另一个维度再次印证"会话切换 = 一组会话级状态的整体恢复"。
因此,04 专注的是会话模型两条不变量:(1) 多个会话可共存(New Session 不销毁旧会话);(2) 每个会话持有独立历史,切换时按目标会话恢复(点击列表条目后内容正确切换)。
编写与扩展此类场景的要点
若要在仓库中新增"会话导航"类场景,README 与既有场景文件给出了可复制的实践:
- 在
src/vs/sessions/test/e2e/scenarios/下新建NN-描述.scenario.md,文件名数字前缀决定执行顺序; - 首行写
# Scenario: 一句话描述,随后是## Steps与编号列表,步骤用朴素英文描述,示例模式如Click button "New Session"、Type "build the project" in the chat input、Verify the session changed and the content in the chat is different——agent 能理解自然语言,不必局限于固定句式; - 尽量用 UI 上的精确标签(如按钮名、会话条目文本)定位目标,每条目只做一个动作,保持步骤原子化,便于失败定位;
- 运行
npm run generate生成.commands.json,再npm test验证,最后把.scenario.md与.commands.json一并提交; - 若改动涉及 Mock 的文件编辑关键字,需同步更新 test/web.test.ts 的
getMockResponseWithEdits()与 mock 扩展的文件仓库(extensions/sessions-e2e-mock),并保证路径位于/mock-repo/之下。
一个实用建议(README 亦强调):这类场景的断言应落在文本内容与可见性上,例如校验"切换回来后能看到第一段会话特有的响应文本",而不是断言 Mock 内部实现细节,这样当 Mock 文件内容演进时,场景依然稳定、可长期回归。
小结
04-navigate-sessions 用 9 行自然语言精炼地锁定了一条高价值的会话产品级回归路径:创建第二个会话、回到第一个会话、并断言各自历史完整隔离加载。透过 .commands.json 可以看到它在真实 Agent Sessions 工作台上的确定性执行形态;而它对会话条目"以首条消息为标签"的依赖、对 canned 响应文本的断言策略,也为我们理解该工作台"会话 = 独立历史容器"的核心模型提供了最直观的测试侧证据。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00