From f37081b60b540bb14d85d9ec10b21ddfa6f0408f Mon Sep 17 00:00:00 2001 From: Benjamin Admin Date: Wed, 1 Jul 2026 14:06:02 +0200 Subject: [PATCH] test(advisor): E2E for the Clarity-Gate chain (Playwright, stubbed endpoint) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per the "always write E2E" rule: drives the floating advisor widget end-to-end against a stubbed /api/sdk/compliance-advisor/chat with contract fixtures — clarify (L1 + context chips), answer ([n] citation + evidence pane), and clarify->pick-context->scoped-answer. No backend needed (route interception). Runs on CI/macmini (Next app on :3002); validated here via tsc + `playwright --list` (3 tests discovered). check-loc 0. Co-Authored-By: Claude Opus 4.7 --- .../e2e/specs/compliance-advisor.spec.ts | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 admin-compliance/e2e/specs/compliance-advisor.spec.ts diff --git a/admin-compliance/e2e/specs/compliance-advisor.spec.ts b/admin-compliance/e2e/specs/compliance-advisor.spec.ts new file mode 100644 index 00000000..e176794a --- /dev/null +++ b/admin-compliance/e2e/specs/compliance-advisor.spec.ts @@ -0,0 +1,99 @@ +/** + * 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' }, + ], + 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() + await expect(sdkPage.getByText('Cyber Resilience Act (CRA)')).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() + }) +})