prompt-async.spec.ts 1.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243
  1. import { test, expect } from "../fixtures"
  2. import { promptSelector } from "../selectors"
  3. import { sessionIDFromUrl } from "../actions"
  4. // Regression test for Issue #12453: the synchronous POST /message endpoint holds
  5. // the connection open while the agent works, causing "Failed to fetch" over
  6. // VPN/Tailscale. The fix switches to POST /prompt_async which returns immediately.
  7. test("prompt succeeds when sync message endpoint is unreachable", async ({ page, sdk, gotoSession }) => {
  8. test.setTimeout(120_000)
  9. // Simulate Tailscale/VPN killing the long-lived sync connection
  10. await page.route("**/session/*/message", (route) => route.abort("connectionfailed"))
  11. await gotoSession()
  12. const token = `E2E_ASYNC_${Date.now()}`
  13. await page.locator(promptSelector).click()
  14. await page.keyboard.type(`Reply with exactly: ${token}`)
  15. await page.keyboard.press("Enter")
  16. await expect(page).toHaveURL(/\/session\/[^/?#]+/, { timeout: 30_000 })
  17. const sessionID = sessionIDFromUrl(page.url())!
  18. try {
  19. // Agent response arrives via SSE despite sync endpoint being dead
  20. await expect
  21. .poll(
  22. async () => {
  23. const messages = await sdk.session.messages({ sessionID, limit: 50 }).then((r) => r.data ?? [])
  24. return messages
  25. .filter((m) => m.info.role === "assistant")
  26. .flatMap((m) => m.parts)
  27. .filter((p) => p.type === "text")
  28. .map((p) => p.text)
  29. .join("\n")
  30. },
  31. { timeout: 90_000 },
  32. )
  33. .toContain(token)
  34. } finally {
  35. await sdk.session.delete({ sessionID }).catch(() => undefined)
  36. }
  37. })