324 lines
10 KiB
TypeScript
324 lines
10 KiB
TypeScript
|
|
import { test, expect, Page } from '@playwright/test';
|
||
|
|
|
||
|
|
const BASE_URL = process.env.VITE_API_URL || 'http://localhost';
|
||
|
|
const API_URL = process.env.VITE_API_URL || 'http://localhost:8080/api/v1';
|
||
|
|
|
||
|
|
interface TestResult {
|
||
|
|
test: string;
|
||
|
|
status: 'pass' | 'fail' | 'skip';
|
||
|
|
error?: string;
|
||
|
|
details?: any;
|
||
|
|
}
|
||
|
|
|
||
|
|
const results: TestResult[] = [];
|
||
|
|
|
||
|
|
// Helper pour capturer les erreurs console
|
||
|
|
async function captureConsoleErrors(page: Page): Promise<string[]> {
|
||
|
|
const errors: string[] = [];
|
||
|
|
page.on('console', msg => {
|
||
|
|
if (msg.type() === 'error') {
|
||
|
|
errors.push(msg.text());
|
||
|
|
}
|
||
|
|
});
|
||
|
|
return errors;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Helper pour capturer les erreurs réseau
|
||
|
|
async function captureNetworkErrors(page: Page): Promise<any[]> {
|
||
|
|
const networkErrors: any[] = [];
|
||
|
|
page.on('response', response => {
|
||
|
|
if (response.status() >= 400) {
|
||
|
|
networkErrors.push({
|
||
|
|
url: response.url(),
|
||
|
|
status: response.status(),
|
||
|
|
statusText: response.statusText(),
|
||
|
|
});
|
||
|
|
}
|
||
|
|
});
|
||
|
|
return networkErrors;
|
||
|
|
}
|
||
|
|
|
||
|
|
test.describe('QA E2E Audit - Veza Frontend', () => {
|
||
|
|
let page: Page;
|
||
|
|
let consoleErrors: string[] = [];
|
||
|
|
let networkErrors: any[] = [];
|
||
|
|
const testUser = {
|
||
|
|
email: `test.veza.qa+${Date.now()}@example.com`,
|
||
|
|
password: 'Test1234!@#',
|
||
|
|
username: `qa_test_user_${Date.now()}`,
|
||
|
|
};
|
||
|
|
|
||
|
|
test.beforeEach(async ({ page: testPage }) => {
|
||
|
|
page = testPage;
|
||
|
|
consoleErrors = [];
|
||
|
|
networkErrors = [];
|
||
|
|
|
||
|
|
// Capturer les erreurs
|
||
|
|
page.on('console', msg => {
|
||
|
|
if (msg.type() === 'error') {
|
||
|
|
consoleErrors.push(msg.text());
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
page.on('response', response => {
|
||
|
|
if (response.status() >= 400) {
|
||
|
|
networkErrors.push({
|
||
|
|
url: response.url(),
|
||
|
|
status: response.status(),
|
||
|
|
statusText: response.statusText(),
|
||
|
|
});
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
test('1. Health Check - Backend API', async () => {
|
||
|
|
const response = await page.request.get(`${API_URL}/health`);
|
||
|
|
expect(response.status()).toBe(200);
|
||
|
|
results.push({
|
||
|
|
test: 'Backend API Health',
|
||
|
|
status: response.status() === 200 ? 'pass' : 'fail',
|
||
|
|
details: await response.json(),
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
test('2.1. Register - Test complet', async () => {
|
||
|
|
await page.goto(`${BASE_URL}/register`);
|
||
|
|
await page.waitForLoadState('networkidle');
|
||
|
|
|
||
|
|
// Test 1: Inscription avec données valides
|
||
|
|
await page.fill('input[name="email"]', testUser.email);
|
||
|
|
await page.fill('input[name="username"]', testUser.username);
|
||
|
|
await page.fill('input[name="password"]', testUser.password);
|
||
|
|
await page.fill('input[name="confirmPassword"]', testUser.password);
|
||
|
|
|
||
|
|
// Accepter les termes si checkbox existe
|
||
|
|
const termsCheckbox = page.locator('input[type="checkbox"]').first();
|
||
|
|
if (await termsCheckbox.isVisible()) {
|
||
|
|
await termsCheckbox.check();
|
||
|
|
}
|
||
|
|
|
||
|
|
// Capturer la réponse avant soumission
|
||
|
|
const responsePromise = page.waitForResponse(
|
||
|
|
response => response.url().includes('/auth/register') && response.request().method() === 'POST'
|
||
|
|
);
|
||
|
|
|
||
|
|
await page.click('button[type="submit"]');
|
||
|
|
|
||
|
|
try {
|
||
|
|
const response = await responsePromise;
|
||
|
|
const status = response.status();
|
||
|
|
const body = await response.json().catch(() => ({}));
|
||
|
|
|
||
|
|
results.push({
|
||
|
|
test: 'Register - Valid data',
|
||
|
|
status: status === 200 || status === 201 ? 'pass' : 'fail',
|
||
|
|
error: status >= 400 ? `Status ${status}: ${JSON.stringify(body)}` : undefined,
|
||
|
|
details: { status, body, consoleErrors: [...consoleErrors], networkErrors: [...networkErrors] },
|
||
|
|
});
|
||
|
|
|
||
|
|
if (status === 200 || status === 201) {
|
||
|
|
// Vérifier redirection vers dashboard
|
||
|
|
await page.waitForURL('**/dashboard', { timeout: 5000 }).catch(() => {});
|
||
|
|
}
|
||
|
|
} catch (error: any) {
|
||
|
|
results.push({
|
||
|
|
test: 'Register - Valid data',
|
||
|
|
status: 'fail',
|
||
|
|
error: error.message,
|
||
|
|
details: { consoleErrors: [...consoleErrors], networkErrors: [...networkErrors] },
|
||
|
|
});
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
test('2.2. Register - Validation errors', async () => {
|
||
|
|
await page.goto(`${BASE_URL}/register`);
|
||
|
|
await page.waitForLoadState('networkidle');
|
||
|
|
|
||
|
|
// Test email invalide
|
||
|
|
await page.fill('input[name="email"]', 'invalid-email');
|
||
|
|
await page.fill('input[name="password"]', 'short');
|
||
|
|
await page.click('button[type="submit"]');
|
||
|
|
|
||
|
|
const emailError = await page.locator('text=/email|Email/i').first().isVisible().catch(() => false);
|
||
|
|
results.push({
|
||
|
|
test: 'Register - Email validation',
|
||
|
|
status: emailError ? 'pass' : 'fail',
|
||
|
|
details: { emailError },
|
||
|
|
});
|
||
|
|
|
||
|
|
// Test mot de passe court
|
||
|
|
await page.fill('input[name="email"]', 'test@example.com');
|
||
|
|
await page.fill('input[name="password"]', 'short');
|
||
|
|
await page.click('button[type="submit"]');
|
||
|
|
|
||
|
|
const passwordError = await page.locator('text=/password|mot de passe/i').first().isVisible().catch(() => false);
|
||
|
|
results.push({
|
||
|
|
test: 'Register - Password validation',
|
||
|
|
status: passwordError ? 'pass' : 'fail',
|
||
|
|
details: { passwordError },
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
test('2.3. Login - Test complet', async () => {
|
||
|
|
await page.goto(`${BASE_URL}/login`);
|
||
|
|
await page.waitForLoadState('networkidle');
|
||
|
|
|
||
|
|
// Test login avec mauvais mot de passe
|
||
|
|
await page.fill('input[name="email"]', testUser.email);
|
||
|
|
await page.fill('input[name="password"]', 'wrongpassword');
|
||
|
|
|
||
|
|
const responsePromise = page.waitForResponse(
|
||
|
|
response => response.url().includes('/auth/login') && response.request().method() === 'POST'
|
||
|
|
);
|
||
|
|
|
||
|
|
await page.click('button[type="submit"]');
|
||
|
|
|
||
|
|
try {
|
||
|
|
const response = await responsePromise;
|
||
|
|
const status = response.status();
|
||
|
|
results.push({
|
||
|
|
test: 'Login - Wrong password',
|
||
|
|
status: status === 401 ? 'pass' : 'fail',
|
||
|
|
error: status !== 401 ? `Expected 401, got ${status}` : undefined,
|
||
|
|
details: { status },
|
||
|
|
});
|
||
|
|
} catch (error: any) {
|
||
|
|
results.push({
|
||
|
|
test: 'Login - Wrong password',
|
||
|
|
status: 'fail',
|
||
|
|
error: error.message,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// Test login valide
|
||
|
|
await page.fill('input[name="password"]', testUser.password);
|
||
|
|
const loginResponsePromise = page.waitForResponse(
|
||
|
|
response => response.url().includes('/auth/login') && response.request().method() === 'POST'
|
||
|
|
);
|
||
|
|
|
||
|
|
await page.click('button[type="submit"]');
|
||
|
|
|
||
|
|
try {
|
||
|
|
const response = await loginResponsePromise;
|
||
|
|
const status = response.status();
|
||
|
|
const body = await response.json().catch(() => ({}));
|
||
|
|
|
||
|
|
results.push({
|
||
|
|
test: 'Login - Valid credentials',
|
||
|
|
status: status === 200 ? 'pass' : 'fail',
|
||
|
|
error: status !== 200 ? `Status ${status}: ${JSON.stringify(body)}` : undefined,
|
||
|
|
details: { status, body },
|
||
|
|
});
|
||
|
|
|
||
|
|
if (status === 200) {
|
||
|
|
await page.waitForURL('**/dashboard', { timeout: 5000 }).catch(() => {});
|
||
|
|
}
|
||
|
|
} catch (error: any) {
|
||
|
|
results.push({
|
||
|
|
test: 'Login - Valid credentials',
|
||
|
|
status: 'fail',
|
||
|
|
error: error.message,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
test('3. Navigation - Toutes les pages', async () => {
|
||
|
|
// Se connecter d'abord
|
||
|
|
await page.goto(`${BASE_URL}/login`);
|
||
|
|
await page.fill('input[name="email"]', testUser.email);
|
||
|
|
await page.fill('input[name="password"]', testUser.password);
|
||
|
|
await page.click('button[type="submit"]');
|
||
|
|
await page.waitForURL('**/dashboard', { timeout: 10000 }).catch(() => {});
|
||
|
|
|
||
|
|
const pages = [
|
||
|
|
{ name: 'Dashboard', path: '/dashboard' },
|
||
|
|
{ name: 'Chat', path: '/chat' },
|
||
|
|
{ name: 'Library', path: '/library' },
|
||
|
|
{ name: 'Profile', path: '/profile' },
|
||
|
|
{ name: 'Settings', path: '/settings' },
|
||
|
|
{ name: 'Marketplace', path: '/marketplace' },
|
||
|
|
];
|
||
|
|
|
||
|
|
for (const pageInfo of pages) {
|
||
|
|
await page.goto(`${BASE_URL}${pageInfo.path}`);
|
||
|
|
await page.waitForLoadState('networkidle');
|
||
|
|
|
||
|
|
const title = await page.title();
|
||
|
|
const url = page.url();
|
||
|
|
const hasErrors = consoleErrors.length > 0 || networkErrors.length > 0;
|
||
|
|
|
||
|
|
results.push({
|
||
|
|
test: `Navigation - ${pageInfo.name}`,
|
||
|
|
status: url.includes(pageInfo.path) && !hasErrors ? 'pass' : 'fail',
|
||
|
|
error: hasErrors ? `Console errors: ${consoleErrors.length}, Network errors: ${networkErrors.length}` : undefined,
|
||
|
|
details: { url, title, consoleErrors: [...consoleErrors], networkErrors: [...networkErrors] },
|
||
|
|
});
|
||
|
|
|
||
|
|
// Reset errors pour la prochaine page
|
||
|
|
consoleErrors = [];
|
||
|
|
networkErrors = [];
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
test('4. Buttons and Actions - Dashboard', async () => {
|
||
|
|
await page.goto(`${BASE_URL}/dashboard`);
|
||
|
|
await page.waitForLoadState('networkidle');
|
||
|
|
|
||
|
|
// Tester tous les boutons visibles
|
||
|
|
const buttons = await page.locator('button').all();
|
||
|
|
const buttonTests: any[] = [];
|
||
|
|
|
||
|
|
for (const button of buttons.slice(0, 10)) { // Limiter à 10 pour éviter trop de tests
|
||
|
|
const text = await button.textContent().catch(() => '');
|
||
|
|
const isVisible = await button.isVisible().catch(() => false);
|
||
|
|
const isEnabled = await button.isEnabled().catch(() => false);
|
||
|
|
|
||
|
|
buttonTests.push({ text, isVisible, isEnabled });
|
||
|
|
}
|
||
|
|
|
||
|
|
results.push({
|
||
|
|
test: 'Dashboard - Buttons',
|
||
|
|
status: 'pass',
|
||
|
|
details: { buttons: buttonTests },
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
test('5. Logout', async () => {
|
||
|
|
await page.goto(`${BASE_URL}/dashboard`);
|
||
|
|
await page.waitForLoadState('networkidle');
|
||
|
|
|
||
|
|
// Ouvrir le menu utilisateur
|
||
|
|
const userMenuButton = page.locator('button[aria-label*="user" i], button:has-text("User")').first();
|
||
|
|
if (await userMenuButton.isVisible()) {
|
||
|
|
await userMenuButton.click();
|
||
|
|
await page.waitForTimeout(500);
|
||
|
|
|
||
|
|
// Cliquer sur logout
|
||
|
|
const logoutButton = page.locator('text=/logout|déconnexion/i').first();
|
||
|
|
if (await logoutButton.isVisible()) {
|
||
|
|
await logoutButton.click();
|
||
|
|
await page.waitForURL('**/login', { timeout: 5000 }).catch(() => {});
|
||
|
|
|
||
|
|
results.push({
|
||
|
|
test: 'Logout',
|
||
|
|
status: page.url().includes('/login') ? 'pass' : 'fail',
|
||
|
|
details: { finalUrl: page.url() },
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
test.afterAll(async () => {
|
||
|
|
// Générer le rapport
|
||
|
|
console.log('\n=== QA AUDIT RESULTS ===\n');
|
||
|
|
results.forEach(result => {
|
||
|
|
const icon = result.status === 'pass' ? '✅' : result.status === 'fail' ? '❌' : '⏭️';
|
||
|
|
console.log(`${icon} ${result.test}: ${result.status}`);
|
||
|
|
if (result.error) {
|
||
|
|
console.log(` Error: ${result.error}`);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|