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>
221 lines
9.2 KiB
TypeScript
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);
|
|
});
|
|
});
|
|
});
|