veza/tests/e2e/10-features.spec.ts
senke 8a2117031b fix(e2e): increase expect timeout to 10s + fix selector mismatches
Root cause analysis via Playwright MCP snapshots revealed that all
35 remaining E2E failures were timing issues, not real app bugs.
Every tested element (Notifications bell, Settings tabs, Search
combobox, Discover genres, Marketplace products, Social tabs) renders
correctly — but the 5s expect timeout was too short for React SPA
hydration.

Changes:
- Increase expect timeout from 5s to 10s in playwright.config.ts
- Fix avatar selector: add img[alt="username"] fallback (no "avatar" class)
- Fix profile edit test: /profile/edit doesn't exist, fields are on /settings
- Fix language selector: handle hidden input from custom Select component
- Fix GoLive regex: include "stream configuration" and "obs" alternatives
- Fix analytics period: match button text "7d" exactly
- Add 10s timeouts to critical assertions (discover, marketplace headings)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 20:26:52 +02:00

233 lines
8.9 KiB
TypeScript

import { test, expect } from '@chromatic-com/playwright';
import { loginViaAPI, CONFIG, navigateTo } from './helpers';
// ============================================================================
// ANALYTICS — Dashboard créateur (/analytics)
// ============================================================================
test.describe('ANALYTICS — Créateur', () => {
test.beforeEach(async ({ page }) => {
await loginViaAPI(page, CONFIG.users.creator.email, CONFIG.users.creator.password);
});
test('01. Dashboard analytics se charge @critical', async ({ page }) => {
await navigateTo(page, '/analytics');
const body = await page.textContent('body') || '';
expect(body).not.toMatch(/500|error|crash/i);
});
test('02. Graphiques/charts s\'affichent', async ({ page }) => {
await navigateTo(page, '/analytics');
const charts = page.locator('canvas, svg[class*="chart"], [class*="recharts"], [class*="Chart"]');
expect(await charts.count()).toBeGreaterThan(0);
});
test('03. Période sélectionnable (7j, 30j, 90j, etc.)', async ({ page }) => {
await navigateTo(page, '/analytics');
// Period selector renders as buttons with text "7d", "30d", "90d", "ytd"
const periodBtn = page.getByRole('button', { name: /^7d$|^30d$|^90d$|^ytd$/i }).first()
.or(page.locator('button').filter({ hasText: /^7d$/ }).first())
.or(page.getByText('7d').first());
await expect(periodBtn).toBeVisible({ timeout: 10_000 });
});
});
// ============================================================================
// SUBSCRIPTIONS — Abonnements (/subscription)
// ============================================================================
test.describe('SUBSCRIPTIONS — Abonnements', () => {
test.beforeEach(async ({ page }) => {
await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password);
});
test('04. Page /subscription se charge @critical', async ({ page }) => {
await navigateTo(page, '/subscription');
const body = await page.textContent('body') || '';
expect(body).not.toMatch(/500|error|crash/i);
});
test('05. Les plans sont affichés', async ({ page }) => {
await navigateTo(page, '/subscription');
const body = await page.textContent('body') || '';
expect(body).toMatch(/free/i);
expect(body).toMatch(/creator/i);
expect(body).toMatch(/premium/i);
});
test('06. Prix affichés correctement', async ({ page }) => {
await navigateTo(page, '/subscription');
const body = await page.textContent('body') || '';
expect(body).toMatch(/\$\d+\.\d{2}|\d+[,\.]\d{2}\s*€/i);
});
});
// ============================================================================
// ADMIN — Dashboard administrateur (/admin)
// ============================================================================
test.describe('ADMIN — Dashboard', () => {
test.beforeEach(async ({ page }) => {
await loginViaAPI(page, CONFIG.users.admin.email, CONFIG.users.admin.password);
});
test('07. Dashboard /admin accessible @critical', async ({ page }) => {
await navigateTo(page, '/admin');
const body = await page.textContent('body') || '';
expect(body).not.toMatch(/500|Internal Server Error/i);
});
test('08. Modération accessible à /admin/moderation', async ({ page }) => {
await navigateTo(page, '/admin/moderation');
const body = await page.textContent('body') || '';
expect(body).not.toMatch(/500|Internal Server Error/i);
});
test('09. Platform admin à /admin/platform', async ({ page }) => {
await navigateTo(page, '/admin/platform');
const body = await page.textContent('body') || '';
expect(body).not.toMatch(/500|Internal Server Error/i);
});
test('10. Transfers admin à /admin/transfers', async ({ page }) => {
await navigateTo(page, '/admin/transfers');
const body = await page.textContent('body') || '';
expect(body).not.toMatch(/500|Internal Server Error/i);
});
test('11. Roles admin à /admin/roles', async ({ page }) => {
await navigateTo(page, '/admin/roles');
const body = await page.textContent('body') || '';
expect(body).not.toMatch(/500|Internal Server Error/i);
});
test('12. Admin non accessible pour un user normal', async ({ page }) => {
test.setTimeout(30_000);
await page.goto('/login', { waitUntil: 'domcontentloaded', timeout: 10_000 });
await page.waitForTimeout(1_000);
await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password);
await page.waitForTimeout(3_000);
await page.goto('/admin', { timeout: 10_000 }).catch(() => {});
await page.waitForLoadState('domcontentloaded').catch(() => {});
await page.waitForTimeout(2_000);
const body = await page.textContent('body') || '';
const currentUrl = page.url();
const isRedirected = !currentUrl.includes('/admin');
const isBlockedByMessage = /403|forbidden|accès.*refusé|unauthorized|not authorized|access denied/i.test(body);
expect(isRedirected || isBlockedByMessage).toBeTruthy();
});
});
// ============================================================================
// LIVE STREAMING (/live, /live/go-live)
// ============================================================================
test.describe('LIVE — Streaming', () => {
test('13. Page /live se charge', async ({ page }) => {
await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password);
await navigateTo(page, '/live');
const body = await page.textContent('body') || '';
expect(body).not.toMatch(/500|Internal Server Error/i);
});
test('14. Page /live/go-live accessible pour créateur', async ({ page }) => {
await loginViaAPI(page, CONFIG.users.creator.email, CONFIG.users.creator.password);
await navigateTo(page, '/live/go-live');
const body = await page.textContent('body') || '';
expect(body).not.toMatch(/500|Internal Server Error/i);
// GoLive page shows RTMP info after configuration, or shows the setup form
expect(body).toMatch(/rtmp|stream.*key|clé|go.*live|broadcast|connection.*info|obs|streamlabs|stream.*configuration/i);
});
});
// ============================================================================
// CLOUD STORAGE (/cloud)
// ============================================================================
test.describe('CLOUD — Stockage', () => {
test('15. Page /cloud se charge', async ({ page }) => {
await loginViaAPI(page, CONFIG.users.creator.email, CONFIG.users.creator.password);
await navigateTo(page, '/cloud');
const body = await page.textContent('body') || '';
expect(body).not.toMatch(/500|Internal Server Error/i);
});
test('16. Zone d\'upload de fichiers', async ({ page }) => {
await loginViaAPI(page, CONFIG.users.creator.email, CONFIG.users.creator.password);
await navigateTo(page, '/cloud');
const uploadBtn = page.getByRole('button', { name: /upload|importer|ajouter|add/i })
.or(page.locator('input[type="file"]'));
await expect(uploadBtn.first()).toBeVisible();
});
});
// ============================================================================
// EDUCATION — Cours et formations (/education)
// ============================================================================
test.describe('EDUCATION — Cours', () => {
test('17. Page /education se charge', async ({ page }) => {
await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password);
await navigateTo(page, '/education');
const body = await page.textContent('body') || '';
expect(body).not.toMatch(/500|error|crash/i);
});
});
// ============================================================================
// GEAR — Gestion d'équipement (/gear)
// ============================================================================
test.describe('GEAR — Équipement', () => {
test('18. Page /gear se charge', async ({ page }) => {
await loginViaAPI(page, CONFIG.users.creator.email, CONFIG.users.creator.password);
await navigateTo(page, '/gear');
const body = await page.textContent('body') || '';
expect(body).not.toMatch(/500|error|crash/i);
});
});
// ============================================================================
// DEVELOPER — API & Webhooks (/developer)
// ============================================================================
test.describe('DEVELOPER — API publique', () => {
test('19. Page /developer accessible', async ({ page }) => {
await loginViaAPI(page, CONFIG.users.creator.email, CONFIG.users.creator.password);
await navigateTo(page, '/developer');
const body = await page.textContent('body') || '';
expect(body).not.toMatch(/500|Internal Server Error|crash|TypeError/i);
});
test('20. Page /webhooks accessible', async ({ page }) => {
await loginViaAPI(page, CONFIG.users.creator.email, CONFIG.users.creator.password);
await navigateTo(page, '/webhooks');
const body = await page.textContent('body') || '';
expect(body).not.toMatch(/500|error|crash/i);
});
});