- 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>
213 lines
8.6 KiB
TypeScript
213 lines
8.6 KiB
TypeScript
import { test, expect } from '@chromatic-com/playwright';
|
|
import { loginViaAPI, CONFIG, navigateTo } from './helpers';
|
|
|
|
/**
|
|
* MARKETPLACE & CHECKOUT — Tests du flux d'achat
|
|
* Sélecteurs basés sur MarketplacePage.tsx, ProductCard.tsx, Cart.tsx, CartStore.ts
|
|
*/
|
|
|
|
test.describe('MARKETPLACE & CHECKOUT @critical', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password);
|
|
// Clear cart before each test
|
|
await page.evaluate(() => {
|
|
localStorage.removeItem('veza-cart-storage');
|
|
});
|
|
});
|
|
|
|
test('Marketplace — produits affichés avec prix et boutons @critical', async ({ page }) => {
|
|
await navigateTo(page, '/marketplace');
|
|
|
|
// Wait for products grid to load
|
|
const productCard = page.locator('[aria-label^="Product:"]').first()
|
|
.or(page.locator('[class*="CardFooter"]').first());
|
|
|
|
const hasProducts = await productCard.isVisible({ timeout: 10_000 }).catch(() => false);
|
|
|
|
if (hasProducts) {
|
|
// Verify price is visible (soft check)
|
|
const price = page.locator('text=/\\$|€|USD/').first();
|
|
const hasPrice = await price.isVisible({ timeout: 3000 }).catch(() => false);
|
|
console.log(` Products found, price visible: ${hasPrice}`);
|
|
|
|
// Verify Buy button exists (soft check)
|
|
const buyBtn = page.getByRole('button', { name: /buy|acheter/i }).first();
|
|
const hasBuy = await buyBtn.isVisible({ timeout: 3000 }).catch(() => false);
|
|
console.log(` Buy button visible: ${hasBuy}`);
|
|
} else {
|
|
// Empty marketplace is valid — just check the page loaded without crash
|
|
const body = await page.textContent('body') || '';
|
|
expect(body).not.toMatch(/500|Internal Server Error/);
|
|
expect(body.length).toBeGreaterThan(50);
|
|
console.log(' No products found — marketplace may be empty (valid state)');
|
|
}
|
|
});
|
|
|
|
test('Recherche marketplace — filtrer les produits', async ({ page }) => {
|
|
await navigateTo(page, '/marketplace');
|
|
|
|
const searchInput = page.locator('input[placeholder*="Search" i]').first()
|
|
.or(page.locator('input[placeholder*="Recherch" i]').first());
|
|
|
|
if (await searchInput.isVisible({ timeout: 5000 }).catch(() => false)) {
|
|
await searchInput.fill('beat');
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Results should update (either products or empty state)
|
|
const body = await page.textContent('body');
|
|
expect(body!.length).toBeGreaterThan(50);
|
|
}
|
|
});
|
|
|
|
test('Ajout au panier → badge panier incrémente @critical', async ({ page }) => {
|
|
await navigateTo(page, '/marketplace');
|
|
|
|
// Find a product card
|
|
const productCard = page.locator('[aria-label^="Product:"]').first();
|
|
|
|
if (await productCard.isVisible({ timeout: 10_000 }).catch(() => false)) {
|
|
// Hover to reveal Add to Cart
|
|
await productCard.hover();
|
|
await page.waitForTimeout(300);
|
|
|
|
const addToCartBtn = productCard.getByRole('button', { name: /add to cart|ajouter/i }).first()
|
|
.or(productCard.locator('button[class*="outline"]').first());
|
|
|
|
if (await addToCartBtn.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
await addToCartBtn.click();
|
|
await page.waitForTimeout(500);
|
|
|
|
// Check cart badge updated
|
|
const cartBadge = page.locator('text=/^1$|^[1-9]$/').first();
|
|
const hasBadge = await cartBadge.isVisible({ timeout: 3000 }).catch(() => false);
|
|
if (hasBadge) {
|
|
console.log('✅ Cart badge shows item count');
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
test('Ouvrir le panier — affiche les produits ajoutés @critical', async ({ page }) => {
|
|
await navigateTo(page, '/marketplace');
|
|
|
|
// Add a product to cart first
|
|
const productCard = page.locator('[aria-label^="Product:"]').first();
|
|
if (await productCard.isVisible({ timeout: 10_000 }).catch(() => false)) {
|
|
await productCard.hover();
|
|
await page.waitForTimeout(300);
|
|
const addBtn = productCard.getByRole('button', { name: /add to cart|ajouter/i }).first()
|
|
.or(productCard.locator('button[class*="outline"]').first());
|
|
if (await addBtn.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
await addBtn.click();
|
|
await page.waitForTimeout(500);
|
|
}
|
|
}
|
|
|
|
// Open cart
|
|
const cartBtn = page.getByRole('button', { name: /cart|panier/i }).first()
|
|
.or(page.locator('button').filter({ has: page.locator('[class*="ShoppingCart"]') }).first());
|
|
|
|
if (await cartBtn.isVisible({ timeout: 5000 }).catch(() => false)) {
|
|
await cartBtn.click();
|
|
await page.waitForTimeout(500);
|
|
|
|
// Cart dialog should open
|
|
const cartDialog = page.locator('[role="dialog"]').first();
|
|
if (await cartDialog.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
// Should show cart title
|
|
const cartTitle = cartDialog.locator('text=/shopping cart|panier/i').first();
|
|
await expect(cartTitle).toBeVisible({ timeout: 3000 });
|
|
}
|
|
}
|
|
});
|
|
|
|
test('Panier — supprimer un produit @critical', async ({ page }) => {
|
|
await navigateTo(page, '/marketplace');
|
|
|
|
// Add product then open cart
|
|
const productCard = page.locator('[aria-label^="Product:"]').first();
|
|
if (await productCard.isVisible({ timeout: 10_000 }).catch(() => false)) {
|
|
await productCard.hover();
|
|
const addBtn = productCard.getByRole('button', { name: /add to cart|ajouter/i }).first()
|
|
.or(productCard.locator('button[class*="outline"]').first());
|
|
if (await addBtn.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
await addBtn.click();
|
|
await page.waitForTimeout(500);
|
|
}
|
|
}
|
|
|
|
const cartBtn = page.getByRole('button', { name: /cart|panier/i }).first();
|
|
if (await cartBtn.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
await cartBtn.click();
|
|
await page.waitForTimeout(500);
|
|
|
|
const removeBtn = page.locator('[aria-label="Remove item"]').first();
|
|
if (await removeBtn.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
await removeBtn.click();
|
|
await page.waitForTimeout(500);
|
|
|
|
// Cart should now show empty
|
|
const emptyCart = page.locator('text=/cart is empty|panier est vide/i').first();
|
|
const isEmpty = await emptyCart.isVisible({ timeout: 3000 }).catch(() => false);
|
|
if (isEmpty) {
|
|
console.log('✅ Cart emptied after removing item');
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
test('Panier vide — message et CTA vers marketplace', async ({ page }) => {
|
|
await navigateTo(page, '/marketplace');
|
|
|
|
// Open cart without adding anything
|
|
const cartBtn = page.getByRole('button', { name: /cart|panier/i }).first();
|
|
if (await cartBtn.isVisible({ timeout: 5000 }).catch(() => false)) {
|
|
await cartBtn.click();
|
|
await page.waitForTimeout(500);
|
|
|
|
const emptyMsg = page.locator('text=/cart is empty|panier est vide/i').first();
|
|
await expect(emptyMsg).toBeVisible({ timeout: 3000 });
|
|
}
|
|
});
|
|
|
|
test('Checkout — le formulaire de paiement se charge @critical', async ({ page }) => {
|
|
await navigateTo(page, '/marketplace');
|
|
|
|
// Add product and go to checkout
|
|
const productCard = page.locator('[aria-label^="Product:"]').first();
|
|
if (await productCard.isVisible({ timeout: 10_000 }).catch(() => false)) {
|
|
await productCard.hover();
|
|
const addBtn = productCard.getByRole('button', { name: /add to cart|ajouter/i }).first()
|
|
.or(productCard.locator('button[class*="outline"]').first());
|
|
if (await addBtn.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
await addBtn.click();
|
|
await page.waitForTimeout(500);
|
|
}
|
|
}
|
|
|
|
const cartBtn = page.getByRole('button', { name: /cart|panier/i }).first();
|
|
if (await cartBtn.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
await cartBtn.click();
|
|
await page.waitForTimeout(500);
|
|
|
|
// Look for checkout/pay button
|
|
const checkoutBtn = page.getByRole('button', { name: /checkout|payer|pay/i }).first();
|
|
if (await checkoutBtn.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
await checkoutBtn.click();
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Verify payment form loads (Hyperswitch iframe or payment form)
|
|
const paymentForm = page.locator('iframe').first()
|
|
.or(page.locator('text=/complete payment|paiement/i').first());
|
|
const hasPayment = await paymentForm.isVisible({ timeout: 5000 }).catch(() => false);
|
|
|
|
if (hasPayment) {
|
|
console.log('✅ Payment form loaded');
|
|
} else {
|
|
// Payment might need server-side setup
|
|
console.warn('⚠ Payment form not loaded (Hyperswitch may not be configured)');
|
|
}
|
|
}
|
|
}
|
|
});
|
|
});
|