veza/tests/e2e/40-library.spec.ts
senke 9a4c0d2af4 feat(web): update all features, stories, e2e tests, and auth interceptor
Update auth, playlists, tracks, search, profile, dashboard, player,
settings, and social features. Add e2e audit specs for all major pages.
Update ESLint config, vitest config, and route configuration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 19:16:36 +02:00

221 lines
9.2 KiB
TypeScript

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);
});
});
});