346 lines
12 KiB
TypeScript
346 lines
12 KiB
TypeScript
import { test, expect, type Page } from '@playwright/test';
|
|
|
|
/**
|
|
* Diagnostic Test - Full Stack Compatibility Check
|
|
*
|
|
* Ce test vérifie l'intégration Frontend-Backend après le refactoring de l'authentification.
|
|
* Il capture toutes les erreurs réseau, console, CORS et vérifie le stockage des tokens.
|
|
*/
|
|
|
|
// Configuration
|
|
const BASE_URL = process.env.VITE_API_URL || 'http://localhost:8080/api/v1';
|
|
const FRONTEND_URL = process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:3000';
|
|
const TEST_EMAIL = process.env.TEST_EMAIL || 'user@example.com';
|
|
const TEST_PASSWORD = process.env.TEST_PASSWORD || 'password123';
|
|
|
|
// Collecteurs d'erreurs
|
|
interface DiagnosticReport {
|
|
networkErrors: Array<{
|
|
url: string;
|
|
status: number;
|
|
method: string;
|
|
error: string;
|
|
}>;
|
|
consoleErrors: Array<{
|
|
type: string;
|
|
message: string;
|
|
stack?: string;
|
|
}>;
|
|
corsErrors: Array<{
|
|
url: string;
|
|
reason: string;
|
|
}>;
|
|
localStorage: Record<string, string>;
|
|
navigationSuccess: boolean;
|
|
finalUrl: string;
|
|
formVisible: boolean;
|
|
errorMessage?: string;
|
|
}
|
|
|
|
test.describe('Full Stack Compatibility Diagnostic', () => {
|
|
let report: DiagnosticReport;
|
|
|
|
test.beforeEach(() => {
|
|
report = {
|
|
networkErrors: [],
|
|
consoleErrors: [],
|
|
corsErrors: [],
|
|
localStorage: {},
|
|
navigationSuccess: false,
|
|
finalUrl: '',
|
|
formVisible: false,
|
|
};
|
|
});
|
|
|
|
test('Login Flow - Complete Diagnostic', async ({ page, context }) => {
|
|
// Setup: Écouter les erreurs console AVANT toute navigation
|
|
const consoleMessages: Array<{ type: string; text: string }> = [];
|
|
page.on('console', (msg) => {
|
|
const type = msg.type();
|
|
const text = msg.text();
|
|
consoleMessages.push({ type, text });
|
|
|
|
if (type === 'error' || type === 'warning') {
|
|
report.consoleErrors.push({
|
|
type,
|
|
message: text,
|
|
stack: msg.location()?.url,
|
|
});
|
|
console.log(`🔴 [CONSOLE ${type.toUpperCase()}] ${text}`);
|
|
}
|
|
});
|
|
|
|
// Setup: Écouter les erreurs de page (uncaught exceptions)
|
|
page.on('pageerror', (error) => {
|
|
report.consoleErrors.push({
|
|
type: 'pageerror',
|
|
message: error.message,
|
|
stack: error.stack,
|
|
});
|
|
console.log(`🔴 [PAGE ERROR] ${error.message}`);
|
|
});
|
|
|
|
// Setup: Écouter les requêtes réseau échouées
|
|
page.on('response', (response) => {
|
|
const status = response.status();
|
|
const url = response.url();
|
|
|
|
// Capturer les erreurs 4xx et 5xx
|
|
if (status >= 400) {
|
|
report.networkErrors.push({
|
|
url,
|
|
status,
|
|
method: response.request().method(),
|
|
error: `HTTP ${status}`,
|
|
});
|
|
|
|
// Détecter les erreurs CORS potentielles
|
|
if (status === 0 || url.includes('localhost:8080')) {
|
|
const headers = response.headers();
|
|
if (!headers['access-control-allow-origin']) {
|
|
report.corsErrors.push({
|
|
url,
|
|
reason: 'Missing CORS headers',
|
|
});
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Setup: Écouter les requêtes échouées (network errors)
|
|
page.on('requestfailed', (request) => {
|
|
const failure = request.failure();
|
|
if (failure) {
|
|
report.networkErrors.push({
|
|
url: request.url(),
|
|
status: 0,
|
|
method: request.method(),
|
|
error: failure.errorText || 'Network error',
|
|
});
|
|
|
|
// Détecter les erreurs CORS
|
|
if (failure.errorText?.includes('CORS') || failure.errorText?.includes('Access-Control')) {
|
|
report.corsErrors.push({
|
|
url: request.url(),
|
|
reason: failure.errorText,
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
// Étape 1: Aller sur la page de login
|
|
console.log('🔍 [DIAGNOSTIC] Navigation vers /login...');
|
|
try {
|
|
await page.goto(`${FRONTEND_URL}/login`, { waitUntil: 'domcontentloaded', timeout: 30000 });
|
|
} catch (error) {
|
|
console.error('❌ [DIAGNOSTIC] Erreur lors de la navigation:', error);
|
|
report.finalUrl = page.url();
|
|
return;
|
|
}
|
|
|
|
// Attendre que la page soit chargée
|
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {
|
|
console.warn('⚠️ [DIAGNOSTIC] Timeout sur networkidle, continuation...');
|
|
});
|
|
|
|
// Prendre une capture d'écran pour debug
|
|
await page.screenshot({ path: 'e2e/diagnostic-login-page.png', fullPage: true });
|
|
|
|
// Attendre que le formulaire soit chargé (plusieurs stratégies)
|
|
try {
|
|
// Essayer d'attendre un élément de formulaire
|
|
await page.waitForSelector('form, input[type="email"], input[type="password"]', { timeout: 10000 });
|
|
} catch (e) {
|
|
console.warn('⚠️ [DIAGNOSTIC] Timeout en attendant le formulaire');
|
|
}
|
|
|
|
// Attendre un peu pour que React hydrate
|
|
await page.waitForTimeout(3000);
|
|
|
|
// Vérifier que le formulaire est visible avec plusieurs sélecteurs possibles
|
|
const emailInput = page.locator('input[type="email"], input[name="email"], input[placeholder*="email" i]').first();
|
|
const passwordInput = page.locator('input[type="password"], input[name="password"]').first();
|
|
const submitButton = page.locator('button[type="submit"], button:has-text("connecter"), button:has-text("login"), button:has-text("Se connecter")').first();
|
|
|
|
// Vérifier aussi avec des sélecteurs plus génériques
|
|
const allInputs = await page.locator('input').count();
|
|
const allButtons = await page.locator('button').count();
|
|
console.log('📄 [DIAGNOSTIC] Nombre d\'inputs sur la page:', allInputs);
|
|
console.log('📄 [DIAGNOSTIC] Nombre de boutons sur la page:', allButtons);
|
|
|
|
const emailVisible = await emailInput.isVisible().catch(() => false);
|
|
const passwordVisible = await passwordInput.isVisible().catch(() => false);
|
|
const submitVisible = await submitButton.isVisible().catch(() => false);
|
|
|
|
report.formVisible = emailVisible && passwordVisible;
|
|
|
|
// Logger le contenu de la page pour debug
|
|
const pageContent = await page.content();
|
|
const hasForm = pageContent.includes('form') || pageContent.includes('email') || pageContent.includes('password');
|
|
|
|
console.log('📄 [DIAGNOSTIC] Page title:', await page.title());
|
|
console.log('📄 [DIAGNOSTIC] URL actuelle:', page.url());
|
|
console.log('📄 [DIAGNOSTIC] Email input visible:', emailVisible);
|
|
console.log('📄 [DIAGNOSTIC] Password input visible:', passwordVisible);
|
|
console.log('📄 [DIAGNOSTIC] Submit button visible:', submitVisible);
|
|
console.log('📄 [DIAGNOSTIC] Page contient "form":', hasForm);
|
|
|
|
if (!report.formVisible) {
|
|
console.error('❌ [DIAGNOSTIC] Le formulaire de login n\'est pas visible');
|
|
|
|
// Logger le HTML pour debug
|
|
const bodyText = await page.locator('body').textContent();
|
|
console.log('📄 [DIAGNOSTIC] Contenu de la page (premiers 500 chars):', bodyText?.substring(0, 500));
|
|
|
|
// Logger toutes les erreurs console capturées
|
|
if (consoleMessages.length > 0) {
|
|
console.log('\n🔴 [DIAGNOSTIC] Messages console capturés:');
|
|
consoleMessages.forEach((msg) => {
|
|
console.log(` [${msg.type}] ${msg.text}`);
|
|
});
|
|
}
|
|
|
|
// Vérifier s'il y a des scripts qui ont échoué à charger
|
|
const failedResources = await page.evaluate(() => {
|
|
const resources: Array<{ url: string; error: string }> = [];
|
|
const scripts = document.querySelectorAll('script[src]');
|
|
scripts.forEach((script) => {
|
|
const src = script.getAttribute('src');
|
|
if (src && !(script as any).loaded) {
|
|
resources.push({ url: src, error: 'Script not loaded' });
|
|
}
|
|
});
|
|
return resources;
|
|
});
|
|
|
|
if (failedResources.length > 0) {
|
|
console.log('🔴 [DIAGNOSTIC] Scripts non chargés:', failedResources);
|
|
}
|
|
|
|
// Sauvegarder le rapport même en cas d'échec
|
|
await page.evaluate((report) => {
|
|
(window as any).__diagnosticReport = report;
|
|
}, report);
|
|
|
|
return;
|
|
}
|
|
|
|
console.log('✅ [DIAGNOSTIC] Formulaire de login visible');
|
|
|
|
// Étape 2: Remplir le formulaire
|
|
console.log('🔍 [DIAGNOSTIC] Remplissage du formulaire...');
|
|
await emailInput.fill(TEST_EMAIL);
|
|
await passwordInput.fill(TEST_PASSWORD);
|
|
|
|
// Vérifier si checkbox "remember me" existe
|
|
const rememberMeCheckbox = page.locator('input[type="checkbox"][id*="remember"]');
|
|
if (await rememberMeCheckbox.count() > 0) {
|
|
await rememberMeCheckbox.check();
|
|
}
|
|
|
|
// Étape 3: Cliquer sur le bouton de connexion
|
|
console.log('🔍 [DIAGNOSTIC] Clic sur le bouton de connexion...');
|
|
|
|
// Attendre la navigation ou un message d'erreur
|
|
const navigationPromise = page.waitForURL(
|
|
(url) => url.pathname === '/dashboard' || url.pathname === '/',
|
|
{ timeout: 10000 }
|
|
).catch(() => null);
|
|
|
|
const errorMessagePromise = page
|
|
.waitForSelector('.bg-red-100, [role="alert"], .text-red-700', { timeout: 5000 })
|
|
.catch(() => null);
|
|
|
|
await submitButton.click();
|
|
|
|
// Attendre soit la navigation, soit un message d'erreur
|
|
const navigationResult = await navigationPromise;
|
|
const errorElement = await errorMessagePromise;
|
|
|
|
if (navigationResult) {
|
|
report.navigationSuccess = true;
|
|
report.finalUrl = page.url();
|
|
console.log('✅ [DIAGNOSTIC] Navigation réussie vers:', report.finalUrl);
|
|
} else if (errorElement) {
|
|
report.errorMessage = await errorElement.textContent() || 'Erreur inconnue';
|
|
console.log('❌ [DIAGNOSTIC] Message d\'erreur détecté:', report.errorMessage);
|
|
} else {
|
|
// Attendre un peu plus pour voir si quelque chose se passe
|
|
await page.waitForTimeout(2000);
|
|
report.finalUrl = page.url();
|
|
console.log('⚠️ [DIAGNOSTIC] Pas de navigation ni d\'erreur visible. URL actuelle:', report.finalUrl);
|
|
}
|
|
|
|
// Étape 4: Vérifier le localStorage
|
|
console.log('🔍 [DIAGNOSTIC] Vérification du localStorage...');
|
|
const localStorageData = await context.storageState();
|
|
const localStorageItems = await page.evaluate(() => {
|
|
const items: Record<string, string> = {};
|
|
for (let i = 0; i < localStorage.length; i++) {
|
|
const key = localStorage.key(i);
|
|
if (key) {
|
|
items[key] = localStorage.getItem(key) || '';
|
|
}
|
|
}
|
|
return items;
|
|
});
|
|
|
|
report.localStorage = localStorageItems;
|
|
|
|
// Vérifier spécifiquement les tokens
|
|
const hasAccessToken = 'access_token' in localStorageItems ||
|
|
'veza_access_token' in localStorageItems ||
|
|
localStorageItems['access_token'] !== undefined ||
|
|
localStorageItems['veza_access_token'] !== undefined;
|
|
|
|
console.log('📦 [DIAGNOSTIC] LocalStorage:', Object.keys(localStorageItems));
|
|
console.log(hasAccessToken ? '✅ [DIAGNOSTIC] Token d\'accès présent' : '❌ [DIAGNOSTIC] Token d\'accès absent');
|
|
|
|
// Générer le rapport
|
|
console.log('\n📊 [DIAGNOSTIC] === RAPPORT DE DIAGNOSTIC ===');
|
|
console.log('Erreurs réseau:', report.networkErrors.length);
|
|
console.log('Erreurs console:', report.consoleErrors.length);
|
|
console.log('Erreurs CORS:', report.corsErrors.length);
|
|
console.log('Navigation réussie:', report.navigationSuccess);
|
|
console.log('Token présent:', hasAccessToken);
|
|
|
|
// Afficher les détails des erreurs
|
|
if (report.networkErrors.length > 0) {
|
|
console.log('\n🔴 Erreurs réseau:');
|
|
report.networkErrors.forEach((err) => {
|
|
console.log(` - ${err.method} ${err.url}: ${err.error}`);
|
|
});
|
|
}
|
|
|
|
if (report.consoleErrors.length > 0) {
|
|
console.log('\n🔴 Erreurs console:');
|
|
report.consoleErrors.forEach((err) => {
|
|
console.log(` - [${err.type}] ${err.message}`);
|
|
});
|
|
}
|
|
|
|
if (report.corsErrors.length > 0) {
|
|
console.log('\n🟠 Erreurs CORS:');
|
|
report.corsErrors.forEach((err) => {
|
|
console.log(` - ${err.url}: ${err.reason}`);
|
|
});
|
|
}
|
|
|
|
// Sauvegarder le rapport pour l'analyse
|
|
await page.evaluate((report) => {
|
|
(window as any).__diagnosticReport = report;
|
|
}, report);
|
|
});
|
|
|
|
test.afterEach(async ({ page }) => {
|
|
// Récupérer le rapport depuis la page si disponible
|
|
const savedReport = await page.evaluate(() => {
|
|
return (window as any).__diagnosticReport;
|
|
}).catch(() => null);
|
|
|
|
if (savedReport) {
|
|
report = savedReport;
|
|
}
|
|
});
|
|
});
|
|
|