session-undo-redo.spec.ts 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. import type { Page } from "@playwright/test"
  2. import { test, expect } from "../fixtures"
  3. import { withSession } from "../actions"
  4. import { createSdk, modKey } from "../utils"
  5. import { promptSelector } from "../selectors"
  6. async function seedConversation(input: {
  7. page: Page
  8. sdk: ReturnType<typeof createSdk>
  9. sessionID: string
  10. token: string
  11. }) {
  12. const prompt = input.page.locator(promptSelector)
  13. await expect(prompt).toBeVisible()
  14. await prompt.click()
  15. await input.page.keyboard.type(`Reply with exactly: ${input.token}`)
  16. await input.page.keyboard.press("Enter")
  17. let userMessageID: string | undefined
  18. await expect
  19. .poll(
  20. async () => {
  21. const messages = await input.sdk.session
  22. .messages({ sessionID: input.sessionID, limit: 50 })
  23. .then((r) => r.data ?? [])
  24. const users = messages.filter((m) => m.info.role === "user")
  25. if (users.length === 0) return false
  26. const user = users.reduce((acc, item) => (item.info.id > acc.info.id ? item : acc))
  27. userMessageID = user.info.id
  28. const assistantText = messages
  29. .filter((m) => m.info.role === "assistant")
  30. .flatMap((m) => m.parts)
  31. .filter((p) => p.type === "text")
  32. .map((p) => p.text)
  33. .join("\n")
  34. return assistantText.includes(input.token)
  35. },
  36. { timeout: 90_000 },
  37. )
  38. .toBe(true)
  39. if (!userMessageID) throw new Error("Expected a user message id")
  40. return { prompt, userMessageID }
  41. }
  42. test("slash undo sets revert and restores prior prompt", async ({ page, withProject }) => {
  43. test.setTimeout(120_000)
  44. const token = `undo_${Date.now()}`
  45. await withProject(async (project) => {
  46. const sdk = createSdk(project.directory)
  47. await withSession(sdk, `e2e undo ${Date.now()}`, async (session) => {
  48. await project.gotoSession(session.id)
  49. const seeded = await seedConversation({ page, sdk, sessionID: session.id, token })
  50. await seeded.prompt.click()
  51. await page.keyboard.type("/undo")
  52. const undo = page.locator('[data-slash-id="session.undo"]').first()
  53. await expect(undo).toBeVisible()
  54. await page.keyboard.press("Enter")
  55. await expect
  56. .poll(async () => await sdk.session.get({ sessionID: session.id }).then((r) => r.data?.revert?.messageID), {
  57. timeout: 30_000,
  58. })
  59. .toBe(seeded.userMessageID)
  60. await expect(seeded.prompt).toContainText(token)
  61. await expect(page.locator(`[data-message-id="${seeded.userMessageID}"]`)).toHaveCount(0)
  62. })
  63. })
  64. })
  65. test("slash redo clears revert and restores latest state", async ({ page, withProject }) => {
  66. test.setTimeout(120_000)
  67. const token = `redo_${Date.now()}`
  68. await withProject(async (project) => {
  69. const sdk = createSdk(project.directory)
  70. await withSession(sdk, `e2e redo ${Date.now()}`, async (session) => {
  71. await project.gotoSession(session.id)
  72. const seeded = await seedConversation({ page, sdk, sessionID: session.id, token })
  73. await seeded.prompt.click()
  74. await page.keyboard.type("/undo")
  75. const undo = page.locator('[data-slash-id="session.undo"]').first()
  76. await expect(undo).toBeVisible()
  77. await page.keyboard.press("Enter")
  78. await expect
  79. .poll(async () => await sdk.session.get({ sessionID: session.id }).then((r) => r.data?.revert?.messageID), {
  80. timeout: 30_000,
  81. })
  82. .toBe(seeded.userMessageID)
  83. await seeded.prompt.click()
  84. await page.keyboard.press(`${modKey}+A`)
  85. await page.keyboard.press("Backspace")
  86. await page.keyboard.type("/redo")
  87. const redo = page.locator('[data-slash-id="session.redo"]').first()
  88. await expect(redo).toBeVisible()
  89. await page.keyboard.press("Enter")
  90. await expect
  91. .poll(async () => await sdk.session.get({ sessionID: session.id }).then((r) => r.data?.revert?.messageID), {
  92. timeout: 30_000,
  93. })
  94. .toBeUndefined()
  95. await expect(seeded.prompt).not.toContainText(token)
  96. await expect(page.locator(`[data-message-id="${seeded.userMessageID}"]`).first()).toBeVisible()
  97. })
  98. })
  99. })