feat: Add comprehensive test suite with Playwright
Test Suite: - playwright.config.js: Config for dev environment (http://192.168.2.49:3098) - tests/dev-smoke-test.spec.js: 8 tests covering: * Login functionality * Dashboard loading * Navigation to Exercises/Clubs * Desktop-Sidebar visibility (≥1024px) * Bottom-Nav visibility (Mobile) * Session persistence after reload * Console error checking Gitea CI/CD: - .gitea/workflows/test.yml: Automated testing after deploy * Backend syntax check * Frontend build test * Playwright E2E tests * Screenshot upload on failure Test Coverage: - Auth flow (login, session persistence) - Responsive navigation (mobile + desktop) - Page navigation (dashboard, exercises, clubs) - Error detection (console errors) Next: Run tests locally, then implement Exercise CRUD
This commit is contained in:
parent
46a90ae910
commit
c44bbefc5e
89
.gitea/workflows/test.yml
Normal file
89
.gitea/workflows/test.yml
Normal file
|
|
@ -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
|
||||||
12
playwright.config.js
Normal file
12
playwright.config.js
Normal file
|
|
@ -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',
|
||||||
|
};
|
||||||
146
tests/dev-smoke-test.spec.js
Normal file
146
tests/dev-smoke-test.spec.js
Normal file
|
|
@ -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);
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user