import { test, expect } from '@chromatic-com/playwright'; import { loginViaAPI, CONFIG, navigateTo } from './helpers'; /** * UPLOAD - Track upload flow tests * Selectors based on UploadModal.tsx, UploadModalDropzone.tsx, UploadModalMetadataForm.tsx */ // Create a minimal valid MP3 buffer for testing function createTestMP3Buffer(): Buffer { return Buffer.from( '4944330300000000000a544954320000000500000054657374fffb90440000000000000000000000000000000000000000', 'hex', ); } test.describe('UPLOAD - Track upload flow @critical', () => { test.beforeEach(async ({ page }) => { // Login as creator (has upload permissions) await loginViaAPI(page, CONFIG.users.creator.email, CONFIG.users.creator.password); }); test('should complete full upload flow: file, metadata, publish, visible in library @critical', async ({ page }) => { await navigateTo(page, '/library'); // Find and click upload button const uploadBtn = page.getByRole('button', { name: /upload|uploader/i }).first(); const uploadVisible = await uploadBtn.isVisible({ timeout: 10_000 }).catch(() => false); if (!uploadVisible) { console.log(' Upload button not found — skipping'); return; } await uploadBtn.click(); await page.waitForTimeout(500); // Wait for upload modal/dialog const dialog = page.locator('[role="dialog"]').first(); const dialogVisible = await dialog.isVisible({ timeout: 5000 }).catch(() => false); if (!dialogVisible) { console.log(' Upload dialog did not appear — skipping'); return; } // Set file via the hidden input inside the dropzone const fileInput = dialog.locator('input[type="file"]').first(); const fileInputExists = await fileInput.count(); if (fileInputExists === 0) { console.log(' File input not found in upload dialog — skipping'); return; } const uniqueTitle = `E2E Upload ${Date.now()}`; await fileInput.setInputFiles({ name: 'test-track.mp3', mimeType: 'audio/mpeg', buffer: createTestMP3Buffer(), }); // Wait for file to be processed (dropzone disappears, metadata form appears) await page.waitForTimeout(1000); // Fill metadata const titleInput = dialog.locator('#title').or(dialog.locator('input[name="title"]')); if (!await titleInput.isVisible({ timeout: 5000 }).catch(() => false)) { console.log(' Title input not visible after file upload — skipping'); return; } await titleInput.fill(uniqueTitle); const artistInput = dialog.locator('#artist').or(dialog.locator('input[name="artist"]')); if (await artistInput.isVisible().catch(() => false)) { await artistInput.fill('E2E Test Artist'); } const genreInput = dialog.locator('#genre').or(dialog.locator('input[name="genre"]')); if (await genreInput.isVisible().catch(() => false)) { await genreInput.fill('Electronic'); } // Submit the form const submitBtn = dialog.locator('button[type="submit"]') .or(dialog.locator('button[form="upload-track-form"]')) .or(dialog.getByRole('button', { name: /uploader/i })); if (!await submitBtn.isVisible({ timeout: 3000 }).catch(() => false)) { console.log(' Submit button not visible — skipping'); return; } await submitBtn.click(); // Wait for upload completion (success message or dialog closes) const success = dialog.locator('text=/upload|success|succ/i').first(); const dialogClosed = page.waitForSelector('[role="dialog"]', { state: 'hidden', timeout: 60_000 }).catch(() => null); await Promise.race([ success.waitFor({ state: 'visible', timeout: 60_000 }).catch(() => {}), dialogClosed, ]); // Verify track appears in library after reload await navigateTo(page, '/library'); await page.waitForTimeout(2000); // Search for the uploaded track const trackInLibrary = page.locator(`text=${uniqueTitle}`).first(); const isVisible = await trackInLibrary.isVisible({ timeout: 10_000 }).catch(() => false); if (isVisible) { console.log(' Track visible in library'); } else { console.warn(' Track not yet visible (may still be processing)'); } }); test('should show error for invalid file format', async ({ page }) => { await navigateTo(page, '/library'); const uploadBtn = page.getByRole('button', { name: /upload|uploader/i }).first(); const uploadVisible = await uploadBtn.isVisible({ timeout: 10_000 }).catch(() => false); if (!uploadVisible) { console.log(' Upload button not found — skipping'); return; } await uploadBtn.click(); await page.waitForTimeout(500); const dialog = page.locator('[role="dialog"]').first(); const dialogVisible = await dialog.isVisible({ timeout: 5000 }).catch(() => false); if (!dialogVisible) { console.log(' Upload dialog did not appear — skipping'); return; } const fileInput = dialog.locator('input[type="file"]').first(); if (await fileInput.count() === 0) { console.log(' File input not found — skipping'); return; } // Try uploading a text file await fileInput.setInputFiles({ name: 'invalid.txt', mimeType: 'text/plain', buffer: Buffer.from('This is not an audio file'), }); await page.waitForTimeout(1000); // Either: file is rejected (dropzone still visible), or error message appears const errorMsg = dialog.locator('text=/format|invalid|non supporté|rejected/i').first(); const dropzoneStillVisible = dialog.locator('text=/glissez|drag|drop/i').first(); const hasError = await errorMsg.isVisible({ timeout: 3000 }).catch(() => false); const dropzoneBack = await dropzoneStillVisible.isVisible({ timeout: 3000 }).catch(() => false); expect(hasError || dropzoneBack).toBeTruthy(); }); test('should show validation error when submitting without file', async ({ page }) => { await navigateTo(page, '/library'); const uploadBtn = page.getByRole('button', { name: /upload|uploader/i }).first(); const uploadVisible = await uploadBtn.isVisible({ timeout: 10_000 }).catch(() => false); if (!uploadVisible) { console.log(' Upload button not found — skipping'); return; } await uploadBtn.click(); await page.waitForTimeout(500); const dialog = page.locator('[role="dialog"]').first(); const dialogVisible = await dialog.isVisible({ timeout: 5000 }).catch(() => false); if (!dialogVisible) { console.log(' Upload dialog did not appear — skipping'); return; } // The submit button should be disabled when no file is selected const submitBtn = dialog.locator('button[type="submit"]') .or(dialog.locator('button[form="upload-track-form"]')) .or(dialog.getByRole('button', { name: /uploader/i })); if (await submitBtn.isVisible({ timeout: 3000 }).catch(() => false)) { const isDisabled = await submitBtn.isDisabled(); expect(isDisabled).toBeTruthy(); } }); test('should close modal with Escape or close button', async ({ page }) => { await navigateTo(page, '/library'); const uploadBtn = page.getByRole('button', { name: /upload|uploader/i }).first(); const uploadVisible = await uploadBtn.isVisible({ timeout: 10_000 }).catch(() => false); if (!uploadVisible) { console.log(' Upload button not found — skipping'); return; } await uploadBtn.click(); await page.waitForTimeout(500); const dialog = page.locator('[role="dialog"]').first(); const dialogVisible = await dialog.isVisible({ timeout: 5000 }).catch(() => false); if (!dialogVisible) { console.log(' Upload dialog did not appear — skipping'); return; } // Close via button const closeBtn = dialog.getByRole('button', { name: /close|cancel|fermer|annuler/i }).first(); if (await closeBtn.isVisible().catch(() => false)) { await closeBtn.click(); await expect(dialog).not.toBeVisible({ timeout: 3000 }); } else { // Close via Escape await page.keyboard.press('Escape'); await expect(dialog).not.toBeVisible({ timeout: 3000 }); } }); });