import { test, expect } from '@chromatic-com/playwright'; import { loginViaAPI, CONFIG, navigateTo } from './helpers'; /** * UPLOAD - Track upload flow tests * STRICT: every step must succeed or the test fails. * No silent skips, no console.log fallbacks. */ function createTestMP3Buffer(): Buffer { return Buffer.from( '4944330300000000000a544954320000000500000054657374fffb90440000000000000000000000000000000000000000', 'hex', ); } test.describe('UPLOAD - Track upload flow @critical', () => { test.beforeEach(async ({ page }) => { await loginViaAPI(page, CONFIG.users.creator.email, CONFIG.users.creator.password); }); test('should show upload button on library page', async ({ page }) => { await navigateTo(page, '/library'); const uploadBtn = page.getByRole('button', { name: /upload|uploader/i }).first(); await expect(uploadBtn, 'Upload button must be visible on /library').toBeVisible({ timeout: 10_000 }); }); test('should open upload modal when clicking upload button', async ({ page }) => { await navigateTo(page, '/library'); const uploadBtn = page.getByRole('button', { name: /upload|uploader/i }).first(); await expect(uploadBtn).toBeVisible({ timeout: 10_000 }); await uploadBtn.click(); const dialog = page.locator('[role="dialog"]').first(); await expect(dialog, 'Upload dialog must appear after clicking upload').toBeVisible({ timeout: 5_000 }); // Dialog must contain a file input const fileInput = dialog.locator('input[type="file"]'); expect(await fileInput.count(), 'File input must exist in upload dialog').toBeGreaterThan(0); }); test('should complete full upload flow: file, metadata, publish @critical', async ({ page }) => { test.setTimeout(120_000); await navigateTo(page, '/library'); // Step 1: Open upload modal const uploadBtn = page.getByRole('button', { name: /upload|uploader/i }).first(); await expect(uploadBtn).toBeVisible({ timeout: 10_000 }); await uploadBtn.click(); const dialog = page.locator('[role="dialog"]').first(); await expect(dialog).toBeVisible({ timeout: 5_000 }); // Step 2: Set file const fileInput = dialog.locator('input[type="file"]').first(); expect(await fileInput.count(), 'File input must exist').toBeGreaterThan(0); const uniqueTitle = `E2E Upload ${Date.now()}`; await fileInput.setInputFiles({ name: 'test-track.mp3', mimeType: 'audio/mpeg', buffer: createTestMP3Buffer(), }); // Step 3: Fill metadata — title input must appear after file is processed const titleInput = dialog.locator('#title').or(dialog.locator('input[name="title"]')); await expect(titleInput, 'Title input must appear after file upload').toBeVisible({ timeout: 10_000 }); await titleInput.fill(uniqueTitle); const artistInput = dialog.locator('#artist').or(dialog.locator('input[name="artist"]')); if (await artistInput.isVisible({ timeout: 2_000 }).catch(() => false)) { await artistInput.fill('E2E Test Artist'); } const genreInput = dialog.locator('#genre').or(dialog.locator('input[name="genre"]')); if (await genreInput.isVisible({ timeout: 2_000 }).catch(() => false)) { await genreInput.fill('Electronic'); } // Step 4: Submit const submitBtn = dialog.locator('button[type="submit"]') .or(dialog.locator('button[form="upload-track-form"]')) .or(dialog.getByRole('button', { name: /uploader/i })); await expect(submitBtn, 'Submit button must be visible').toBeVisible({ timeout: 3_000 }); await submitBtn.click(); // Step 5: Wait for upload to complete — dialog must close or show success await expect(dialog, 'Upload dialog must close after successful upload').not.toBeVisible({ timeout: 60_000 }); // Step 6: Verify track appears in library await navigateTo(page, '/library'); await page.waitForTimeout(2_000); const trackInLibrary = page.locator(`text=${uniqueTitle}`).first(); await expect( trackInLibrary, `Uploaded track "${uniqueTitle}" must be visible in library`, ).toBeVisible({ timeout: 15_000 }); }); test('should show error for invalid file format', async ({ page }) => { await navigateTo(page, '/library'); const uploadBtn = page.getByRole('button', { name: /upload|uploader/i }).first(); await expect(uploadBtn).toBeVisible({ timeout: 10_000 }); await uploadBtn.click(); const dialog = page.locator('[role="dialog"]').first(); await expect(dialog).toBeVisible({ timeout: 5_000 }); const fileInput = dialog.locator('input[type="file"]').first(); expect(await fileInput.count()).toBeGreaterThan(0); // Upload a text file — must be rejected await fileInput.setInputFiles({ name: 'invalid.txt', mimeType: 'text/plain', buffer: Buffer.from('This is not an audio file'), }); await page.waitForTimeout(1_000); // Either: error message appears, OR dropzone is still shown (file was rejected silently) 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: 3_000 }).catch(() => false); const dropzoneBack = await dropzoneStillVisible.isVisible({ timeout: 3_000 }).catch(() => false); expect( hasError || dropzoneBack, 'Invalid file must be rejected: error message or dropzone should remain', ).toBeTruthy(); }); test('should disable submit button when no file is selected', async ({ page }) => { await navigateTo(page, '/library'); const uploadBtn = page.getByRole('button', { name: /upload|uploader/i }).first(); await expect(uploadBtn).toBeVisible({ timeout: 10_000 }); await uploadBtn.click(); const dialog = page.locator('[role="dialog"]').first(); await expect(dialog).toBeVisible({ timeout: 5_000 }); // Submit button should either not exist yet (no file) or be disabled const submitBtn = dialog.locator('button[type="submit"]') .or(dialog.locator('button[form="upload-track-form"]')) .or(dialog.getByRole('button', { name: /uploader/i })); const isVisible = await submitBtn.isVisible({ timeout: 3_000 }).catch(() => false); if (isVisible) { await expect(submitBtn, 'Submit button must be disabled when no file is selected').toBeDisabled(); } // If submit button is not visible at all (only appears after file selection), that's also correct }); test('should close modal with Escape key', async ({ page }) => { await navigateTo(page, '/library'); const uploadBtn = page.getByRole('button', { name: /upload|uploader/i }).first(); await expect(uploadBtn).toBeVisible({ timeout: 10_000 }); await uploadBtn.click(); const dialog = page.locator('[role="dialog"]').first(); await expect(dialog).toBeVisible({ timeout: 5_000 }); // Close via Escape await page.keyboard.press('Escape'); await expect(dialog, 'Dialog must close after pressing Escape').not.toBeVisible({ timeout: 3_000 }); }); test('should close modal with close button', async ({ page }) => { await navigateTo(page, '/library'); const uploadBtn = page.getByRole('button', { name: /upload|uploader/i }).first(); await expect(uploadBtn).toBeVisible({ timeout: 10_000 }); await uploadBtn.click(); const dialog = page.locator('[role="dialog"]').first(); await expect(dialog).toBeVisible({ timeout: 5_000 }); // Close via button const closeBtn = dialog.getByRole('button', { name: /close|cancel|fermer|annuler/i }).first(); await expect(closeBtn, 'Close/Cancel button must exist in dialog').toBeVisible({ timeout: 3_000 }); await closeBtn.click(); await expect(dialog, 'Dialog must close after clicking close button').not.toBeVisible({ timeout: 3_000 }); }); });