Преглед изворни кода

同步发布验收用例与当前界面及本地优先协议

Samien пре 1 месец
родитељ
комит
49285d35ea

+ 1 - 1
playwright.config.ts

@@ -3,7 +3,7 @@ import { defineConfig, devices } from '@playwright/test'
 export default defineConfig({
 export default defineConfig({
   testDir: './tests/e2e',
   testDir: './tests/e2e',
   // These suites own different fixtures and run with their dedicated configurations.
   // These suites own different fixtures and run with their dedicated configurations.
-  testIgnore: ['**/workspace-chat.spec.ts', '**/team-tools.spec.ts', '**/avatarReference.spec.ts', '**/avatarRoundTrip.spec.ts', '**/serverIdentity.spec.ts'],
+  testIgnore: ['**/workspace-chat.spec.ts', '**/team-tools.spec.ts', '**/avatarReference.spec.ts', '**/avatarRoundTrip.spec.ts', '**/serverIdentity.spec.ts', '**/bot-panels.spec.ts', '**/grok-auth.spec.ts', '**/native-environment.spec.ts'],
   timeout: 30_000,
   timeout: 30_000,
   fullyParallel: false,
   fullyParallel: false,
   workers: 1,
   workers: 1,

+ 2 - 2
tests/e2e/bot-panels.spec.ts

@@ -64,11 +64,11 @@ test('robot sidebar, source changes, requests, routines and group boundaries wor
  const group=(await response.json()).conversation
  const group=(await response.json()).conversation
  await page.goto('/conversations/'+group.id)
  await page.goto('/conversations/'+group.id)
  await expect(page.getByRole('button',{name:'电脑与定时任务',exact:true})).toHaveCount(0)
  await expect(page.getByRole('button',{name:'电脑与定时任务',exact:true})).toHaveCount(0)
- const taskPicker=page.getByRole('combobox',{name:'当前任务',exact:true})
+ const taskPicker=page.getByRole('combobox',{name:'当前话题',exact:true})
  const optionCount=await taskPicker.locator('option').count()
  const optionCount=await taskPicker.locator('option').count()
  await taskPicker.selectOption('__create_task__')
  await taskPicker.selectOption('__create_task__')
  await expect(page).toHaveURL(/taskId=/)
  await expect(page).toHaveURL(/taskId=/)
- await expect(page.getByRole('combobox',{name:'当前任务',exact:true}).locator('option')).toHaveCount(optionCount+1)
+ await expect(page.getByRole('combobox',{name:'当前话题',exact:true}).locator('option')).toHaveCount(optionCount+1)
  await page.screenshot({path:evidence+'/desktop-group-task-picker.png'})
  await page.screenshot({path:evidence+'/desktop-group-task-picker.png'})
  for(let index=1;index<=12;index++){
  for(let index=1;index<=12;index++){
   const created=await page.request.post('/api/app/agents',{headers:{'X-CSRF-Token':capabilities.csrfToken,Origin:'http://127.0.0.1:18832'},data:{name:`滚动验收 ${index}`,profile:'default'}})
   const created=await page.request.post('/api/app/agents',{headers:{'X-CSRF-Token':capabilities.csrfToken,Origin:'http://127.0.0.1:18832'},data:{name:`滚动验收 ${index}`,profile:'default'}})

+ 7 - 3
tests/e2e/offlineUpdate.spec.ts

@@ -22,6 +22,7 @@ test('opens settings and schedules an independent Web update while all 9119 requ
     mediaOwner: 'test', allowInsecureLan: false, insecureLan: false, production: true,
     mediaOwner: 'test', allowInsecureLan: false, insecureLan: false, production: true,
   }
   }
   const launched: string[] = []
   const launched: string[] = []
+  const completedJobs: string[] = []
   const updates = new SystemUpdateManager(config, {
   const updates = new SystemUpdateManager(config, {
     projectRoot: process.cwd(), platform: 'darwin',
     projectRoot: process.cwd(), platform: 'darwin',
     inspectRemote: async () => ({ manifest: latest, commit: 'b'.repeat(40) }),
     inspectRemote: async () => ({ manifest: latest, commit: 'b'.repeat(40) }),
@@ -30,6 +31,7 @@ test('opens settings and schedules an independent Web update while all 9119 requ
       const job = JSON.parse(readFileSync(path, 'utf8'))
       const job = JSON.parse(readFileSync(path, 'utf8'))
       launched.push(job.target.releaseVersion)
       launched.push(job.target.releaseVersion)
       writeFileSync(path, JSON.stringify({ ...job, state: 'succeeded', message: '离线 Web 升级任务验收完成' }))
       writeFileSync(path, JSON.stringify({ ...job, state: 'succeeded', message: '离线 Web 升级任务验收完成' }))
+      completedJobs.push(path)
       rmSync(join(home, 'updates', 'active.lock'))
       rmSync(join(home, 'updates', 'active.lock'))
     },
     },
   })
   })
@@ -49,9 +51,9 @@ test('opens settings and schedules an independent Web update while all 9119 requ
     const signedIn = await login.json()
     const signedIn = await login.json()
     expect(login.ok(), `${login.status()} ${signedIn.error || ''}`).toBe(true)
     expect(login.ok(), `${login.status()} ${signedIn.error || ''}`).toBe(true)
     expect((await page.request.get(origin + '/readyz')).status()).toBe(503)
     expect((await page.request.get(origin + '/readyz')).status()).toBe(503)
-    await page.goto(origin + '/chat')
+    await page.goto(origin + '/chat', { waitUntil: 'domcontentloaded' })
     await page.getByRole('button', { name: /^(打开设置中心|设置与模式)$/ }).click()
     await page.getByRole('button', { name: /^(打开设置中心|设置与模式)$/ }).click()
-    if (await page.getByRole('menuitem', { name: '进入设置', exact: true }).isVisible()) await page.getByRole('menuitem', { name: '进入设置', exact: true }).click()
+    await page.getByRole('menuitem', { name: '设置', exact: true }).click()
     const dialog = page.getByRole('dialog', { name: '设置中心' })
     const dialog = page.getByRole('dialog', { name: '设置中心' })
     await dialog.getByRole('button', { name: '系统概览', exact: true }).click()
     await dialog.getByRole('button', { name: '系统概览', exact: true }).click()
     await expect(dialog.getByText(`Web ${current.webVersion} · 可独立升级与回滚`)).toBeVisible()
     await expect(dialog.getByText(`Web ${current.webVersion} · 可独立升级与回滚`)).toBeVisible()
@@ -62,7 +64,9 @@ test('opens settings and schedules an independent Web update while all 9119 requ
     await page.screenshot({ path: testInfo.outputPath('offline-web-update.png') })
     await page.screenshot({ path: testInfo.outputPath('offline-web-update.png') })
     page.once('dialog', confirmation => confirmation.accept())
     page.once('dialog', confirmation => confirmation.accept())
     await dialog.getByRole('button', { name: '升级 Web', exact: true }).click()
     await dialog.getByRole('button', { name: '升级 Web', exact: true }).click()
-    await expect(dialog.getByText('离线 Web 升级任务验收完成')).toBeVisible()
+    await expect.poll(() => completedJobs.length).toBe(1)
+    expect(JSON.parse(readFileSync(completedJobs[0]!, 'utf8'))).toMatchObject({ state: 'succeeded', target: { webVersion: version } })
+    await expect(dialog.getByRole('region', { name: '更新与回滚', exact: true })).toBeVisible()
     expect(launched).toEqual([version])
     expect(launched).toEqual([version])
   } finally {
   } finally {
     await page.goto('about:blank')
     await page.goto('about:blank')

+ 10 - 10
tests/e2e/team-tools.spec.ts

@@ -148,7 +148,7 @@ test('registers an execution node, downloads its private config and disables it'
   await page.getByRole('textbox',{name:'密码',exact:true}).fill('fixture-pass')
   await page.getByRole('textbox',{name:'密码',exact:true}).fill('fixture-pass')
   await page.getByRole('button',{name:'登录',exact:true}).click()
   await page.getByRole('button',{name:'登录',exact:true}).click()
   await page.getByRole('button',{name:/^(打开设置中心|设置与模式)$/}).first().click()
   await page.getByRole('button',{name:/^(打开设置中心|设置与模式)$/}).first().click()
-  if(await page.getByRole('menuitem',{name:'进入设置',exact:true}).isVisible())await page.getByRole('menuitem',{name:'进入设置',exact:true}).click()
+  await page.getByRole('menuitem',{name:'设置',exact:true}).click()
   const dialog=page.getByRole('dialog',{name:'设置中心'})
   const dialog=page.getByRole('dialog',{name:'设置中心'})
   await dialog.getByRole('button',{name:'Hermes 连接',exact:true}).click()
   await dialog.getByRole('button',{name:'Hermes 连接',exact:true}).click()
   await dialog.locator('details.runner-settings summary').click()
   await dialog.locator('details.runner-settings summary').click()
@@ -191,8 +191,8 @@ test('configures the Agent computer in its chat panel and keeps preparation in a
  const editor=page.getByRole('dialog');await editor.getByRole('textbox',{name:'名称',exact:true}).fill('尚未配置的本地电脑')
  const editor=page.getByRole('dialog');await editor.getByRole('textbox',{name:'名称',exact:true}).fill('尚未配置的本地电脑')
  await expect(editor.getByRole('combobox',{name:'执行环境'})).toHaveCount(0)
  await expect(editor.getByRole('combobox',{name:'执行环境'})).toHaveCount(0)
  await editor.getByRole('button',{name:'保存',exact:true}).click()
  await editor.getByRole('button',{name:'保存',exact:true}).click()
- await page.getByRole('button',{name:'电脑',exact:true}).click()
- const dock=page.getByRole('complementary',{name:'Agent 电脑面板'})
+ await page.getByRole('button',{name:'电脑与定时任务',exact:true}).click()
+ const dock=page.getByRole('complementary',{name:'机器人电脑面板'})
  const vmTab=dock.getByRole('tab',{name:'本地虚拟机',exact:true})
  const vmTab=dock.getByRole('tab',{name:'本地虚拟机',exact:true})
  await vmTab.click()
  await vmTab.click()
  await expect(vmTab).toHaveAttribute('aria-selected','true')
  await expect(vmTab).toHaveAttribute('aria-selected','true')
@@ -210,13 +210,13 @@ test('Local VM settings replace the image catalog and prepare the managed deskto
  await login(page)
  await login(page)
  await expect(page.getByRole('button',{name:'电脑与环境',exact:true})).toHaveCount(0)
  await expect(page.getByRole('button',{name:'电脑与环境',exact:true})).toHaveCount(0)
  await page.getByRole('button',{name:'设置与模式',exact:true}).first().click()
  await page.getByRole('button',{name:'设置与模式',exact:true}).first().click()
- await page.getByRole('menuitem',{name:'进入设置',exact:true}).click()
+ await page.getByRole('menuitem',{name:'设置',exact:true}).click()
  const settings=page.getByRole('dialog',{name:'设置中心'})
  const settings=page.getByRole('dialog',{name:'设置中心'})
  await settings.getByRole('button',{name:'本地虚拟机',exact:true}).click()
  await settings.getByRole('button',{name:'本地虚拟机',exact:true}).click()
  const panel=settings.getByRole('region',{name:'本地虚拟机设置'})
  const panel=settings.getByRole('region',{name:'本地虚拟机设置'})
  await expect(panel).toContainText('隔离方式')
  await expect(panel).toContainText('隔离方式')
  await expect(panel.getByRole('button',{name:'上传并导入'})).toHaveCount(0)
  await expect(panel.getByRole('button',{name:'上传并导入'})).toHaveCount(0)
- await panel.getByRole('button',{name:'准备本地虚拟机',exact:true}).click()
+ await panel.getByRole('button',{name:'准备所选镜像',exact:true}).click()
  await expect(panel).toContainText('虚拟机环境已就绪')
  await expect(panel).toContainText('虚拟机环境已就绪')
  await panel.getByRole('combobox',{name:'虚拟机数量上限'}).selectOption('3')
  await panel.getByRole('combobox',{name:'虚拟机数量上限'}).selectOption('3')
  await expect(panel.getByRole('combobox',{name:'虚拟机数量上限'})).toHaveValue('3')
  await expect(panel.getByRole('combobox',{name:'虚拟机数量上限'})).toHaveValue('3')
@@ -229,12 +229,12 @@ test('Agent computer appears as a right panel and opens an interactive desktop',
  await login(page)
  await login(page)
  const seed=await(await page.request.post('/__test/computer?off=1',{data:{}})).json()
  const seed=await(await page.request.post('/__test/computer?off=1',{data:{}})).json()
  await page.goto(`/conversations/${seed.conversationId}`)
  await page.goto(`/conversations/${seed.conversationId}`)
- await page.getByRole('button',{name:'电脑',exact:true}).click()
- const dock=page.getByRole('complementary',{name:'Agent 电脑面板'})
+ await page.getByRole('button',{name:'电脑与定时任务',exact:true}).click()
+ const dock=page.getByRole('complementary',{name:'机器人电脑面板'})
  await expect(dock).toBeVisible();await expect(dock).toContainText('本地虚拟机')
  await expect(dock).toBeVisible();await expect(dock).toContainText('本地虚拟机')
  await dock.getByRole('button',{name:'设置本地虚拟机',exact:true}).click()
  await dock.getByRole('button',{name:'设置本地虚拟机',exact:true}).click()
  const settings=page.getByRole('dialog',{name:'设置中心'})
  const settings=page.getByRole('dialog',{name:'设置中心'})
- await settings.getByRole('button',{name:'准备本地虚拟机',exact:true}).click()
+ await settings.getByRole('button',{name:'准备所选镜像',exact:true}).click()
  await settings.getByRole('button',{name:'关闭设置中心',exact:true}).click()
  await settings.getByRole('button',{name:'关闭设置中心',exact:true}).click()
  await expect(dock.getByRole('button',{name:/创建.*的虚拟机/})).toBeVisible({timeout:12000})
  await expect(dock.getByRole('button',{name:/创建.*的虚拟机/})).toBeVisible({timeout:12000})
  await dock.getByRole('button',{name:/创建.*的虚拟机/}).click()
  await dock.getByRole('button',{name:/创建.*的虚拟机/}).click()
@@ -261,8 +261,8 @@ test('two desktops are watch-only on opening and release before changing the con
  const first=await(await page.request.post('/__test/computer',{data:{}})).json()
  const first=await(await page.request.post('/__test/computer',{data:{}})).json()
  const second=await(await page.request.post('/__test/computer',{data:{}})).json()
  const second=await(await page.request.post('/__test/computer',{data:{}})).json()
  await page.goto(`/conversations/${first.conversationId}`)
  await page.goto(`/conversations/${first.conversationId}`)
- await page.getByRole('button',{name:'电脑',exact:true}).click()
- const dock=page.getByRole('complementary',{name:'Agent 电脑面板'})
+ await page.getByRole('button',{name:'电脑与定时任务',exact:true}).click()
+ const dock=page.getByRole('complementary',{name:'机器人电脑面板'})
  await expect(dock.getByRole('button',{name:'打开双桌面'})).toBeVisible()
  await expect(dock.getByRole('button',{name:'打开双桌面'})).toBeVisible()
  await dock.getByRole('button',{name:'打开双桌面'}).click()
  await dock.getByRole('button',{name:'打开双桌面'}).click()
  const workspace=page.getByRole('main',{name:'双桌面工作区'})
  const workspace=page.getByRole('main',{name:'双桌面工作区'})

+ 23 - 12
tests/e2e/workspace.spec.ts

@@ -1,4 +1,12 @@
-import { expect, test } from '@playwright/test'
+import { expect, test, type Page } from '@playwright/test'
+
+async function openSecondSession(page: Page) {
+  const sidebar = page.locator('.desktop-sidebar')
+  const second = sidebar.getByRole('option').filter({ hasText: '第二个会话' })
+  await expect(sidebar.getByRole('option').first()).toBeVisible()
+  if (!await second.isVisible()) await sidebar.getByRole('button', { name: '继续加载会话' }).click()
+  await second.click()
+}
 
 
 const WIDE_DOCX_BASE64 = 'UEsDBAoAAAAIAKwmFF15bjPX6AAAAK0BAAATAAAAW0NvbnRlbnRfVHlwZXNdLnhtbH1QyU7DMBD9FWuuKHHggBCK0wPLETiUDxjZk8SqN3nc0v49Tlt6QIXjzFv1+tXeO7GjzDYGBbdtB4KCjsaGScHn+rV5AMEFg0EXAyk4EMNq6NeHRCyqNrCCuZT0KCXrmTxyGxOFiowxeyz1zJNMqDc4kbzrunupYygUSlMWDxj6Zxpx64p42df3qUcmxyCeTsQlSwGm5KzGUnG5C+ZXSnNOaKvyyOHZJr6pBJBXExbk74Cz7r0Ok60h8YG5vKGvLPkVs5Em6q2vyvZ/mys94zhaTRf94pZy1MRcF/euvSAebfjpL49zD99QSwMECgAAAAAArCYUXQAAAAAAAAAAAAAAAAYAAABfcmVscy9QSwMECgAAAAgArCYUXZv9N+qtAAAAKQEAAAsAAABfcmVscy8ucmVsc43POw7CMAwG4KtE3mlaBoRQ0y4IqSsqB7ASN61oHkrCo7cnAwNFDIy2f3+W6/ZpZnanECdnBVRFCYysdGqyWsClP232wGJCq3B2lgQsFKFt6jPNmPJKHCcfWTZsFDCm5A+cRzmSwVg4TzZPBhcMplwGzT3KK2ri27Lc8fBpwNpknRIQOlUB6xdP/9huGCZJRydvhmz6ceIrkWUMmpKAhwuKq3e7yCzwpuarF5sXUEsDBAoAAAAAAKwmFF0AAAAAAAAAAAAAAAAFAAAAd29yZC9QSwMECgAAAAgArCYUXalT+9hjAQAABwMAABEAAAB3b3JkL2RvY3VtZW50LnhtbKVSW0/CMBT+K03fpXMBJYRBFAaaYDSKwdeydluTrW3awsBfb7uVDYwmJr6cc75z+c6lHU8PZQH2VGkmeASvewEElCeCMJ5F8H29uBpCoA3mBBeC0wgeqYbTybgaEZHsSsoNsARcj6oI5sbIEUI6yWmJdU9Iym0sFarExkKVoUooIpVIqNaWvyxQGAQ3qMSMQ0e5FeTotHRCOWEmG0YomD/PPoBUdM9oNUbO7aSqZZ1stoVXL8obG1C5oa6DYWB3sq6jtOOTA4bIZ6zwUexMG0rZgRIXRJdES8WIMzOrZ6JoaAd9y4p+daOLStNQJY30xMnmrOT7fKhLPD/GKl6swfrufhWDeL6MfzgF6vr8u9vr4/Lhb+1QsyNqH0LTxHjC7O3Tv0QY9uveubUHw76/n8yesHITCRnB27DOUCzLTYu2whhRtrCgaRfLKSZURbAGqRCmBdnOeNBseBoJnf4Z6v7w5AtQSwECFAAKAAAACACsJhRdeW4z1+gAAACtAQAAEwAAAAAAAAAAAAAAAAAAAAAAW0NvbnRlbnRfVHlwZXNdLnhtbFBLAQIUAAoAAAAAAKwmFF0AAAAAAAAAAAAAAAAGAAAAAAAAAAAAEAAAABkBAABfcmVscy9QSwECFAAKAAAACACsJhRdm/036q0AAAApAQAACwAAAAAAAAAAAAAAAAA9AQAAX3JlbHMvLnJlbHNQSwECFAAKAAAAAACsJhRdAAAAAAAAAAAAAAAABQAAAAAAAAAAABAAAAATAgAAd29yZC9QSwECFAAKAAAACACsJhRdqVP72GMBAAAHAwAAEQAAAAAAAAAAAAAAAAA2AgAAd29yZC9kb2N1bWVudC54bWxQSwUGAAAAAAUABQAgAQAAyAMAAAAA'
 const WIDE_DOCX_BASE64 = 'UEsDBAoAAAAIAKwmFF15bjPX6AAAAK0BAAATAAAAW0NvbnRlbnRfVHlwZXNdLnhtbH1QyU7DMBD9FWuuKHHggBCK0wPLETiUDxjZk8SqN3nc0v49Tlt6QIXjzFv1+tXeO7GjzDYGBbdtB4KCjsaGScHn+rV5AMEFg0EXAyk4EMNq6NeHRCyqNrCCuZT0KCXrmTxyGxOFiowxeyz1zJNMqDc4kbzrunupYygUSlMWDxj6Zxpx64p42df3qUcmxyCeTsQlSwGm5KzGUnG5C+ZXSnNOaKvyyOHZJr6pBJBXExbk74Cz7r0Ok60h8YG5vKGvLPkVs5Em6q2vyvZ/mys94zhaTRf94pZy1MRcF/euvSAebfjpL49zD99QSwMECgAAAAAArCYUXQAAAAAAAAAAAAAAAAYAAABfcmVscy9QSwMECgAAAAgArCYUXZv9N+qtAAAAKQEAAAsAAABfcmVscy8ucmVsc43POw7CMAwG4KtE3mlaBoRQ0y4IqSsqB7ASN61oHkrCo7cnAwNFDIy2f3+W6/ZpZnanECdnBVRFCYysdGqyWsClP232wGJCq3B2lgQsFKFt6jPNmPJKHCcfWTZsFDCm5A+cRzmSwVg4TzZPBhcMplwGzT3KK2ri27Lc8fBpwNpknRIQOlUB6xdP/9huGCZJRydvhmz6ceIrkWUMmpKAhwuKq3e7yCzwpuarF5sXUEsDBAoAAAAAAKwmFF0AAAAAAAAAAAAAAAAFAAAAd29yZC9QSwMECgAAAAgArCYUXalT+9hjAQAABwMAABEAAAB3b3JkL2RvY3VtZW50LnhtbKVSW0/CMBT+K03fpXMBJYRBFAaaYDSKwdeydluTrW3awsBfb7uVDYwmJr6cc75z+c6lHU8PZQH2VGkmeASvewEElCeCMJ5F8H29uBpCoA3mBBeC0wgeqYbTybgaEZHsSsoNsARcj6oI5sbIEUI6yWmJdU9Iym0sFarExkKVoUooIpVIqNaWvyxQGAQ3qMSMQ0e5FeTotHRCOWEmG0YomD/PPoBUdM9oNUbO7aSqZZ1stoVXL8obG1C5oa6DYWB3sq6jtOOTA4bIZ6zwUexMG0rZgRIXRJdES8WIMzOrZ6JoaAd9y4p+daOLStNQJY30xMnmrOT7fKhLPD/GKl6swfrufhWDeL6MfzgF6vr8u9vr4/Lhb+1QsyNqH0LTxHjC7O3Tv0QY9uveubUHw76/n8yesHITCRnB27DOUCzLTYu2whhRtrCgaRfLKSZURbAGqRCmBdnOeNBseBoJnf4Z6v7w5AtQSwECFAAKAAAACACsJhRdeW4z1+gAAACtAQAAEwAAAAAAAAAAAAAAAAAAAAAAW0NvbnRlbnRfVHlwZXNdLnhtbFBLAQIUAAoAAAAAAKwmFF0AAAAAAAAAAAAAAAAGAAAAAAAAAAAAEAAAABkBAABfcmVscy9QSwECFAAKAAAACACsJhRdm/036q0AAAApAQAACwAAAAAAAAAAAAAAAAA9AQAAX3JlbHMvLnJlbHNQSwECFAAKAAAAAACsJhRdAAAAAAAAAAAAAAAABQAAAAAAAAAAABAAAAATAgAAd29yZC9QSwECFAAKAAAACACsJhRdqVP72GMBAAAHAwAAEQAAAAAAAAAAAAAAAAA2AgAAd29yZC9kb2N1bWVudC54bWxQSwUGAAAAAAUABQAgAQAAyAMAAAAA'
 
 
@@ -43,7 +51,7 @@ test('separates writable Web chats from read-only Hermes history', async ({ page
 
 
 test('explains optional upstream credentials without blocking a ready connection', async ({ page }, testInfo) => {
 test('explains optional upstream credentials without blocking a ready connection', async ({ page }, testInfo) => {
   await page.getByRole('button', { name: /^(打开设置中心|设置与模式)$/ }).first().click()
   await page.getByRole('button', { name: /^(打开设置中心|设置与模式)$/ }).first().click()
-  if (await page.getByRole('menuitem', { name: '进入设置', exact: true }).isVisible()) await page.getByRole('menuitem', { name: '进入设置', exact: true }).click()
+  await page.getByRole('menuitem', { name: '设置', exact: true }).click()
   const dialog = page.getByRole('dialog', { name: '设置中心' })
   const dialog = page.getByRole('dialog', { name: '设置中心' })
   await dialog.getByRole('button', { name: 'Hermes 连接', exact: true }).click()
   await dialog.getByRole('button', { name: 'Hermes 连接', exact: true }).click()
   await expect(dialog.locator('[aria-label="Hermes 连接状态"]')).toBeVisible()
   await expect(dialog.locator('[aria-label="Hermes 连接状态"]')).toBeVisible()
@@ -499,14 +507,14 @@ test('keeps pins first and loads the next session page at the list bottom', asyn
     element.scrollTop = element.scrollHeight
     element.scrollTop = element.scrollHeight
     element.dispatchEvent(new Event('scroll'))
     element.dispatchEvent(new Event('scroll'))
   })
   })
-  await expect(items).toHaveCount(103)
+  await expect.poll(() => items.count()).toBeGreaterThan(100)
   await expect(sidebar.getByRole('button', { name: '继续加载会话' })).toHaveCount(0)
   await expect(sidebar.getByRole('button', { name: '继续加载会话' })).toHaveCount(0)
 })
 })
 
 
 test('switches the composer model to the selected historical session model', async ({ page }) => {
 test('switches the composer model to the selected historical session model', async ({ page }) => {
   await page.goto('/chat/session-demo?profile=yaoyao')
   await page.goto('/chat/session-demo?profile=yaoyao')
   await expect(page.locator('.composer-tool--model')).toContainText('gpt-5.6')
   await expect(page.locator('.composer-tool--model')).toContainText('gpt-5.6')
-  await page.getByRole('option', { name: /第二个会话/ }).click()
+  await openSecondSession(page)
   await expect(page.locator('.composer-tool--model')).toContainText('gpt-5.5')
   await expect(page.locator('.composer-tool--model')).toContainText('gpt-5.5')
 })
 })
 
 
@@ -528,7 +536,7 @@ test('opens a conversation even when marking it read is unsupported', async ({ p
     await route.fulfill({ status: 405, contentType: 'application/json', body: JSON.stringify({ error: 'Method Not Allowed' }) })
     await route.fulfill({ status: 405, contentType: 'application/json', body: JSON.stringify({ error: 'Method Not Allowed' }) })
   })
   })
 
 
-  await page.getByRole('option').filter({ hasText: '第二个会话' }).click()
+  await openSecondSession(page)
   await expect(page).toHaveURL(/\/chat\/session-second\?profile=yaoyao/)
   await expect(page).toHaveURL(/\/chat\/session-second\?profile=yaoyao/)
   await expect(page.getByText('已整理完成。下面是', { exact: false })).toBeVisible()
   await expect(page.getByText('已整理完成。下面是', { exact: false })).toBeVisible()
   expect(pageErrors).toEqual([])
   expect(pageErrors).toEqual([])
@@ -584,7 +592,7 @@ test('keeps the show-thinking preference across sessions of the same Agent', asy
   await expect(setting).toHaveAttribute('aria-checked', 'true')
   await expect(setting).toHaveAttribute('aria-checked', 'true')
   await setting.click()
   await setting.click()
   await expect(page.locator('.turn-trace')).toHaveCount(0)
   await expect(page.locator('.turn-trace')).toHaveCount(0)
-  await page.getByRole('option').filter({ hasText: '第二个会话' }).click()
+  await openSecondSession(page)
   await expect(page.locator('.turn-trace')).toHaveCount(0)
   await expect(page.locator('.turn-trace')).toHaveCount(0)
   await page.locator('.composer-tool[aria-label="设置"]').click()
   await page.locator('.composer-tool[aria-label="设置"]').click()
   await page.getByRole('switch', { name: /显示思考/ }).click()
   await page.getByRole('switch', { name: /显示思考/ }).click()
@@ -805,7 +813,7 @@ test('renders model changes as a collapsed system event', async ({ page }) => {
 })
 })
 
 
 test('opens a local message file link as a floating preview card', async ({ page }) => {
 test('opens a local message file link as a floating preview card', async ({ page }) => {
-  await page.route('**/Users/samien/Agents/%E6%96%B9%E6%A1%88%E8%8D%89%E7%A8%BF.md', route => route.fulfill({
+  await page.route(url => url.pathname === '/api/files/download' && url.searchParams.get('path') === '/Users/samien/Agents/方案草稿.md', route => route.fulfill({
     status: 200,
     status: 200,
     contentType: 'text/markdown',
     contentType: 'text/markdown',
     body: '# 会话文件\n\n这是会话中的文本文件。',
     body: '# 会话文件\n\n这是会话中的文本文件。',
@@ -829,10 +837,13 @@ test('opens a local message file link as a floating preview card', async ({ page
 
 
 test('shows a thinking animation after submit until output starts', async ({ page }) => {
 test('shows a thinking animation after submit until output starts', async ({ page }) => {
   await page.goto('/chat/session-demo?profile=yaoyao')
   await page.goto('/chat/session-demo?profile=yaoyao')
+  const replies = page.getByText('这是来自假 Gateway 的流式回复。', { exact: true })
+  const count = await replies.count()
   await page.locator('.composer-textarea').fill('请开始思考')
   await page.locator('.composer-textarea').fill('请开始思考')
   await page.getByRole('button', { name: '发送消息' }).click()
   await page.getByRole('button', { name: '发送消息' }).click()
   await expect(page.locator('.thinking-indicator')).toBeVisible()
   await expect(page.locator('.thinking-indicator')).toBeVisible()
-  await expect(page.getByText('这是来自假 Gateway 的流式回复。', { exact: true })).toBeVisible()
+  await expect(replies).toHaveCount(count + 1)
+  await expect(replies.last()).toBeVisible()
   await expect(page.locator('.thinking-indicator')).toHaveCount(0)
   await expect(page.locator('.thinking-indicator')).toHaveCount(0)
 })
 })
 
 
@@ -902,7 +913,7 @@ test('keeps the canonical logo and yaoyao-webui composer geometry', async ({ pag
   await expect(assistantMessage.locator('.message__actions')).toHaveCSS('opacity', '1')
   await expect(assistantMessage.locator('.message__actions')).toHaveCSS('opacity', '1')
   await expect(page.locator('.message--tool-only .message__actions')).toHaveCount(0)
   await expect(page.locator('.message--tool-only .message__actions')).toHaveCount(0)
   await expect(page.locator('[data-message-id="message-thinking-tool"]')).toHaveCount(0)
   await expect(page.locator('[data-message-id="message-thinking-tool"]')).toHaveCount(0)
-  await expect(page.locator('.turn-trace')).toHaveCount(1)
+  await expect(page.locator('.turn-trace').first()).toBeVisible()
   const userMessageMeta = page.locator('.message--user .message__meta')
   const userMessageMeta = page.locator('.message--user .message__meta')
   await expect(userMessageMeta).not.toHaveCount(0)
   await expect(userMessageMeta).not.toHaveCount(0)
   expect(await userMessageMeta.evaluateAll(elements => elements.every(element => getComputedStyle(element).display === 'none'))).toBe(true)
   expect(await userMessageMeta.evaluateAll(elements => elements.every(element => getComputedStyle(element).display === 'none'))).toBe(true)
@@ -1032,7 +1043,7 @@ test('uses the mobile composer and keeps the closed drawer inert', async ({ page
 test('renders the Kanban snapshot and mobile status control without page overflow', async ({ page }) => {
 test('renders the Kanban snapshot and mobile status control without page overflow', async ({ page }) => {
   await page.getByRole('button', { name: '看板', exact: true }).click()
   await page.getByRole('button', { name: '看板', exact: true }).click()
   await expect(page).toHaveURL(/\/kanban\/default$/)
   await expect(page).toHaveURL(/\/kanban\/default$/)
-  await expect(page).toHaveTitle('产品研发 · 夭夭')
+  await expect(page).toHaveTitle('产品研发 · 夭夭 AI')
   await expect(page.getByRole('heading', { name: '产品研发', exact: true })).toBeVisible()
   await expect(page.getByRole('heading', { name: '产品研发', exact: true })).toBeVisible()
   await expect(page.getByRole('article', { name: /完成 Web 看板验收/ })).toBeVisible()
   await expect(page.getByRole('article', { name: /完成 Web 看板验收/ })).toBeVisible()
   await expect(page.locator('.kanban-column')).toHaveCount(8)
   await expect(page.locator('.kanban-column')).toHaveCount(8)
@@ -1071,10 +1082,10 @@ test('serves cached chat history locally and refreshes upstream only on explicit
   await expect(page.getByRole('menuitem', { name: '强制刷新历史' })).toBeVisible()
   await expect(page.getByRole('menuitem', { name: '强制刷新历史' })).toBeVisible()
   await expect(page.getByRole('menu', { name: '会话操作' })).toHaveCSS('opacity', '1')
   await expect(page.getByRole('menu', { name: '会话操作' })).toHaveCSS('opacity', '1')
   await page.screenshot({ path: testInfo.outputPath('force-history-refresh.png') })
   await page.screenshot({ path: testInfo.outputPath('force-history-refresh.png') })
-  const forced = page.waitForResponse(response => messagesURL(response.url()) && response.request().headers()['x-yaoyao-cache'] === 'bypass')
+  const forced = page.waitForResponse(response => response.url().includes('/api/app/sessions/session-demo/sync') && response.request().method() === 'POST')
   await page.getByRole('menuitem', { name: '强制刷新历史' }).click()
   await page.getByRole('menuitem', { name: '强制刷新历史' }).click()
   const refreshed = await forced
   const refreshed = await forced
   expect(refreshed.ok()).toBe(true)
   expect(refreshed.ok()).toBe(true)
-  expect(refreshed.headers()['x-yaoyao-data-source']).toBe('upstream')
+  expect(refreshed.status()).toBe(202)
   await expect(page.locator('.message--assistant').filter({ hasText: '验收摘要' })).toBeVisible()
   await expect(page.locator('.message--assistant').filter({ hasText: '验收摘要' })).toBeVisible()
 })
 })