test(account): align browser flows and diagnose stalled transport
This commit is contained in:
1 parent
2a1eb24285
commit
5b9a33fa61
10 files changed
+47
-15
No files matched your search
@@ -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 "添加模型提供商"
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user