From 50b8ff12cdf509dee30be9e907d1406d1369cded Mon Sep 17 00:00:00 2001 From: Lars Date: Wed, 29 Apr 2026 12:35:30 +0200 Subject: [PATCH] feat: enhance Playwright configuration and CI workflow for E2E testing - Updated playwright.config.js to allow dynamic base URL configuration via environment variables, improving flexibility for different environments. - Modified .gitea/workflows/test.yml to include steps for starting a development stack, seeding test data, and stopping the stack after tests, enhancing the CI process for end-to-end testing. - Refactored login test in dev-smoke-test.spec.js to use a dedicated function for submitting the login form, improving code clarity and maintainability. --- .gitea/workflows/test.yml | 43 +++++++++++++++++--- package-lock.json | 76 ++++++++++++++++++++++++++++++++++++ package.json | 11 ++++++ playwright.config.js | 10 ++++- tests/dev-smoke-test.spec.js | 13 ++++-- 5 files changed, 143 insertions(+), 10 deletions(-) create mode 100644 package-lock.json create mode 100644 package.json diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml index f9296ba..b097b06 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -57,7 +57,6 @@ jobs: 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 @@ -67,19 +66,53 @@ jobs: with: node-version: '20' - - name: Install Playwright + - name: Start dev stack for E2E + env: + DEV_ALLOWED_ORIGINS: http://127.0.0.1:3098,http://localhost:3098,http://host.docker.internal:3098,https://dev.shinkan.jinkendo.de run: | - npm install -D @playwright/test - npx playwright install --with-deps chromium + docker compose -f docker-compose.dev-env.yml up -d --build + echo "Warte auf Frontend + API (/health) …" + for i in $(seq 1 90); do + if curl -sf http://127.0.0.1:3098/health >/dev/null 2>&1; then + echo "Health OK (Versuch $i)" + exit 0 + fi + sleep 2 + done + echo "Timeout: /health nicht erreichbar" + curl -v http://127.0.0.1:3098/health || true + docker compose -f docker-compose.dev-env.yml logs --tail=120 + exit 1 - - name: Run Playwright tests + - name: Seed E2E test user (erste Registrierung in frischer DB) env: TEST_EMAIL: lars@stommer.com TEST_PASSWORD: 12345678 run: | + curl -sf -X POST "http://127.0.0.1:3098/api/auth/register" \ + -H "Content-Type: application/json" \ + -d "{\"email\":\"${TEST_EMAIL}\",\"password\":\"${TEST_PASSWORD}\",\"name\":\"Playwright CI\"}" \ + || echo "(Registrierung übersprungen — Nutzer existiert evtl. schon)" + + - name: Install Playwright + run: | + npm ci || npm install + npx playwright install --with-deps chromium + + - name: Run Playwright tests + env: + PLAYWRIGHT_BASE_URL: http://127.0.0.1:3098 + TEST_EMAIL: lars@stommer.com + TEST_PASSWORD: 12345678 + run: | + mkdir -p screenshots npx playwright test echo "✓ Playwright tests passed" + - name: Stop dev stack + if: always() + run: docker compose -f docker-compose.dev-env.yml down + - name: Upload test screenshots if: failure() uses: actions/upload-artifact@v4 diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..39dc8d1 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,76 @@ +{ + "name": "shinkan-jinkendo", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "shinkan-jinkendo", + "devDependencies": { + "@playwright/test": "^1.49.0" + } + }, + "node_modules/@playwright/test": { + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.59.1.tgz", + "integrity": "sha512-PG6q63nQg5c9rIi4/Z5lR5IVF7yU5MqmKaPOe0HSc0O2cX1fPi96sUQu5j7eo4gKCkB2AnNGoWt7y4/Xx3Kcqg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.59.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/playwright": { + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.1.tgz", + "integrity": "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.59.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.1.tgz", + "integrity": "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..fc9d67e --- /dev/null +++ b/package.json @@ -0,0 +1,11 @@ +{ + "name": "shinkan-jinkendo", + "private": true, + "description": "Workspace-Metadaten für E2E (Playwright). Frontend: frontend/", + "scripts": { + "test:e2e": "playwright test" + }, + "devDependencies": { + "@playwright/test": "^1.49.0" + } +} diff --git a/playwright.config.js b/playwright.config.js index 5c6ea5a..4a6cb8a 100644 --- a/playwright.config.js +++ b/playwright.config.js @@ -1,3 +1,11 @@ +// CI: PLAYWRIGHT_BASE_URL=http://127.0.0.1:3098 nach docker compose dev-env +// Lokal gegen LAN/Dev: export PLAYWRIGHT_BASE_URL=http://192.168.x.x:3098 + +const rawBase = + process.env.PLAYWRIGHT_BASE_URL || + process.env.BASE_URL || + 'http://127.0.0.1:3098'; + module.exports = { testDir: './tests', timeout: 30000, @@ -6,7 +14,7 @@ module.exports = { headless: true, viewport: { width: 390, height: 844 }, screenshot: 'only-on-failure', - baseURL: 'http://192.168.2.49:3098', + baseURL: rawBase.replace(/\/$/, ''), }, reporter: 'list', }; diff --git a/tests/dev-smoke-test.spec.js b/tests/dev-smoke-test.spec.js index c5f5cf5..cc00a75 100644 --- a/tests/dev-smoke-test.spec.js +++ b/tests/dev-smoke-test.spec.js @@ -3,6 +3,11 @@ 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'); @@ -12,7 +17,7 @@ async function login(page) { await page.fill('input[type="email"]', TEST_EMAIL); await page.fill('input[type="password"]', TEST_PASSWORD); - await page.click('button:has-text("Login")'); + await submitLoginForm(page); await page.waitForLoadState('networkidle'); } @@ -21,10 +26,10 @@ test('1. Login funktioniert', async ({ page }) => { 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 submitLoginForm(page); await page.waitForLoadState('networkidle'); - // Nach Login sollte Login-Button verschwinden + // 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 }); @@ -73,7 +78,7 @@ test('4. Navigation zu Vereine', async ({ page }) => { console.log('✓ Vereine-Seite erreichbar'); }); -test('5. Desktop-Sidebar sichtbar (Desktop)', async ({ page, context }) => { +test('5. Desktop-Sidebar sichtbar (Desktop)', async ({ page }) => { // Desktop-Viewport await page.setViewportSize({ width: 1280, height: 800 });