mirror of
https://github.com/complexcaresolutions/dak.c2s.git
synced 2026-03-17 16:03:41 +00:00
test: add Playwright E2E tests (auth, dashboard, cases, admin)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
edf30c02ec
commit
77805191cf
7 changed files with 254 additions and 1 deletions
46
frontend/e2e/admin.spec.ts
Normal file
46
frontend/e2e/admin.spec.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
import { test, expect } from '@playwright/test'
|
||||||
|
|
||||||
|
test.describe('Admin pages', () => {
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
// Log in as admin before each test
|
||||||
|
await page.goto('/login')
|
||||||
|
await page.getByLabel('E-Mail').fill('admin@dak-portal.de')
|
||||||
|
await page.getByLabel('Passwort').fill('admin123')
|
||||||
|
await page.getByRole('button', { name: 'Anmelden' }).click()
|
||||||
|
await expect(page).toHaveURL(/\/dashboard/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('admin users page loads', async ({ page }) => {
|
||||||
|
await page.goto('/admin/users')
|
||||||
|
|
||||||
|
// Page heading should be visible
|
||||||
|
await expect(page.getByRole('heading', { name: 'Benutzer' })).toBeVisible()
|
||||||
|
|
||||||
|
// The create button should be present
|
||||||
|
await expect(page.getByRole('button', { name: /neuen benutzer erstellen/i })).toBeVisible()
|
||||||
|
|
||||||
|
// Table headers should be visible
|
||||||
|
await expect(page.getByRole('columnheader', { name: 'Benutzername' })).toBeVisible()
|
||||||
|
await expect(page.getByRole('columnheader', { name: 'E-Mail' })).toBeVisible()
|
||||||
|
await expect(page.getByRole('columnheader', { name: 'Rolle' })).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('audit log page loads', async ({ page }) => {
|
||||||
|
await page.goto('/admin/audit')
|
||||||
|
|
||||||
|
// Page heading should be visible
|
||||||
|
await expect(page.getByRole('heading', { name: 'Audit-Log' })).toBeVisible()
|
||||||
|
|
||||||
|
// Filter section should be present
|
||||||
|
await expect(page.getByText('Filter')).toBeVisible()
|
||||||
|
await expect(page.getByLabel('Benutzer-ID')).toBeVisible()
|
||||||
|
await expect(page.getByLabel('Aktion')).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('disclosures page loads', async ({ page }) => {
|
||||||
|
await page.goto('/admin/disclosures')
|
||||||
|
|
||||||
|
// Page heading should be visible
|
||||||
|
await expect(page.getByRole('heading', { name: 'Freigabe-Anfragen' })).toBeVisible()
|
||||||
|
})
|
||||||
|
})
|
||||||
53
frontend/e2e/auth.spec.ts
Normal file
53
frontend/e2e/auth.spec.ts
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
import { test, expect } from '@playwright/test'
|
||||||
|
|
||||||
|
test.describe('Authentication', () => {
|
||||||
|
test('login with valid credentials redirects to dashboard', async ({ page }) => {
|
||||||
|
await page.goto('/login')
|
||||||
|
|
||||||
|
await page.getByLabel('E-Mail').fill('admin@dak-portal.de')
|
||||||
|
await page.getByLabel('Passwort').fill('admin123')
|
||||||
|
await page.getByRole('button', { name: 'Anmelden' }).click()
|
||||||
|
|
||||||
|
// Should redirect to dashboard after successful login
|
||||||
|
await expect(page).toHaveURL(/\/dashboard/)
|
||||||
|
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('login with invalid credentials shows error message', async ({ page }) => {
|
||||||
|
await page.goto('/login')
|
||||||
|
|
||||||
|
await page.getByLabel('E-Mail').fill('invalid@example.de')
|
||||||
|
await page.getByLabel('Passwort').fill('wrongpassword')
|
||||||
|
await page.getByRole('button', { name: 'Anmelden' }).click()
|
||||||
|
|
||||||
|
// Should show error alert
|
||||||
|
await expect(page.getByText('Ungueltige Anmeldedaten')).toBeVisible()
|
||||||
|
|
||||||
|
// Should remain on login page
|
||||||
|
await expect(page).toHaveURL(/\/login/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('protected route redirects to login without auth', async ({ page }) => {
|
||||||
|
// Try to access dashboard directly without logging in
|
||||||
|
await page.goto('/dashboard')
|
||||||
|
|
||||||
|
// Should be redirected to login page
|
||||||
|
await expect(page).toHaveURL(/\/login/)
|
||||||
|
await expect(page.getByText('DAK Portal')).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('logout redirects to login', async ({ page }) => {
|
||||||
|
// First log in
|
||||||
|
await page.goto('/login')
|
||||||
|
await page.getByLabel('E-Mail').fill('admin@dak-portal.de')
|
||||||
|
await page.getByLabel('Passwort').fill('admin123')
|
||||||
|
await page.getByRole('button', { name: 'Anmelden' }).click()
|
||||||
|
await expect(page).toHaveURL(/\/dashboard/)
|
||||||
|
|
||||||
|
// Click the logout button in the sidebar/header
|
||||||
|
await page.getByRole('button', { name: /abmelden|logout/i }).click()
|
||||||
|
|
||||||
|
// Should be redirected to login page
|
||||||
|
await expect(page).toHaveURL(/\/login/)
|
||||||
|
})
|
||||||
|
})
|
||||||
52
frontend/e2e/cases.spec.ts
Normal file
52
frontend/e2e/cases.spec.ts
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
import { test, expect } from '@playwright/test'
|
||||||
|
|
||||||
|
test.describe('Cases', () => {
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
// Log in before each test
|
||||||
|
await page.goto('/login')
|
||||||
|
await page.getByLabel('E-Mail').fill('admin@dak-portal.de')
|
||||||
|
await page.getByLabel('Passwort').fill('admin123')
|
||||||
|
await page.getByRole('button', { name: 'Anmelden' }).click()
|
||||||
|
await expect(page).toHaveURL(/\/dashboard/)
|
||||||
|
|
||||||
|
// Navigate to cases page
|
||||||
|
await page.getByRole('link', { name: /fälle/i }).click()
|
||||||
|
await expect(page).toHaveURL(/\/cases/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('case list table is visible', async ({ page }) => {
|
||||||
|
// Table headers should be visible
|
||||||
|
await expect(page.getByRole('columnheader', { name: 'Fall-ID' })).toBeVisible()
|
||||||
|
await expect(page.getByRole('columnheader', { name: 'Datum' })).toBeVisible()
|
||||||
|
await expect(page.getByRole('columnheader', { name: 'KVNR' })).toBeVisible()
|
||||||
|
await expect(page.getByRole('columnheader', { name: 'Fallgruppe' })).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('search input filters cases', async ({ page }) => {
|
||||||
|
// The search input should be present
|
||||||
|
const searchInput = page.getByPlaceholder(/suche/i)
|
||||||
|
await expect(searchInput).toBeVisible()
|
||||||
|
|
||||||
|
// Type a search query
|
||||||
|
await searchInput.fill('onko')
|
||||||
|
|
||||||
|
// Wait for debounce and results to update
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
|
||||||
|
// The page should still show the cases heading
|
||||||
|
await expect(page.getByRole('heading', { name: 'Fälle' })).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('clicking a row opens detail sheet', async ({ page }) => {
|
||||||
|
// Wait for table rows to appear
|
||||||
|
const firstRow = page.locator('tbody tr').first()
|
||||||
|
await expect(firstRow).toBeVisible()
|
||||||
|
|
||||||
|
// Click the first row
|
||||||
|
await firstRow.click()
|
||||||
|
|
||||||
|
// The detail sheet should open with case information
|
||||||
|
await expect(page.getByText('Fall-ID')).toBeVisible()
|
||||||
|
await expect(page.getByRole('button', { name: /bearbeiten/i })).toBeVisible()
|
||||||
|
})
|
||||||
|
})
|
||||||
45
frontend/e2e/dashboard.spec.ts
Normal file
45
frontend/e2e/dashboard.spec.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
import { test, expect } from '@playwright/test'
|
||||||
|
|
||||||
|
test.describe('Dashboard', () => {
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
// Log in before each test
|
||||||
|
await page.goto('/login')
|
||||||
|
await page.getByLabel('E-Mail').fill('admin@dak-portal.de')
|
||||||
|
await page.getByLabel('Passwort').fill('admin123')
|
||||||
|
await page.getByRole('button', { name: 'Anmelden' }).click()
|
||||||
|
await expect(page).toHaveURL(/\/dashboard/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('KPI cards are visible after login', async ({ page }) => {
|
||||||
|
// All four KPI cards should be displayed
|
||||||
|
await expect(page.getByText('Fälle gesamt')).toBeVisible()
|
||||||
|
await expect(page.getByText('Offene ICD')).toBeVisible()
|
||||||
|
await expect(page.getByText('Offene Codierung')).toBeVisible()
|
||||||
|
await expect(page.getByText('Gutachten gesamt')).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('year selector exists and can be changed', async ({ page }) => {
|
||||||
|
// The year selector trigger should be visible
|
||||||
|
const yearSelector = page.locator('button').filter({ hasText: /^\d{4}$/ })
|
||||||
|
await expect(yearSelector).toBeVisible()
|
||||||
|
|
||||||
|
// Click it to open the dropdown
|
||||||
|
await yearSelector.click()
|
||||||
|
|
||||||
|
// Previous years should be available in the dropdown
|
||||||
|
const currentYear = new Date().getFullYear()
|
||||||
|
const previousYear = currentYear - 1
|
||||||
|
await expect(page.getByRole('option', { name: String(previousYear) })).toBeVisible()
|
||||||
|
|
||||||
|
// Select the previous year
|
||||||
|
await page.getByRole('option', { name: String(previousYear) }).click()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Fallgruppen chart section is visible', async ({ page }) => {
|
||||||
|
await expect(page.getByText('Fallgruppen')).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('weekly chart section is visible', async ({ page }) => {
|
||||||
|
await expect(page.getByText('Wöchentliche Übersicht')).toBeVisible()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -10,7 +10,8 @@
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
"test:watch": "vitest",
|
"test:watch": "vitest",
|
||||||
"test:coverage": "vitest run --coverage"
|
"test:coverage": "vitest run --coverage",
|
||||||
|
"test:e2e": "playwright test"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hookform/resolvers": "^5.2.2",
|
"@hookform/resolvers": "^5.2.2",
|
||||||
|
|
@ -35,6 +36,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.39.1",
|
"@eslint/js": "^9.39.1",
|
||||||
|
"@playwright/test": "^1.58.2",
|
||||||
"@tailwindcss/vite": "^4.2.1",
|
"@tailwindcss/vite": "^4.2.1",
|
||||||
"@testing-library/jest-dom": "^6.9.1",
|
"@testing-library/jest-dom": "^6.9.1",
|
||||||
"@testing-library/react": "^16.3.2",
|
"@testing-library/react": "^16.3.2",
|
||||||
|
|
|
||||||
17
frontend/playwright.config.ts
Normal file
17
frontend/playwright.config.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { defineConfig } from '@playwright/test'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
testDir: './e2e',
|
||||||
|
timeout: 30_000,
|
||||||
|
retries: 1,
|
||||||
|
use: {
|
||||||
|
baseURL: 'http://localhost:5173',
|
||||||
|
headless: true,
|
||||||
|
screenshot: 'only-on-failure',
|
||||||
|
},
|
||||||
|
webServer: {
|
||||||
|
command: 'pnpm dev',
|
||||||
|
port: 5173,
|
||||||
|
reuseExistingServer: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
@ -54,6 +54,9 @@ importers:
|
||||||
'@eslint/js':
|
'@eslint/js':
|
||||||
specifier: ^9.39.1
|
specifier: ^9.39.1
|
||||||
version: 9.39.3
|
version: 9.39.3
|
||||||
|
'@playwright/test':
|
||||||
|
specifier: ^1.58.2
|
||||||
|
version: 1.58.2
|
||||||
'@tailwindcss/vite':
|
'@tailwindcss/vite':
|
||||||
specifier: ^4.2.1
|
specifier: ^4.2.1
|
||||||
version: 4.2.1(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1))
|
version: 4.2.1(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1))
|
||||||
|
|
@ -680,6 +683,11 @@ packages:
|
||||||
'@open-draft/until@2.1.0':
|
'@open-draft/until@2.1.0':
|
||||||
resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==}
|
resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==}
|
||||||
|
|
||||||
|
'@playwright/test@1.58.2':
|
||||||
|
resolution: {integrity: sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
'@radix-ui/number@1.1.1':
|
'@radix-ui/number@1.1.1':
|
||||||
resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==}
|
resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==}
|
||||||
|
|
||||||
|
|
@ -2483,6 +2491,11 @@ packages:
|
||||||
resolution: {integrity: sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==}
|
resolution: {integrity: sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==}
|
||||||
engines: {node: '>=14.14'}
|
engines: {node: '>=14.14'}
|
||||||
|
|
||||||
|
fsevents@2.3.2:
|
||||||
|
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
|
||||||
|
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
fsevents@2.3.3:
|
fsevents@2.3.3:
|
||||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||||
|
|
@ -3167,6 +3180,16 @@ packages:
|
||||||
resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==}
|
resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==}
|
||||||
engines: {node: '>=16.20.0'}
|
engines: {node: '>=16.20.0'}
|
||||||
|
|
||||||
|
playwright-core@1.58.2:
|
||||||
|
resolution: {integrity: sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
playwright@1.58.2:
|
||||||
|
resolution: {integrity: sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
postcss-selector-parser@7.1.1:
|
postcss-selector-parser@7.1.1:
|
||||||
resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==}
|
resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
|
|
@ -4422,6 +4445,10 @@ snapshots:
|
||||||
|
|
||||||
'@open-draft/until@2.1.0': {}
|
'@open-draft/until@2.1.0': {}
|
||||||
|
|
||||||
|
'@playwright/test@1.58.2':
|
||||||
|
dependencies:
|
||||||
|
playwright: 1.58.2
|
||||||
|
|
||||||
'@radix-ui/number@1.1.1': {}
|
'@radix-ui/number@1.1.1': {}
|
||||||
|
|
||||||
'@radix-ui/primitive@1.1.3': {}
|
'@radix-ui/primitive@1.1.3': {}
|
||||||
|
|
@ -6287,6 +6314,9 @@ snapshots:
|
||||||
jsonfile: 6.2.0
|
jsonfile: 6.2.0
|
||||||
universalify: 2.0.1
|
universalify: 2.0.1
|
||||||
|
|
||||||
|
fsevents@2.3.2:
|
||||||
|
optional: true
|
||||||
|
|
||||||
fsevents@2.3.3:
|
fsevents@2.3.3:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
|
@ -6865,6 +6895,14 @@ snapshots:
|
||||||
|
|
||||||
pkce-challenge@5.0.1: {}
|
pkce-challenge@5.0.1: {}
|
||||||
|
|
||||||
|
playwright-core@1.58.2: {}
|
||||||
|
|
||||||
|
playwright@1.58.2:
|
||||||
|
dependencies:
|
||||||
|
playwright-core: 1.58.2
|
||||||
|
optionalDependencies:
|
||||||
|
fsevents: 2.3.2
|
||||||
|
|
||||||
postcss-selector-parser@7.1.1:
|
postcss-selector-parser@7.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
cssesc: 3.0.0
|
cssesc: 3.0.0
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue