veza/apps/web/e2e/critical_flows.spec.ts
2026-01-07 19:39:21 +01:00

363 lines
12 KiB
TypeScript

/**
* Critical User Flows E2E Tests
* FE-TEST-012: Test login, upload, playlist creation end-to-end
*
* This test suite covers the most critical user journeys:
* 1. User login flow
* 2. Track upload flow
* 3. Playlist creation flow
*
* These tests ensure that the core functionality works together seamlessly.
*/
/* eslint-disable no-console */
import { test, expect } from '@playwright/test';
import {
TEST_CONFIG,
TEST_USERS,
forceSubmitForm,
fillField,
waitForToast,
setupErrorCapture,
openModal,
} from './utils/test-helpers';
import { createMockMP3Buffer } from './fixtures/file-helpers';
test.describe('Critical User Flows - End-to-End', () => {
// Reset storage state for these tests to ensure we start unauthenticated
test.use({ storageState: { cookies: [], origins: [] } });
let consoleErrors: string[] = [];
let networkErrors: Array<{ url: string; status: number; method: string }> = [];
test.beforeEach(async ({ page }) => {
const errorCapture = setupErrorCapture(page);
consoleErrors = errorCapture.consoleErrors;
networkErrors = errorCapture.networkErrors;
});
/**
* CRITICAL FLOW 1: Complete user journey from login to playlist creation
*
* This test simulates a real user scenario:
* 1. User logs in
* 2. User uploads a track
* 3. User creates a playlist
* 4. User adds the uploaded track to the playlist
*/
test('Complete user journey: Login → Upload → Create Playlist → Add Track', async ({ page }) => {
test.setTimeout(180000); // 3 minutes for complete flow
console.log('🚀 [CRITICAL FLOW] Starting complete user journey test...');
// ========== STEP 1: LOGIN ==========
console.log('📝 [CRITICAL FLOW] Step 1: User login...');
await page.goto(`${TEST_CONFIG.FRONTEND_URL}/login`);
await page.waitForLoadState('domcontentloaded');
// Wait for form to be ready
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => { });
await page.waitForTimeout(500);
// Fill login form
await fillField(
page,
'input[type="email"], input[name="email"]',
TEST_USERS.default.email
);
await fillField(
page,
'input[type="password"], input[name="password"]',
TEST_USERS.default.password
);
// Submit form and wait for navigation
const navigationPromise = page.waitForURL(
(url) => url.pathname === '/dashboard' || url.pathname === '/',
{ timeout: 15000 }
);
await forceSubmitForm(page, 'form');
await navigationPromise;
// Verify user is authenticated
await expect(page).toHaveURL(/\/(dashboard|$)/);
await expect(page.locator('nav[role="navigation"], aside[role="navigation"]')).toBeVisible({
timeout: 10000,
});
// Wait for auth state to be persisted
await page.waitForTimeout(1000);
const isAuthenticated = await page.evaluate(() => {
try {
const authStorage = localStorage.getItem('auth-storage');
if (authStorage) {
const parsed = JSON.parse(authStorage);
return parsed.state?.isAuthenticated === true;
}
} catch {
return false;
}
return false;
});
expect(isAuthenticated).toBe(true);
console.log('✅ [CRITICAL FLOW] Step 1: Login successful');
// ========== STEP 2: UPLOAD TRACK ==========
console.log('📤 [CRITICAL FLOW] Step 2: Uploading track...');
// Navigate to library
await page.goto(`${TEST_CONFIG.FRONTEND_URL}/library`);
await page.waitForLoadState('domcontentloaded');
await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => {
console.warn('⚠️ [CRITICAL FLOW] Timeout on networkidle, continuing...');
});
// Open upload modal
await openModal(page, /upload/i);
// Select and upload file
const fileInput = page.locator('input[type="file"][accept*="audio"]').first();
await expect(fileInput).toBeAttached({ timeout: 5000 });
const validMp3Buffer = createMockMP3Buffer();
await fileInput.setInputFiles({
name: 'critical-flow-test.mp3',
mimeType: 'audio/mpeg',
buffer: validMp3Buffer,
});
console.log('✅ [CRITICAL FLOW] File selected');
await page.waitForTimeout(1000);
// Check for rejection errors
const errorMessage = page.locator('[data-testid="upload-error"], [role="alert"]:has-text("Format")').first();
const hasRejectionError = await errorMessage.isVisible().catch(() => false);
if (hasRejectionError) {
const errorText = await errorMessage.textContent();
throw new Error(`File was rejected: ${errorText}`);
}
// Fill metadata
await fillField(page, 'input[id="title"], input[name="title"]', 'Critical Flow Test Track');
await fillField(page, 'input[id="artist"], input[name="artist"]', 'Test Artist');
// Submit upload
const uploadButton = page.locator('button:has-text("Upload"), button:has-text("Envoyer"), button[type="submit"]').first();
await expect(uploadButton).toBeVisible({ timeout: 5000 });
await uploadButton.click();
// Wait for upload success
await waitForToast(page, /success|succès|uploaded|téléchargé/i, { timeout: 60000 });
console.log('✅ [CRITICAL FLOW] Step 2: Track uploaded successfully');
// Close modal if still open
const closeButton = page.locator('button[aria-label*="close"], button[aria-label*="fermer"]').first();
if (await closeButton.isVisible().catch(() => false)) {
await closeButton.click();
await page.waitForTimeout(500);
}
// ========== STEP 3: CREATE PLAYLIST ==========
console.log('📋 [CRITICAL FLOW] Step 3: Creating playlist...');
// Navigate to playlists
await page.goto(`${TEST_CONFIG.FRONTEND_URL}/playlists`, { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(500);
await page.waitForURL(/\/playlists/, { timeout: 15000 }).catch(() => { });
// Wait for page to load
try {
await Promise.race([
page.locator('h1:has-text("Playlist"), h1:has-text("Playlists")').first().waitFor({ state: 'visible', timeout: 10000 }),
page.locator('[data-testid="playlists-page"]').first().waitFor({ state: 'visible', timeout: 10000 }),
]);
} catch {
console.warn('⚠️ [CRITICAL FLOW] Page load check timeout, continuing...');
}
// Open create playlist modal
await openModal(page, /create|créer|nouvelle/i);
// Fill playlist form
await fillField(
page,
'input[id="title"], input[name="title"]',
'Critical Flow Test Playlist'
);
await fillField(
page,
'textarea[id="description"], textarea[name="description"]',
'Playlist created during critical flow test'
);
// Submit playlist creation
const createButton = page.locator('button:has-text("Créer"), button:has-text("Create"), button[type="submit"]').first();
await expect(createButton).toBeVisible({ timeout: 5000 });
await createButton.click();
// Wait for success
await waitForToast(page, /success|succès|created|créé/i, { timeout: 15000 });
console.log('✅ [CRITICAL FLOW] Step 3: Playlist created successfully');
// ========== STEP 4: VERIFY PLAYLIST EXISTS ==========
console.log('🔍 [CRITICAL FLOW] Step 4: Verifying playlist exists...');
// Wait for modal to close
await page.waitForTimeout(1000);
// Check that playlist appears in the list
const playlistTitle = page.locator('text="Critical Flow Test Playlist"').first();
await expect(playlistTitle).toBeVisible({ timeout: 10000 });
console.log('✅ [CRITICAL FLOW] Step 4: Playlist verified in list');
// ========== VERIFY NO ERRORS ==========
console.log('🔍 [CRITICAL FLOW] Verifying no errors occurred...');
// Check for console errors
if (consoleErrors.length > 0) {
console.warn('⚠️ [CRITICAL FLOW] Console errors detected:', consoleErrors);
}
// Check for network errors (excluding expected ones)
const criticalNetworkErrors = networkErrors.filter(
(error) => error.status >= 500 || (error.status >= 400 && !error.url.includes('favicon'))
);
if (criticalNetworkErrors.length > 0) {
console.warn('⚠️ [CRITICAL FLOW] Network errors detected:', criticalNetworkErrors);
}
console.log('✅ [CRITICAL FLOW] Complete user journey test passed!');
});
/**
* CRITICAL FLOW 2: Login and immediate playlist creation
*
* Tests the scenario where a user logs in and immediately creates a playlist
* without uploading anything first.
*/
test('Login → Create Playlist (no upload)', async ({ page }) => {
test.setTimeout(90000); // 90 seconds
console.log('🚀 [CRITICAL FLOW] Starting login → playlist creation test...');
// Login
await page.goto(`${TEST_CONFIG.FRONTEND_URL}/login`);
await page.waitForLoadState('domcontentloaded');
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => { });
await fillField(
page,
'input[type="email"], input[name="email"]',
TEST_USERS.default.email
);
await fillField(
page,
'input[type="password"], input[name="password"]',
TEST_USERS.default.password
);
const navigationPromise = page.waitForURL(
(url) => url.pathname === '/dashboard' || url.pathname === '/',
{ timeout: 15000 }
);
await forceSubmitForm(page, 'form');
await navigationPromise;
await expect(page).toHaveURL(/\/(dashboard|$)/);
console.log('✅ [CRITICAL FLOW] Login successful');
// Navigate to playlists
await page.goto(`${TEST_CONFIG.FRONTEND_URL}/playlists`, { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(1000);
// Create playlist
await openModal(page, /create|créer|nouvelle/i);
await fillField(
page,
'input[id="title"], input[name="title"]',
'Quick Test Playlist'
);
const createButton = page.locator('button:has-text("Créer"), button:has-text("Create"), button[type="submit"]').first();
await expect(createButton).toBeVisible({ timeout: 5000 });
await createButton.click();
await waitForToast(page, /success|succès|created|créé/i, { timeout: 15000 });
console.log('✅ [CRITICAL FLOW] Playlist created successfully');
});
/**
* CRITICAL FLOW 3: Login and upload only
*
* Tests the scenario where a user logs in and uploads a track
* without creating a playlist.
*/
test('Login → Upload Track (no playlist)', async ({ page }) => {
test.setTimeout(120000); // 2 minutes
console.log('🚀 [CRITICAL FLOW] Starting login → upload test...');
// Login
await page.goto(`${TEST_CONFIG.FRONTEND_URL}/login`);
await page.waitForLoadState('domcontentloaded');
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => { });
await fillField(
page,
'input[type="email"], input[name="email"]',
TEST_USERS.default.email
);
await fillField(
page,
'input[type="password"], input[name="password"]',
TEST_USERS.default.password
);
const navigationPromise = page.waitForURL(
(url) => url.pathname === '/dashboard' || url.pathname === '/',
{ timeout: 15000 }
);
await forceSubmitForm(page, 'form');
await navigationPromise;
await expect(page).toHaveURL(/\/(dashboard|$)/);
console.log('✅ [CRITICAL FLOW] Login successful');
// Navigate to library and upload
await page.goto(`${TEST_CONFIG.FRONTEND_URL}/library`);
await page.waitForLoadState('domcontentloaded');
await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => { });
await openModal(page, /upload/i);
const fileInput = page.locator('input[type="file"][accept*="audio"]').first();
await expect(fileInput).toBeAttached({ timeout: 5000 });
const validMp3Buffer = createMockMP3Buffer();
await fileInput.setInputFiles({
name: 'upload-only-test.mp3',
mimeType: 'audio/mpeg',
buffer: validMp3Buffer,
});
await page.waitForTimeout(1000);
await fillField(page, 'input[id="title"], input[name="title"]', 'Upload Only Test');
await fillField(page, 'input[id="artist"], input[name="artist"]', 'Test Artist');
const uploadButton = page.locator('button:has-text("Upload"), button:has-text("Envoyer"), button[type="submit"]').first();
await expect(uploadButton).toBeVisible({ timeout: 5000 });
await uploadButton.click();
await waitForToast(page, /success|succès|uploaded|téléchargé/i, { timeout: 60000 });
console.log('✅ [CRITICAL FLOW] Upload successful');
});
});