feat: enhance Playwright configuration and CI workflow for E2E testing
Some checks failed
Deploy Development / deploy (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 5s
Test Suite / playwright-tests (push) Failing after 10s

- 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.
This commit is contained in:
Lars 2026-04-29 12:35:30 +02:00
parent 1f2c8ea0f1
commit 50b8ff12cd
5 changed files with 143 additions and 10 deletions

View File

@ -57,7 +57,6 @@ jobs:
playwright-tests: playwright-tests:
if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }} if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [lint-backend, build-frontend]
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4
@ -67,19 +66,53 @@ jobs:
with: with:
node-version: '20' 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: | run: |
npm install -D @playwright/test docker compose -f docker-compose.dev-env.yml up -d --build
npx playwright install --with-deps chromium 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: env:
TEST_EMAIL: lars@stommer.com TEST_EMAIL: lars@stommer.com
TEST_PASSWORD: 12345678 TEST_PASSWORD: 12345678
run: | 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 npx playwright test
echo "✓ Playwright tests passed" echo "✓ Playwright tests passed"
- name: Stop dev stack
if: always()
run: docker compose -f docker-compose.dev-env.yml down
- name: Upload test screenshots - name: Upload test screenshots
if: failure() if: failure()
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4

76
package-lock.json generated Normal file
View File

@ -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"
}
}
}
}

11
package.json Normal file
View File

@ -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"
}
}

View File

@ -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 = { module.exports = {
testDir: './tests', testDir: './tests',
timeout: 30000, timeout: 30000,
@ -6,7 +14,7 @@ module.exports = {
headless: true, headless: true,
viewport: { width: 390, height: 844 }, viewport: { width: 390, height: 844 },
screenshot: 'only-on-failure', screenshot: 'only-on-failure',
baseURL: 'http://192.168.2.49:3098', baseURL: rawBase.replace(/\/$/, ''),
}, },
reporter: 'list', reporter: 'list',
}; };

View File

@ -3,6 +3,11 @@ const { test, expect } = require('@playwright/test');
const TEST_EMAIL = process.env.TEST_EMAIL || 'lars@stommer.com'; const TEST_EMAIL = process.env.TEST_EMAIL || 'lars@stommer.com';
const TEST_PASSWORD = process.env.TEST_PASSWORD || '12345678'; 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) { async function login(page) {
await page.goto('/'); await page.goto('/');
await page.waitForLoadState('networkidle'); 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="email"]', TEST_EMAIL);
await page.fill('input[type="password"]', TEST_PASSWORD); await page.fill('input[type="password"]', TEST_PASSWORD);
await page.click('button:has-text("Login")'); await submitLoginForm(page);
await page.waitForLoadState('networkidle'); await page.waitForLoadState('networkidle');
} }
@ -21,10 +26,10 @@ test('1. Login funktioniert', async ({ page }) => {
await page.waitForSelector('input[type="email"]', { timeout: 10000 }); await page.waitForSelector('input[type="email"]', { timeout: 10000 });
await page.fill('input[type="email"]', TEST_EMAIL); await page.fill('input[type="email"]', TEST_EMAIL);
await page.fill('input[type="password"]', TEST_PASSWORD); await page.fill('input[type="password"]', TEST_PASSWORD);
await page.click('button:has-text("Login")'); await submitLoginForm(page);
await page.waitForLoadState('networkidle'); 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")'); const loginButton = page.locator('button:has-text("Login")');
await expect(loginButton).toHaveCount(0, { timeout: 10000 }); await expect(loginButton).toHaveCount(0, { timeout: 10000 });
@ -73,7 +78,7 @@ test('4. Navigation zu Vereine', async ({ page }) => {
console.log('✓ Vereine-Seite erreichbar'); console.log('✓ Vereine-Seite erreichbar');
}); });
test('5. Desktop-Sidebar sichtbar (Desktop)', async ({ page, context }) => { test('5. Desktop-Sidebar sichtbar (Desktop)', async ({ page }) => {
// Desktop-Viewport // Desktop-Viewport
await page.setViewportSize({ width: 1280, height: 800 }); await page.setViewportSize({ width: 1280, height: 800 });