/** * E2E: Compliance Advisor — Clarity Gate (v3 contract) * * Drives the floating advisor widget end-to-end against a stubbed /api/sdk/compliance-advisor/chat * (contract fixtures), so the whole FE chain is exercised without the RAG/LLM backend: * - underspecified question -> clarify mode (L1 general answer + domain context chips) * - specific question -> answer mode (markdown + [n] citation coupling + evidence pane) * - clarify -> pick a context -> scoped answer * Runs on CI / macmini (needs the Next app on :3002). */ import { test, expect } from '../fixtures/sdk-fixtures' const CHAT_ROUTE = '**/api/sdk/compliance-advisor/chat' const openAdvisor = 'Compliance Advisor oeffnen' const inputPlaceholder = 'Frage eingeben...' const CLARIFY = { mode: 'clarify', question: 'Was ist PDCA?', clarity: { is_underspecified: true, concentration: 0.3, suggested_contexts: [ { id: 'datenschutz', label: 'Datenschutz' }, { id: 'cyber', label: 'Cybersecurity' }, ], }, general_answer: 'PDCA steht für **Plan-Do-Check-Act**.', answer: null, evidence: [], citations: [], visual_evidence: [], footnotes: [], } const ANSWER = { mode: 'answer', question: 'CRA Meldefrist', clarity: { is_underspecified: false, dominant_context: 'cyber', concentration: 0.88 }, answer: 'Die Meldung erfolgt unverzüglich [1].', evidence: [ { evidence_id: 'e1', document: 'CRA', section: 'Art. 14', paragraph: 'Abs. 1', snippet: 'unverzüglich melden', bindingness: 'binding' }, ], citations: [ { citation_id: 'c1', number: 1, evidence_id: 'e1', document: 'CRA', section: 'Art. 14', paragraph: 'Abs. 1' }, ], visual_evidence: [], footnotes: [], } async function ask(page: import('@playwright/test').Page, question: string) { await page.getByRole('button', { name: openAdvisor }).click() const input = page.getByPlaceholder(inputPlaceholder) await input.fill(question) await input.press('Enter') } test.describe('Compliance Advisor — Clarity Gate', () => { test('underspecified question -> clarify (L1 definition + context chips, no evidence)', async ({ sdkPage }) => { await sdkPage.route(CHAT_ROUTE, (r) => r.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(CLARIFY) }), ) await ask(sdkPage, 'Was ist PDCA?') await expect(sdkPage.getByText('Allgemeine Definition')).toBeVisible() await expect(sdkPage.getByText('Plan-Do-Check-Act')).toBeVisible() await expect(sdkPage.getByRole('button', { name: 'Datenschutz' })).toBeVisible() await expect(sdkPage.getByRole('button', { name: 'Cybersecurity' })).toBeVisible() }) test('specific question -> answer with [n] citation + evidence pane', async ({ sdkPage }) => { await sdkPage.route(CHAT_ROUTE, (r) => r.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(ANSWER) }), ) await ask(sdkPage, 'CRA Meldefrist') await expect(sdkPage.getByText(/unverzüglich/)).toBeVisible() await expect(sdkPage.getByTitle('Beleg 1 anzeigen')).toBeVisible() // bindingness present -> header splits into Rechtsgrundlagen vs Leitlinien (evidence framing) await expect(sdkPage.getByText('Rechtsgrundlagen').first()).toBeVisible() // family name resolved for the user (shown both in the summary breakdown and the evidence card) await expect(sdkPage.getByText('Cyber Resilience Act (CRA)').first()).toBeVisible() }) test('clarify -> pick a context -> scoped answer', async ({ sdkPage }) => { let calls = 0 await sdkPage.route(CHAT_ROUTE, (r) => { calls += 1 r.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(calls === 1 ? CLARIFY : ANSWER), }) }) await ask(sdkPage, 'Was ist PDCA?') await sdkPage.getByRole('button', { name: 'Datenschutz' }).click() await expect(sdkPage.getByText(/unverzüglich/)).toBeVisible() await expect(sdkPage.getByTitle('Beleg 1 anzeigen')).toBeVisible() }) })