import { test, expect, Page } from '@playwright/test'; import * as path from 'path'; // Test data const testEmail = `user+files-${Date.now()}@lab.veza`; const testPassword = `V3za!files-${Date.now()}`; // Helper to register and login async function registerAndLogin(page: Page) { await page.goto('/register'); await page.fill('input[type="email"]', testEmail); await page.fill('input[type="password"]', testPassword); const confirmField = page.locator('input[name="confirmPassword"]'); if (await confirmField.isVisible()) { await confirmField.fill(testPassword); } await page.locator('button[type="submit"]').click(); await expect(page).toHaveURL(/\/(dashboard|home|app|files)/, { timeout: 10000 }); } test.describe('File Management', () => { test.beforeEach(async ({ page }) => { await registerAndLogin(page); }); test('should load files interface', async ({ page }) => { await page.goto('/files'); // Check main elements await expect(page.locator('[data-testid="files-container"], .files-container, #files')).toBeVisible(); await expect(page.locator('button:has-text("Upload"), [data-testid="upload-button"]')).toBeVisible(); // Check for file list/grid const filesList = page.locator('[data-testid="files-list"], .files-list, .files-grid'); await expect(filesList).toBeVisible(); }); test('should upload text file', async ({ page }) => { await page.goto('/files'); // Create test file const fileName = `test-${Date.now()}.txt`; const fileContent = 'This is a test file content'; // Find upload input const uploadInput = page.locator('input[type="file"]'); if (!await uploadInput.isVisible()) { // Click upload button to reveal input await page.locator('button:has-text("Upload")').click(); } // Upload file await uploadInput.setInputFiles({ name: fileName, mimeType: 'text/plain', buffer: Buffer.from(fileContent), }); // Wait for upload to complete await expect(page.locator(`text="${fileName}"`)).toBeVisible({ timeout: 10000 }); // Check file details const fileItem = page.locator(`[data-filename="${fileName}"], text="${fileName}"`).locator('xpath=ancestor::*[contains(@class, "file")]'); // Should show file size await expect(fileItem.locator('text=/\\d+\\s*(B|KB|MB)/i')).toBeVisible(); }); test('should upload image file', async ({ page }) => { await page.goto('/files'); const imagePath = path.join(__dirname, '../../data/images/avatar.jpg'); // Upload image const uploadInput = page.locator('input[type="file"]'); await uploadInput.setInputFiles(imagePath); // Wait for upload await expect(page.locator('text=avatar.jpg')).toBeVisible({ timeout: 10000 }); // Should show thumbnail const thumbnail = page.locator('img[src*="avatar"], [data-testid="file-thumbnail"]'); if (await thumbnail.isVisible({ timeout: 2000 }).catch(() => false)) { const src = await thumbnail.getAttribute('src'); expect(src).toBeTruthy(); } }); test('should handle multiple file upload', async ({ page }) => { await page.goto('/files'); // Create multiple test files const files = []; for (let i = 0; i < 3; i++) { files.push({ name: `multi-${i}-${Date.now()}.txt`, mimeType: 'text/plain', buffer: Buffer.from(`Content of file ${i}`), }); } // Upload multiple files const uploadInput = page.locator('input[type="file"]'); await uploadInput.setInputFiles(files); // All files should appear for (const file of files) { await expect(page.locator(`text="${file.name}"`)).toBeVisible({ timeout: 10000 }); } }); test('should show upload progress', async ({ page }) => { await page.goto('/files'); // Create large file (1MB) const largeContent = 'x'.repeat(1024 * 1024); const fileName = `large-${Date.now()}.txt`; // Start upload const uploadInput = page.locator('input[type="file"]'); await uploadInput.setInputFiles({ name: fileName, mimeType: 'text/plain', buffer: Buffer.from(largeContent), }); // Look for progress indicator const progressBar = page.locator('[role="progressbar"], .upload-progress, [data-testid="upload-progress"]'); if (await progressBar.isVisible({ timeout: 1000 }).catch(() => false)) { // Progress should be visible during upload const progress = await progressBar.getAttribute('aria-valuenow') || await progressBar.getAttribute('value'); expect(progress).toBeTruthy(); } }); test('should handle file size limits', async ({ page }) => { await page.goto('/files'); // Try to upload file larger than limit (simulate) await page.evaluate(() => { // Trigger file size error const event = new CustomEvent('error', { detail: { message: 'File too large' } }); window.dispatchEvent(event); }); // Or try actual large file const veryLargeContent = 'x'.repeat(50 * 1024 * 1024); // 50MB const uploadInput = page.locator('input[type="file"]'); try { await uploadInput.setInputFiles({ name: 'too-large.txt', mimeType: 'text/plain', buffer: Buffer.from(veryLargeContent), }); // Should show error await expect(page.locator('text=/too large|size limit|exceeds/i')).toBeVisible({ timeout: 5000 }); } catch (e) { // File might be too large for Playwright itself console.log('Skipping very large file test'); } }); test('should preview text files', async ({ page }) => { await page.goto('/files'); // Upload text file const fileName = `preview-${Date.now()}.txt`; const fileContent = 'Preview this text content\nLine 2\nLine 3'; await page.locator('input[type="file"]').setInputFiles({ name: fileName, mimeType: 'text/plain', buffer: Buffer.from(fileContent), }); // Wait for file to appear await page.waitForTimeout(2000); // Click file to preview await page.locator(`text="${fileName}"`).click(); // Should show preview const preview = page.locator('[data-testid="file-preview"], .file-preview, .modal'); if (await preview.isVisible({ timeout: 2000 }).catch(() => false)) { await expect(preview.locator('text="Preview this text content"')).toBeVisible(); } }); test('should download files', async ({ page }) => { await page.goto('/files'); // Upload a file first const fileName = `download-${Date.now()}.txt`; await page.locator('input[type="file"]').setInputFiles({ name: fileName, mimeType: 'text/plain', buffer: Buffer.from('Download me'), }); await page.waitForTimeout(2000); // Find download button const fileItem = page.locator(`text="${fileName}"`).locator('xpath=ancestor::*[contains(@class, "file")]'); const downloadButton = fileItem.locator('button[aria-label="Download"], [data-testid="download-file"]'); if (await downloadButton.isVisible({ timeout: 2000 }).catch(() => false)) { // Start download const downloadPromise = page.waitForEvent('download'); await downloadButton.click(); const download = await downloadPromise; // Verify download expect(download.suggestedFilename()).toBe(fileName); } }); test('should delete files', async ({ page }) => { await page.goto('/files'); // Upload a file const fileName = `delete-${Date.now()}.txt`; await page.locator('input[type="file"]').setInputFiles({ name: fileName, mimeType: 'text/plain', buffer: Buffer.from('Delete me'), }); await page.waitForTimeout(2000); // Find delete button const fileItem = page.locator(`text="${fileName}"`).locator('xpath=ancestor::*[contains(@class, "file")]'); const deleteButton = fileItem.locator('button[aria-label="Delete"], [data-testid="delete-file"]'); if (await deleteButton.isVisible({ timeout: 2000 }).catch(() => false)) { await deleteButton.click(); // Confirm deletion if needed const confirmButton = page.locator('button:has-text("Confirm"), button:has-text("Delete")').last(); if (await confirmButton.isVisible({ timeout: 1000 }).catch(() => false)) { await confirmButton.click(); } // File should be removed await expect(page.locator(`text="${fileName}"`)).not.toBeVisible({ timeout: 5000 }); } }); test('should rename files', async ({ page }) => { await page.goto('/files'); // Upload a file const fileName = `rename-${Date.now()}.txt`; await page.locator('input[type="file"]').setInputFiles({ name: fileName, mimeType: 'text/plain', buffer: Buffer.from('Rename me'), }); await page.waitForTimeout(2000); // Find rename option const fileItem = page.locator(`text="${fileName}"`).locator('xpath=ancestor::*[contains(@class, "file")]'); // Right-click or find menu await fileItem.click({ button: 'right' }); const renameOption = page.locator('text="Rename"'); if (await renameOption.isVisible({ timeout: 1000 }).catch(() => false)) { await renameOption.click(); // Enter new name const newName = `renamed-${Date.now()}.txt`; const input = page.locator('input[value*="rename"]'); await input.fill(newName); await input.press('Enter'); // New name should appear await expect(page.locator(`text="${newName}"`)).toBeVisible({ timeout: 5000 }); await expect(page.locator(`text="${fileName}"`)).not.toBeVisible(); } }); test('should search files', async ({ page }) => { await page.goto('/files'); // Upload files with different names const searchTerm = `searchable-${Date.now()}`; const files = [ `${searchTerm}-1.txt`, `${searchTerm}-2.txt`, `other-file.txt`, ]; for (const fileName of files) { await page.locator('input[type="file"]').setInputFiles({ name: fileName, mimeType: 'text/plain', buffer: Buffer.from('Content'), }); await page.waitForTimeout(500); } // Search const searchInput = page.locator('input[placeholder*="Search"], [data-testid="search-files"]'); if (await searchInput.isVisible()) { await searchInput.fill(searchTerm); await searchInput.press('Enter'); // Should show only matching files await expect(page.locator(`text="${searchTerm}-1.txt"`)).toBeVisible(); await expect(page.locator(`text="${searchTerm}-2.txt"`)).toBeVisible(); await expect(page.locator('text="other-file.txt"')).not.toBeVisible(); } }); test('should sort files', async ({ page }) => { await page.goto('/files'); // Upload files with different dates/sizes const files = [ { name: 'a-file.txt', content: 'Small' }, { name: 'z-file.txt', content: 'Large content here' }, { name: 'm-file.txt', content: 'Medium size' }, ]; for (const file of files) { await page.locator('input[type="file"]').setInputFiles({ name: file.name, mimeType: 'text/plain', buffer: Buffer.from(file.content), }); await page.waitForTimeout(500); } // Find sort dropdown const sortDropdown = page.locator('select[aria-label*="Sort"], [data-testid="sort-files"]'); if (await sortDropdown.isVisible()) { // Sort by name await sortDropdown.selectOption('name'); // Check order const fileNames = await page.locator('.file-name').allTextContents(); expect(fileNames[0]).toContain('a-file'); expect(fileNames[fileNames.length - 1]).toContain('z-file'); } }); test('should show file details', async ({ page }) => { await page.goto('/files'); // Upload file const fileName = `details-${Date.now()}.txt`; const fileContent = 'File with details'; await page.locator('input[type="file"]').setInputFiles({ name: fileName, mimeType: 'text/plain', buffer: Buffer.from(fileContent), }); await page.waitForTimeout(2000); // Click info button or file const fileItem = page.locator(`text="${fileName}"`).locator('xpath=ancestor::*[contains(@class, "file")]'); const infoButton = fileItem.locator('button[aria-label="Info"], [data-testid="file-info"]'); if (await infoButton.isVisible()) { await infoButton.click(); } else { await fileItem.click(); } // Should show details modal/panel const details = page.locator('[data-testid="file-details"], .file-details'); if (await details.isVisible({ timeout: 2000 }).catch(() => false)) { await expect(details.locator(`text="${fileName}"`)).toBeVisible(); await expect(details.locator('text=/size/i')).toBeVisible(); await expect(details.locator('text=/type.*text/i')).toBeVisible(); await expect(details.locator('text=/uploaded/i')).toBeVisible(); } }); test('should handle drag and drop upload', async ({ page }) => { await page.goto('/files'); // Find drop zone const dropZone = page.locator('[data-testid="drop-zone"], .drop-zone, .files-container'); // Create data transfer const dataTransfer = await page.evaluateHandle(() => new DataTransfer()); // Simulate drag over await dropZone.dispatchEvent('dragover', { dataTransfer }); // Should show drop indicator await expect(dropZone).toHaveClass(/drag-over|dropping/); // Note: Full drag-drop file upload is complex in Playwright // This test mainly checks the UI responds to drag events }); });