363 lines
12 KiB
TypeScript
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');
|
|
});
|
|
});
|
|
|