test: Cypress en Playwright e2e-suites voor login, agenda, cortex en verpleegrapportage

Configs voor beide runners, login via session caching, AI-chat
mocks (SSE) voor deterministische Cortex-tests. Supabase local
config toegevoegd. Auth-state en reports zijn gitignored.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
colinislit
2026-07-09 23:16:17 +02:00
parent 924988dd15
commit 9195eb9fed
20 changed files with 1611 additions and 0 deletions

View File

@@ -0,0 +1,108 @@
import { test, expect } from '@playwright/test';
const PATIENT_NAAM = 'Robbert Robot';
const AUTH_STATE = 'tests/playwright/.auth/user.json';
const RAPPORTAGES = [
{
type: 'Voortgang',
inhoud:
'Cliënt heeft vandaag goed geslapen en voelt zich uitgerust. Medicatie correct ingenomen zonder bijwerkingen. Stemming is stabiel en positief gedurende de gehele dag.',
},
{
type: 'Contact',
inhoud:
'Telefonisch contact gehad met de familie van de cliënt. Positieve terugkoppeling over het afgelopen weekend. Familie ervaart momenteel weinig zorgen over de thuissituatie.',
},
{
type: 'Vrije notitie',
inhoud:
'Cliënt heeft interesse getoond in deelname aan de dagactiviteitengroep. Dit voorstel wordt volgende week besproken tijdens het multidisciplinair overleg met het behandelteam.',
},
];
test.describe(`Rapportages schrijven - ${PATIENT_NAAM}`, () => {
let rapportageUrl: string;
test.beforeAll(async ({ browser }) => {
// Gebruik een aparte context om het patiënt-ID op te zoeken
const context = await browser.newContext({ storageState: AUTH_STATE });
const page = await context.newPage();
await page.goto('/epd/patients');
await page.waitForLoadState('networkidle');
// Zoek Robbert Robot via de zoekbalk
await page.getByPlaceholder('Zoek op naam of BSN...').fill('Robbert');
await expect(page.getByText(PATIENT_NAAM).first()).toBeVisible({ timeout: 8_000 });
await page.getByText(PATIENT_NAAM).first().click();
await page.waitForURL(/\/epd\/patients\/.+/);
const patientId = page.url().match(/\/epd\/patients\/([^/?#]+)/)?.[1];
if (!patientId) throw new Error(`Patiënt "${PATIENT_NAAM}" niet gevonden in de patiëntenlijst`);
rapportageUrl = `/epd/patients/${patientId}/rapportage`;
await context.close();
});
test.beforeEach(async ({ page }) => {
await page.goto(rapportageUrl);
await page.waitForLoadState('networkidle');
// Wacht tot de TipTap editor volledig geladen is
await expect(page.locator('.ProseMirror')).toBeVisible({ timeout: 10_000 });
// Verwijder eventuele localStorage drafts voor een schone start
await page.evaluate(() => {
Object.keys(localStorage)
.filter((k) => k.startsWith('rapportage-draft-'))
.forEach((k) => localStorage.removeItem(k));
});
});
RAPPORTAGES.forEach((rapportage, i) => {
test(`rapportage ${i + 1}: ${rapportage.type}`, async ({ page }) => {
// Selecteer het rapportage type via de QuickActions (exact=true voorkomt match met tijdlijn items)
await page.getByRole('button', { name: rapportage.type, exact: true }).click();
// Klik op de editor en typ de inhoud
const editor = page.locator('.ProseMirror');
await editor.click();
await page.keyboard.type(rapportage.inhoud);
// Verifieer dat de tekst meetelt in de karakterteller
await expect(page.locator('text=/\\d+ \\/ 5000 karakters/')).toBeVisible();
// Opslaan knop moet actief zijn (inhoud > 20 karakters)
const opslaanKnop = page.getByRole('button', { name: 'Opslaan' });
await expect(opslaanKnop).toBeEnabled();
await opslaanKnop.click();
// Verifieer de succesmelding
await expect(page.getByText('Rapportage opgeslagen').first()).toBeVisible({ timeout: 10_000 });
// Na opslaan is de editor leeg → Opslaan knop is weer uitgeschakeld
await expect(opslaanKnop).toBeDisabled({ timeout: 5_000 });
});
});
test('alle drie rapportages staan in de tijdlijn', async ({ page }) => {
// Schrijf de drie rapportages in volgorde
for (const rapportage of RAPPORTAGES) {
await page.getByRole('button', { name: rapportage.type, exact: true }).click();
await page.locator('.ProseMirror').click();
await page.keyboard.type(rapportage.inhoud);
await page.getByRole('button', { name: 'Opslaan', exact: true }).click();
await expect(page.getByText('Rapportage opgeslagen').first()).toBeVisible({ timeout: 10_000 });
// exact: true onderscheidt "Opslaan" (klaar) van "Opslaan…" (bezig) — wacht tot opslaan echt klaar is
await expect(page.getByRole('button', { name: 'Opslaan', exact: true })).toBeDisabled({ timeout: 10_000 });
}
// Controleer dat de tijdlijn de rapportages toont
// TimelineCard rendert als <article role="button"> — anders dan QuickAction <button> elementen
const tijdlijnKaarten = page.locator('article[role="button"]');
await expect(tijdlijnKaarten.filter({ hasText: 'Voortgang' }).first()).toBeVisible({ timeout: 5_000 });
await expect(tijdlijnKaarten.filter({ hasText: 'Contact' }).first()).toBeVisible({ timeout: 5_000 });
await expect(tijdlijnKaarten.filter({ hasText: 'Vrije notitie' }).first()).toBeVisible({ timeout: 5_000 });
});
});