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:
92
tests/cypress/agenda/agenda.cy.ts
Normal file
92
tests/cypress/agenda/agenda.cy.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
const MOCK_PATIENT = {
|
||||
id: 'test-patient-uuid',
|
||||
name_family: 'Robot',
|
||||
name_given: ['Robbert'],
|
||||
birth_date: '1990-01-01',
|
||||
identifier_client_number: '99001',
|
||||
};
|
||||
|
||||
const FHIR_RESPONSE = {
|
||||
entry: [{
|
||||
resource: {
|
||||
id: MOCK_PATIENT.id,
|
||||
name: [{ family: MOCK_PATIENT.name_family, given: MOCK_PATIENT.name_given }],
|
||||
birthDate: MOCK_PATIENT.birth_date,
|
||||
identifier: [{ system: 'http://example.com/client/999.7.6', value: MOCK_PATIENT.identifier_client_number }],
|
||||
},
|
||||
}],
|
||||
};
|
||||
|
||||
describe('Agenda', () => {
|
||||
beforeEach(() => {
|
||||
cy.login();
|
||||
cy.visit('/epd/agenda');
|
||||
});
|
||||
|
||||
it('laadt de agendapagina', () => {
|
||||
cy.contains('h1', 'Agenda').should('be.visible');
|
||||
cy.contains('Nieuwe Afspraak').should('be.visible');
|
||||
});
|
||||
|
||||
it('toont navigatieknoppen en weergave-switcher', () => {
|
||||
cy.contains('Vandaag').should('be.visible');
|
||||
cy.contains('Dag').should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Afspraak maken', () => {
|
||||
beforeEach(() => {
|
||||
cy.intercept('GET', '/api/fhir/Patient**', FHIR_RESPONSE).as('patientSearch');
|
||||
cy.login();
|
||||
cy.visit('/epd/agenda');
|
||||
});
|
||||
|
||||
it('opent de "Nieuwe Afspraak" modal', () => {
|
||||
cy.contains('Nieuwe Afspraak').click();
|
||||
cy.get('[role="dialog"]').should('be.visible');
|
||||
cy.get('[role="dialog"]').contains('Nieuwe Afspraak');
|
||||
});
|
||||
|
||||
it('modal toont alle verplichte velden', () => {
|
||||
cy.contains('Nieuwe Afspraak').click();
|
||||
cy.get('[role="dialog"]').within(() => {
|
||||
cy.get('input[placeholder*="Zoek op naam"]').should('be.visible');
|
||||
cy.get('input[type="date"]').should('be.visible');
|
||||
cy.get('input[type="time"]').first().should('be.visible');
|
||||
cy.contains('button', 'Afspraak maken').should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it('zoeken naar patiënt toont resultaat', () => {
|
||||
cy.contains('Nieuwe Afspraak').click();
|
||||
cy.get('[role="dialog"]').within(() => {
|
||||
cy.get('input[placeholder*="Zoek op naam"]').type('Robbert');
|
||||
});
|
||||
cy.wait('@patientSearch');
|
||||
cy.contains('Robbert Robot').should('be.visible');
|
||||
});
|
||||
|
||||
it('selecteren van patiënt vult het veld in', () => {
|
||||
cy.contains('Nieuwe Afspraak').click();
|
||||
cy.get('[role="dialog"]').within(() => {
|
||||
cy.get('input[placeholder*="Zoek op naam"]').type('Robbert');
|
||||
});
|
||||
cy.wait('@patientSearch');
|
||||
cy.contains('Robbert Robot').click();
|
||||
cy.get('.bg-teal-50').should('be.visible');
|
||||
});
|
||||
|
||||
it('afspraak maken knop is uitgeschakeld zonder patiënt', () => {
|
||||
cy.contains('Nieuwe Afspraak').click();
|
||||
cy.get('[role="dialog"]').within(() => {
|
||||
cy.contains('button', 'Afspraak maken').should('be.disabled');
|
||||
});
|
||||
});
|
||||
|
||||
it('sluiten sluit de modal', () => {
|
||||
cy.contains('Nieuwe Afspraak').click();
|
||||
cy.get('[role="dialog"]').should('be.visible');
|
||||
cy.get('[role="dialog"]').contains('button', 'Sluiten').click();
|
||||
cy.get('[role="dialog"]').should('not.exist');
|
||||
});
|
||||
});
|
||||
35
tests/cypress/cortex/command-center.cy.ts
Normal file
35
tests/cypress/cortex/command-center.cy.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
describe('Cortex Command Center - UI', () => {
|
||||
beforeEach(() => {
|
||||
cy.login();
|
||||
cy.visit('/epd/cortex');
|
||||
cy.get('textarea[placeholder="Typ of spreek wat je wilt doen..."]:first').should('be.visible');
|
||||
});
|
||||
|
||||
it('laadt de command center interface', () => {
|
||||
cy.get('textarea[placeholder="Typ of spreek wat je wilt doen..."]:first').should('be.visible');
|
||||
cy.contains('Welkom bij Cortex Assistent').first().should('be.visible');
|
||||
});
|
||||
|
||||
it('chat input accepteert tekst', () => {
|
||||
cy.get('textarea[placeholder="Typ of spreek wat je wilt doen..."]:first').type('test invoer');
|
||||
cy.get('textarea[placeholder="Typ of spreek wat je wilt doen..."]:first').should('have.value', 'test invoer');
|
||||
});
|
||||
|
||||
it('keyboard shortcut ⌘K focust de input', () => {
|
||||
cy.get('body').click();
|
||||
cy.get('body').type('{meta}k');
|
||||
cy.get('textarea[placeholder="Typ of spreek wat je wilt doen..."]:first').should('be.focused');
|
||||
});
|
||||
|
||||
it('escape wist de input', () => {
|
||||
cy.get('textarea[placeholder="Typ of spreek wat je wilt doen..."]:first').type('iets typen');
|
||||
cy.get('textarea[placeholder="Typ of spreek wat je wilt doen..."]:first').type('{esc}');
|
||||
cy.get('textarea[placeholder="Typ of spreek wat je wilt doen..."]:first').should('have.value', '');
|
||||
});
|
||||
|
||||
it('toont voorbeeldcommandos in de empty state', () => {
|
||||
cy.contains('Dagnotitie maken').first().should('be.visible');
|
||||
cy.contains('Patiënt zoeken').first().should('be.visible');
|
||||
cy.contains('Overdracht maken').first().should('be.visible');
|
||||
});
|
||||
});
|
||||
44
tests/cypress/cortex/dagnotitie-flow.cy.ts
Normal file
44
tests/cypress/cortex/dagnotitie-flow.cy.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
describe('Cortex - Demo flows', () => {
|
||||
beforeEach(() => {
|
||||
cy.login();
|
||||
cy.visit('/epd/cortex');
|
||||
cy.get('textarea[placeholder="Typ of spreek wat je wilt doen..."]:first').should('be.visible');
|
||||
});
|
||||
|
||||
it('dagnotitie flow: bericht → AI response → artifact verschijnt', () => {
|
||||
cy.mockDagnotitieChat('Jan de Vries');
|
||||
cy.get('textarea[placeholder="Typ of spreek wat je wilt doen..."]:first').type('notitie jan de vries voelt zich goed vandaag{enter}');
|
||||
cy.contains('notitie jan de vries voelt zich goed vandaag').first().should('be.visible');
|
||||
cy.contains('Genoteerd.').first().should('be.visible');
|
||||
});
|
||||
|
||||
it('dagnotitie flow: input is leeg na verzenden', () => {
|
||||
cy.mockDagnotitieChat();
|
||||
cy.get('textarea[placeholder="Typ of spreek wat je wilt doen..."]:first').type('notitie jan medicatie gegeven{enter}');
|
||||
cy.get('textarea[placeholder="Typ of spreek wat je wilt doen..."]:first').should('have.value', '');
|
||||
});
|
||||
|
||||
it('zoeken flow: bericht → zoek-artifact verschijnt', () => {
|
||||
cy.mockZoekenChat('Marie');
|
||||
cy.get('textarea[placeholder="Typ of spreek wat je wilt doen..."]:first').type('zoek marie{enter}');
|
||||
cy.contains('Even kijken.').first().should('be.visible');
|
||||
});
|
||||
|
||||
it('onbekende intent: AI vraagt om verduidelijking', () => {
|
||||
cy.mockOnbekendChat();
|
||||
cy.get('textarea[placeholder="Typ of spreek wat je wilt doen..."]:first').type('xyz{enter}');
|
||||
cy.contains('snap niet helemaal wat je bedoelt').first().should('be.visible');
|
||||
});
|
||||
|
||||
it('meerdere berichten: chat history groeit', () => {
|
||||
cy.mockDagnotitieChat();
|
||||
cy.get('textarea[placeholder="Typ of spreek wat je wilt doen..."]:first').type('notitie jan medicatie gegeven{enter}');
|
||||
cy.contains('Genoteerd.').first().should('be.visible');
|
||||
|
||||
cy.mockDagnotitieChat('Marie');
|
||||
cy.get('textarea[placeholder="Typ of spreek wat je wilt doen..."]:first').type('notitie marie slaapt slecht{enter}');
|
||||
|
||||
cy.contains('notitie jan medicatie gegeven').first().should('be.visible');
|
||||
cy.contains('notitie marie slaapt slecht').first().should('be.visible');
|
||||
});
|
||||
});
|
||||
36
tests/cypress/login.cy.ts
Normal file
36
tests/cypress/login.cy.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
describe('Login flow', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/login');
|
||||
});
|
||||
|
||||
it('toont de loginpagina correct', () => {
|
||||
cy.get('h2').should('contain', 'Welkom terug');
|
||||
cy.get('#email').should('be.visible');
|
||||
cy.get('#password').should('be.visible');
|
||||
cy.get('button[type="submit"]').should('contain', 'Inloggen');
|
||||
});
|
||||
|
||||
it('toont foutmelding bij verkeerde inloggegevens', () => {
|
||||
cy.get('#email').type('verkeerd@email.nl');
|
||||
cy.get('#password').type('foutWachtwoord123');
|
||||
cy.get('button[type="submit"]').click();
|
||||
cy.contains('onjuist').should('be.visible');
|
||||
});
|
||||
|
||||
it('demo login knop is zichtbaar en werkt', () => {
|
||||
cy.contains('Demo Account Proberen').should('be.visible').click();
|
||||
cy.url().should('match', /\/epd\//);
|
||||
});
|
||||
|
||||
it('redirect naar EPD na succesvol inloggen', () => {
|
||||
cy.get('#email').type(Cypress.env('TEST_EMAIL') ?? 'demo@mini-ecd.demo');
|
||||
cy.get('#password').type(Cypress.env('TEST_PASSWORD') ?? 'Demo2024!');
|
||||
cy.get('button[type="submit"]').click();
|
||||
cy.url().should('match', /\/epd\//);
|
||||
});
|
||||
|
||||
it('beveiligde route stuurt door naar login', () => {
|
||||
cy.visit('/epd/dashboard');
|
||||
cy.url().should('include', '/login');
|
||||
});
|
||||
});
|
||||
77
tests/cypress/support/commands.ts
Normal file
77
tests/cypress/support/commands.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
/// <reference types="cypress" />
|
||||
|
||||
const TEST_EMAIL = Cypress.env('TEST_EMAIL') ?? 'demo@mini-ecd.demo';
|
||||
const TEST_PASSWORD = Cypress.env('TEST_PASSWORD') ?? 'Demo2024!';
|
||||
|
||||
// Login command met session caching — logt maar één keer in per testrun
|
||||
Cypress.Commands.add('login', () => {
|
||||
cy.session('epd-user', () => {
|
||||
cy.visit('/login');
|
||||
cy.get('h2').should('contain', 'Welkom terug');
|
||||
cy.get('#email').type(TEST_EMAIL);
|
||||
cy.get('#password').type(TEST_PASSWORD);
|
||||
cy.get('button[type="submit"]').click();
|
||||
cy.url().should('match', /\/epd\//);
|
||||
});
|
||||
});
|
||||
|
||||
// AI chat mock — onderschept de Cortex chat API
|
||||
Cypress.Commands.add('mockDagnotitieChat', (patientName = 'Jan de Vries') => {
|
||||
const sseBody = [
|
||||
`data: ${JSON.stringify({ type: 'content', text: `Genoteerd.\n\n\`\`\`json\n${JSON.stringify({
|
||||
type: 'action',
|
||||
intent: 'dagnotitie',
|
||||
entities: { patientName, category: 'observatie', content: 'Patiënt voelt zich goed vandaag.' },
|
||||
confidence: 0.95,
|
||||
artifact: { type: 'dagnotitie', prefill: { patientName, category: 'observatie', content: 'Patiënt voelt zich goed vandaag.' } },
|
||||
}, null, 2)}\n\`\`\`` })}\n\n`,
|
||||
`data: ${JSON.stringify({ type: 'done' })}\n\n`,
|
||||
].join('');
|
||||
|
||||
cy.intercept('POST', '/api/cortex/chat', {
|
||||
statusCode: 200,
|
||||
headers: { 'Content-Type': 'text/event-stream' },
|
||||
body: sseBody,
|
||||
}).as('cortexChat');
|
||||
});
|
||||
|
||||
Cypress.Commands.add('mockZoekenChat', (query = 'Jan') => {
|
||||
const sseBody = [
|
||||
`data: ${JSON.stringify({ type: 'content', text: `Even kijken.\n\n\`\`\`json\n${JSON.stringify({
|
||||
type: 'action', intent: 'zoeken',
|
||||
entities: { query }, confidence: 0.97,
|
||||
artifact: { type: 'zoeken', prefill: { query } },
|
||||
}, null, 2)}\n\`\`\`` })}\n\n`,
|
||||
`data: ${JSON.stringify({ type: 'done' })}\n\n`,
|
||||
].join('');
|
||||
|
||||
cy.intercept('POST', '/api/cortex/chat', {
|
||||
statusCode: 200,
|
||||
headers: { 'Content-Type': 'text/event-stream' },
|
||||
body: sseBody,
|
||||
}).as('cortexChat');
|
||||
});
|
||||
|
||||
Cypress.Commands.add('mockOnbekendChat', () => {
|
||||
const sseBody = [
|
||||
`data: ${JSON.stringify({ type: 'content', text: 'Hmm, snap niet helemaal wat je bedoelt. Kun je het anders zeggen?' })}\n\n`,
|
||||
`data: ${JSON.stringify({ type: 'done' })}\n\n`,
|
||||
].join('');
|
||||
|
||||
cy.intercept('POST', '/api/cortex/chat', {
|
||||
statusCode: 200,
|
||||
headers: { 'Content-Type': 'text/event-stream' },
|
||||
body: sseBody,
|
||||
}).as('cortexChat');
|
||||
});
|
||||
|
||||
declare global {
|
||||
namespace Cypress {
|
||||
interface Chainable {
|
||||
login(): Chainable<void>;
|
||||
mockDagnotitieChat(patientName?: string): Chainable<void>;
|
||||
mockZoekenChat(query?: string): Chainable<void>;
|
||||
mockOnbekendChat(): Chainable<void>;
|
||||
}
|
||||
}
|
||||
}
|
||||
1
tests/cypress/support/e2e.ts
Normal file
1
tests/cypress/support/e2e.ts
Normal file
@@ -0,0 +1 @@
|
||||
import './commands';
|
||||
31
tests/cypress/verpleegrapportage/overzicht.cy.ts
Normal file
31
tests/cypress/verpleegrapportage/overzicht.cy.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
describe('Verpleegrapportage', () => {
|
||||
beforeEach(() => {
|
||||
cy.login();
|
||||
});
|
||||
|
||||
it('laadt de rapportage pagina', () => {
|
||||
cy.visit('/epd/verpleegrapportage');
|
||||
cy.url().should('not.include', '/login');
|
||||
cy.get('body').should('be.visible');
|
||||
});
|
||||
|
||||
it('toont lege staat of patiëntenlijst', () => {
|
||||
cy.visit('/epd/verpleegrapportage');
|
||||
cy.get('body').then(($body) => {
|
||||
const hasContent =
|
||||
$body.find('[class*="rapportage"]').length > 0 ||
|
||||
$body.find('[class*="patient"]').length > 0 ||
|
||||
$body.text().includes('Geen patiënten');
|
||||
expect(hasContent).to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Overdracht pagina', () => {
|
||||
it('laadt de overdracht pagina', () => {
|
||||
cy.login();
|
||||
cy.visit('/epd/verpleegrapportage/overdracht');
|
||||
cy.url().should('not.include', '/login');
|
||||
cy.get('body').should('be.visible');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user