diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml new file mode 100644 index 0000000..67f529f --- /dev/null +++ b/.gitea/workflows/test.yml @@ -0,0 +1,89 @@ +name: Test Suite + +on: + push: + branches: [main, develop] + workflow_run: + workflows: ["Deploy Development", "Deploy Production"] + types: [completed] + +jobs: + lint-backend: + if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }} + runs-on: ubuntu-latest + steps: + - name: Check backend syntax + run: | + EVENT_NAME="${{ github.event_name }}" + REF_NAME="${{ github.ref_name }}" + RUN_WORKFLOW="${{ github.event.workflow_run.name }}" + APP_DIR="/home/lars/docker/shinkan" + + if [ "$EVENT_NAME" = "workflow_run" ]; then + if [ "$RUN_WORKFLOW" = "Deploy Development" ]; then + APP_DIR="/home/lars/docker/shinkan-dev" + fi + elif [ "$REF_NAME" = "develop" ]; then + APP_DIR="/home/lars/docker/shinkan-dev" + fi + + python3 -m py_compile "$APP_DIR/backend/main.py" + echo "✓ Backend syntax OK" + + build-frontend: + if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }} + runs-on: ubuntu-latest + steps: + - name: Build frontend + run: | + EVENT_NAME="${{ github.event_name }}" + REF_NAME="${{ github.ref_name }}" + RUN_WORKFLOW="${{ github.event.workflow_run.name }}" + APP_DIR="/home/lars/docker/shinkan" + + if [ "$EVENT_NAME" = "workflow_run" ]; then + if [ "$RUN_WORKFLOW" = "Deploy Development" ]; then + APP_DIR="/home/lars/docker/shinkan-dev" + fi + elif [ "$REF_NAME" = "develop" ]; then + APP_DIR="/home/lars/docker/shinkan-dev" + fi + + cd "$APP_DIR/frontend" + npm install + npm run build + echo "✓ Frontend build OK" + + playwright-tests: + if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }} + runs-on: ubuntu-latest + needs: [lint-backend, build-frontend] + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install Playwright + run: | + npm install -D @playwright/test + npx playwright install chromium + + - name: Run Playwright tests + env: + TEST_EMAIL: lars@stommer.com + TEST_PASSWORD: 12345678 + run: | + npx playwright test + echo "✓ Playwright tests passed" + + - name: Upload test screenshots + if: failure() + uses: actions/upload-artifact@v4 + with: + name: playwright-screenshots + path: screenshots/ + retention-days: 7 diff --git a/playwright.config.js b/playwright.config.js new file mode 100644 index 0000000..ed2c6ed --- /dev/null +++ b/playwright.config.js @@ -0,0 +1,12 @@ +module.exports = { + testDir: './tests', + timeout: 30000, + use: { + channel: 'chrome', + headless: true, + viewport: { width: 390, height: 844 }, + screenshot: 'only-on-failure', + baseURL: 'http://192.168.2.49:3098', + }, + reporter: 'list', +}; diff --git a/tests/dev-smoke-test.spec.js b/tests/dev-smoke-test.spec.js new file mode 100644 index 0000000..c5f5cf5 --- /dev/null +++ b/tests/dev-smoke-test.spec.js @@ -0,0 +1,146 @@ +const { test, expect } = require('@playwright/test'); + +const TEST_EMAIL = process.env.TEST_EMAIL || 'lars@stommer.com'; +const TEST_PASSWORD = process.env.TEST_PASSWORD || '12345678'; + +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 page.click('button:has-text("Login")'); + 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 page.click('button:has-text("Login")'); + await page.waitForLoadState('networkidle'); + + // Nach Login sollte Login-Button verschwinden + 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 }); + + // Prüfe ob Dashboard-Inhalt da ist + await expect(page.locator('text=Willkommen')).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); + + // Bottom-Nav: Übungen klicken + await page.click('text=Übungen'); + 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); + + // Bottom-Nav: Vereine klicken + await page.click('text=Vereine'); + await page.waitForLoadState('networkidle'); + + // Prüfe ob Vereine-Seite geladen + await expect(page.locator('h1, h2, .page-title')).toContainText(/vereine|clubs/i, { timeout: 5000 }); + + await page.screenshot({ path: 'screenshots/04-vereine.png' }); + console.log('✓ Vereine-Seite erreichbar'); +}); + +test('5. Desktop-Sidebar sichtbar (Desktop)', async ({ page, context }) => { + // 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); + + // Warte auf Dashboard + await expect(page.locator('.spinner')).toHaveCount(0, { timeout: 10000 }); + + // Reload + await page.reload(); + await page.waitForLoadState('networkidle'); + + // Sollte NICHT zur Login-Seite redirecten + const loginButton = page.locator('button:has-text("Login")'); + await expect(loginButton).toHaveCount(0, { timeout: 5000 }); + + 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); +});