cold-blank-session.e2e.ts 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. /** Pending Inbox recovery from detached persistence through the shipped Web profile. */
  2. import { fileURLToPath } from 'node:url'
  3. import { chromium, type Browser, type Page } from 'playwright'
  4. import { afterAll, beforeAll, describe, expect, it, onTestFailed } from 'vitest'
  5. import { createUserMessage } from '@deepseek-ai/dsh-llm'
  6. import { SESSION_FORMAT_VERSION, SessionId, SessionSeq } from '@deepseek-ai/dsh-session'
  7. import {
  8. captureStableAria, compareOrRefreshGolden, launchWebScaffold,
  9. watchConsole, webSnapshotMode, type WebScaffold,
  10. } from './scaffold.ts'
  11. import { newEnglishPage, saveFailureShot } from './support.ts'
  12. const SESSION_ID = SessionId('cold-inbox-web-e2e')
  13. const PENDING_TEXT = 'Accepted before the Host restarted'
  14. const EXPECTED = fileURLToPath(new URL('./expected/cold-blank-session/queue.expected.md', import.meta.url))
  15. describe('web e2e: cold Inbox recovery', () => {
  16. let scaffold: WebScaffold
  17. let browser: Browser
  18. let page: Page
  19. let tripwire: ReturnType<typeof watchConsole>
  20. beforeAll(async () => {
  21. scaffold = await launchWebScaffold({})
  22. const createdAt = Date.now() - 60_000
  23. const handle = await scaffold.ctx.sessionPersistence.create({
  24. version: SESSION_FORMAT_VERSION, id: SESSION_ID, createdAt,
  25. cwd: scaffold.workspaceCwd, isSeeded: false, delegationDepth: 0,
  26. })
  27. try {
  28. await handle.append([{
  29. type: 'agent/inbox/spliced', seq: SessionSeq(0), time: createdAt,
  30. data: { target: 'next-turn', start: 0, inserted: [createUserMessage({
  31. content: [{ type: 'text', text: PENDING_TEXT }], source: { kind: 'user' },
  32. })] },
  33. }])
  34. } finally {
  35. await handle.close()
  36. }
  37. expect(scaffold.ctx.agents.get(SESSION_ID)).toBeUndefined()
  38. expect(scaffold.ctx.sessions.get(SESSION_ID)).toBeUndefined()
  39. browser = await chromium.launch()
  40. page = await newEnglishPage(browser)
  41. tripwire = watchConsole(page)
  42. await page.goto(scaffold.authenticatedUrl, { waitUntil: 'load' })
  43. await page.waitForSelector('[class*="frame"]', { timeout: 30_000 })
  44. }, 120_000)
  45. afterAll(async () => {
  46. await browser?.close()
  47. await scaffold?.close()
  48. })
  49. it('restores a pending row on opening and reload, then edits and removes it', async () => {
  50. onTestFailed(() => saveFailureShot(page, 'web-e2e-cold-inbox'))
  51. const group = page.locator('[role="treeitem"]').first()
  52. await group.waitFor({ timeout: 15_000 })
  53. await group.click()
  54. await page.locator('[role="treeitem"]').nth(1).click()
  55. const dock = page.locator('[data-queue-dock]')
  56. await dock.getByText(PENDING_TEXT, { exact: true }).waitFor({ timeout: 15_000 })
  57. await compareOrRefreshGolden(EXPECTED,
  58. await captureStableAria(page, '[data-queue-dock]', scaffold.workspaceCwd), webSnapshotMode())
  59. await page.reload({ waitUntil: 'load' })
  60. await dock.getByText(PENDING_TEXT, { exact: true }).waitFor({ timeout: 15_000 })
  61. await dock.getByRole('button', { name: 'Edit queued message', exact: true }).click()
  62. await dock.getByRole('textbox').fill('Edited after recovery')
  63. await dock.getByRole('button', { name: 'Save queued message', exact: true }).click()
  64. await dock.getByText('Edited after recovery', { exact: true }).waitFor()
  65. await dock.getByRole('button', { name: 'Remove queued message', exact: true }).click()
  66. await dock.waitFor({ state: 'detached' })
  67. expect(tripwire.pageErrors).toEqual([])
  68. })
  69. })