import { test, expect } from '@playwright/test'; test.describe('Claude Chat Workflow', () => { test('create new chat and send message to Claude', async ({ page }) => { // Enable console logging to debug WebSocket issues page.on('console', (msg) => { console.log(`[Browser ${msg.type()}]`, msg.text()); }); // Log WebSocket frames page.on('websocket', (ws) => { console.log(`[WebSocket] Connected to ${ws.url()}`); ws.on('framesent', (frame) => console.log(`[WS Sent]`, frame.payload)); ws.on('framereceived', (frame) => console.log(`[WS Received]`, frame.payload)); ws.on('close', () => console.log('[WebSocket] Closed')); }); // Log network requests to /api page.on('request', (request) => { if (request.url().includes('/api')) { console.log(`[Request] ${request.method()} ${request.url()}`); } }); page.on('response', (response) => { if (response.url().includes('/api')) { console.log(`[Response] ${response.status()} ${response.url()}`); } }); // 1. Navigate to homepage await page.goto('/'); await expect(page).toHaveTitle(/Spiceflow/i); // 2. Click the + button to open new session menu const createButton = page.locator('button[title="New Session"]'); await expect(createButton).toBeVisible(); await createButton.click(); // 3. Select Claude Code from the dropdown const claudeOption = page.locator('button:has-text("Claude Code")'); await expect(claudeOption).toBeVisible(); await claudeOption.click(); // 4. Wait for navigation to session page await page.waitForURL(/\/session\/.+/); console.log('[Test] Navigated to session page:', page.url()); // 5. Wait for the page to load (no loading spinner) await expect(page.locator('text=Loading')).not.toBeVisible({ timeout: 5000 }); // 6. Verify we see the empty message state await expect(page.locator('text=No messages yet')).toBeVisible(); // 7. Type a message in the textarea const textarea = page.locator('textarea'); await expect(textarea).toBeVisible(); await textarea.fill('say hi. respond in a single concise sentence'); // 8. Click the send button const sendButton = page.locator('button[type="submit"]'); await expect(sendButton).toBeEnabled(); await sendButton.click(); // 9. Verify user message appears immediately (optimistic update) await expect(page.locator('text=say hi. respond in a single concise sentence')).toBeVisible(); console.log('[Test] User message displayed'); // 10. Verify thinking indicator appears immediately after sending // The thinking indicator is a .rounded-lg.border div with bouncing dots inside const bouncingDots = page.locator('.animate-bounce'); // Thinking indicator should appear almost immediately (within 2 seconds) await expect(bouncingDots.first()).toBeVisible({ timeout: 2000 }); console.log('[Test] Thinking indicator appeared immediately'); // 11. Wait for streaming to complete - progress indicator should disappear // The streaming indicator has animate-bounce dots and animate-pulse cursor // Only look for pulsing cursor inside markdown-content (not the header status indicator) const pulsingCursor = page.locator('.markdown-content .animate-pulse'); // Wait for streaming indicators to disappear (they should be gone after message-stop) await expect(bouncingDots).toHaveCount(0, { timeout: 30000 }); await expect(pulsingCursor).toHaveCount(0, { timeout: 30000 }); console.log('[Test] Streaming complete - progress indicator disappeared'); // 12. Verify the response contains some text content // The assistant message is the last .rounded-lg.border with .markdown-content inside const assistantMessage = page.locator('.rounded-lg.border').filter({ has: page.locator('.markdown-content') }).last(); const responseText = await assistantMessage.locator('.markdown-content').textContent(); console.log('[Test] Assistant response text:', responseText); expect(responseText).toBeTruthy(); expect(responseText!.length).toBeGreaterThan(0); }); });