Some checks failed
Backend API CI / test-unit (push) Failing after 3m49s
Backend API CI / test-integration (push) Failing after 2m2s
Veza CD / Build and push images (push) Failing after 2m27s
Veza CI/CD / TMT Vital — Backend (Go) (push) Failing after 37s
Veza CI/CD / TMT Vital — Rust Services (push) Failing after 4s
Veza CI/CD / TMT Vital — Frontend (Web) (push) Failing after 2m49s
Veza CI/CD / Storybook Audit (push) Failing after 46s
Veza CI/CD / E2E (Playwright) (push) Failing after 56s
CodeQL SAST / analyze (go) (push) Failing after 4s
CodeQL SAST / analyze (javascript-typescript) (push) Failing after 11s
Veza CD / Deploy to staging (push) Has been skipped
Veza CI/CD / Notify on failure (push) Successful in 2s
Veza CD / Smoke tests post-deploy (push) Has been skipped
Security Scan / Secret Scanning (gitleaks) (push) Failing after 4s
Convert 20 files from fake assertions (console.log with ✓/✗) to real
expect() assertions. This completes the conversion started in the
previous session — zero console.log calls remain in the E2E suite.
Files converted (by batch):
Batch 1: 16-forms-validation (38→0), 13-workflows (18→0), 14-edge-cases (8→0)
Batch 2: 15-routes-coverage (8→0), 20-network-errors (5→0), 04-tracks (4→0),
32-deep-pages (4→0), 19-responsive (3→0), 11-accessibility-ethics (3→0)
Batch 3: 25-profile (2→0), 12-api (2→0), 29-chat-functional (2→0),
30-marketplace-checkout (1→0), 22-performance (1→0),
31-auth-sessions (1→0), 26-smoke (1→0), 02-navigation (1→0)
Batch 4: 24-cross-browser (0 fakes, 12 info→0), 34-workflows-empty (0→0),
33-visual-bugs (0→0)
Total: 139 fake assertions → real expect(), 159 informational logs removed
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
218 lines
8.1 KiB
TypeScript
218 lines
8.1 KiB
TypeScript
import { test, expect } from '@chromatic-com/playwright';
|
|
import { loginViaAPI, CONFIG, navigateTo, assertPageLoads, assertNoDebugText, assertNotBroken } from './helpers';
|
|
|
|
test.describe('NAVIGATION — Pages publiques (sans auth)', () => {
|
|
test('01. Page d\'accueil / redirige vers /dashboard ou /login', async ({ page }) => {
|
|
const errors = await assertPageLoads(page, '/');
|
|
expect(errors.length).toBeLessThan(3);
|
|
// Root / should redirect to /dashboard (if auth) or /login (if not)
|
|
await expect(page).toHaveURL(/dashboard|login/);
|
|
await assertNoDebugText(page);
|
|
});
|
|
|
|
test('02. Page /login se charge', async ({ page }) => {
|
|
await assertPageLoads(page, '/login');
|
|
});
|
|
|
|
test('03. Page /register se charge', async ({ page }) => {
|
|
await assertPageLoads(page, '/register');
|
|
});
|
|
|
|
test('04. Page /discover redirige vers /login si non authentifié', async ({ page }) => {
|
|
test.setTimeout(60_000);
|
|
await page.goto('/discover', { waitUntil: 'domcontentloaded' });
|
|
await page.waitForLoadState('networkidle').catch(() => {});
|
|
// /discover is a protected route, should redirect to login
|
|
// The app may take time to check auth and redirect
|
|
await expect(page).toHaveURL(/login/, { timeout: 20_000 });
|
|
});
|
|
|
|
test('05. Page 404 pour route inexistante', async ({ page }) => {
|
|
await navigateTo(page, '/this-page-does-not-exist-12345');
|
|
const body = await page.textContent('body');
|
|
// Should display a proper 404, not a crash
|
|
expect(body).toMatch(/404|not found|page.*introuvable|n'existe pas/i);
|
|
});
|
|
});
|
|
|
|
test.describe('NAVIGATION — Pages authentifiées', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password);
|
|
});
|
|
|
|
const authenticatedPages = [
|
|
{ path: '/dashboard', name: 'Dashboard' },
|
|
{ path: '/library', name: 'Bibliothèque' },
|
|
{ path: '/playlists', name: 'Playlists' },
|
|
{ path: '/notifications', name: 'Notifications' },
|
|
{ path: '/chat', name: 'Chat' },
|
|
{ path: '/settings', name: 'Paramètres' },
|
|
{ path: '/profile', name: 'Profil' },
|
|
{ path: '/feed', name: 'Feed' },
|
|
{ path: '/discover', name: 'Découverte' },
|
|
{ path: '/search', name: 'Recherche' },
|
|
];
|
|
|
|
for (const { path, name } of authenticatedPages) {
|
|
test(`06. Page ${name} (${path}) se charge @critical`, async ({ page }) => {
|
|
await navigateTo(page, path);
|
|
|
|
// No crash
|
|
const body = await page.textContent('body') || '';
|
|
expect(body).not.toMatch(/500|Internal Server Error|unexpected error|something went wrong/i);
|
|
|
|
// Page has content (not just an infinite spinner)
|
|
expect(body.length).toBeGreaterThan(100);
|
|
|
|
await assertNoDebugText(page);
|
|
});
|
|
}
|
|
});
|
|
|
|
test.describe('NAVIGATION — Layout principal', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password);
|
|
});
|
|
|
|
test('07. La sidebar est visible @critical', async ({ page }) => {
|
|
// Check login succeeded
|
|
await navigateTo(page, '/dashboard');
|
|
|
|
const sidebar = page.getByTestId('app-sidebar');
|
|
await expect(sidebar).toBeVisible({ timeout: 10_000 });
|
|
});
|
|
|
|
test('08. Le header est visible et le logo est dans la sidebar', async ({ page }) => {
|
|
await navigateTo(page, '/dashboard');
|
|
|
|
// Header has data-testid="app-header"
|
|
const header = page.locator('[data-testid="app-header"], header').first();
|
|
await expect(header).toBeVisible({ timeout: 5_000 });
|
|
|
|
// Logo "veza" is an h2 in the sidebar — it may be visually hidden when collapsed but still attached
|
|
const sidebar = page.getByTestId('app-sidebar');
|
|
await expect(sidebar).toBeVisible({ timeout: 5_000 });
|
|
// The h2 "veza" may be collapsed (opacity-0 max-w-0) but still in DOM
|
|
await expect(sidebar.locator('h2')).toBeAttached();
|
|
});
|
|
|
|
test('09. Les liens de navigation principaux sont présents et cliquables', async ({ page }) => {
|
|
await navigateTo(page, '/dashboard');
|
|
|
|
const navLinks = [
|
|
/dashboard/i,
|
|
/discover/i,
|
|
/library/i,
|
|
];
|
|
|
|
const sidebar = page.getByTestId('app-sidebar');
|
|
|
|
for (const linkText of navLinks) {
|
|
const link = sidebar.getByRole('link', { name: linkText })
|
|
.or(sidebar.getByRole('button', { name: linkText }))
|
|
.first();
|
|
|
|
await expect(link).toBeVisible({ timeout: 5_000 });
|
|
}
|
|
});
|
|
|
|
test('10. Le player bar est présent dans le DOM', async ({ page }) => {
|
|
await navigateTo(page, '/dashboard');
|
|
|
|
const playerBar = page.getByTestId('global-player');
|
|
|
|
// The player bar container should exist in the DOM even if not visible (nothing playing)
|
|
await expect(playerBar).toBeAttached({ timeout: 5_000 });
|
|
});
|
|
|
|
test('10b. Le search est dans le header avec role="search"', async ({ page }) => {
|
|
await navigateTo(page, '/dashboard');
|
|
|
|
// Header search: data-testid="search-input" type="search" inside role="search" container
|
|
const searchInput = page.locator('[data-testid="search-input"]')
|
|
.or(page.locator('[role="search"] input'))
|
|
.or(page.locator('input[type="search"]'));
|
|
// Check it exists in DOM even if hidden on small viewports (hidden md:block)
|
|
await expect(searchInput.first()).toBeAttached({ timeout: 5_000 });
|
|
});
|
|
});
|
|
|
|
test.describe('NAVIGATION — Responsive mobile @mobile', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password);
|
|
});
|
|
|
|
test('11. La page d\'accueil est utilisable sur mobile', async ({ page }) => {
|
|
await navigateTo(page, '/dashboard');
|
|
|
|
// No horizontal scroll (sign of broken layout)
|
|
const hasHorizontalScroll = await page.evaluate(() => {
|
|
return document.documentElement.scrollWidth > document.documentElement.clientWidth;
|
|
});
|
|
expect(hasHorizontalScroll).toBeFalsy();
|
|
});
|
|
|
|
test('12. Le menu hamburger fonctionne sur mobile', async ({ page }) => {
|
|
await navigateTo(page, '/dashboard');
|
|
|
|
const menuButton = page.getByRole('button', { name: /menu/i })
|
|
.or(page.locator('[class*="hamburger"]'))
|
|
.or(page.locator('[class*="menu-toggle"]'))
|
|
.or(page.getByTestId('mobile-menu'));
|
|
|
|
if (await menuButton.isVisible().catch(() => false)) {
|
|
await menuButton.click();
|
|
|
|
// Menu should open
|
|
const sidebar = page.getByTestId('app-sidebar');
|
|
await expect(sidebar).toBeVisible({ timeout: 3_000 });
|
|
}
|
|
});
|
|
});
|
|
|
|
test.describe('NAVIGATION — Internationalisation (i18n)', () => {
|
|
test('13. Changement de langue FR -> EN', async ({ page }) => {
|
|
await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password);
|
|
await navigateTo(page, '/settings');
|
|
|
|
// Find the language selector
|
|
const langSelector = page.getByLabel(/langue|language/i)
|
|
.or(page.locator('select[name*="lang"]'))
|
|
.or(page.getByTestId('language-selector'));
|
|
|
|
const langVisible = await langSelector.isVisible().catch(() => false);
|
|
if (!langVisible) {
|
|
test.skip(true, 'Language selector not found in /settings');
|
|
return;
|
|
}
|
|
|
|
await langSelector.selectOption({ label: /english/i });
|
|
await page.waitForTimeout(1_000);
|
|
|
|
// Verify English text appears
|
|
const body = await page.textContent('body') || '';
|
|
expect(body).toMatch(/settings|profile|account|logout/i);
|
|
});
|
|
|
|
test('14. Pas de clés i18n brutes visibles (ex: "auth.login.title")', async ({ page }) => {
|
|
await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password);
|
|
|
|
const pagesToCheck = ['/dashboard', '/discover', '/settings', '/library'];
|
|
|
|
for (const path of pagesToCheck) {
|
|
await navigateTo(page, path);
|
|
const body = await page.textContent('body') || '';
|
|
|
|
// Pattern: "word.word.word" that looks like an untranslated i18n key
|
|
const i18nKeyPattern = /\b[a-z]+\.[a-z]+\.[a-z]+\b/g;
|
|
const matches = body.match(i18nKeyPattern) || [];
|
|
// Filter false positives (URLs, etc.)
|
|
const suspiciousKeys = matches.filter(m =>
|
|
!m.includes('http') && !m.includes('www') && !m.includes('com') &&
|
|
!m.includes('min') && !m.includes('max') && m.length < 50
|
|
);
|
|
|
|
expect(suspiciousKeys.length).toBeLessThanOrEqual(5);
|
|
}
|
|
});
|
|
});
|