test(advisor): E2E for the Clarity-Gate chain (Playwright, stubbed endpoint)

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 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-07-01 14:06:02 +02:00
parent 3f372bcb39
commit f37081b60b
@@ -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()
})
})