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>
257 lines
11 KiB
TypeScript
257 lines
11 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
import { CONFIG, loginViaAPI, navigateTo } from './helpers';
|
|
|
|
// ===========================================================================
|
|
// PLAYLISTS PAGE AUDIT
|
|
// ===========================================================================
|
|
|
|
test.describe('Playlists Page Audit (/playlists)', () => {
|
|
// -------------------------------------------------------------------------
|
|
// Chargement & Rendu
|
|
// -------------------------------------------------------------------------
|
|
test.describe('Chargement & Rendu', () => {
|
|
test('01. la page /playlists se charge sans crash', async ({ page }) => {
|
|
await loginViaAPI(page, CONFIG.users.creator.email, CONFIG.users.creator.password);
|
|
await navigateTo(page, '/playlists');
|
|
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.creator.email, CONFIG.users.creator.password);
|
|
await navigateTo(page, '/playlists');
|
|
await expect(page).toHaveTitle(/Playlists/i);
|
|
});
|
|
|
|
test('03. les playlists se chargent et s\'affichent', async ({ page }) => {
|
|
await loginViaAPI(page, CONFIG.users.creator.email, CONFIG.users.creator.password);
|
|
await navigateTo(page, '/playlists');
|
|
|
|
// Wait for playlist cards to appear (not the empty state)
|
|
const playlistArticle = page.getByRole('article').first();
|
|
await expect(playlistArticle).toBeVisible({ timeout: 10_000 });
|
|
});
|
|
|
|
test('04. heading et subtitle sont visibles', async ({ page }) => {
|
|
await loginViaAPI(page, CONFIG.users.creator.email, CONFIG.users.creator.password);
|
|
await navigateTo(page, '/playlists');
|
|
|
|
await expect(page.getByRole('heading', { name: /playlists/i, level: 1 })).toBeVisible();
|
|
await expect(page.getByText(/discover and manage/i)).toBeVisible();
|
|
});
|
|
|
|
test('05. pagination est visible avec plusieurs playlists', async ({ page }) => {
|
|
await loginViaAPI(page, CONFIG.users.creator.email, CONFIG.users.creator.password);
|
|
await navigateTo(page, '/playlists');
|
|
|
|
// Wait for playlists to load
|
|
await expect(page.getByRole('article').first()).toBeVisible({ timeout: 10_000 });
|
|
|
|
// Pagination should be present
|
|
const pagination = page.getByRole('navigation', { name: /pagination/i });
|
|
await expect(pagination).toBeVisible();
|
|
});
|
|
});
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Fonctionnalites
|
|
// -------------------------------------------------------------------------
|
|
test.describe('Fonctionnalites', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await loginViaAPI(page, CONFIG.users.creator.email, CONFIG.users.creator.password);
|
|
await navigateTo(page, '/playlists');
|
|
// Wait for playlists to load
|
|
await page.getByRole('article').first().waitFor({ state: 'visible', timeout: 10_000 }).catch(() => {});
|
|
});
|
|
|
|
test('06. bouton Create ouvre le dialog', async ({ page }) => {
|
|
await page.getByTestId('create-playlist-btn').click();
|
|
const dialog = page.getByRole('dialog');
|
|
await expect(dialog).toBeVisible();
|
|
});
|
|
|
|
test('07. Create dialog se ferme avec Escape', async ({ page }) => {
|
|
await page.getByTestId('create-playlist-btn').click();
|
|
const dialog = page.getByRole('dialog');
|
|
await expect(dialog).toBeVisible();
|
|
await page.keyboard.press('Escape');
|
|
await expect(dialog).not.toBeVisible();
|
|
});
|
|
|
|
test('08. search input fonctionne', async ({ page }) => {
|
|
const searchInput = page.getByTestId('playlist-search');
|
|
await expect(searchInput).toBeVisible();
|
|
await searchInput.fill('test');
|
|
// Should trigger a search (we just verify it doesn't crash)
|
|
await page.waitForTimeout(1000);
|
|
});
|
|
|
|
test('09. bouton Filters toggle le panneau de filtres', async ({ page }) => {
|
|
await page.getByRole('button', { name: /filters/i }).click();
|
|
// Filter panel should appear with labels
|
|
await expect(page.getByText(/visibility/i)).toBeVisible();
|
|
await expect(page.getByText(/owner/i)).toBeVisible();
|
|
await expect(page.getByText(/sort by/i)).toBeVisible();
|
|
});
|
|
|
|
test('10. selection mode toggle', async ({ page }) => {
|
|
const selectBtn = page.getByRole('button', { name: /enable selection/i });
|
|
await expect(selectBtn).toBeVisible();
|
|
await selectBtn.click();
|
|
|
|
// Button should now show disable selection
|
|
await expect(page.getByRole('button', { name: /disable selection/i })).toBeVisible();
|
|
});
|
|
|
|
test('11. clic sur une playlist navigue vers le detail', async ({ page }) => {
|
|
const firstLink = page.getByRole('link', { name: /view playlist/i }).first();
|
|
await expect(firstLink).toBeVisible();
|
|
const href = await firstLink.getAttribute('href');
|
|
expect(href).toMatch(/\/playlists\//);
|
|
});
|
|
});
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Securite
|
|
// -------------------------------------------------------------------------
|
|
test.describe('Securite', () => {
|
|
test('12. /playlists est protege par authentification', async ({ page }) => {
|
|
await page.goto(`${CONFIG.baseURL}/playlists`);
|
|
await page.waitForURL(/\/(login|playlists)/, { timeout: CONFIG.timeouts.navigation });
|
|
const url = page.url();
|
|
if (!url.includes('/playlists')) {
|
|
expect(url).toContain('/login');
|
|
}
|
|
});
|
|
});
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Accessibilite
|
|
// -------------------------------------------------------------------------
|
|
test.describe('Accessibilite', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await loginViaAPI(page, CONFIG.users.creator.email, CONFIG.users.creator.password);
|
|
await navigateTo(page, '/playlists');
|
|
await page.getByRole('article').first().waitFor({ state: 'visible', timeout: 10_000 }).catch(() => {});
|
|
});
|
|
|
|
test('13. search input a un label accessible', async ({ page }) => {
|
|
const searchInput = page.getByTestId('playlist-search');
|
|
const ariaLabel = await searchInput.getAttribute('aria-label');
|
|
expect(ariaLabel).toBeTruthy();
|
|
});
|
|
|
|
test('14. playlist cards sont des articles avec liens', async ({ page }) => {
|
|
const firstArticle = page.getByRole('article').first();
|
|
await expect(firstArticle).toBeVisible();
|
|
const link = firstArticle.getByRole('link');
|
|
await expect(link).toBeVisible();
|
|
});
|
|
|
|
test('15. sort toggle button a un aria-label', async ({ page }) => {
|
|
// Open filters first
|
|
await page.getByRole('button', { name: /filters/i }).click();
|
|
// The sort toggle button should have aria-label
|
|
const sortBtn = page.getByRole('button', { name: /toggle sort/i });
|
|
await expect(sortBtn).toBeVisible();
|
|
});
|
|
});
|
|
|
|
// -------------------------------------------------------------------------
|
|
// i18n
|
|
// -------------------------------------------------------------------------
|
|
test.describe('i18n', () => {
|
|
test('16. pas de melange FR/EN dans la section principale', async ({ page }) => {
|
|
await loginViaAPI(page, CONFIG.users.creator.email, CONFIG.users.creator.password);
|
|
await navigateTo(page, '/playlists');
|
|
|
|
// Main heading and subtitle should be consistent (not mixed)
|
|
const subtitle = await page.getByText(/discover and manage|découvrez et gérez/i).textContent();
|
|
expect(subtitle).toBeTruthy();
|
|
|
|
// Should not have French button text mixed with English
|
|
const body = await page.textContent('body') || '';
|
|
// The main page buttons should be in one language
|
|
expect(body).not.toMatch(/\bCréer une nouvelle playlist\b/);
|
|
});
|
|
|
|
test('17. pas de cles i18n brutes', async ({ page }) => {
|
|
await loginViaAPI(page, CONFIG.users.creator.email, CONFIG.users.creator.password);
|
|
await navigateTo(page, '/playlists');
|
|
|
|
const body = await page.textContent('body') || '';
|
|
expect(body).not.toMatch(/\bplaylists\.\w+/);
|
|
expect(body).not.toMatch(/\bcommon\.\w+/);
|
|
});
|
|
});
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Responsive
|
|
// -------------------------------------------------------------------------
|
|
test.describe('Responsive', () => {
|
|
test('18. mobile 375px: page se charge correctement', async ({ page }) => {
|
|
await loginViaAPI(page, CONFIG.users.creator.email, CONFIG.users.creator.password);
|
|
await page.setViewportSize({ width: 375, height: 812 });
|
|
await navigateTo(page, '/playlists');
|
|
|
|
await expect(page.getByRole('heading', { name: /playlists/i, level: 1 })).toBeVisible();
|
|
});
|
|
|
|
test('19. tablet 768px: layout correct', async ({ page }) => {
|
|
await loginViaAPI(page, CONFIG.users.creator.email, CONFIG.users.creator.password);
|
|
await page.setViewportSize({ width: 768, height: 1024 });
|
|
await navigateTo(page, '/playlists');
|
|
|
|
await expect(page.getByRole('heading', { name: /playlists/i, level: 1 })).toBeVisible();
|
|
});
|
|
});
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Regression
|
|
// -------------------------------------------------------------------------
|
|
test.describe('Regression', () => {
|
|
test('20. BUG#1: playlists se chargent maintenant (plus de faux empty state)', async ({ page }) => {
|
|
await loginViaAPI(page, CONFIG.users.creator.email, CONFIG.users.creator.password);
|
|
await navigateTo(page, '/playlists');
|
|
|
|
// Should NOT show the empty state when user has playlists
|
|
// Wait a bit for data to load
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Should have playlist articles, not empty state
|
|
const articles = page.getByRole('article');
|
|
const emptyHeading = page.getByRole('heading', { name: /no playlists yet/i });
|
|
|
|
// Either articles are visible or empty state (for users with no playlists)
|
|
const hasArticles = await articles.first().isVisible().catch(() => false);
|
|
const hasEmpty = await emptyHeading.isVisible().catch(() => false);
|
|
|
|
// At least one should be visible (not stuck loading forever)
|
|
expect(hasArticles || hasEmpty).toBe(true);
|
|
|
|
// For the creator account which has playlists, articles should be visible
|
|
if (hasArticles) {
|
|
expect(await articles.count()).toBeGreaterThan(0);
|
|
}
|
|
});
|
|
|
|
test('21. BUG#2: pas de melange FR/EN dans les boutons', async ({ page }) => {
|
|
await loginViaAPI(page, CONFIG.users.creator.email, CONFIG.users.creator.password);
|
|
await navigateTo(page, '/playlists');
|
|
|
|
const body = await page.textContent('body') || '';
|
|
// Should not have French "Créer" or "Sélectionner" in English locale
|
|
expect(body).not.toMatch(/\bDécouvrez et gérez\b/);
|
|
expect(body).not.toMatch(/\bRechercher des playlists\b/);
|
|
});
|
|
|
|
test('22. BUG#3: page title est defini', async ({ page }) => {
|
|
await loginViaAPI(page, CONFIG.users.creator.email, CONFIG.users.creator.password);
|
|
await navigateTo(page, '/playlists');
|
|
const title = await page.title();
|
|
expect(title).not.toBe('Veza');
|
|
expect(title).toMatch(/Playlists/i);
|
|
});
|
|
});
|
|
});
|