import { test, expect } from '@playwright/test'; import { CONFIG, loginViaAPI } from './helpers'; const BASE = CONFIG.baseURL; test.describe('Bibliothèque musicale (/library)', () => { test.describe('Chargement & Rendu', () => { test('la page nécessite une authentification', async ({ page }) => { await page.goto(`${BASE}/library`, { waitUntil: 'domcontentloaded' }); await page.waitForTimeout(3000); // Should redirect to login if not authenticated expect(page.url()).toContain('/login'); }); test('la page se charge sans erreur pour un utilisateur connecté', async ({ page }) => { await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password); await page.goto(`${BASE}/library`, { waitUntil: 'domcontentloaded' }); await page.waitForTimeout(3000); expect(page.url()).toContain('/library'); await expect(page.getByRole('heading', { level: 1 })).toBeVisible(); }); test('le titre et la toolbar sont affichés', async ({ page }) => { await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password); await page.goto(`${BASE}/library`, { waitUntil: 'domcontentloaded' }); await page.waitForTimeout(3000); // Title await expect(page.getByRole('heading', { level: 1 })).toContainText(/Library|Bibliothèque|Biblioteca/); // Search input await expect(page.getByRole('textbox')).toBeVisible(); // Grid/List toggle buttons await expect(page.getByRole('button', { name: /Grid view|Vue grille|Vista de cuadrícula/ })).toBeVisible(); await expect(page.getByRole('button', { name: /List view|Vue liste|Vista de lista/ })).toBeVisible(); // New button await expect(page.getByRole('button', { name: /New|Nouveau|Nuevo/ })).toBeVisible(); }); }); test.describe('Fonctionnalités', () => { test('état vide pour un nouveau compte', async ({ page }) => { await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password); await page.goto(`${BASE}/library`, { waitUntil: 'domcontentloaded' }); await page.waitForTimeout(3000); // Empty state with translated text await expect(page.getByText(/Your library is empty|Votre bibliothèque est vide|Tu biblioteca está vacía/)).toBeVisible(); // Upload Track button await expect(page.getByRole('button', { name: /Upload Track|Téléverser un titre|Subir pista/ })).toBeVisible(); }); test('basculer entre vue grille et vue liste', async ({ page }) => { await loginViaAPI(page, CONFIG.users.admin.email, CONFIG.users.admin.password); await page.goto(`${BASE}/library`, { waitUntil: 'domcontentloaded' }); await page.waitForTimeout(3000); // Click list view await page.getByRole('button', { name: /List view|Vue liste|Vista de lista/ }).click(); await page.waitForTimeout(1000); // Table should appear const table = page.getByRole('table'); if (await table.isVisible()) { // Table headers should be visible await expect(page.getByRole('columnheader', { name: /Title|Titre|Título/ })).toBeVisible(); } // Click grid view await page.getByRole('button', { name: /Grid view|Vue grille|Vista de cuadrícula/ }).click(); await page.waitForTimeout(1000); }); test('la recherche filtre les pistes', async ({ page }) => { await loginViaAPI(page, CONFIG.users.admin.email, CONFIG.users.admin.password); await page.goto(`${BASE}/library`, { waitUntil: 'domcontentloaded' }); await page.waitForTimeout(3000); // Type in search const searchInput = page.getByRole('textbox'); await searchInput.fill('test_nonexistent_query'); await page.waitForTimeout(500); // Should show empty or filtered results }); }); test.describe('Accessibilité', () => { test('le bouton play dans la grille a un aria-label traduit', async ({ page }) => { await loginViaAPI(page, CONFIG.users.admin.email, CONFIG.users.admin.password); await page.goto(`${BASE}/library`, { waitUntil: 'domcontentloaded' }); await page.waitForTimeout(3000); // BUG #3 regression: play button should have aria-label const playButtons = await page.evaluate(() => { return Array.from(document.querySelectorAll('button')) .map(b => b.getAttribute('aria-label') || '') .filter(l => /Play |Lire |Reproducir /.test(l)); }); // If there are tracks, there should be play buttons with labels // (empty library won't have any) }); test('le bouton menu dans la liste a un aria-label traduit', async ({ page }) => { await loginViaAPI(page, CONFIG.users.admin.email, CONFIG.users.admin.password); await page.goto(`${BASE}/library`, { waitUntil: 'domcontentloaded' }); await page.waitForTimeout(3000); // Switch to list view await page.getByRole('button', { name: /List view|Vue liste|Vista de lista/ }).click(); await page.waitForTimeout(1000); // BUG #2 regression: more options buttons should have aria-label const moreButtons = await page.evaluate(() => { return Array.from(document.querySelectorAll('button')) .map(b => b.getAttribute('aria-label') || '') .filter(l => /More options|Plus d'options|Más opciones/.test(l)); }); // If there are tracks in list view, there should be more options buttons }); test('la table en vue liste a un aria-label', async ({ page }) => { await loginViaAPI(page, CONFIG.users.admin.email, CONFIG.users.admin.password); await page.goto(`${BASE}/library`, { waitUntil: 'domcontentloaded' }); await page.waitForTimeout(3000); // Switch to list view await page.getByRole('button', { name: /List view|Vue liste|Vista de lista/ }).click(); await page.waitForTimeout(1000); // BUG #4 regression: table should have aria-label const table = page.getByRole('table'); if (await table.isVisible()) { const label = await table.getAttribute('aria-label'); expect(label).toBeTruthy(); } }); test('la section grille a un aria-label traduit', async ({ page }) => { await loginViaAPI(page, CONFIG.users.admin.email, CONFIG.users.admin.password); await page.goto(`${BASE}/library`, { waitUntil: 'domcontentloaded' }); await page.waitForTimeout(3000); // BUG #5 regression: grid section should have translated aria-label const section = page.locator('section'); if (await section.isVisible()) { const label = await section.getAttribute('aria-label'); expect(label).toBeTruthy(); } }); }); test.describe('i18n', () => { test('pas de clés i18n brutes affichées', async ({ page }) => { await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password); await page.goto(`${BASE}/library`, { waitUntil: 'domcontentloaded' }); await page.waitForTimeout(3000); const text = await page.textContent('body'); expect(text).not.toMatch(/library\./); }); test('les textes utilisent des traductions (pas de hardcoded)', async ({ page }) => { await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password); await page.goto(`${BASE}/library`, { waitUntil: 'domcontentloaded' }); await page.waitForTimeout(3000); // BUG #1 regression: all visible text should come from i18n // The heading should be translated const heading = page.getByRole('heading', { level: 1 }); const headingText = await heading.textContent(); expect(headingText).toMatch(/Library|Bibliothèque|Biblioteca/); }); }); test.describe('Responsive', () => { test('mobile 375px - la page est fonctionnelle', async ({ page }) => { await page.setViewportSize({ width: 375, height: 812 }); await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password); await page.goto(`${BASE}/library`, { waitUntil: 'domcontentloaded' }); await page.waitForTimeout(3000); // Search and buttons should be visible await expect(page.getByRole('textbox')).toBeVisible(); await expect(page.getByRole('button', { name: /New|Nouveau|Nuevo/ })).toBeVisible(); }); test('tablet 768px - la page est fonctionnelle', async ({ page }) => { await page.setViewportSize({ width: 768, height: 1024 }); await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password); await page.goto(`${BASE}/library`, { waitUntil: 'domcontentloaded' }); await page.waitForTimeout(3000); await expect(page.getByRole('heading', { level: 1 })).toBeVisible(); }); }); test.describe('Réseau & API', () => { test('l\'API /tracks retourne 200', async ({ page }) => { let tracksStatus = 0; page.on('response', (response) => { if (response.url().includes('/api/v1/tracks') && !response.url().includes('/likes')) { tracksStatus = response.status(); } }); await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password); await page.goto(`${BASE}/library`, { waitUntil: 'domcontentloaded' }); await page.waitForTimeout(3000); expect(tracksStatus).toBe(200); }); }); });