feat: v0.8.2 安全纵深补全 · 协议保真 · 断链修复 — 图片SSRF/根MEMORY.md保护根治 · Anthropic thinking回传+pause_turn续传 · 2523 用例全量回归 + E2E 扩充
CI / 类型检查 + Lint + 单元测试 (push) Failing after 9m45s
CI / 全量测试 (Electron ABI) (push) Failing after 6m28s
CI / 产物编译验证 (push) Successful in 11m18s

This commit is contained in:
2026-09-08 14:30:27 +08:00
parent 69776e447f
commit 4cd6e997b5
86 changed files with 4303 additions and 956 deletions
+63
View File
@@ -0,0 +1,63 @@
/**
* MetonaAI Desktop — E2E 中断链路(v0.8.2 P3-3
*
* 独立应用实例(独立 userData + mock),杜绝跨用例运行态竞态:
* 发送挂起脚本(mock 首帧后保持连接、心跳保活)→ 流式态出现中断按钮 →
* 点击中断 → 恢复 idle → 再次发送正常消息可完成(恢复力闭环)。
*/
import { expect, test } from '@playwright/test';
import { launchApp, type AppHarness } from './app-harness';
import { HANG_PREFIX, MOCK_REPLY } from './mock-llm';
let harness: AppHarness;
test.beforeAll(async () => {
harness = await launchApp();
});
test.afterAll(async () => {
await harness?.cleanup();
});
test('中断:挂起的流式回复 → 中断 → 恢复 idle → 可继续对话', async () => {
const page = harness.page;
const input = page.locator('[data-chat-input] textarea').first();
await expect(input).toBeVisible({ timeout: 30_000 });
// 发送挂起脚本:mock 推送首帧后保持连接(心跳保活,不会自然结束)
await input.click();
await input.fill('请执行 __E2E_HANG__ 脚本');
await input.press('Enter');
// 流式态:挂起脚本的首帧文本可见
await expect(page.getByText(HANG_PREFIX).first()).toBeVisible({ timeout: 30_000 });
// 中断按钮出现(发送按钮在流式态变为「中断」)
const stopButton = page.getByRole('button', { name: /中断|Stop/ }).first();
await expect(stopButton).toBeVisible({ timeout: 15_000 });
await stopButton.click();
// 中断后恢复 idle:输入框重新可用
await expect(input).toBeEnabled({ timeout: 15_000 });
// 恢复力闭环:中断后可继续正常对话(新 run → mock 正常回复)。
// abort 点击后引擎需短暂时间真正终止(waitForAbort),期间的发送会被
// 同会话防重入拒绝(busy 提示)—— 以"忙则等待重试"保证确定性。
const busyHint = page.getByText(/该会话正在执行任务|already running/).first();
let sent = false;
for (let attempt = 0; attempt < 10 && !sent; attempt++) {
await input.click();
await input.fill('中断后请再回复一次');
await input.press('Enter');
try {
await busyHint.waitFor({ state: 'visible', timeout: 1_500 });
await page.waitForTimeout(1_000);
} catch {
sent = true; // 未出现 busy 提示 → 本次发送已被接受
}
}
expect(sent).toBe(true);
// 独立实例中首条 MOCK_REPLY 即恢复对话的回答
await expect(page.getByText(MOCK_REPLY).first()).toBeVisible({ timeout: 30_000 });
});