All checks were successful
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / playwright-tests (push) Successful in 1m42s
- Added a check to ensure the loading spinner is not visible before navigating to the exercises page, improving test reliability. - Updated navigation logic to wait for both the URL change and the click event on the exercises link, reducing race conditions. - Modified the assertion to check for the visibility of the main heading on the exercises page, ensuring stricter validation of page load success.
548 lines
21 KiB
JavaScript
548 lines
21 KiB
JavaScript
const { test, expect } = require('@playwright/test');
|
||
|
||
const TEST_EMAIL = process.env.TEST_EMAIL || 'lars@stommer.com';
|
||
const TEST_PASSWORD = process.env.TEST_PASSWORD || '12345678';
|
||
|
||
/** Primärer Submit auf der Login-Seite (nicht den Tab "Login" vs. "Registrieren"). */
|
||
async function submitLoginForm(page) {
|
||
await page.getByRole('button', { name: 'Anmelden' }).click();
|
||
}
|
||
|
||
async function login(page) {
|
||
await page.goto('/');
|
||
await page.waitForLoadState('networkidle');
|
||
|
||
// Warte bis Login-Seite geladen ist
|
||
await page.waitForSelector('input[type="email"]', { timeout: 10000 });
|
||
|
||
await page.fill('input[type="email"]', TEST_EMAIL);
|
||
await page.fill('input[type="password"]', TEST_PASSWORD);
|
||
await submitLoginForm(page);
|
||
// Wait until auth is complete: URL leaves /login and Dashboard is rendered
|
||
await page.waitForURL((url) => !url.toString().includes('/login'), { timeout: 15000 });
|
||
await page.waitForLoadState('networkidle');
|
||
}
|
||
|
||
test('1. Login funktioniert', async ({ page }) => {
|
||
await page.goto('/');
|
||
await page.waitForSelector('input[type="email"]', { timeout: 10000 });
|
||
await page.fill('input[type="email"]', TEST_EMAIL);
|
||
await page.fill('input[type="password"]', TEST_PASSWORD);
|
||
await submitLoginForm(page);
|
||
await page.waitForLoadState('networkidle');
|
||
|
||
// Nach Login soll der Tab "Login" (Moduswahl) verschwinden — nicht der Submit "Anmelden"
|
||
const loginButton = page.locator('button:has-text("Login")');
|
||
await expect(loginButton).toHaveCount(0, { timeout: 10000 });
|
||
|
||
await page.screenshot({ path: 'screenshots/01-nach-login.png' });
|
||
console.log('✓ Login erfolgreich');
|
||
});
|
||
|
||
test('2. Dashboard lädt ohne Fehler', async ({ page }) => {
|
||
await login(page);
|
||
|
||
// Warte bis Spinner verschwunden
|
||
await expect(page.locator('.spinner')).toHaveCount(0, { timeout: 10000 });
|
||
|
||
// Dashboard: h1 „Dashboard“ + Begrüßungstext (nicht mehr „Willkommen bei Shinkan“ als Überschrift)
|
||
const main = page.locator('.app-main');
|
||
await expect(main.getByRole('heading', { level: 1, name: 'Dashboard' })).toBeVisible({
|
||
timeout: 5000,
|
||
});
|
||
await expect(main.getByText(/Shinkan unterstützt dich/i)).toBeVisible({ timeout: 5000 });
|
||
|
||
await page.screenshot({ path: 'screenshots/02-dashboard.png' });
|
||
console.log('✓ Dashboard OK');
|
||
});
|
||
|
||
test('3. Navigation zu Übungen', async ({ page }) => {
|
||
await login(page);
|
||
|
||
await expect(page.locator('.spinner')).toHaveCount(0, { timeout: 10000 });
|
||
|
||
// Bei Viewport ≥1024px ist .bottom-nav versteckt — Mobile garantieren wie in playwright.config.js
|
||
await page.setViewportSize({ width: 390, height: 844 });
|
||
|
||
// Bottom-Nav: Navigation und URL gemeinsam abwarten (vermeidet race mit networkidle)
|
||
const exercisesLink = page.locator('.bottom-nav').getByRole('link', { name: /Übungen/i });
|
||
await Promise.all([
|
||
page.waitForURL(
|
||
(u) => {
|
||
const path = u.pathname.replace(/\/$/, '') || '/'
|
||
return path === '/exercises'
|
||
},
|
||
{ timeout: 15000 },
|
||
),
|
||
exercisesLink.click(),
|
||
]);
|
||
await page.waitForLoadState('networkidle');
|
||
|
||
// Wie Test 4 (Vereine): eine eindeutige h1 — nicht h1,h2-Kombi (Strict Mode + mehrere Treffer)
|
||
const main = page.locator('.app-main');
|
||
await expect(main.getByRole('heading', { level: 1, name: /Übungen/i })).toBeVisible({
|
||
timeout: 10000,
|
||
});
|
||
|
||
await page.screenshot({ path: 'screenshots/03-uebungen.png' });
|
||
console.log('✓ Übungen-Seite erreichbar');
|
||
});
|
||
|
||
test('4. Navigation zu Vereine', async ({ page }) => {
|
||
await login(page);
|
||
await page.setViewportSize({ width: 390, height: 844 });
|
||
|
||
await page.locator('.bottom-nav a[href="/clubs"]').click();
|
||
await page.waitForLoadState('networkidle');
|
||
|
||
// ClubsPage: <h1>Vereinsverwaltung</h1> + Tab <h2>Vereine</h2> → ein kombinierter
|
||
// Selektor löst 2 Treffer aus (Playwright strict mode). URL + primäre Überschrift reichen.
|
||
await expect(page).toHaveURL(/\/clubs\/?$/, { timeout: 5000 });
|
||
await expect(page.getByRole('heading', { level: 1, name: /Vereinsverwaltung/i })).toBeVisible({
|
||
timeout: 5000,
|
||
});
|
||
|
||
await page.screenshot({ path: 'screenshots/04-vereine.png' });
|
||
console.log('✓ Vereine-Seite erreichbar');
|
||
});
|
||
|
||
test('5. Desktop-Sidebar sichtbar (Desktop)', async ({ page }) => {
|
||
// Desktop-Viewport
|
||
await page.setViewportSize({ width: 1280, height: 800 });
|
||
|
||
await login(page);
|
||
|
||
// Prüfe ob Desktop-Sidebar existiert
|
||
const sidebar = page.locator('.desktop-sidebar');
|
||
await expect(sidebar).toBeVisible({ timeout: 5000 });
|
||
|
||
await page.screenshot({ path: 'screenshots/05-desktop-sidebar.png' });
|
||
console.log('✓ Desktop-Sidebar sichtbar');
|
||
});
|
||
|
||
test('6. Bottom-Nav sichtbar (Mobile)', async ({ page }) => {
|
||
// Mobile-Viewport
|
||
await page.setViewportSize({ width: 390, height: 844 });
|
||
|
||
await login(page);
|
||
|
||
// Prüfe ob Bottom-Nav existiert
|
||
const bottomNav = page.locator('.bottom-nav');
|
||
await expect(bottomNav).toBeVisible({ timeout: 5000 });
|
||
|
||
await page.screenshot({ path: 'screenshots/06-mobile-bottom-nav.png' });
|
||
console.log('✓ Bottom-Nav sichtbar');
|
||
});
|
||
|
||
test('7. Session-Persistenz nach Reload', async ({ page }) => {
|
||
await login(page);
|
||
|
||
await expect(page.locator('.spinner')).toHaveCount(0, { timeout: 10000 });
|
||
await expect(
|
||
page.locator('.app-main').getByRole('heading', { level: 1, name: 'Dashboard' }),
|
||
).toBeVisible({ timeout: 10000 });
|
||
|
||
await page.reload({ waitUntil: 'domcontentloaded' });
|
||
await page.waitForLoadState('networkidle');
|
||
|
||
// Auth lädt erst nach Spinner – nicht auf /login stranden (stabiler als Button „Login“-Tab auf Login-Screen)
|
||
await expect(page.locator('.spinner')).toHaveCount(0, { timeout: 20000 });
|
||
await expect(page).not.toHaveURL(/\/login(?:\/|$|\?|#)/, { timeout: 20000 });
|
||
await expect(
|
||
page.locator('.app-main').getByRole('heading', { level: 1, name: 'Dashboard' }),
|
||
).toBeVisible({
|
||
timeout: 20000,
|
||
});
|
||
|
||
await page.screenshot({ path: 'screenshots/07-nach-reload.png' });
|
||
console.log('✓ Session bleibt nach Reload erhalten');
|
||
});
|
||
|
||
/**
|
||
* Refaktor Phase 2 (Dashboard): Kurzüberblick per GET /api/dashboard/kpis; genau zwei GET /api/training-units (Übersicht).
|
||
* Production-ähnlicher Build empfohlen (kein React StrictMode-Doppel-Mount im lokalen Vite-Dev).
|
||
*/
|
||
test('8. Dashboard API-Budget nach Reload (profiles/me, training-units, dashboard/kpis)', async ({ page }) => {
|
||
await login(page);
|
||
|
||
let profilesMe = 0;
|
||
let trainingUnits = 0;
|
||
let dashboardKpis = 0;
|
||
|
||
const onRequest = (request) => {
|
||
if (request.method() !== 'GET') return;
|
||
let pathname = '';
|
||
try {
|
||
pathname = new URL(request.url()).pathname;
|
||
} catch {
|
||
return;
|
||
}
|
||
if (pathname === '/api/profiles/me') profilesMe += 1;
|
||
if (pathname === '/api/training-units') trainingUnits += 1;
|
||
if (pathname === '/api/dashboard/kpis') dashboardKpis += 1;
|
||
};
|
||
|
||
page.on('request', onRequest);
|
||
|
||
try {
|
||
await page.reload({ waitUntil: 'networkidle' });
|
||
|
||
const main = page.locator('.app-main');
|
||
await expect(main.getByRole('heading', { level: 1, name: 'Dashboard' })).toBeVisible({
|
||
timeout: 15000,
|
||
});
|
||
await expect(page.locator('.spinner')).toHaveCount(0, { timeout: 10000 });
|
||
await expect(page.getByRole('heading', { name: 'Nächste Termine' })).toBeVisible({
|
||
timeout: 20000,
|
||
});
|
||
|
||
expect(profilesMe).toBe(1);
|
||
expect(trainingUnits).toBe(2);
|
||
expect(dashboardKpis).toBe(1);
|
||
} finally {
|
||
page.off('request', onRequest);
|
||
}
|
||
|
||
console.log('✓ Dashboard API-Budget: 1× profiles/me, 2× training-units, 1× dashboard/kpis');
|
||
});
|
||
|
||
test('P-12: sessionStorage wird bei Logout bereinigt (sj_coach_* Schlüssel)', async ({ page }) => {
|
||
await page.setViewportSize({ width: 1280, height: 800 });
|
||
await login(page);
|
||
await page.waitForLoadState('networkidle');
|
||
|
||
// Coach-Sitzungsdaten simulieren (wie sie TrainingCoachPage schreibt)
|
||
await page.evaluate(() => {
|
||
sessionStorage.setItem('sj_coach_step_42', '3');
|
||
sessionStorage.setItem('sj_coach_deltas_42', '[{"id":1}]');
|
||
sessionStorage.setItem('sj_coach_debrief_42', '0');
|
||
sessionStorage.setItem('fremd_anderer_key', 'muss_erhalten_bleiben');
|
||
});
|
||
|
||
const vorLogout = await page.evaluate(() => ({
|
||
step: sessionStorage.getItem('sj_coach_step_42'),
|
||
fremd: sessionStorage.getItem('fremd_anderer_key'),
|
||
}));
|
||
expect(vorLogout.step).toBe('3');
|
||
expect(vorLogout.fremd).toBe('muss_erhalten_bleiben');
|
||
|
||
// App.jsx DesktopSidebar-Logout zeigt confirm() — in Playwright headless akzeptieren
|
||
page.once('dialog', dialog => dialog.accept());
|
||
await page.getByRole('button', { name: 'Abmelden' }).click();
|
||
await page.waitForLoadState('networkidle');
|
||
|
||
const nachLogout = await page.evaluate(() => ({
|
||
step: sessionStorage.getItem('sj_coach_step_42'),
|
||
deltas: sessionStorage.getItem('sj_coach_deltas_42'),
|
||
debrief: sessionStorage.getItem('sj_coach_debrief_42'),
|
||
fremd: sessionStorage.getItem('fremd_anderer_key'),
|
||
token: localStorage.getItem('authToken'),
|
||
}));
|
||
|
||
expect(nachLogout.step).toBeNull();
|
||
expect(nachLogout.deltas).toBeNull();
|
||
expect(nachLogout.debrief).toBeNull();
|
||
expect(nachLogout.fremd).toBe('muss_erhalten_bleiben');
|
||
expect(nachLogout.token).toBeNull();
|
||
|
||
await page.screenshot({ path: 'screenshots/p12-session-storage-nach-logout.png' });
|
||
console.log('✓ P-12: sj_coach_* entfernt, Fremd-Key erhalten, authToken entfernt');
|
||
});
|
||
|
||
// P-01: Rechtstextseiten – öffentliche Routen ohne Auth
|
||
|
||
const LEGAL_ROUTES = [
|
||
{ path: '/impressum', label: 'Impressum' },
|
||
{ path: '/datenschutz', label: 'Datenschutz' },
|
||
{ path: '/nutzungsbedingungen', label: 'Nutzungsbedingungen' },
|
||
{ path: '/medienrichtlinie', label: 'Medienrichtlinie' },
|
||
];
|
||
|
||
for (const route of LEGAL_ROUTES) {
|
||
test(`P-01: ${route.label} ohne Auth erreichbar und enthält Platzhalterhinweis`, async ({ page }) => {
|
||
// Direkt aufrufen ohne Login
|
||
await page.goto(route.path);
|
||
await page.waitForLoadState('networkidle');
|
||
|
||
// Seite ist erreichbar (kein Redirect zur Login-Seite)
|
||
expect(page.url()).toContain(route.path);
|
||
|
||
// Seitentitel korrekt
|
||
await expect(page.getByRole('heading', { level: 1 })).toContainText(route.label);
|
||
|
||
// Platzhalterhinweis sichtbar – nur wenn noch kein echtes Dokument veröffentlicht wurde
|
||
const hasPlaceholder = await page.getByText('MUSTER / PLATZHALTER').first().isVisible().catch(() => false);
|
||
if (hasPlaceholder) {
|
||
console.log(`✓ P-01: ${route.label} – Platzhalter sichtbar`);
|
||
} else {
|
||
console.log(`✓ P-01: ${route.label} – echtes Dokument veröffentlicht (kein Platzhalter)`);
|
||
}
|
||
|
||
// Reload funktioniert
|
||
await page.reload();
|
||
await page.waitForLoadState('networkidle');
|
||
expect(page.url()).toContain(route.path);
|
||
|
||
console.log(`✓ P-01: ${route.label} – ohne Auth erreichbar, Reload OK`);
|
||
});
|
||
}
|
||
|
||
test('P-01: Login-Seite enthält Links zu allen vier Rechtstextseiten', async ({ page }) => {
|
||
await page.goto('/login');
|
||
await page.waitForLoadState('networkidle');
|
||
|
||
for (const route of LEGAL_ROUTES) {
|
||
const link = page.locator(`a[href="${route.path}"]`);
|
||
await expect(link).toBeVisible();
|
||
}
|
||
|
||
console.log('✓ P-01: Login-Seite – alle vier Rechtstext-Links vorhanden');
|
||
});
|
||
|
||
// P-01b: Rechtliches über Einstellungen (Mobile/PWA-Erreichbarkeit)
|
||
|
||
async function gotoAuthenticated(page, path) {
|
||
await page.goto(path);
|
||
await page.waitForLoadState('networkidle');
|
||
// After full-page reload the app re-checks auth from localStorage; wait for spinner to clear
|
||
await expect(page.locator('.spinner')).toHaveCount(0, { timeout: 15000 });
|
||
}
|
||
|
||
test('P-01b: Einstellungen enthält Link zu Rechtliches', async ({ page }) => {
|
||
await login(page);
|
||
await expect(page.locator('.spinner')).toHaveCount(0, { timeout: 10000 });
|
||
|
||
await gotoAuthenticated(page, '/settings');
|
||
await expect(page.locator('.app-main').getByRole('heading', { level: 1, name: 'Einstellungen' })).toBeVisible({ timeout: 8000 });
|
||
|
||
const link = page.locator('.app-main a[href="/settings/legal"]');
|
||
await expect(link).toBeVisible();
|
||
|
||
console.log('✓ P-01b: Einstellungen enthält Link zu /settings/legal');
|
||
});
|
||
|
||
test('P-01b: /settings/legal enthält Links zu allen vier Rechtstextseiten', async ({ page }) => {
|
||
await login(page);
|
||
await expect(page.locator('.spinner')).toHaveCount(0, { timeout: 10000 });
|
||
|
||
await gotoAuthenticated(page, '/settings/legal');
|
||
await expect(page.locator('.app-main').getByRole('heading', { level: 1, name: 'Rechtliches' })).toBeVisible({ timeout: 8000 });
|
||
|
||
for (const route of LEGAL_ROUTES) {
|
||
const link = page.locator(`.app-main a[href="${route.path}"]`);
|
||
await expect(link).toBeVisible();
|
||
}
|
||
|
||
console.log('✓ P-01b: /settings/legal – Überschrift + alle vier Rechtstext-Links vorhanden');
|
||
});
|
||
|
||
test('P-01b: Jeder Rechtstext-Link aus /settings/legal führt zur korrekten Route', async ({ page }) => {
|
||
await login(page);
|
||
await expect(page.locator('.spinner')).toHaveCount(0, { timeout: 10000 });
|
||
|
||
for (const route of LEGAL_ROUTES) {
|
||
await gotoAuthenticated(page, '/settings/legal');
|
||
await expect(page.locator('.app-main').getByRole('heading', { level: 1, name: 'Rechtliches' })).toBeVisible({ timeout: 8000 });
|
||
|
||
await page.locator(`.app-main a[href="${route.path}"]`).click();
|
||
await page.waitForLoadState('networkidle');
|
||
|
||
expect(page.url()).toContain(route.path);
|
||
await expect(page.getByRole('heading', { level: 1 })).toContainText(route.label);
|
||
|
||
console.log(`✓ P-01b: ${route.label} – Link aus /settings/legal korrekt`);
|
||
}
|
||
});
|
||
|
||
// P-01c: Admin-konfigurierbare Rechtstexte
|
||
|
||
test('P-01c: Rechtstextseiten zeigen Platzhalter-Banner wenn kein published-Dokument', async ({ page }) => {
|
||
// Keine Auth — public route
|
||
await page.goto('/impressum');
|
||
await page.waitForLoadState('networkidle');
|
||
|
||
// Seite erreichbar (kein Redirect zu /login)
|
||
expect(page.url()).toContain('/impressum');
|
||
|
||
// Spinner weg, dann entweder Platzhalter-Banner oder API-Inhalt (beides OK)
|
||
await expect(page.locator('.spinner')).toHaveCount(0, { timeout: 10000 });
|
||
await expect(page.getByRole('heading', { level: 1 })).toBeVisible({ timeout: 5000 });
|
||
|
||
console.log('✓ P-01c: /impressum lädt ohne Fehler (API-fetch mit Fallback)');
|
||
});
|
||
|
||
test('P-01c: /admin/legal-documents erreichbar für Superadmin', async ({ page }) => {
|
||
await page.setViewportSize({ width: 1280, height: 800 });
|
||
await login(page);
|
||
await expect(page.locator('.spinner')).toHaveCount(0, { timeout: 10000 });
|
||
|
||
await gotoAuthenticated(page, '/admin/legal-documents');
|
||
// Superadmin sieht die Seite; normaler Admin landet auf 403 (PlatformAdminRoute)
|
||
// Test läuft mit Superadmin-Account (TEST_EMAIL), also Seite sichtbar
|
||
await expect(page.locator('.app-main').getByRole('heading', { level: 1, name: 'Rechtstexte verwalten' })).toBeVisible({ timeout: 8000 });
|
||
|
||
await page.screenshot({ path: 'screenshots/p01c-admin-legal-documents.png' });
|
||
console.log('✓ P-01c: /admin/legal-documents erreichbar, Überschrift sichtbar');
|
||
});
|
||
|
||
test('P-01c: Admin-Nav enthält Link zu Rechtstexten', async ({ page }) => {
|
||
await page.setViewportSize({ width: 1280, height: 800 });
|
||
await login(page);
|
||
await expect(page.locator('.spinner')).toHaveCount(0, { timeout: 10000 });
|
||
|
||
await gotoAuthenticated(page, '/admin/hierarchy');
|
||
await expect(page.locator('.spinner')).toHaveCount(0, { timeout: 10000 });
|
||
|
||
const link = page.locator('a[href="/admin/legal-documents"]');
|
||
await expect(link).toBeVisible({ timeout: 5000 });
|
||
|
||
console.log('✓ P-01c: Admin-Nav enthält Link /admin/legal-documents');
|
||
});
|
||
|
||
// ── P-06: Upload-Einwilligungsdialog ────────────────────────────────────────
|
||
|
||
test('P-06a: Medienbibliothek lädt ohne Fehler', async ({ page }) => {
|
||
await login(page);
|
||
await expect(page.locator('.spinner')).toHaveCount(0, { timeout: 10000 });
|
||
|
||
await gotoAuthenticated(page, '/media');
|
||
await expect(page.locator('h1')).toContainText('Medienbibliothek', { timeout: 8000 });
|
||
|
||
await page.screenshot({ path: 'screenshots/p06a-media-library.png' });
|
||
console.log('✓ P-06a: Medienbibliothek erreichbar');
|
||
});
|
||
|
||
test('P-06b: Rechte-Dialog erscheint bei Dateiauswahl', async ({ page }) => {
|
||
await login(page);
|
||
await expect(page.locator('.spinner')).toHaveCount(0, { timeout: 10000 });
|
||
|
||
await gotoAuthenticated(page, '/media');
|
||
await expect(page.locator('h1')).toContainText('Medienbibliothek', { timeout: 8000 });
|
||
|
||
// Datei-Upload simulieren (ohne echte Datei – Datei-Input finden und Datei setzen)
|
||
const fileInput = page.locator('input[type="file"]').first();
|
||
await fileInput.setInputFiles({
|
||
name: 'test.png',
|
||
mimeType: 'image/png',
|
||
buffer: Buffer.from('PNG-Testinhalt'),
|
||
});
|
||
|
||
// Rechte-Dialog muss erscheinen
|
||
await expect(page.locator('[role="dialog"]')).toBeVisible({ timeout: 5000 });
|
||
await expect(page.locator('[role="dialog"]')).toContainText('Rechte-Erklärung');
|
||
await expect(page.locator('[role="dialog"]')).toContainText('VORLÄUFIG');
|
||
|
||
await page.screenshot({ path: 'screenshots/p06b-rights-dialog.png' });
|
||
console.log('✓ P-06b: Rechte-Dialog erscheint bei Dateiauswahl');
|
||
});
|
||
|
||
test('P-06c: Dialog-Abbrechen bricht Upload ab', async ({ page }) => {
|
||
await login(page);
|
||
await expect(page.locator('.spinner')).toHaveCount(0, { timeout: 10000 });
|
||
|
||
await gotoAuthenticated(page, '/media');
|
||
await expect(page.locator('h1')).toContainText('Medienbibliothek', { timeout: 8000 });
|
||
|
||
const fileInput = page.locator('input[type="file"]').first();
|
||
await fileInput.setInputFiles({
|
||
name: 'test.png',
|
||
mimeType: 'image/png',
|
||
buffer: Buffer.from('PNG-Testinhalt'),
|
||
});
|
||
|
||
await expect(page.locator('[role="dialog"]')).toBeVisible({ timeout: 5000 });
|
||
await page.locator('[role="dialog"] button:has-text("Abbrechen")').click();
|
||
|
||
// Dialog geschlossen, kein Upload-Fortschrittsbalken
|
||
await expect(page.locator('[role="dialog"]')).toHaveCount(0, { timeout: 3000 });
|
||
|
||
await page.screenshot({ path: 'screenshots/p06c-dialog-cancel.png' });
|
||
console.log('✓ P-06c: Dialog-Abbrechen schließt Dialog ohne Upload');
|
||
});
|
||
|
||
test('P-06d: Dialog-Bestätigung ohne Pflichtfelder zeigt Fehler', async ({ page }) => {
|
||
await login(page);
|
||
await expect(page.locator('.spinner')).toHaveCount(0, { timeout: 10000 });
|
||
|
||
await gotoAuthenticated(page, '/media');
|
||
await expect(page.locator('h1')).toContainText('Medienbibliothek', { timeout: 8000 });
|
||
|
||
const fileInput = page.locator('input[type="file"]').first();
|
||
await fileInput.setInputFiles({
|
||
name: 'test.png',
|
||
mimeType: 'image/png',
|
||
buffer: Buffer.from('PNG-Testinhalt'),
|
||
});
|
||
|
||
await expect(page.locator('[role="dialog"]')).toBeVisible({ timeout: 5000 });
|
||
// Ohne Felder ausfüllen direkt bestätigen
|
||
await page.locator('[role="dialog"] button:has-text("Bestätigen")').click();
|
||
|
||
// Fehlermeldung im Dialog
|
||
const dialog = page.locator('[role="dialog"]');
|
||
await expect(dialog).toBeVisible({ timeout: 2000 });
|
||
// Fehlermeldung muss sichtbar sein
|
||
await expect(dialog.locator('p[style*="danger"], p[style*="color: var(--danger)"]')).toBeVisible({ timeout: 2000 });
|
||
|
||
await page.screenshot({ path: 'screenshots/p06d-dialog-validation.png' });
|
||
console.log('✓ P-06d: Dialog zeigt Fehler ohne Pflichtfelder');
|
||
});
|
||
|
||
test('P-06e: API-Endpoint /api/admin/media-rights/legacy-summary erreichbar (Superadmin)', async ({ request }) => {
|
||
// Superadmin-Login via API
|
||
const loginRes = await request.post('/api/auth/login', {
|
||
data: { email: TEST_EMAIL, password: TEST_PASSWORD },
|
||
});
|
||
if (!loginRes.ok()) {
|
||
console.log('⚠ P-06e: Login fehlgeschlagen – Test übersprungen');
|
||
return;
|
||
}
|
||
const loginData = await loginRes.json();
|
||
const token = loginData.token;
|
||
if (!token) {
|
||
console.log('⚠ P-06e: Kein Token – Test übersprungen');
|
||
return;
|
||
}
|
||
|
||
const res = await request.get('/api/admin/media-rights/legacy-summary', {
|
||
headers: { 'X-Auth-Token': token },
|
||
});
|
||
|
||
// Endpoint existiert (200 oder 403 wenn kein Superadmin — aber nicht 404/500)
|
||
expect([200, 403]).toContain(res.status());
|
||
if (res.status() === 200) {
|
||
const data = await res.json();
|
||
expect(data).toHaveProperty('total_active_assets');
|
||
expect(data).toHaveProperty('legacy_unreviewed');
|
||
console.log(`✓ P-06e: Legacy-Summary: ${data.legacy_unreviewed} von ${data.total_active_assets} Medien`);
|
||
} else {
|
||
console.log('✓ P-06e: Endpoint existiert (403 erwartet für Nicht-Superadmin)');
|
||
}
|
||
});
|
||
|
||
test('9. Keine kritischen Console-Fehler', async ({ page }) => {
|
||
const errors = [];
|
||
page.on('console', msg => {
|
||
if (msg.type() === 'error') errors.push(msg.text());
|
||
});
|
||
|
||
await login(page);
|
||
await page.waitForLoadState('networkidle');
|
||
|
||
// Filtere unkritische Fehler
|
||
const kritisch = errors.filter(e =>
|
||
!e.includes('favicon') &&
|
||
!e.includes('sourceMap') &&
|
||
!e.includes('404') &&
|
||
!e.includes('vite.svg')
|
||
);
|
||
|
||
if (kritisch.length > 0) {
|
||
console.log('⚠ Console-Fehler:', kritisch.join(', '));
|
||
} else {
|
||
console.log('✓ Keine kritischen Console-Fehler');
|
||
}
|
||
|
||
expect(kritisch.length).toBe(0);
|
||
});
|