session-deletion.spec.ts 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. import { expect, test, type Page } from '@playwright/test'
  2. async function login(page: Page) {
  3. await page.goto('/chat')
  4. await page.getByLabel('账号').fill('admin')
  5. await page.getByLabel('密码').fill('e2e-password')
  6. await page.getByRole('button', { name: '登录', exact: true }).click()
  7. await expect(page.getByRole('navigation').first()).toBeVisible()
  8. }
  9. async function openDeleteDialog(page: Page) {
  10. await page.getByRole('button', { name: '会话操作', exact: true }).click()
  11. await page.getByRole('menuitem', { name: '删除会话', exact: true }).click()
  12. const dialog = page.getByRole('dialog', { name: '删除会话', exact: true })
  13. await expect(dialog).toBeVisible()
  14. return dialog
  15. }
  16. for (const mobile of [false, true]) {
  17. test.describe(mobile ? 'mobile ordinary chat deletion' : 'desktop ordinary chat deletion', () => {
  18. test.use({ hasTouch: mobile })
  19. test('cancels safely and deletes a locally retained session missing from Hermes', async ({ page }, testInfo) => {
  20. await login(page)
  21. await page.getByRole('button', { name: '新建聊天', exact: true }).click()
  22. await page.locator('.composer-textarea').fill('删除流程验收')
  23. await page.getByRole('button', { name: '发送消息' }).click()
  24. await expect(page).toHaveURL(/\/chat\/session-new/)
  25. await expect(page.locator('.message__content').filter({ hasText: '这是来自假 Gateway 的流式回复。' })).toHaveCount(1)
  26. if (mobile) await page.setViewportSize({ width: 375, height: 812 })
  27. const originalURL = page.url()
  28. const deletions: string[] = []
  29. page.on('request', request => {
  30. if (request.method() === 'DELETE') deletions.push(request.url())
  31. })
  32. let dialog = await openDeleteDialog(page)
  33. await expect(dialog.getByRole('button', { name: '取消', exact: true })).toBeVisible()
  34. await page.screenshot({ path: testInfo.outputPath('delete-confirmation.png') })
  35. if (mobile) await dialog.getByRole('button', { name: '取消', exact: true }).tap()
  36. else await dialog.getByRole('button', { name: '取消', exact: true }).click()
  37. await expect(dialog).toBeHidden()
  38. expect(page.url()).toBe(originalURL)
  39. expect(deletions).toEqual([])
  40. await expect(page.getByRole('button', { name: '会话操作', exact: true })).toBeFocused()
  41. dialog = await openDeleteDialog(page)
  42. await page.keyboard.press('Escape')
  43. await expect(dialog).toBeHidden()
  44. expect(deletions).toEqual([])
  45. dialog = await openDeleteDialog(page)
  46. const removed = page.waitForResponse(response => response.request().method() === 'DELETE')
  47. if (mobile) await dialog.getByRole('button', { name: '删除', exact: true }).tap()
  48. else await dialog.getByRole('button', { name: '删除', exact: true }).click()
  49. expect((await removed).ok()).toBe(true)
  50. await expect(dialog).toBeHidden()
  51. await expect(page).toHaveURL(/\/chat\?profile=yaoyao$/)
  52. expect(deletions).toHaveLength(1)
  53. await page.reload()
  54. const exists = await page.evaluate(async () => {
  55. const response = await fetch('/api/app/sessions?view=chat&profile=yaoyao')
  56. const body = await response.json()
  57. return body.sessions.some((session: { id: string }) => session.id === 'session-new')
  58. })
  59. expect(exists).toBe(false)
  60. })
  61. test('shows a failure, blocks duplicate requests, and lets the user cancel or retry', async ({ page }) => {
  62. await login(page)
  63. await page.goto('/chat/session-demo?profile=yaoyao')
  64. if (mobile) await page.setViewportSize({ width: 375, height: 812 })
  65. const originalURL = page.url()
  66. let finish!: () => void
  67. let attempts = 0
  68. await page.route('**/api/app/sessions/session-demo?profile=yaoyao', async route => {
  69. if (route.request().method() !== 'DELETE') return route.continue()
  70. attempts++
  71. await new Promise<void>(resolve => { finish = resolve })
  72. await route.fulfill({ status: 503, json: { error: '删除服务暂时不可用,请重试' } })
  73. })
  74. const dialog = await openDeleteDialog(page)
  75. await dialog.getByRole('button', { name: '删除', exact: true }).click()
  76. await expect(dialog.getByRole('button', { name: '删除中…', exact: true })).toBeDisabled()
  77. await expect(dialog.getByRole('button', { name: '取消', exact: true })).toBeDisabled()
  78. await page.keyboard.press('Escape')
  79. await expect(dialog).toBeVisible()
  80. await expect.poll(() => attempts).toBe(1)
  81. finish()
  82. await expect(dialog.getByRole('alert')).toHaveText('删除服务暂时不可用,请重试')
  83. expect(page.url()).toBe(originalURL)
  84. await dialog.getByRole('button', { name: '删除', exact: true }).click()
  85. await expect.poll(() => attempts).toBe(2)
  86. finish()
  87. await expect(dialog.getByRole('alert')).toBeVisible()
  88. await dialog.getByRole('button', { name: '取消', exact: true }).click()
  89. await expect(dialog).toBeHidden()
  90. expect(page.url()).toBe(originalURL)
  91. })
  92. })
  93. }