Files
ThothII/frontend/e2e/f1.spec.ts
T
marcopanandClaude Sonnet 4.6 1171181f9a test(frontend): Playwright e2e F1 vs backend+fake-pi (hermetic)
Adds a real-browser Playwright e2e of the full F1 disambiguation loop,
driven against the real backend + fake binaries (no VPN, no real Pi, no Python).

- frontend/e2e/fixtures/fake-tht.mjs: stubs tht CLI (session new/list/show)
- frontend/e2e/fixtures/fake-pi.mjs: wraps harness fake-pi-rpc with f1_disambiguation.json
- frontend/playwright.config.ts: two webServer entries (backend:8799, frontend:5199)
- frontend/e2e/f1.spec.ts: open app → create session → wait for SelectWidget → respond

Bug fixes discovered during e2e:
- useSessionStream: add addEventListener for named SSE events (backend sends
  'event: ui_request' etc.; onmessage only fires for unnamed 'event: message')
- FakeEventSource: add no-op addEventListener/removeEventListener stubs
- backend SSE route: add CORS headers manually in writeHead() since
  reply.raw bypasses the @fastify/cors onSend hook
- backend: install and register @fastify/cors for all non-SSE routes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 13:27:00 +02:00

46 lines
1.9 KiB
TypeScript

import { test, expect } from "@playwright/test";
/**
* 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: open app → open NewSessionDialog → fill question → submit (Crea)
* → wait for F1 select widget → click an option → assert no error shown.
*/
test("F1 loop: new question → F1 widget → respond", async ({ page }) => {
await page.goto("/");
// Open the new-session dialog.
await page.getByRole("button", { name: /nuova/i }).click();
// Fill in the question.
await page.getByLabel(/domanda/i).fill("quante cardioversioni nel 2024");
// Submit — the backend creates the session (fake-tht) and spawns Pi (fake-pi).
await page.getByRole("button", { name: /^crea$/i }).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(/errore/i);
});