veza/tests/e2e/39-feed.spec.ts

224 lines
8.8 KiB
TypeScript
Raw Normal View History

import { test, expect } from '@playwright/test';
import { CONFIG, loginViaAPI } from './helpers';
const BASE = CONFIG.baseURL;
test.describe('Fil d\'actualité (/feed)', () => {
test.beforeEach(async ({ page }) => {
await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password);
});
test.describe('Chargement & Rendu', () => {
test('la page se charge sans erreur', async ({ page }) => {
await page.goto(`${BASE}/feed`, { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(3000);
expect(page.url()).toContain('/feed');
await expect(page.getByRole('heading', { level: 1 })).toBeVisible();
});
test('le titre et sous-titre sont affichés', async ({ page }) => {
await page.goto(`${BASE}/feed`, { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(3000);
await expect(page.getByRole('heading', { level: 1 })).toContainText(/Feed/);
await expect(page.getByText(/Latest tracks|Derniers morceaux|Últimas pistas/)).toBeVisible();
});
test('la grille de tracks est visible', async ({ page }) => {
await page.goto(`${BASE}/feed`, { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(3000);
const grid = page.getByRole('grid');
await expect(grid).toBeVisible();
const articles = page.getByRole('article');
expect(await articles.count()).toBeGreaterThan(0);
});
});
test.describe('Fonctionnalités', () => {
test('les cartes de tracks ont un bouton play', async ({ page }) => {
await page.goto(`${BASE}/feed`, { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(3000);
// Hover over first track card to reveal play button
const firstArticle = page.getByRole('article').first();
await firstArticle.hover();
// Play button should be visible with proper aria-label
const playButton = firstArticle.getByRole('button', { name: /Play|Lire|Reproducir/ });
await expect(playButton).toBeVisible();
});
test('le widget suggestions est visible sur desktop', async ({ page }) => {
await page.setViewportSize({ width: 1280, height: 800 });
await page.goto(`${BASE}/feed`, { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(3000);
await expect(page.getByRole('heading', { name: /Suggested Accounts|Comptes suggérés|Cuentas sugeridas/ })).toBeVisible();
});
test('le widget suggestions a des boutons follow', async ({ page }) => {
await page.setViewportSize({ width: 1280, height: 800 });
await page.goto(`${BASE}/feed`, { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(3000);
const followButtons = page.getByRole('button', { name: /Follow|Suivre|Seguir/ });
expect(await followButtons.count()).toBeGreaterThan(0);
});
test('le lien See all mène vers /social', async ({ page }) => {
await page.setViewportSize({ width: 1280, height: 800 });
await page.goto(`${BASE}/feed`, { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(3000);
const seeAllLink = page.getByRole('link', { name: /See all|Voir tout|Ver todo/ });
await expect(seeAllLink).toHaveAttribute('href', '/social');
});
});
test.describe('Sécurité', () => {
test('la page nécessite une authentification', async ({ page }) => {
// Clear auth state
await page.goto(`${BASE}/`, { waitUntil: 'commit' });
await page.evaluate(() => localStorage.removeItem('auth-storage'));
await page.goto(`${BASE}/feed`, { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(3000);
// Should redirect to login
expect(page.url()).toContain('/login');
});
test('pas de fuite de token dans le DOM', async ({ page }) => {
await page.goto(`${BASE}/feed`, { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(3000);
const html = await page.content();
expect(html).not.toMatch(/eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/); // JWT pattern
});
});
test.describe('Accessibilité', () => {
test('la grille a un aria-label traduit (pas de FR hardcodé)', async ({ page }) => {
await page.goto(`${BASE}/feed`, { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(3000);
// BUG #3 regression: grid aria-label should be translated
const grid = page.getByRole('grid');
const gridLabel = await grid.getAttribute('aria-label');
expect(gridLabel).toBeTruthy();
// Should not be the old hardcoded French if UI is English
// (it will be French if user switched to FR, which is correct)
});
test('les boutons play ont un aria-label traduit (pas de mélange FR/EN)', async ({ page }) => {
await page.goto(`${BASE}/feed`, { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(3000);
// BUG #2 regression: play button aria-labels should not mix FR/EN
const firstArticle = page.getByRole('article').first();
await firstArticle.hover();
const playButton = firstArticle.getByRole('button', { name: /Play|Lire|Reproducir/ });
const label = await playButton.getAttribute('aria-label');
expect(label).toBeTruthy();
// If EN: should start with "Play" not "Lire"
// If FR: should start with "Lire" not "Play"
// Should not contain "pour" if English
if (label?.includes('Play')) {
expect(label).not.toMatch(/\bpour\b/);
}
});
test('les éléments interactifs sont focusables par Tab', async ({ page }) => {
await page.goto(`${BASE}/feed`, { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(3000);
// Tab should move focus to interactive elements
await page.keyboard.press('Tab');
const focusedElement = await page.evaluate(() => document.activeElement?.tagName);
expect(focusedElement).toBeTruthy();
});
});
test.describe('i18n', () => {
test('pas de clés i18n brutes affichées', async ({ page }) => {
await page.goto(`${BASE}/feed`, { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(3000);
const text = await page.textContent('body');
expect(text).not.toMatch(/feed\./);
expect(text).not.toMatch(/tracks\.grid\./);
});
test('pas de mélange FR/EN dans les aria-labels', async ({ page }) => {
await page.goto(`${BASE}/feed`, { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(3000);
// BUG #2 regression: check that aria-labels are consistent
const frLabels = await page.evaluate(() => {
const buttons = Array.from(document.querySelectorAll('button'));
return buttons
.map(b => b.getAttribute('aria-label') || '')
.filter(l => /\bpour\b/.test(l) && /More options/.test(l));
});
// Should have no "More options pour" (mixed FR/EN) labels
expect(frLabels).toHaveLength(0);
});
test('SuggestionsWidget utilise des traductions', async ({ page }) => {
await page.setViewportSize({ width: 1280, height: 800 });
await page.goto(`${BASE}/feed`, { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(3000);
// BUG #1 regression: "Suggested Accounts" should come from i18n
const heading = page.getByRole('heading', { name: /Suggested Accounts|Comptes suggérés|Cuentas sugeridas/ });
await expect(heading).toBeVisible();
// "followers" text should be present and translated
await expect(page.getByText(/followers|abonnés|seguidores/i).first()).toBeVisible();
});
});
test.describe('Responsive', () => {
test('mobile 375px - la page est fonctionnelle', async ({ page }) => {
await page.setViewportSize({ width: 375, height: 812 });
await page.goto(`${BASE}/feed`, { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(3000);
// Feed heading should be visible
await expect(page.getByRole('heading', { level: 1 })).toBeVisible();
// Track grid should be visible
await expect(page.getByRole('grid')).toBeVisible();
});
test('tablet 768px - la page est fonctionnelle', async ({ page }) => {
await page.setViewportSize({ width: 768, height: 1024 });
await page.goto(`${BASE}/feed`, { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(3000);
await expect(page.getByRole('heading', { level: 1 })).toBeVisible();
await expect(page.getByRole('grid')).toBeVisible();
});
});
test.describe('Réseau & API', () => {
test('l\'API /feed retourne 200', async ({ page }) => {
let feedStatus = 0;
page.on('response', (response) => {
if (response.url().includes('/api/v1/feed')) {
feedStatus = response.status();
}
});
await page.goto(`${BASE}/feed`, { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(3000);
expect(feedStatus).toBe(200);
});
});
});