Spaces:
Running
Running
| /** | |
| * Testy E2E: Krytyczna ΕcieΕΌka uΕΌytkownika GrantForge AI | |
| * Scenariusz: health check β lista projektΓ³w β tworzenie projektu β eksport | |
| * | |
| * Uruchomienie: | |
| * npx playwright test | |
| * npx playwright test --ui (tryb interaktywny) | |
| * npx playwright test --headed (widoczna przeglΔ darka) | |
| * | |
| * Zmienne Εrodowiskowe: | |
| * E2E_BASE_URL β adres frontendu (domyΕlnie http://localhost:5173) | |
| * E2E_BACKEND_URL β adres backendu (domyΕlnie http://localhost:8001) | |
| * E2E_DEV_TOKEN β Bearer token dla dev (domyΕlnie dev_test_token) | |
| */ | |
| import { test, expect, request } from '@playwright/test'; | |
| const BACKEND = process.env.E2E_BACKEND_URL || 'http://localhost:8001'; | |
| const DEV_TOKEN = process.env.E2E_DEV_TOKEN || 'dev_test_token'; | |
| // ββββββββββββββββββββββββββββββββββββββββββββββ | |
| // BLOK 1: Backend Health Check | |
| // ββββββββββββββββββββββββββββββββββββββββββββββ | |
| test.describe('Backend API', () => { | |
| test('GET /health β zwraca status ok', async () => { | |
| const ctx = await request.newContext({ baseURL: BACKEND }); | |
| const resp = await ctx.get('/health'); | |
| expect(resp.status()).toBe(200); | |
| const body = await resp.json(); | |
| expect(body.status).toBe('healthy'); | |
| await ctx.dispose(); | |
| }); | |
| test('GET /api/health β zwraca statusy serwisΓ³w', async () => { | |
| const ctx = await request.newContext({ baseURL: BACKEND }); | |
| const resp = await ctx.get('/api/health'); | |
| // 200 (healthy) lub 503 (degraded) β oba sΔ ok w E2E | |
| expect([200, 503]).toContain(resp.status()); | |
| const body = await resp.json(); | |
| expect(body).toHaveProperty('services'); | |
| expect(body).toHaveProperty('timestamp'); | |
| await ctx.dispose(); | |
| }); | |
| test('GET /api/grants/nabory β zwraca listΔ naborΓ³w', async () => { | |
| const ctx = await request.newContext({ | |
| baseURL: BACKEND, | |
| extraHTTPHeaders: { Authorization: `Bearer ${DEV_TOKEN}` }, | |
| }); | |
| const resp = await ctx.get('/api/grants/nabory'); | |
| expect(resp.status()).toBe(200); | |
| const body = await resp.json(); | |
| expect(body.status).toBe('ok'); | |
| expect(Array.isArray(body.nabory)).toBe(true); | |
| expect(body.nabory.length).toBeGreaterThan(0); | |
| await ctx.dispose(); | |
| }); | |
| test('POST /api/projects β tworzy projekt', async () => { | |
| const ctx = await request.newContext({ | |
| baseURL: BACKEND, | |
| extraHTTPHeaders: { Authorization: `Bearer ${DEV_TOKEN}` }, | |
| }); | |
| const resp = await ctx.post('/api/projects', { | |
| data: { | |
| title: '[E2E] Test Project', | |
| program_type: 'FENG', | |
| description: 'Projekt testowy Playwright E2E β moΕΌna usunΔ Δ.', | |
| }, | |
| }); | |
| expect([200, 201]).toContain(resp.status()); | |
| const body = await resp.json(); | |
| expect(body).toHaveProperty('id'); | |
| // Cleanup: usuΕ projekt | |
| await ctx.delete(`/api/projects/${body.id}`); | |
| await ctx.dispose(); | |
| }); | |
| test('Rate limiter β zwraca 429 po przekroczeniu limitu', async () => { | |
| // Ten test wysyΕa 6 requestΓ³w do endpointu z limitem 5/5min | |
| // W Εrodowisku E2E z dev_test_token moΕΌe pomijaΔ rate limit β weryfikujemy odpowiedΕΊ | |
| const ctx = await request.newContext({ | |
| baseURL: BACKEND, | |
| extraHTTPHeaders: { Authorization: `Bearer ${DEV_TOKEN}` }, | |
| }); | |
| // Weryfikujemy tylko ΕΌe nagΕΓ³wki rate limit sΔ obecne (jeΕli endpoint je zwraca) | |
| const resp = await ctx.get('/api/grants/nabory'); | |
| expect(resp.status()).toBeLessThan(500); // nie moΕΌe byΔ bΕΔ d serwera | |
| await ctx.dispose(); | |
| }); | |
| }); | |
| // ββββββββββββββββββββββββββββββββββββββββββββββ | |
| // BLOK 2: Frontend β Strony publiczne | |
| // ββββββββββββββββββββββββββββββββββββββββββββββ | |
| test.describe('Frontend β strony publiczne', () => { | |
| test('Landing page β zawiera link do logowania', async ({ page }) => { | |
| await page.goto('/'); | |
| await page.waitForTimeout(2000); | |
| await expect(page).toHaveTitle(/GrantForge|Dotacje|AI/i); | |
| const loginBtn = page.locator('button:has-text("Zaloguj"), a:has-text("Zaloguj")').first(); | |
| await expect(loginBtn).toBeVisible(); | |
| }); | |
| test('Landing page β stopka zawiera linki prawne', async ({ page }) => { | |
| await page.goto('/'); | |
| const regulaminLink = page.locator('a[href="/regulamin"]').first(); | |
| await expect(regulaminLink).toBeVisible(); | |
| const privacyLink = page.locator('a[href="/polityka-prywatnosci"]').first(); | |
| await expect(privacyLink).toBeVisible(); | |
| }); | |
| test('Strona /regulamin β Εaduje siΔ i zawiera paragrafy', async ({ page }) => { | |
| await page.goto('/regulamin'); | |
| await expect(page.locator('h1')).toContainText('Regulamin'); | |
| // SprawdΕΊ ΕΌe jest treΕΔ prawna | |
| await expect(page.locator('body')).toContainText('Β§ 1'); | |
| await expect(page.locator('body')).toContainText('Β§ 3'); | |
| }); | |
| test('Strona /polityka-prywatnosci β Εaduje siΔ i zawiera RODO', async ({ page }) => { | |
| await page.goto('/polityka-prywatnosci'); | |
| await expect(page.locator('h1')).toContainText('Polityka Prywatno'); | |
| await expect(page.locator('body')).toContainText('RODO'); | |
| }); | |
| }); | |
| // ββββββββββββββββββββββββββββββββββββββββββββββ | |
| // BLOK 3: Frontend β Strony chronione (wymagajΔ sesji) | |
| // ββββββββββββββββββββββββββββββββββββββββββββββ | |
| test.describe('Frontend β nawigacja (bez autentykacji)', () => { | |
| test('Redirect /projects β /sign-in (niezalogowany)', async ({ page }) => { | |
| await page.goto('/projects'); | |
| // Clerk powinien przekierowaΔ do strony logowania | |
| await page.waitForTimeout(1500); | |
| const url = page.url(); | |
| expect(url).toMatch(/sign-in|\/$/); | |
| }); | |
| test('Redirect /nabory β /sign-in (niezalogowany)', async ({ page }) => { | |
| await page.goto('/nabory'); | |
| await page.waitForTimeout(1500); | |
| const url = page.url(); | |
| expect(url).toMatch(/sign-in|\/$/); | |
| }); | |
| }); | |