- Fix 98 TypeScript errors across 37 files: - Service layer double-unwrapping (subscriptionService, distributionService, gearService) - Self-referencing variables in SearchPageResults - FeedView/ExploreView .posts→.items alignment - useQueueSync Zustand subscribe API - AdminAuditLogsView missing interface fields - Toast proxy type, interceptor type narrowing - 22 unused imports/variables removed - 5 storybook mock data fixes - Align frontend API calls with backend endpoints: - Analytics: useAnalyticsView now calls /creator/analytics/dashboard (was /analytics) - Chat: chatService uses /conversations (was mock data), WS URL from backend token - Dashboard StatsSection: uses real /dashboard API data (was hardcoded zeros) - Settings: suppress 2FA toast error when endpoint unavailable - Fix marketplace products: seed uses 'active' status (was 'published') - Enrich seed: admin follows all creators (feed has content) - Optimize bundle: vendor catch-all 793KB→318KB gzip (-60%) Split into vendor-charts, vendor-emoji, vendor-swagger, vendor-media, etc. - Clean repo: remove ~100 orphaned screenshots, audit reports, logs from root Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
203 lines
6.6 KiB
TypeScript
203 lines
6.6 KiB
TypeScript
import { test, expect } from '@chromatic-com/playwright';
|
|
import { loginViaAPI, CONFIG, navigateTo, fillForm } from './helpers';
|
|
|
|
/**
|
|
* Smoke Tests @smoke @critical
|
|
*
|
|
* Combined from smoke-post-deploy.spec.ts and smoke.spec.ts.
|
|
* Quick checks to verify the application is functional.
|
|
*/
|
|
|
|
test.describe('SMOKE TESTS @smoke @critical', () => {
|
|
test.describe('Post-deploy smoke checks', () => {
|
|
test('homepage loads', async ({ page }) => {
|
|
const response = await page.goto('/', {
|
|
waitUntil: 'domcontentloaded',
|
|
timeout: 15000,
|
|
});
|
|
expect(response?.status()).toBeLessThan(500);
|
|
});
|
|
|
|
test('login page loads', async ({ page }) => {
|
|
const response = await page.goto('/login', {
|
|
waitUntil: 'domcontentloaded',
|
|
timeout: 15000,
|
|
});
|
|
expect(response?.status()).toBeLessThan(500);
|
|
});
|
|
|
|
test('API health check', async ({ request }) => {
|
|
const baseURL = CONFIG.apiURL;
|
|
const apiUrl = `${baseURL}/api/v1/health`;
|
|
try {
|
|
const response = await request.get(apiUrl, { timeout: 10000 });
|
|
expect(response.status()).toBeLessThan(500);
|
|
} catch {
|
|
// API health endpoint may not be reachable from this context
|
|
}
|
|
});
|
|
});
|
|
|
|
test.describe('Critical User Flows', () => {
|
|
test('complete user journey: Login -> Dashboard -> Navigation', async ({
|
|
page,
|
|
}) => {
|
|
test.setTimeout(90000);
|
|
|
|
// Step 1: Login
|
|
await loginViaAPI(
|
|
page,
|
|
CONFIG.users.listener.email,
|
|
CONFIG.users.listener.password,
|
|
);
|
|
|
|
// Verify user is authenticated — after login, URL should not be /login
|
|
await page.waitForURL((url) => !url.pathname.includes('/login'), { timeout: 15000 }).catch(() => {});
|
|
expect(page.url()).not.toContain('/login');
|
|
await expect(
|
|
page.locator('nav[role="navigation"], aside[role="navigation"]'),
|
|
).toBeVisible({ timeout: 10000 });
|
|
|
|
const isAuthenticated = await page.evaluate(() => {
|
|
try {
|
|
const authStorage = localStorage.getItem('auth-storage');
|
|
if (authStorage) {
|
|
const parsed = JSON.parse(authStorage);
|
|
return parsed.state?.isAuthenticated === true;
|
|
}
|
|
} catch {
|
|
return false;
|
|
}
|
|
return false;
|
|
});
|
|
expect(isAuthenticated).toBe(true);
|
|
|
|
// Step 2: Navigate to playlists
|
|
await navigateTo(page, '/playlists');
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Verify page loaded
|
|
const body = page.locator('body');
|
|
const bodyText = (await body.textContent()) || '';
|
|
expect(bodyText.length).toBeGreaterThan(50);
|
|
});
|
|
|
|
test('Login -> Create Playlist (no upload)', async ({ page }) => {
|
|
test.setTimeout(90000);
|
|
|
|
await loginViaAPI(
|
|
page,
|
|
CONFIG.users.listener.email,
|
|
CONFIG.users.listener.password,
|
|
);
|
|
|
|
await page.waitForURL((url) => !url.pathname.includes('/login'), { timeout: 15000 }).catch(() => {});
|
|
await page.waitForTimeout(1_000);
|
|
if (page.url().includes('/login')) {
|
|
console.log(' Login failed — skipping');
|
|
return;
|
|
}
|
|
|
|
// Navigate to playlists
|
|
await navigateTo(page, '/playlists');
|
|
|
|
// Try to find and click create button — scope to main content to avoid sidebar matches
|
|
const mainContent = page.locator('main').first();
|
|
const mainVisible = await mainContent.isVisible({ timeout: 5_000 }).catch(() => false);
|
|
const searchScope = mainVisible ? mainContent : page;
|
|
|
|
const createButton = searchScope
|
|
.locator(
|
|
'button:has-text("Create"), button:has-text("Créer"), button:has-text("Nouvelle"), button:has-text("New")',
|
|
)
|
|
.first();
|
|
const isCreateVisible = await createButton
|
|
.isVisible({ timeout: 10_000 })
|
|
.catch(() => false);
|
|
|
|
if (!isCreateVisible) {
|
|
console.log(' Create button not visible — skipping playlist creation');
|
|
} else {
|
|
await createButton.click({ force: true, timeout: 10_000 });
|
|
await page.waitForTimeout(500);
|
|
|
|
// Fill playlist form if modal appeared
|
|
const titleInput = page
|
|
.locator('input[id="title"], input[name="title"]')
|
|
.first();
|
|
const isTitleVisible = await titleInput
|
|
.isVisible({ timeout: 3000 })
|
|
.catch(() => false);
|
|
|
|
if (isTitleVisible) {
|
|
await titleInput.fill('Quick Test Playlist');
|
|
|
|
// Scope to the dialog to avoid clicking the sidebar button behind the modal overlay
|
|
const dialog = page.locator('[role="dialog"]').first();
|
|
const dialogVisible = await dialog.isVisible({ timeout: 3000 }).catch(() => false);
|
|
const submitScope = dialogVisible ? dialog : page;
|
|
const submitBtn = submitScope
|
|
.locator(
|
|
'button:has-text("Créer"), button:has-text("Create"), button[type="submit"]',
|
|
)
|
|
.first();
|
|
if (await submitBtn.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
await submitBtn.click();
|
|
await page.waitForTimeout(2000);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Verify page is still functional
|
|
const body = page.locator('body');
|
|
await expect(body).toBeVisible();
|
|
});
|
|
|
|
test('Login -> Upload Track (no playlist)', async ({ page }) => {
|
|
test.setTimeout(120000);
|
|
|
|
await loginViaAPI(
|
|
page,
|
|
CONFIG.users.listener.email,
|
|
CONFIG.users.listener.password,
|
|
);
|
|
|
|
await page.waitForURL((url) => !url.pathname.includes('/login'), { timeout: 15000 }).catch(() => {});
|
|
await page.waitForTimeout(1_000);
|
|
if (page.url().includes('/login')) {
|
|
console.log(' Login failed — skipping');
|
|
return;
|
|
}
|
|
|
|
// Navigate to library
|
|
await navigateTo(page, '/library');
|
|
|
|
// Try to find upload button
|
|
const uploadButton = page
|
|
.locator(
|
|
'button:has-text("Upload"), button:has-text("Envoyer"), button:has-text("Importer")',
|
|
)
|
|
.first();
|
|
const isUploadVisible = await uploadButton
|
|
.isVisible({ timeout: 5000 })
|
|
.catch(() => false);
|
|
|
|
if (isUploadVisible) {
|
|
await uploadButton.click();
|
|
await page.waitForTimeout(500);
|
|
|
|
// Check for file input
|
|
const fileInputLocator = page.locator('input[type="file"][accept*="audio"]');
|
|
const fileInputCount = await fileInputLocator.count();
|
|
|
|
if (fileInputCount > 0) {
|
|
console.log('Upload modal opened with file input');
|
|
}
|
|
}
|
|
|
|
// Verify page is still functional
|
|
const body = page.locator('body');
|
|
await expect(body).toBeVisible();
|
|
});
|
|
});
|
|
});
|