2026-04-22 16:33:03 +02:00
|
|
|
|
const { test, expect } = require('@playwright/test');
|
|
|
|
|
|
|
|
|
|
|
|
const TEST_EMAIL = process.env.TEST_EMAIL || 'lars@stommer.com';
|
|
|
|
|
|
const TEST_PASSWORD = process.env.TEST_PASSWORD || '12345678';
|
|
|
|
|
|
|
2026-04-29 12:35:30 +02:00
|
|
|
|
/** Primärer Submit auf der Login-Seite (nicht den Tab "Login" vs. "Registrieren"). */
|
|
|
|
|
|
async function submitLoginForm(page) {
|
|
|
|
|
|
await page.getByRole('button', { name: 'Anmelden' }).click();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-22 16:33:03 +02:00
|
|
|
|
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);
|
2026-04-29 12:35:30 +02:00
|
|
|
|
await submitLoginForm(page);
|
2026-04-22 16:33:03 +02:00
|
|
|
|
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);
|
2026-04-29 12:35:30 +02:00
|
|
|
|
await submitLoginForm(page);
|
2026-04-22 16:33:03 +02:00
|
|
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
|
|
|
2026-04-29 12:35:30 +02:00
|
|
|
|
// Nach Login soll der Tab "Login" (Moduswahl) verschwinden — nicht der Submit "Anmelden"
|
2026-04-22 16:33:03 +02:00
|
|
|
|
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 });
|
|
|
|
|
|
|
2026-05-07 07:51:24 +02:00
|
|
|
|
// 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 });
|
2026-04-22 16:33:03 +02:00
|
|
|
|
|
|
|
|
|
|
await page.screenshot({ path: 'screenshots/02-dashboard.png' });
|
|
|
|
|
|
console.log('✓ Dashboard OK');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('3. Navigation zu Übungen', async ({ page }) => {
|
|
|
|
|
|
await login(page);
|
|
|
|
|
|
|
2026-04-29 13:41:17 +02:00
|
|
|
|
// Bei Viewport ≥1024px ist .bottom-nav versteckt — Mobile garantieren wie in playwright.config.js
|
|
|
|
|
|
await page.setViewportSize({ width: 390, height: 844 });
|
|
|
|
|
|
|
|
|
|
|
|
// Desktop-Sidebar enthält ebenfalls Übungen – nur Mobile-Bottom-Nav klicken (sichtbarer Link)
|
|
|
|
|
|
await page.locator('.bottom-nav a[href="/exercises"]').click();
|
2026-04-22 16:33:03 +02:00
|
|
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
|
|
|
|
|
|
|
|
// Prüfe ob Übungen-Seite geladen
|
|
|
|
|
|
await expect(page.locator('h1, h2, .page-title')).toContainText(/übungen/i, { timeout: 5000 });
|
|
|
|
|
|
|
|
|
|
|
|
await page.screenshot({ path: 'screenshots/03-uebungen.png' });
|
|
|
|
|
|
console.log('✓ Übungen-Seite erreichbar');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('4. Navigation zu Vereine', async ({ page }) => {
|
|
|
|
|
|
await login(page);
|
2026-04-29 13:41:17 +02:00
|
|
|
|
await page.setViewportSize({ width: 390, height: 844 });
|
2026-04-22 16:33:03 +02:00
|
|
|
|
|
2026-04-29 13:41:17 +02:00
|
|
|
|
await page.locator('.bottom-nav a[href="/clubs"]').click();
|
2026-04-22 16:33:03 +02:00
|
|
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
|
|
|
2026-05-05 23:03:40 +02:00
|
|
|
|
// 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,
|
|
|
|
|
|
});
|
2026-04-22 16:33:03 +02:00
|
|
|
|
|
|
|
|
|
|
await page.screenshot({ path: 'screenshots/04-vereine.png' });
|
|
|
|
|
|
console.log('✓ Vereine-Seite erreichbar');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-04-29 12:35:30 +02:00
|
|
|
|
test('5. Desktop-Sidebar sichtbar (Desktop)', async ({ page }) => {
|
2026-04-22 16:33:03 +02:00
|
|
|
|
// 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 });
|
2026-05-05 23:03:40 +02:00
|
|
|
|
await expect(
|
|
|
|
|
|
page.locator('.app-main').getByRole('heading', { level: 1, name: 'Dashboard' }),
|
|
|
|
|
|
).toBeVisible({ timeout: 10000 });
|
2026-04-22 16:33:03 +02:00
|
|
|
|
|
2026-04-29 13:41:17 +02:00
|
|
|
|
await page.reload({ waitUntil: 'domcontentloaded' });
|
2026-04-22 16:33:03 +02:00
|
|
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
|
|
|
2026-04-29 13:41:17 +02:00
|
|
|
|
// 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 });
|
2026-05-05 23:03:40 +02:00
|
|
|
|
await expect(page).not.toHaveURL(/\/login(?:\/|$|\?|#)/, { timeout: 20000 });
|
|
|
|
|
|
await expect(
|
|
|
|
|
|
page.locator('.app-main').getByRole('heading', { level: 1, name: 'Dashboard' }),
|
|
|
|
|
|
).toBeVisible({
|
|
|
|
|
|
timeout: 20000,
|
2026-04-29 13:41:17 +02:00
|
|
|
|
});
|
2026-04-22 16:33:03 +02:00
|
|
|
|
|
|
|
|
|
|
await page.screenshot({ path: 'screenshots/07-nach-reload.png' });
|
|
|
|
|
|
console.log('✓ Session bleibt nach Reload erhalten');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('8. 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);
|
|
|
|
|
|
});
|