hermesMedia.spec.ts 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. import { expect, test, type APIRequestContext, type Locator } from '@playwright/test'
  2. const mediaPaths = [
  3. '/Users/samien/.hermes/profiles/yaoer/cache/images/openai_codex_gpt-image-2-high_20260902_194820_1d225f08.png',
  4. '/Users/samien/.hermes/profiles/yaoer/cache/images/国漫三视图.png',
  5. ]
  6. const mediaNames = mediaPaths.map(path => path.split('/').at(-1)!)
  7. async function signIn(request: APIRequestContext, origin: string) {
  8. const bootstrap = await (await request.get('/api/app/bootstrap')).json()
  9. if (bootstrap.setupRequired) {
  10. const setup = await request.post('/api/app/setup', {
  11. headers: { Origin: origin, 'X-CSRF-Token': bootstrap.csrfToken },
  12. data: { username: 'admin', password: 'e2e-password' },
  13. })
  14. expect(setup.ok()).toBe(true)
  15. return
  16. }
  17. const login = await request.post('/api/app/login', {
  18. headers: { Origin: origin, 'X-CSRF-Token': bootstrap.csrfToken },
  19. data: { username: 'admin', password: 'e2e-password' },
  20. })
  21. expect(login.ok()).toBe(true)
  22. }
  23. async function expectDecodedImage(image: Locator) {
  24. await expect(image).toBeVisible()
  25. await expect.poll(() => image.evaluate(element => (element as HTMLImageElement).naturalWidth)).toBeGreaterThan(0)
  26. }
  27. test('loads Hermes profile cache images through the Web server and switches their full-size previews', async ({ page, baseURL }, testInfo) => {
  28. await signIn(page.request, new URL(baseURL!).origin)
  29. // No image interception: this exercises the real Web route, upstream account
  30. // authentication, attachment-to-inline response handling, and browser decode.
  31. const responses = mediaPaths.map(path => page.waitForResponse(response => (
  32. decodeURIComponent(new URL(response.url()).pathname) === path
  33. )))
  34. await page.goto('/chat/session-media?profile=yaoer')
  35. await expect(page).toHaveTitle('瑶儿生成图片验收 · 夭夭')
  36. for (const [index, name] of mediaNames.entries()) {
  37. const image = page.getByRole('button', { name: `预览图片 ${name}`, exact: true })
  38. await image.scrollIntoViewIfNeeded()
  39. await expectDecodedImage(image)
  40. const response = await responses[index]!
  41. expect(response.status()).toBe(200)
  42. expect(response.headers()['content-type']).toContain('image/png')
  43. expect(response.headers()['content-disposition'] ?? '').not.toMatch(/^attachment(?:;|$)/)
  44. expect(new URL(response.url()).origin).toBe(new URL(baseURL!).origin)
  45. }
  46. await page.getByRole('button', { name: `预览图片 ${mediaNames[0]}`, exact: true }).click()
  47. const preview = page.locator('.image-preview-overlay')
  48. await expect(preview).toHaveAccessibleName(`预览 ${mediaNames[0]}`)
  49. await expectDecodedImage(preview.getByRole('img', { name: mediaNames[0], exact: true }))
  50. await expect(preview.locator('.image-preview-counter')).toHaveText('1 / 2')
  51. await preview.getByRole('button', { name: '下一张媒体' }).click()
  52. await expect(preview).toHaveAccessibleName(`预览 ${mediaNames[1]}`)
  53. const secondPreview = preview.getByRole('img', { name: mediaNames[1], exact: true })
  54. await expectDecodedImage(secondPreview)
  55. expect(decodeURIComponent(new URL(await secondPreview.getAttribute('src') || '', baseURL).pathname)).toBe(mediaPaths[1])
  56. await expect(preview.locator('.image-preview-counter')).toHaveText('2 / 2')
  57. await page.keyboard.press('ArrowLeft')
  58. await expect(preview).toHaveAccessibleName(`预览 ${mediaNames[0]}`)
  59. await expectDecodedImage(preview.getByRole('img', { name: mediaNames[0], exact: true }))
  60. await expect(preview.locator('.image-preview-counter')).toHaveText('1 / 2')
  61. await page.screenshot({ path: testInfo.outputPath('hermes-profile-cache-preview.png') })
  62. await page.keyboard.press('Escape')
  63. await expect(preview).toBeHidden()
  64. })
  65. test('restores nine user Profile uploads as images and opens the whole batch in the preview', async ({ page, baseURL }, testInfo) => {
  66. await signIn(page.request, new URL(baseURL!).origin)
  67. await page.goto('/chat/session-user-media?profile=yaoer')
  68. const message = page.locator('.message--user')
  69. await expect(message).toContainText('读取图片中的提示词')
  70. await expect(message).not.toContainText('@image:')
  71. await expect(message).not.toContainText('[用户附加图片')
  72. await expect(message).not.toContainText('[screenshot]')
  73. const images = message.locator('img')
  74. await expect(images).toHaveCount(9)
  75. for (const image of await images.all()) {
  76. await image.scrollIntoViewIfNeeded()
  77. await expectDecodedImage(image)
  78. const url = new URL(await image.getAttribute('src') || '', baseURL)
  79. expect(url.origin).toBe(new URL(baseURL!).origin)
  80. expect(url.pathname).toContain('/.hermes/profiles/yaoer/images/')
  81. }
  82. await page.screenshot({ path: testInfo.outputPath('user-profile-images.png') })
  83. await images.first().click()
  84. const preview = page.locator('.image-preview-overlay')
  85. await expect(preview.locator('.image-preview-counter')).toHaveText('1 / 9')
  86. await expectDecodedImage(preview.getByRole('img', { name: '照片-1.png', exact: true }))
  87. await preview.getByRole('button', { name: '下一张媒体' }).click()
  88. await expect(preview.locator('.image-preview-counter')).toHaveText('2 / 9')
  89. await expectDecodedImage(preview.getByRole('img', { name: '照片-2.png', exact: true }))
  90. await page.screenshot({ path: testInfo.outputPath('user-profile-image-preview.png') })
  91. })