Spaces:
Running
Running
File size: 6,737 Bytes
3b7f713 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 | /**
* 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|\/$/);
});
});
|