import { test, expect } from "@playwright/test"; /** * E2E test for the real-backend SSE chat path. * * Stubs the actual endpoints the chat page uses: * - POST /api/projects → ensures a project exists * - POST /api/projects/proj-test/tasks → creates a task (conversation_id) * - POST ${PHYLO_BACKEND}/api/tasks/{id}/messages/stream → SSE stream * * The page calls createConversation() which calls ensureDefaultProject() + * createTask(), then getStreamUrl() which posts to /api/tasks/{id}/messages/stream. */ test("SSE chat path renders streamed assistant response", async ({ page }) => { const taskId = "task-e2e-test"; // Stub project creation/lookup await page.route("**/api/projects", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ id: "proj-test", name: "Quick Tasks" }), }); }); // Stub task creation (createConversation → createTask) await page.route("**/api/projects/proj-test/tasks", async (route) => { if (route.request().method() === "POST") { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ id: taskId, projectId: "proj-test", title: "(new conversation)", createdAt: Date.now(), updatedAt: Date.now(), messages: [], artifacts: [] }), }); } else { await route.fallback(); } }); // Stub the SSE stream endpoint (where sendReal posts the message) await page.route(`**/api/tasks/${taskId}/messages/stream`, async (route) => { const sseBody = [ 'data: {"type":"text_delta","text":"Hello "}', 'data: {"type":"text_delta","text":"world"}', 'data: {"type":"done"}', "", ].join("\n"); await route.fulfill({ status: 200, contentType: "text/event-stream", body: sseBody, }); }); // Stub dataset sync status (called on mount for greeting runtime spec) await page.route("**/api/datasets/sync/status", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify([]), }); }); // Stub task PATCH (persistence after assistant response) await page.route(`**/api/tasks/${taskId}`, async (route) => { if (route.request().method() === "PATCH") { await route.fulfill({ status: 200, contentType: "application/json", body: "{}" }); } else { await route.fallback(); } }); // Stub message append await page.route(`**/api/tasks/${taskId}/messages`, async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: "{}" }); }); await page.goto("/chat"); // Wait for the page to be ready (conversation created, input available) const textarea = page.locator("textarea"); await expect(textarea).toBeVisible({ timeout: 10_000 }); // Type a message and send await textarea.fill("Test message"); await textarea.press("Enter"); // Assert the assistant message contains the streamed text await expect(page.locator("text=Hello world")).toBeVisible({ timeout: 10_000 }); // Assert no error banners await expect(page.locator("text=Connection error")).not.toBeVisible(); });