import { expect, test } from "@playwright/test" // Helper to create SSE response function createMockSSEResponse(xml: string, text: string) { const messageId = `msg_${Date.now()}` const toolCallId = `call_${Date.now()}` const textId = `text_${Date.now()}` const events = [ { type: "start", messageId }, { type: "text-start", id: textId }, { type: "text-delta", id: textId, delta: text }, { type: "text-end", id: textId }, { type: "tool-input-start", toolCallId, toolName: "display_diagram" }, { type: "tool-input-available", toolCallId, toolName: "display_diagram", input: { xml }, }, { type: "tool-output-available", toolCallId, output: "Successfully displayed the diagram", }, { type: "finish" }, ] return ( events.map((e) => `data: ${JSON.stringify(e)}\n\n`).join("") + "data: [DONE]\n\n" ) } test.describe("History and Session Restore", () => { test("new chat button clears conversation", async ({ page }) => { await page.route("**/api/chat", async (route) => { await route.fulfill({ status: 200, contentType: "text/event-stream", body: createMockSSEResponse( ``, "Created your test diagram.", ), }) }) 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 }) // Send a message await chatInput.fill("Create a test diagram") await chatInput.press("ControlOrMeta+Enter") // Wait for response await expect( page.locator('text="Created your test diagram."'), ).toBeVisible({ timeout: 15000, }) // Find and click new chat button const newChatButton = page.locator( 'button[aria-label*="New"], button:has(svg.lucide-plus), button:has-text("New Chat")', ) if ((await newChatButton.count()) > 0) { await newChatButton.first().click() // Conversation should be cleared await expect( page.locator('text="Created your test diagram."'), ).not.toBeVisible({ timeout: 5000 }) } }) test("chat history sidebar shows past conversations", async ({ page }) => { await page.goto("/", { waitUntil: "networkidle" }) await page .locator("iframe") .waitFor({ state: "visible", timeout: 30000 }) // Look for history/sidebar button that is enabled const historyButton = page.locator( 'button[aria-label*="History"]:not([disabled]), button:has(svg.lucide-history):not([disabled]), button:has(svg.lucide-menu):not([disabled]), button:has(svg.lucide-sidebar):not([disabled]), button:has(svg.lucide-panel-left):not([disabled])', ) if ((await historyButton.count()) > 0) { await historyButton.first().click() await page.waitForTimeout(500) } // Test passes if no error - history feature may or may not be available }) test("conversation persists after page reload", async ({ page }) => { let requestCount = 0 await page.route("**/api/chat", async (route) => { requestCount++ await route.fulfill({ status: 200, contentType: "text/event-stream", body: createMockSSEResponse( ``, "This message should persist.", ), }) }) 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 }) // Send a message await chatInput.fill("Create persistent diagram") await chatInput.press("ControlOrMeta+Enter") // Wait for response await expect( page.locator('text="This message should persist."'), ).toBeVisible({ timeout: 15000 }) // Reload page await page.reload({ waitUntil: "networkidle" }) await page .locator("iframe") .waitFor({ state: "visible", timeout: 30000 }) // Check if conversation persisted (depends on implementation) // The message might or might not be there depending on local storage usage await page.waitForTimeout(1000) }) test("diagram state persists after reload", async ({ page }) => { await page.route("**/api/chat", async (route) => { await route.fulfill({ status: 200, contentType: "text/event-stream", body: createMockSSEResponse( ``, "Created a diagram that should be saved.", ), }) }) 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 }) // Generate a diagram await chatInput.fill("Create saveable diagram") await chatInput.press("ControlOrMeta+Enter") await expect(page.locator('text="Complete"')).toBeVisible({ timeout: 15000, }) // Wait for diagram to render await page.waitForTimeout(1000) // Reload await page.reload({ waitUntil: "networkidle" }) await page .locator("iframe") .waitFor({ state: "visible", timeout: 30000 }) // Diagram state is typically stored - check iframe is still functional const frame = page.frameLocator("iframe") await expect( frame .locator(".geMenubarContainer, .geDiagramContainer, canvas") .first(), ).toBeVisible({ timeout: 30000 }) }) test("can restore from browser back/forward", async ({ page }) => { await page.route("**/api/chat", async (route) => { await route.fulfill({ status: 200, contentType: "text/event-stream", body: createMockSSEResponse( ``, "Testing browser navigation.", ), }) }) 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 }) // Send a message await chatInput.fill("Test navigation") await chatInput.press("ControlOrMeta+Enter") await expect( page.locator('text="Testing browser navigation."'), ).toBeVisible({ timeout: 15000, }) // Navigate to about page await page.goto("/about", { waitUntil: "networkidle" }) // Go back await page.goBack({ waitUntil: "networkidle" }) await page .locator("iframe") .waitFor({ state: "visible", timeout: 30000 }) // Page should be functional await expect(chatInput).toBeVisible({ timeout: 10000 }) }) test("settings are restored after reload", async ({ page }) => { await page.goto("/", { waitUntil: "networkidle" }) await page .locator("iframe") .waitFor({ state: "visible", timeout: 30000 }) // Open settings const settingsButton = page.locator( 'button[aria-label*="Settings"], button:has(svg.lucide-settings)', ) await settingsButton.first().click() // Settings dialog should open await page.waitForTimeout(500) await expect( page.locator('[role="dialog"], [role="menu"], form').first(), ).toBeVisible({ timeout: 5000 }) // Close settings await page.keyboard.press("Escape") // Reload await page.reload({ waitUntil: "networkidle" }) await page .locator("iframe") .waitFor({ state: "visible", timeout: 30000 }) // Open settings again await settingsButton.first().click() // Settings should still be accessible await page.waitForTimeout(500) await expect( page.locator('[role="dialog"], [role="menu"], form').first(), ).toBeVisible({ timeout: 5000 }) }) test("model selection persists", async ({ page }) => { await page.goto("/", { waitUntil: "networkidle" }) await page .locator("iframe") .waitFor({ state: "visible", timeout: 30000 }) // Find model selector const modelSelector = page.locator( 'button[aria-label*="Model"], [data-testid="model-selector"], button:has-text("Claude")', ) if ((await modelSelector.count()) > 0) { const initialModel = await modelSelector.first().textContent() // Reload page await page.reload({ waitUntil: "networkidle" }) await page .locator("iframe") .waitFor({ state: "visible", timeout: 30000 }) // Check model is still selected const modelAfterReload = await modelSelector.first().textContent() expect(modelAfterReload).toBe(initialModel) } }) test("handles localStorage quota exceeded gracefully", async ({ page }) => { await page.goto("/", { waitUntil: "networkidle" }) await page .locator("iframe") .waitFor({ state: "visible", timeout: 30000 }) // Fill up localStorage (simulate quota exceeded scenario) await page.evaluate(() => { try { // This might throw if quota is exceeded const largeData = "x".repeat(5 * 1024 * 1024) // 5MB localStorage.setItem("test-large-data", largeData) } catch { // Expected to fail on some browsers } }) // App should still function const chatInput = page.locator('textarea[aria-label="Chat input"]') await expect(chatInput).toBeVisible({ timeout: 10000 }) // Clean up await page.evaluate(() => { localStorage.removeItem("test-large-data") }) }) })