test(account): align browser flows and diagnose stalled transport

This commit is contained in:
lsdsjy committed 2026-09-21 22:48:12 +08:00
1 parent 2a1eb24285
commit 5b9a33fa61
10 files changed
+47 -15

No files matched your search

+1 -1
View File
@@ -158,7 +158,7 @@ describe('web e2e: Agent Teams panel', () => {
expect(await trigger.evaluate(element => element === document.activeElement)).toBe(true)
await page.keyboard.press('Enter')
await panel.waitFor()
const outside = page.getByRole('button', { name: 'Settings', exact: true })
const outside = page.getByRole('button', { name: 'Account menu', exact: true })
await outside.focus()
await panel.waitFor({ state: 'detached' })
expect(await outside.evaluate(element => element === document.activeElement)).toBe(true)
@@ -35,7 +35,7 @@ import {
webSnapshotMode,
type WebScaffold,
} from './scaffold.ts'
import { newEnglishPage, saveFailureShot } from './support.ts'
import { openSettingsFromAccountMenu, newEnglishPage, saveFailureShot } from './support.ts'
const SNAPSHOT_DIR = fileURLToPath(new URL('./expected/clickable-links-gallery', import.meta.url))
const UI_EXPECTED = fileURLToPath(new URL('./expected/clickable-links-gallery/ui.expected.md', import.meta.url))
@@ -455,7 +455,7 @@ describe('web e2e: clickable links gallery', () => {
await markdown.locator(`a[href="${HTTP_URL}"]`).click()
await expect.poll(() => browserAddress.inputValue()).toBe(HTTP_URL)
await page.getByRole('button', { name: 'Settings', exact: true }).click()
await openSettingsFromAccountMenu(page, 'en')
await page.getByRole('button', { name: 'Built-in browser', exact: true }).click()
await page.getByRole('menuitem', { name: 'New browser tab', exact: true }).click()
await expect.poll(() => scaffold.ctx.settings.describe().find(row => row.ns === 'ui-chat')?.value).toMatchObject({ linkOpening: 'new-tab' })
@@ -472,7 +472,7 @@ describe('web e2e: clickable links gallery', () => {
}
await page.reload()
await page.getByRole('button', { name: 'Settings', exact: true }).click()
await openSettingsFromAccountMenu(page, 'en')
await page.getByRole('button', { name: 'New browser tab', exact: true }).click()
await page.getByRole('menuitem', { name: 'Built-in browser', exact: true }).click()
await expect.poll(() => scaffold.ctx.settings.describe().find(row => row.ns === 'ui-chat')?.value).toMatchObject({ linkOpening: 'sidebar' })
@@ -8,7 +8,7 @@
- button "打开配置文件"
- button "关闭"
- heading "模型" [level=2]
- paragraph: 填入各提供方的 API 密钥即可使用其模型。
- paragraph: 填入各提供商的 API 密钥即可使用其模型。
- list:
- listitem:
- text: DeepSeek deepseek-official API 密钥
@@ -17,5 +17,4 @@
- group: 自定义设置
- button "取消"
- button "保存"
- button "添加提供方"
- button "添加自定义提供方"
- button "添加模型提供商"
+2 -2
View File
@@ -14,7 +14,7 @@ import {
SCAFFOLD_DEFAULTS_BUNDLE, assertFixtureInventory, captureStableAria, compareOrRefreshGolden,
launchWebScaffold, watchConsole, webSnapshotMode, type WebScaffold,
} from './scaffold.ts'
import { ZH_BROWSER_LOCALE, connectFreshWorkspaceZh, saveFailureShot } from './support.ts'
import { ZH_BROWSER_LOCALE, connectFreshWorkspaceZh, openSettingsFromAccountMenu, saveFailureShot } from './support.ts'
const SNAPSHOT_DIR = fileURLToPath(new URL('./expected/plugin-manager', import.meta.url))
const MANAGER_EXPECTED = join(SNAPSHOT_DIR, 'manager.expected.md')
@@ -64,7 +64,7 @@ describe('web e2e: plugin manager', () => {
const source = language === 'en' ? '中文' : 'English'
const target = language === 'en' ? 'English' : '中文'
if (await page.getByRole('dialog', { name: settings }).count() === 0) {
await page.getByRole('button', { name: settings, exact: true }).click()
await openSettingsFromAccountMenu(page, language === 'en' ? 'zh' : 'en')
}
await page.getByRole('dialog', { name: settings }).getByRole('button', { name: source }).click()
await page.getByRole('menuitem', { name: target }).click()
+1
View File
@@ -316,6 +316,7 @@ async function bootPreview(origin: string, browser: Browser): Promise<void> {
const continueButton = page.getByRole('button', { name: 'Continue' })
await continueButton.waitFor({ timeout: HERO_TIMEOUT_MS })
await continueButton.click()
await page.getByRole('button', { name: 'Add API Key', exact: true }).click()
const configureLater = page.getByRole('button', { name: 'Configure later' })
await configureLater.waitFor({ timeout: 30_000 })
await configureLater.click()
@@ -15,8 +15,15 @@ import * as Retry from '../src/index.ts'
let context: Context | undefined
const servers: Promise<MockLlmServer>[] = []
let failureDiagnostics: (() => object) | undefined
afterEach(async () => {
afterEach(async (test) => {
const diagnose = failureDiagnostics
failureDiagnostics = undefined
// Capture the pending request before disposal aborts it and closes its socket.
if (test.task.result?.state === 'fail' && diagnose !== undefined) {
console.error('transport recovery failure:', JSON.stringify(diagnose()))
}
const ownedContext = context
context = undefined
// A failed transport can still await a stalled socket while its context disposes.
@@ -207,19 +214,44 @@ describe('bounded retry through the real DeepSeek HTTP/SSE adapter', () => {
})
it('turns a stalled body into TIMEOUT and succeeds on the next request', async () => {
const started = performance.now()
let phase = 'starting server'
const observed: { server?: MockLlmServer; agent?: Agent } = {}
let idleSettled = false
failureDiagnostics = () => {
const events = observed.agent?.session.snapshotEvents() ?? []
return {
node: process.version,
phase,
elapsedMs: Math.round(performance.now() - started),
idleSettled,
agentStatus: observed.agent?.status,
requestCount: observed.server?.requests.length ?? 0,
requests: observed.server?.requests.map(({ attempt, behavior, outcome, chunksSent }) =>
({ attempt, behavior, outcome, chunksSent })),
retries: events.filter(event => event.type === 'llm/retry').map(event => event.data),
lastEvents: events.slice(-10).map(event => ({ seq: event.seq, type: event.type })),
}
}
const server = await start(['stall', 'success'], {
apiKey: 'mock-key',
successText: 'recovered after timeout',
})
observed.server = server
phase = 'mounting context'
// This crosses the real HTTP idle timer, so leave scheduler slack between
// the stalled attempt and the mock server's immediate successful response.
context = await harness(server.baseURL, { streamIdleTimeoutMs: 1_000 })
phase = 'creating agent'
const agent = await context.agentLoop.create(SessionId('wire-stall'), {
provider: 'deepseek-official',
model: 'mock-model',
})
await sendAndWait(context, agent)
observed.agent = agent
phase = 'awaiting agent idle'
await sendAndWait(context, agent).finally(() => { idleSettled = true })
phase = 'asserting recovery'
expect(server.requests.map(record => record.behavior)).toEqual(['stall', 'success'])
expect(agent.session.snapshotEvents().filter(event => event.type === 'llm/retry').map(event => event.data.failure.code))
@@ -1,2 +1,2 @@
- button "Settings"
- button "Account menu": Not signed in
- button "Reconnecting, reconnect now": Reconnecting
@@ -11,7 +11,7 @@
- tree "Sessions":
- treeitem "workspace" [expanded]
- treeitem "New Session" [selected]
- button "Settings"
- button "Account menu": Not signed in
- banner:
- button "Open right sidebar"
- text: Into the Unknown Preview
@@ -11,7 +11,7 @@
- tree "会话":
- treeitem "workspace" [expanded]
- treeitem "新会话" [selected]
- button "设置"
- button "账号菜单": 尚未登录
- banner:
- button "打开右侧边栏"
- text: 探索未至之境 预览版
@@ -11,7 +11,7 @@
- tree "Sessions":
- treeitem "workspace" [expanded]
- treeitem "New Session" [selected]
- button "Settings"
- button "Account menu": Not signed in
- banner:
- button "Open right sidebar"
- text: Into the Unknown Preview