import { expect, test } from "@playwright/test"; import { mockLangGraphAPI, MOCK_THREAD_ID, MOCK_THREAD_ID_2, } from "./utils/mock-api"; const THREADS = [ { thread_id: MOCK_THREAD_ID, title: "First conversation", updated_at: "2025-06-01T12:00:00Z", }, { thread_id: MOCK_THREAD_ID_2, title: "Second conversation", updated_at: "2025-06-02T12:00:00Z", }, ]; const DEMO_THREAD_ID = "7cfa5f8f-a2f8-47ad-acbd-da7137baf990"; test.describe("Thread history", () => { test("sidebar shows existing threads", async ({ page }) => { mockLangGraphAPI(page, { threads: THREADS }); await page.goto("/workspace/chats/new"); // Both thread titles should appear in the sidebar await expect(page.getByText("First conversation")).toBeVisible({ timeout: 15_000, }); await expect(page.getByText("Second conversation")).toBeVisible(); }); test("clicking a thread in sidebar navigates to it", async ({ page }) => { mockLangGraphAPI(page, { threads: THREADS }); await page.goto("/workspace/chats/new"); // Wait for sidebar to populate const firstThread = page.getByText("First conversation"); await expect(firstThread).toBeVisible({ timeout: 15_000 }); // Click on the first thread await firstThread.click(); // Should navigate to that thread's URL await page.waitForURL(`**/workspace/chats/${MOCK_THREAD_ID}`); await expect(page).toHaveURL(new RegExp(MOCK_THREAD_ID)); }); test("existing thread loads historical messages", async ({ page }) => { mockLangGraphAPI(page, { threads: THREADS }); // Navigate directly to an existing thread await page.goto(`/workspace/chats/${MOCK_THREAD_ID}`); // The historical AI response should be displayed await expect( page.getByText("Response in thread First conversation"), ).toBeVisible({ timeout: 15_000 }); }); test("mock thread does not load real backend run history", async ({ page, }) => { mockLangGraphAPI(page, { threads: [ { thread_id: DEMO_THREAD_ID, title: "Forecasting 2026 Trends and Opportunities", updated_at: "2025-06-01T12:00:00Z", messages: [ { type: "human", id: `run-human-${DEMO_THREAD_ID}`, content: [ { type: "text", text: "This run-message endpoint should not be called.", }, ], }, ], }, ], }); const backendRunHistoryUrls: string[] = []; await page.route( /\/api\/langgraph\/threads\/[^/]+\/runs(?:\?|$)/, (route) => { if ( route.request().method() === "GET" && route .request() .url() .includes(`/api/langgraph/threads/${DEMO_THREAD_ID}/runs`) ) { backendRunHistoryUrls.push(route.request().url()); return route.fulfill({ status: 500, contentType: "application/json", body: JSON.stringify({ error: "mock=true must not load real runs", }), }); } return route.fallback(); }, ); await page.route( /\/api\/threads\/[^/]+\/runs\/[^/]+\/messages(?:\?|$)/, (route) => { if ( route.request().method() === "GET" && route.request().url().includes(`/api/threads/${DEMO_THREAD_ID}/runs/`) ) { backendRunHistoryUrls.push(route.request().url()); return route.fulfill({ status: 500, contentType: "application/json", body: JSON.stringify({ error: "mock=true must not load real run messages", }), }); } return route.fallback(); }, ); await page.goto(`/workspace/chats/${DEMO_THREAD_ID}?mock=true`); await expect( page.getByText("What might be the trends and opportunities in 2026?"), ).toBeVisible({ timeout: 15_000 }); await expect( page.getByText("I've created a modern, minimalist website"), ).toBeVisible(); expect(backendRunHistoryUrls).toEqual([]); }); test("chats list page shows all threads", async ({ page }) => { mockLangGraphAPI(page, { threads: THREADS }); await page.goto("/workspace/chats"); // Both threads should be listed in the main content area const main = page.locator("main"); await expect(main.getByText("First conversation")).toBeVisible({ timeout: 15_000, }); await expect(main.getByText("Second conversation")).toBeVisible(); }); });