import { expect, test, type Page } from "@playwright/test"; test.setTimeout(150_000); async function startFreshAssistant(page: Page) { await page.goto("/login"); await page.getByLabel("Username").fill(process.env.ADMIN_USERNAME!); await page.getByLabel("Password").fill(process.env.ADMIN_PASSWORD!); await page.getByRole("button", { name: /sign in/i }).click(); await page.waitForURL("/"); const initialConnect = page.waitForResponse((response) => { if (!response.url().includes("/api/copilotkit") || response.request().method() !== "POST") { return false; } try { return JSON.parse(response.request().postData() ?? "{}").method === "agent/connect"; } catch { return false; } }); await page.goto("/asistente"); await expect(page.getByRole("heading", { name: "Asistente" })).toBeVisible(); await (await initialConnect).finished(); const newConversation = page.getByRole("button", { name: "Nueva conversación" }); if (await newConversation.isEnabled()) { await newConversation.click(); const reconnect = page.waitForResponse((response) => { if (!response.url().includes("/api/copilotkit") || response.request().method() !== "POST") { return false; } try { return JSON.parse(response.request().postData() ?? "{}").method === "agent/connect"; } catch { return false; } }); await page.getByRole("button", { name: "Borrar historial" }).click(); await (await reconnect).finished(); await expect( page.getByRole("textbox", { name: "Escribe tu pregunta…" }), ).toBeEnabled({ timeout: 30_000 }); } } async function ask(page: Page, question: string, expectedTool: string) { // The BFF response is the exact CopilotKit stream consumed by the browser. // Checking it catches server-side RUN_ERROR events even if the UI changes. const stream = page.waitForResponse( (response) => { if ( !response.url().includes("/api/copilotkit") || response.request().method() !== "POST" ) { return false; } try { return JSON.parse(response.request().postData() ?? "{}").method === "agent/run"; } catch { return false; } }, ); const composer = page.getByRole("textbox", { name: "Escribe tu pregunta…" }); await expect(composer).toBeEnabled({ timeout: 30_000 }); await composer.fill(question); const sendButton = page.getByTestId("copilot-send-button"); await expect(sendButton).toBeEnabled({ timeout: 30_000 }); await sendButton.click(); await expect(page.getByText(question, { exact: true }).last()).toBeVisible(); const response = await stream; await response.finished(); const events = await response.text(); expect(events).not.toContain("RUN_ERROR"); expect(events).toContain(expectedTool); // The HTTP stream may finish before CopilotKit has committed the tool run // and released its client-side running state. Waiting for the page action // avoids losing the next Enter keypress during that short interval. await expect(page.getByRole("button", { name: "Nueva conversación" })).toBeEnabled({ timeout: 30_000, }); } test("Asistente completes Luna's read-only tool flows", async ({ page }) => { const consoleErrors: string[] = []; page.on("console", (message) => { if (message.type() === "error") consoleErrors.push(message.text()); }); await startFreshAssistant(page); await ask(page, "What date is it today?", "get_current_date"); await ask(page, "List my last 10 transactions.", "get_recent_transactions"); await ask(page, "What is my net worth?", "get_net_worth"); await ask(page, "How much did I spend today?", "get_daily_spending"); await ask(page, "Show my spending by category this month.", "render_spending_summary"); const spendingCard = page.getByTestId("spending-summary-card"); await expect(spendingCard).toBeVisible({ timeout: 30_000 }); await expect(page.getByRole("textbox", { name: "Escribe tu pregunta…" })).toBeEnabled({ timeout: 30_000, }); await page.reload(); await expect(page.getByRole("heading", { name: "Asistente" })).toBeVisible(); await expect(page.getByText("Show my spending by category this month.", { exact: true })).toBeVisible(); await expect(page.getByTestId("spending-summary-card")).toBeVisible(); expect(consoleErrors.join("\n")).not.toContain("ChatClientException"); });