veza/tools/tests/e2e/specs/files.spec.ts
2025-12-03 22:56:50 +01:00

411 lines
No EOL
14 KiB
TypeScript

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
});
});