import { expect, test, type Page } from "@playwright/test"; import { createAuthenticationStack } from "./fixtures/auth-stack.mjs"; /** * E2E F1 loop — hermetic (no VPN, no real Pi, no real Python). * * The backend is wired to: * - fake-tht.mjs : stubs `tht session new/list/show` * - fake-pi.mjs : delegates to harness/tests/fake_pi/fake_pi_rpc.mjs * which emits the f1_disambiguation.json scenario (select widget with * "interpretazione A" / "interpretazione B"). * * Flow: authenticate locally → focus the new-session composer → fill question → submit (Send) * → wait for F1 select widget → click an option → assert no error shown. */ test.describe.configure({ mode: "serial" }); let stack: Awaited>; test.beforeAll(async () => { stack = await createAuthenticationStack({ withF1Workspace: true }); await stack.useLocalMode(); }); test.afterAll(async () => { await stack?.close(); }); async function signInLocally(page: Page): Promise { const account = stack.localAccount("ordinary"); await page.getByLabel("Username").fill(account.username); await page.getByLabel("Password", { exact: true }).fill(account.password); await page.getByRole("button", { name: "Sign in", exact: true }).click(); await expect(page.getByTestId("app-shell")).toBeVisible({ timeout: 30_000 }); } test("F1 loop: new question → F1 widget → respond", async ({ page }) => { await page.goto(stack.publicUrl); await signInLocally(page); // Focus the composer for a new session. await page.getByRole("button", { name: "Session", exact: true }).click(); // Fill in the question. await page.getByLabel("New question").fill("quante cardioversioni nel 2024"); // Submit — the backend creates the session (fake-tht) and spawns Pi (fake-pi). await page.getByRole("button", { name: "Send", exact: true }).click(); // Wait for the F1 disambiguation select widget to appear. // The fake-pi emits extension_ui_request → backend bridges to SSE → frontend // renders SelectWidget with options from f1_disambiguation.json. await expect( page.getByRole("button", { name: /interpretazione A/i }), ).toBeVisible({ timeout: 30_000 }); // Both options from the scenario should be present. await expect(page.getByRole("button", { name: /interpretazione B/i })).toBeVisible(); // Click an option — the frontend POSTs to /sessions/e2e-1/response. await page.getByRole("button", { name: /interpretazione A/i }).click(); // After responding, the widget should disappear (clearPending) and no error shown. await expect( page.getByRole("button", { name: /interpretazione A/i }), ).not.toBeVisible({ timeout: 10_000 }); await expect(page.locator("body")).not.toContainText(/error/i); });