首页
/ Scenario: Send a chat message and verify response

Scenario: Send a chat message and verify response

2026-09-07 18:16:35作者:魏侃纯Zoe

Steps

  1. Type "explain the code" in the chat input
  2. Press Enter to submit
  3. Verify there is a chat response

这 3 个步骤就是整个场景的"骨架"。值得强调的是:这些步骤不是可直接执行的脚本,而是**给 Copilot CLI 理解的自然语言描述**——真正的可执行脚本由生成阶段(Phase 1: Generate)编译产生,并固化在 `scenarios/generated/01-chat-response.commands.json` 中,与 `.scenario.md` 一起提交进 git(见 [e2e/README.md](https://gitcode.com/GitHub_Trending/vscode6/vscode/blob/2e12155dfa05dfe3cd24e6624cb1489ac8a62907/src/vs/sessions/test/e2e/README.md?utm_source=gitcode_repo_files))。

## 三步走:从自然语言步骤到确定性指令

要把自然语言步骤变成测试,需要理解该体系的两阶段设计(详见 [README.md](https://gitcode.com/GitHub_Trending/vscode6/vscode/blob/2e12155dfa05dfe3cd24e6624cb1489ac8a62907/src/vs/sessions/test/e2e/README.md?utm_source=gitcode_repo_files) 的 "How It Works"):

1. **Phase 1: Generate(使用 LLM,运行一次,较慢)** —— 运行 `npm run generate`,脚本为每个 `.scenario.md` 启动 Sessions Web 服务器并在 `playwright-cli` 中打开页面;随后抓取当前页面的可访问性树(accessibility tree)快照,把"自然语言步骤 + 页面快照"一起发给 Copilot CLI,由它返回精确的 `playwright-cli` 命令(例如 `click e43`、`type "hello"`),再执行这些命令推进 UI 状态,进入下一步骤的循环;最后把编译出的命令写入 `scenarios/generated/` 下的同名 `.commands.json`。
2. **Phase 2: Test(无 LLM,快速、确定性)** —— 运行 `npm test`,运行器读取每个 `.commands.json` 并按顺序机械回放 `playwright-cli` 命令,不加任何 LLM 调用、正则匹配或图标剥离,只做顺序命令与断言。

`01-chat-response` 编译后生成的 `.commands.json` 真实内容如下(见 [01-chat-response.commands.json](https://gitcode.com/GitHub_Trending/vscode6/vscode/blob/2e12155dfa05dfe3cd24e6624cb1489ac8a62907/src/vs/sessions/test/e2e/scenarios/generated/01-chat-response.commands.json?utm_source=gitcode_repo_files)):

```json
{
	"scenario": "Scenario: Send a chat message and verify response",
	"generatedAt": "2026-03-06T04:23:25.470Z",
	"steps": [
		{
			"description": "Type \"explain the code\" in the chat input",
			"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."
			]
		}
	]
}

.scenario.md 与这份编译产物逐行对照,可以观察到两个关键事实:

  • 第 1、2 个自然语言步骤被展开为具体的 UI 定位命令。注意编译产物中保留了语义化的角色选择器 textbox "Chat input"(而非 click e43 这类硬编码的 accessibility ref),其原因是 test 阶段运行时可以通过实时快照把语义选择器解析成当前的 ref(详见 test.cjsresolveSemanticCommand),从而避免 UI 渲染顺序变化导致 ref 失配。
  • 第 3 步"验证存在聊天响应"被编译成了一条注释形式的断言,其中内嵌的断言文本与 Mock 代理针对 "explain the code" 这一关键词返回的固定文案完全一致(见下文源码佐证)。断言以 # 注释承载,说明它并不是要下发给 playwright-cli 执行的命令,而是测试运行器需要特殊解析的信号。

description 字段同时被复用为运行日志中步骤的显示名称,即 README 示例输出中的 step 3: Verify there is a chat response

断言背后的真相:ASSERT_VISIBLE 如何证明"有响应"

场景第 3 步要回答的问题是:提交后聊天区域真的出现了一条模型响应。这一验证既不通过 DOM 选择器,也不通过截图像素比对,而是回归到了 可访问性树快照的文本包含判断

test.cjs 的实现可以看到,当运行器遇到 # ASSERT_VISIBLE: 前缀的命令时,会进入一个带重试的轮询断言逻辑:

if (cmd.startsWith('# ASSERT_VISIBLE:')) {
	const text = cmd.slice('# ASSERT_VISIBLE:'.length).trim();
	return pollAssertion(() => {
		const snap = getSnapshot();
		if (!snap.stdout) { return { ok: false, message: 'Failed to get snapshot for assertion' }; }
		if (!snap.stdout.toLowerCase().includes(text.toLowerCase())) {
			return { ok: false, message: `Expected "${text}" to be visible in snapshot` };
		}
		return { ok: true };
	});
}

其中 pollAssertiontest.cjs)以 ASSERT_TIMEOUT_MS = 10_000 毫秒为总超时、ASSERT_POLL_MS = 500 毫秒为轮询间隔,反复调用 getSnapshot() 抓取最新可访问性树,直到快照中出现目标文本——也就是说,断言天然容忍了"模型响应需要一定时间渲染"的异步性,只要 10 秒内文本出现在可访问性树中即判定通过。整段匹配为大小写不敏感的子串包含判断,不要求精确行匹配。

ASSERT_VISIBLE 之外,运行器还支持 ASSERT_DISABLEDASSERT_ENABLED 两种断言(分别检查按钮是否带 [disabled] 属性,见 test.cjs)。断言文本会被 normalizeLabel()test.cjs)剔除 Codicon 私用区字符([\uE000-\uF8FF])并转为小写再比较,因此在按钮类断言的快照比对中不受图标字符干扰。

模拟响应从何而来:Mock 代理的关键词匹配实现

"explain the code" 为什么会得到一句关于项目结构的固定文案?答案在测试工作台的 Mock 聊天代理实现里。src/vs/sessions/test/web.test.ts 中的 getMockResponseWithEdits()web.test.ts)按用户消息的关键词返回预置响应:

if (/build|compile|create/i.test(message)) {
	return {
		text: 'I\'ll help you build the project. Here are the changes:',
		fileEdits: [ /* index.ts、build.ts、package.json 的编辑 */ ],
	};
}
if (/fix|bug/i.test(message)) {
	/* 返回包含 utils.ts 输入校验编辑的响应 */
}
if (/explain|describe/i.test(message)) {
	return {
		text: 'This project has a simple structure with a main entry point and utility functions.',
	};
}
return {
	text: 'I understand your request. Let me work on that.\n\n1. Review the codebase\n2. Make changes\n3. Run tests',
};

对照可见:01-chat-response 中输入的消息 "explain the code" 命中了第三条分支(正则 /explain|describe/i),返回的正是断言文本 This project has a simple structure with a main entry point and utility functions.。该场景刻意选用"纯文本、无文件编辑"的响应分支,把关注点收敛在聊天响应能否出现并渲染上;而需要连带验证文件变更时,则应输入 "build the project"(命中 /build/ 分支,携带 textEdit 编辑,见 web.test.ts)或 "fix the bug"(命中 /fix/ 分支)——这正是 020305 等场景使用这些关键词的原因。

从源码结构看,Mock 是这套测试中唯一注入 canned data 的入口:Mock 代理负责响应 LLM 的"上游"职责,而其下游——包括 ChatModel 通过 acceptResponseProgress() 路由代理进度、ChatEditingService 消费 textEditChangesViewPane 渲染变更树、diff 编辑器打开真实差异——全部走真实服务实现。因此,01-chat-response 虽然断言的是预置文案,但它验证的是真实工作台下聊天提交、代理调用与响应渲染的完整真实链路

场景运行的全貌:Mock 面与真实面的分工

为了在 CI 中无外部依赖地跑通上述链路,README 中明确列出了"最小 Mock 集"与"其余全部真实"的边界:

服务 Mock 方式 原因
IChatEntitlementService 返回 ChatEntitlement.Free CI 中不存在真实 Copilot 账号
IDefaultAccountService 返回一个假签入账号 隐藏 "Sign In" 按钮
IGitService 立即 resolve Web 测试中无真实 git 扩展
聊天代理(copilotcli 等) 关键词匹配的固定响应 无真实 LLM 后端
mock-fs:// FileSystemProvider InMemoryFileSystemProvider 必须在任何服务解析工作区文件前可用
GitHub 认证 总是签入的 mock provider 无真实 OAuth 流程
PR 命令(Create/Open/Merge) 仅打日志并显示 info 消息 无真实 GitHub API

ChatEditingServiceChatModelChangesViewPane、diff 编辑器、上下文键与菜单动作等均运行真实实现(见 README.md 的 "What's Mocked" / "What's Real" 两节)。

README 用一张数据流图概括了消息从输入到渲染的传播路径,其面向本场景的简化形态为:

User types message → Chat Widget → ChatService
  → Mock Agent invoke() 命中关键词分支 → 返回固定响应文本
    → ChatModel 路由进度 → 聊天视图渲染 markdown 响应
      → (可访问性树快照包含该文本)
        → test.cjs 的 ASSERT_VISIBLE 轮询断言通过

在本地复现与调试本场景

运行前提(详见 README.md 的 Prerequisites / Running 两节):

# 1. 先编译 VS Code(仓库根目录)
npm install && npm run compile

# 2. 安装 E2E 依赖
cd src/vs/sessions/test/e2e && npm install

# 3. UI 变化或首次运行前,重新编译场景(需要 Copilot CLI,慢)
npm run generate

# 4. 运行确定性测试(无需 LLM)
npm test

只看这一个场景可以传入文件名过滤。运行器 discoverCommandFiles(filter)test.cjs)会对文件名做子串匹配,因此也可以直接调用底层脚本:

node test.cjs 01-chat-response

运行器每次会:在随机端口(9100 + Math.floor(Math.random() * 900))启动带 mock 的 Sessions Web 服务器(test.cjs),用 playwright-cli 打开浏览器并访问 http://localhost:<port>/?skip-sessions-welcome,等待工作台渲染后逐个回放场景。每两个场景之间会按下 Escape 并重新 goto 页面以重置状态;单个步骤执行前会 sleep 1 秒让 UI 稳定(test.cjs)。若某一步失败,运行器会通过 screenshot --filename=out/failure-01-chat-response-stepN.png 留下失败现场截图(test.cjs),并最终以 Results: N passed, M failed 汇总并以非零码退出。

测试输出中的关键节点大致为:

Found N compiled scenario(s)
Starting sessions web server on port 9542…
Server ready.
▶ Scenario: Send a chat message and verify response
  ✅   step 1: Type "explain the code" in the chat input
  ✅   step 2: Press Enter to submit
  ✅   step 3: Verify there is a chat response
Results: N passed, 0 failed
登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
898
5.82 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
921
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.8 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
391