veza/tests/e2e/search-audit.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

320 lines
13 KiB
TypeScript

import { test, expect } from '@playwright/test';
import { CONFIG, loginViaAPI, navigateTo } from './helpers';
// ===========================================================================
// SEARCH PAGE AUDIT
// ===========================================================================
test.describe('Search Page Audit (/search)', () => {
// -------------------------------------------------------------------------
// Chargement & Rendu
// -------------------------------------------------------------------------
test.describe('Chargement & Rendu', () => {
test('01. la page /search se charge sans crash', async ({ page }) => {
await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password);
await navigateTo(page, '/search');
await expect(page.locator('main')).toBeVisible();
const body = await page.textContent('body');
expect(body).not.toMatch(/500|Internal Server Error/i);
});
test('02. le titre du document est mis a jour', async ({ page }) => {
await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password);
await navigateTo(page, '/search');
await expect(page).toHaveTitle(/Search/i);
});
test('03. etat decouverte affiche les cards de navigation', async ({ page }) => {
await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password);
await navigateTo(page, '/search');
await expect(page.getByRole('heading', { name: /new releases/i })).toBeVisible();
await expect(page.getByRole('heading', { name: /curated mixes/i })).toBeVisible();
await expect(page.getByRole('heading', { name: /explore artists/i })).toBeVisible();
});
test('04. recherche avec resultats affiche les onglets', async ({ page }) => {
await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password);
await navigateTo(page, '/search?q=luna');
// Wait for results to load
await expect(page.getByRole('tab', { name: /all results/i })).toBeVisible({ timeout: 10_000 });
await expect(page.getByRole('tab', { name: /tracks/i })).toBeVisible();
await expect(page.getByRole('tab', { name: /artists/i })).toBeVisible();
await expect(page.getByRole('tab', { name: /playlists/i })).toBeVisible();
});
test('05. recherche sans resultats affiche empty state', async ({ page }) => {
await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password);
await navigateTo(page, '/search?q=zzzznonexistent999');
await expect(page.getByRole('heading', { name: /no results found/i })).toBeVisible({ timeout: 10_000 });
});
});
// -------------------------------------------------------------------------
// Fonctionnalites
// -------------------------------------------------------------------------
test.describe('Fonctionnalites', () => {
test.beforeEach(async ({ page }) => {
await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password);
});
test('06. la recherche fonctionne avec debounce', async ({ page }) => {
await navigateTo(page, '/search');
const input = page.getByRole('combobox', { name: /search/i });
await input.fill('luna');
// Wait for debounce (500ms) + API response
await expect(page.getByRole('tab', { name: /all results/i })).toBeVisible({ timeout: 10_000 });
});
test('07. le bouton clear efface la recherche', async ({ page }) => {
await navigateTo(page, '/search?q=luna');
// Wait for results
await expect(page.getByRole('tab', { name: /all results/i })).toBeVisible({ timeout: 10_000 });
// Click clear
await page.getByRole('button', { name: /clear search/i }).click();
// Input should be empty
const input = page.getByRole('combobox', { name: /search/i });
await expect(input).toHaveValue('');
// Discovery cards should reappear
await expect(page.getByRole('heading', { name: /new releases/i })).toBeVisible({ timeout: 5_000 });
});
test('08. clic sur un resultat artiste navigue vers le profil', async ({ page }) => {
await navigateTo(page, '/search?q=luna');
// Wait for artist results
await expect(page.getByRole('tab', { name: /artists/i })).toBeVisible({ timeout: 10_000 });
// Click first artist link
const firstArtistLink = page.getByRole('link', { name: /luna/ }).first();
await firstArtistLink.click();
// Should navigate to profile
await page.waitForURL(/\/u\//, { timeout: 10_000 });
expect(page.url()).toContain('/u/');
});
test('09. onglets filtrent les resultats', async ({ page }) => {
await navigateTo(page, '/search?q=luna');
await expect(page.getByRole('tab', { name: /all results/i })).toBeVisible({ timeout: 10_000 });
// Click Artists tab
await page.getByRole('tab', { name: /artists/i }).click();
await expect(page.getByRole('tabpanel', { name: /artists/i })).toBeVisible();
// Click Playlists tab
await page.getByRole('tab', { name: /playlists/i }).click();
await expect(page.getByRole('tabpanel', { name: /playlists/i })).toBeVisible();
});
test('10. URL params ?q= pre-remplissent la recherche', async ({ page }) => {
await navigateTo(page, '/search?q=luna');
const input = page.getByRole('combobox', { name: /search/i });
await expect(input).toHaveValue('luna');
});
test('11. historique de recherche apparait', async ({ page }) => {
// First search to create history
await navigateTo(page, '/search');
const input = page.getByRole('combobox', { name: /search/i });
await input.fill('testhistory');
await page.waitForTimeout(1000); // wait for debounce + history save
// Clear and reload
await navigateTo(page, '/search');
// History should show
const historyButton = page.getByRole('button', { name: 'testhistory' });
// History might or might not appear depending on timing
if (await historyButton.isVisible().catch(() => false)) {
await historyButton.click();
await expect(input).toHaveValue('testhistory');
}
});
});
// -------------------------------------------------------------------------
// Securite
// -------------------------------------------------------------------------
test.describe('Securite', () => {
test('12. /search est protege par authentification', async ({ page }) => {
await page.goto(`${CONFIG.baseURL}/search`);
await page.waitForURL(/\/(login|search)/, { timeout: CONFIG.timeouts.navigation });
const url = page.url();
if (!url.includes('/search')) {
expect(url).toContain('/login');
}
});
test('13. pas de token ou email dans l\'URL de recherche', async ({ page }) => {
await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password);
await navigateTo(page, '/search?q=luna');
const url = page.url();
expect(url).not.toMatch(/token|access_token|email|password/i);
});
});
// -------------------------------------------------------------------------
// Accessibilite
// -------------------------------------------------------------------------
test.describe('Accessibilite', () => {
test.beforeEach(async ({ page }) => {
await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password);
});
test('14. search input est un combobox avec aria-controls', async ({ page }) => {
await navigateTo(page, '/search');
const input = page.getByRole('combobox', { name: /search/i });
await expect(input).toBeVisible();
const controls = await input.getAttribute('aria-controls');
expect(controls).toBeTruthy();
});
test('15. discovery cards sont accessibles au clavier', async ({ page }) => {
await navigateTo(page, '/search');
const card = page.getByRole('link', { name: /new releases/i });
await expect(card).toBeVisible();
await card.focus();
await expect(card).toBeFocused();
});
test('16. HelpText dit "Help:" et pas "Aide:"', async ({ page }) => {
await navigateTo(page, '/search');
const helpSpan = page.locator('[aria-label^="Help:"]');
await expect(helpSpan).toBeVisible();
// Should NOT have French prefix
const aidePrefixed = page.locator('[aria-label^="Aide:"]');
await expect(aidePrefixed).toHaveCount(0);
});
test('17. resultat cards ont role="link"', async ({ page }) => {
await navigateTo(page, '/search?q=luna');
await expect(page.getByRole('tab', { name: /all results/i })).toBeVisible({ timeout: 10_000 });
const links = page.getByRole('link', { name: /luna/ });
const count = await links.count();
expect(count).toBeGreaterThan(0);
});
});
// -------------------------------------------------------------------------
// i18n
// -------------------------------------------------------------------------
test.describe('i18n', () => {
test('18. pas de cles i18n brutes', async ({ page }) => {
await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password);
await navigateTo(page, '/search');
const body = await page.textContent('body') || '';
expect(body).not.toMatch(/\bsearch\.\w+/);
expect(body).not.toMatch(/\bcommon\.\w+/);
});
test('19. pas de "Trending" ou "Top Artists" (ORIGIN compliance)', async ({ page }) => {
await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password);
await navigateTo(page, '/search');
const body = await page.textContent('body') || '';
expect(body).not.toMatch(/Trending creators/i);
// "Top Artists" should be replaced by "Explore Artists"
expect(body).not.toMatch(/\bTop Artists\b/);
});
test('20. pas de melange FR/EN', async ({ page }) => {
await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password);
await navigateTo(page, '/search');
const body = await page.textContent('body') || '';
expect(body).not.toMatch(/\bRecherche\b/);
expect(body).not.toMatch(/\bEffacer\b/);
});
});
// -------------------------------------------------------------------------
// Responsive
// -------------------------------------------------------------------------
test.describe('Responsive', () => {
test.beforeEach(async ({ page }) => {
await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password);
});
test('21. mobile 375px: page se charge correctement', async ({ page }) => {
await page.setViewportSize({ width: 375, height: 812 });
await navigateTo(page, '/search');
await expect(page.getByRole('combobox', { name: /search/i })).toBeVisible();
await expect(page.getByRole('heading', { name: /search/i })).toBeVisible();
});
test('22. tablet 768px: resultats affichent correctement', async ({ page }) => {
await page.setViewportSize({ width: 768, height: 1024 });
await navigateTo(page, '/search?q=luna');
await expect(page.getByRole('tab', { name: /all results/i })).toBeVisible({ timeout: 10_000 });
});
});
// -------------------------------------------------------------------------
// Regression
// -------------------------------------------------------------------------
test.describe('Regression', () => {
test.beforeEach(async ({ page }) => {
await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password);
});
test('23. BUG#1: recherche "luna" ne crash plus', async ({ page }) => {
await navigateTo(page, '/search?q=luna');
// Should NOT show error boundary
await expect(page.getByText(/Failed to load/i)).not.toBeVisible({ timeout: 5_000 });
await expect(page.getByText(/Cannot read properties/i)).not.toBeVisible();
// Should show results
await expect(page.getByRole('tab', { name: /all results/i })).toBeVisible({ timeout: 10_000 });
});
test('24. BUG#2: clear search fonctionne correctement', async ({ page }) => {
await navigateTo(page, '/search');
const input = page.getByRole('combobox', { name: /search/i });
await input.fill('luna');
// Wait for results to appear
await expect(page.getByRole('tab', { name: /all results/i })).toBeVisible({ timeout: 10_000 });
// Click clear
await page.getByRole('button', { name: /clear search/i }).click();
// Input should be empty and discovery should return
await expect(input).toHaveValue('');
await expect(page.getByRole('heading', { name: /new releases/i })).toBeVisible({ timeout: 5_000 });
});
test('25. BUG#5: pas de contenu "trending" dans discovery', async ({ page }) => {
await navigateTo(page, '/search');
// Should say "Explore Artists" not "Top Artists"
await expect(page.getByRole('heading', { name: /explore artists/i })).toBeVisible();
// "Trending creators" text should not appear
const body = await page.textContent('body') || '';
expect(body).not.toMatch(/Trending creators/i);
});
test('26. BUG#9: HelpText prefix is "Help:" not "Aide:"', async ({ page }) => {
await navigateTo(page, '/search');
const helpElement = page.locator('[aria-label^="Help:"]');
await expect(helpElement).toBeVisible();
});
});
});