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