import { expect, test } from "@playwright/test" test.describe("Error Handling", () => { test("displays error message when API returns 500", async ({ page }) => { await page.route("**/api/chat", async (route) => { await route.fulfill({ status: 500, contentType: "application/json", body: JSON.stringify({ error: "Internal server error" }), }) }) await page.goto("/", { waitUntil: "networkidle" }) await page .locator("iframe") .waitFor({ state: "visible", timeout: 30000 }) const chatInput = page.locator('textarea[aria-label="Chat input"]') await expect(chatInput).toBeVisible({ timeout: 10000 }) await chatInput.fill("Draw a cat") await chatInput.press("ControlOrMeta+Enter") // After error, user should be able to type again (input still functional) await page.waitForTimeout(2000) // Wait for error response await chatInput.fill("Retry message") await expect(chatInput).toHaveValue("Retry message") }) test("displays error message when API returns 429 rate limit", async ({ page, }) => { await page.route("**/api/chat", async (route) => { await route.fulfill({ status: 429, contentType: "application/json", body: JSON.stringify({ error: "Rate limit exceeded" }), }) }) await page.goto("/", { waitUntil: "networkidle" }) await page .locator("iframe") .waitFor({ state: "visible", timeout: 30000 }) const chatInput = page.locator('textarea[aria-label="Chat input"]') await expect(chatInput).toBeVisible({ timeout: 10000 }) await chatInput.fill("Draw a cat") await chatInput.press("ControlOrMeta+Enter") // After rate limit error, user should be able to type again await page.waitForTimeout(2000) // Wait for error response await chatInput.fill("Retry after rate limit") await expect(chatInput).toHaveValue("Retry after rate limit") }) test("handles network timeout gracefully", async ({ page }) => { await page.route("**/api/chat", async (route) => { // Simulate timeout by not responding for a short time then aborting await new Promise((resolve) => setTimeout(resolve, 2000)) await route.abort("timedout") }) await page.goto("/", { waitUntil: "networkidle" }) await page .locator("iframe") .waitFor({ state: "visible", timeout: 30000 }) const chatInput = page.locator('textarea[aria-label="Chat input"]') await expect(chatInput).toBeVisible({ timeout: 10000 }) await chatInput.fill("Draw a cat") await chatInput.press("ControlOrMeta+Enter") // Wait for timeout error to occur await page.waitForTimeout(3000) // After timeout, user should be able to type again await chatInput.fill("Try again after timeout") await expect(chatInput).toHaveValue("Try again after timeout") }) test("shows truncated badge for incomplete XML", async ({ page }) => { const toolCallId = `call_${Date.now()}` const textId = `text_${Date.now()}` const messageId = `msg_${Date.now()}` // Truncated XML (missing closing tags) const truncatedXml = ` { await route.fulfill({ status: 200, contentType: "text/event-stream", body: events .map((e) => `data: ${JSON.stringify(e)}\n\n`) .join("") + "data: [DONE]\n\n", }) }) await page.goto("/", { waitUntil: "networkidle" }) await page .locator("iframe") .waitFor({ state: "visible", timeout: 30000 }) const chatInput = page.locator('textarea[aria-label="Chat input"]') await expect(chatInput).toBeVisible({ timeout: 10000 }) await chatInput.fill("Draw something") await chatInput.press("ControlOrMeta+Enter") // Should show truncated badge await expect(page.locator('text="Truncated"')).toBeVisible({ timeout: 15000, }) }) })