model-confirmation.spec.ts 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263
  1. import { test, expect } from '@playwright/test'
  2. test('model confirmation survives reconnects and failed inputs retain their position after Allow, new replies and reload', async ({ page, browser }) => {
  3. const modelCommands: Record<string, unknown>[] = []
  4. let nativeApprovals = 0
  5. await page.route('**/api/realtime/channels/*/commands', async route => {
  6. const command = route.request().postDataJSON()
  7. if (command.method === 'approval.respond') nativeApprovals++
  8. if (command.method !== 'config.set' || command.params.key !== 'model') return route.continue()
  9. modelCommands.push(command.params)
  10. const result = command.params.confirm_expensive_model === true
  11. ? { key: 'model', value: 'gpt-5.5', scope: 'session' }
  12. : { confirm_required: true, confirm_message: '长上下文切换验收:请确认重新读取缓存。' }
  13. await route.fulfill({ json: { state: 'confirmed', response: { result } } })
  14. })
  15. await page.goto('/chat/session-demo?profile=yaoyao')
  16. await expect(page.getByRole('heading', { name: '登录夭夭' })).toBeVisible()
  17. if (await page.getByRole('heading', { name: '登录夭夭' }).isVisible()) {
  18. await page.getByLabel('账号').fill('admin')
  19. await page.getByLabel('密码').fill('e2e-password')
  20. await page.getByRole('button', { name: '登录', exact: true }).click()
  21. }
  22. await page.goto('/chat/session-demo?profile=yaoyao')
  23. await expect(page.locator('.composer-tool--model')).toContainText('gpt-5.6')
  24. await page.locator('.composer-tool--model').click()
  25. await page.locator('.model-dialog__item').filter({ hasText: 'gpt-5.5' }).click()
  26. // History can be opened without resuming Hermes; sending applies the pending pick.
  27. await page.locator('.composer-textarea').fill('等待模型确认')
  28. await page.getByRole('button', { name: '发送消息', exact: true }).click()
  29. const confirmation = page.locator('.interaction-card--approval')
  30. await expect(confirmation).toContainText('长上下文切换验收')
  31. for (let i = 0; i < 2; i++) {
  32. const resumed = page.waitForResponse(response => response.url().includes('/api/app/chat/sessions/session-demo/events'))
  33. await page.evaluate(() => document.dispatchEvent(new Event('visibilitychange')))
  34. await resumed
  35. await expect(confirmation).toBeVisible()
  36. }
  37. await confirmation.getByRole('button', { name: '允许', exact: true }).click()
  38. await expect(confirmation).toBeHidden()
  39. expect(modelCommands).toHaveLength(2)
  40. expect(modelCommands[1]).toMatchObject({ confirm_expensive_model: true, value: 'gpt-5.5 --provider openai --session' })
  41. expect(nativeApprovals).toBe(0)
  42. await page.locator('.composer-textarea').fill('模型确认后发送')
  43. await page.getByRole('button', { name: '发送消息', exact: true }).click()
  44. await expect(page.locator('.message__content').filter({ hasText: '这是来自假 Gateway 的流式回复。' })).toHaveCount(1)
  45. expect(modelCommands).toHaveLength(2)
  46. const relevantMessages = ['等待模型确认', '模型确认后发送', '这是来自假 Gateway 的流式回复。']
  47. const visibleOrder = async () => (await page.locator('.message__content').allTextContents())
  48. .map(text => text.trim()).filter(text => relevantMessages.includes(text))
  49. await expect.poll(visibleOrder).toEqual(relevantMessages)
  50. await page.reload()
  51. await expect.poll(visibleOrder).toEqual(relevantMessages)
  52. await expect(page.locator('.message--failed').filter({ hasText: '等待模型确认' })).toHaveCount(1)
  53. // A fresh device gets the accepted transcript, not this browser's unsent outbox.
  54. const other = await browser.newContext({ storageState: await page.context().storageState() })
  55. try {
  56. const otherPage = await other.newPage()
  57. await otherPage.goto(page.url())
  58. await expect(otherPage.locator('.message__content').filter({ hasText: '模型确认后发送' })).toHaveCount(1)
  59. await expect(otherPage.locator('.message__content').filter({ hasText: '等待模型确认' })).toHaveCount(0)
  60. } finally { await other.close() }
  61. })