import { test, expect } from '@playwright/test'; import { faker } from '@faker-js/faker'; test.describe('Admin File Management', () => { test.beforeEach(async ({ page }) => { // Login to admin panel await page.goto('/admin/login'); await page.fill('input[name="username"]', 'admin'); await page.fill('input[name="password"]', 'password'); await page.click('button[type="submit"]'); await page.waitForURL('/admin/dashboard'); // Navigate to files page await page.click('a[href="/admin/files"]'); await page.waitForURL('/admin/files'); }); test('should display files list', async ({ page }) => { // Wait for files to load await page.waitForSelector('[data-testid="files-table"]'); // Check if table headers are present await expect(page.locator('th:has-text("文件名")')).toBeVisible(); await expect(page.locator('th:has-text("类型")')).toBeVisible(); await expect(page.locator('th:has-text("大小")')).toBeVisible(); await expect(page.locator('th:has-text("上传时间")')).toBeVisible(); // Check if at least one file is displayed (or empty state) const filesCount = await page.locator('[data-testid="file-row"]').count(); if (filesCount === 0) { await expect(page.locator('text=暂无文件')).toBeVisible(); } else { await expect(page.locator('[data-testid="file-row"]').first()).toBeVisible(); } }); test('should upload file successfully', async ({ page }) => { // Click upload button await page.click('button:has-text("上传文件")'); // Wait for upload modal await page.waitForSelector('[data-testid="upload-modal"]'); // Create a test file const testFileName = `test-${faker.string.alphanumeric(8)}.txt`; const testFileContent = 'This is a test file content'; // Upload file const fileInput = page.locator('input[type="file"]'); await fileInput.setInputFiles({ name: testFileName, mimeType: 'text/plain', buffer: Buffer.from(testFileContent) }); // Fill optional fields await page.fill('input[name="description"]', 'Test file description'); // Submit upload await page.click('button:has-text("开始上传")'); // Wait for upload to complete await expect(page.locator('text=上传成功')).toBeVisible({ timeout: 30000 }); // Verify file appears in list await page.waitForSelector(`[data-testid="file-row"]:has-text("${testFileName}")`); await expect(page.locator(`text=${testFileName}`)).toBeVisible(); }); test('should search files', async ({ page }) => { // Assume there are some files already await page.waitForSelector('[data-testid="files-table"]'); // Use search functionality const searchTerm = 'document'; await page.fill('input[placeholder="搜索文件"]', searchTerm); await page.keyboard.press('Enter'); // Wait for search results await page.waitForLoadState('networkidle'); // Verify search results (either show results or no results message) const results = await page.locator('[data-testid="file-row"]').count(); if (results === 0) { await expect(page.locator('text=未找到相关文件')).toBeVisible(); } else { // Check that all visible files contain search term in name or description const fileRows = page.locator('[data-testid="file-row"]'); for (let i = 0; i < results; i++) { const rowText = await fileRows.nth(i).textContent(); expect(rowText?.toLowerCase()).toContain(searchTerm.toLowerCase()); } } }); test('should download file', async ({ page }) => { // Wait for files to load await page.waitForSelector('[data-testid="file-row"]'); // Get first file row const firstFile = page.locator('[data-testid="file-row"]').first(); const fileName = await firstFile.locator('[data-testid="file-name"]').textContent(); // Setup download tracking const downloadPromise = page.waitForEvent('download'); // Click download button await firstFile.locator('button:has-text("下载")').click(); // Wait for download to start const download = await downloadPromise; // Verify download filename expect(download.suggestedFilename()).toContain(fileName?.trim() || ''); }); test('should delete file', async ({ page }) => { // Wait for files to load await page.waitForSelector('[data-testid="file-row"]'); // Get first file row const firstFile = page.locator('[data-testid="file-row"]').first(); const fileName = await firstFile.locator('[data-testid="file-name"]').textContent(); // Click delete button await firstFile.locator('button:has-text("删除")').click(); // Confirm deletion in dialog await page.waitForSelector('[role="dialog"]'); await page.click('button:has-text("确认删除")'); // Wait for deletion to complete await expect(page.locator('text=删除成功')).toBeVisible(); // Verify file is removed from list await expect(page.locator(`text=${fileName}`)).not.toBeVisible({ timeout: 5000 }); }); test('should view file details', async ({ page }) => { // Wait for files to load await page.waitForSelector('[data-testid="file-row"]'); // Click view details on first file await page.locator('[data-testid="file-row"]').first().locator('button:has-text("查看")').click(); // Wait for details modal await page.waitForSelector('[data-testid="file-details-modal"]'); // Verify details are displayed await expect(page.locator('[data-testid="file-name"]')).toBeVisible(); await expect(page.locator('[data-testid="file-size"]')).toBeVisible(); await expect(page.locator('[data-testid="file-type"]')).toBeVisible(); await expect(page.locator('[data-testid="upload-time"]')).toBeVisible(); // Close modal await page.click('button[aria-label="Close"]'); }); test('should handle bulk operations', async ({ page }) => { // Wait for files to load await page.waitForSelector('[data-testid="file-row"]'); // Select multiple files const checkboxes = page.locator('input[type="checkbox"][name="file-select"]'); const fileCount = await checkboxes.count(); if (fileCount >= 2) { // Select first two files await checkboxes.nth(0).check(); await checkboxes.nth(1).check(); // Verify bulk actions are visible await expect(page.locator('button:has-text("批量下载")')).toBeVisible(); await expect(page.locator('button:has-text("批量删除")')).toBeVisible(); // Test bulk delete await page.click('button:has-text("批量删除")'); await page.waitForSelector('[role="dialog"]'); await page.click('button:has-text("确认删除")'); await expect(page.locator('text=删除成功')).toBeVisible(); } }); test('should handle file upload errors', async ({ page }) => { // Click upload button await page.click('button:has-text("上传文件")'); await page.waitForSelector('[data-testid="upload-modal"]'); // Try to upload without selecting file await page.click('button:has-text("开始上传")'); // Should show validation error await expect(page.locator('text=请选择要上传的文件')).toBeVisible(); // Close modal await page.click('button[aria-label="Close"]'); }); test('should paginate files list', async ({ page }) => { // Wait for files to load await page.waitForSelector('[data-testid="files-table"]'); // Check if pagination exists const pagination = page.locator('[data-testid="pagination"]'); if (await pagination.isVisible()) { // Test next page await page.click('button:has-text("下一页")'); await page.waitForLoadState('networkidle'); // Test previous page await page.click('button:has-text("上一页")'); await page.waitForLoadState('networkidle'); // Test specific page const pageButtons = page.locator('[data-testid="page-button"]'); if (await pageButtons.count() > 0) { await pageButtons.nth(1).click(); // Click second page await page.waitForLoadState('networkidle'); } } }); test('should filter files by type', async ({ page }) => { // Wait for files to load await page.waitForSelector('[data-testid="files-table"]'); // Open filter dropdown await page.click('button:has-text("筛选")'); await page.waitForSelector('[role="menu"]'); // Filter by image type await page.click('text=图片'); await page.waitForLoadState('networkidle'); // Verify only images are shown (or no results message) const fileRows = page.locator('[data-testid="file-row"]'); const rowCount = await fileRows.count(); if (rowCount > 0) { for (let i = 0; i < rowCount; i++) { const fileType = await fileRows.nth(i).locator('[data-testid="file-type"]').textContent(); expect(fileType?.toLowerCase()).toMatch(/(image|jpg|jpeg|png|gif|webp)/); } } else { await expect(page.locator('text=未找到图片文件')).toBeVisible(); } // Clear filter await page.click('button:has-text("清除筛选")'); await page.waitForLoadState('networkidle'); }); test('should sort files', async ({ page }) => { // Wait for files to load await page.waitForSelector('[data-testid="files-table"]'); // Test sorting by name await page.click('th:has-text("文件名")'); await page.waitForLoadState('networkidle'); // Test sorting by size await page.click('th:has-text("大小")'); await page.waitForLoadState('networkidle'); // Test sorting by upload time await page.click('th:has-text("上传时间")'); await page.waitForLoadState('networkidle'); }); }); test.describe('File Management Accessibility', () => { test('should be keyboard accessible', async ({ page }) => { await page.goto('/admin/files'); await page.waitForSelector('[data-testid="files-table"]'); // Test tab navigation await page.keyboard.press('Tab'); await expect(page.locator('button:has-text("上传文件")')).toBeFocused(); // Test keyboard operations on file rows const firstFileRow = page.locator('[data-testid="file-row"]').first(); await firstFileRow.focus(); await page.keyboard.press('Enter'); await expect(page.locator('[data-testid="file-details-modal"]')).toBeVisible(); // Close modal with Escape await page.keyboard.press('Escape'); await expect(page.locator('[data-testid="file-details-modal"]')).not.toBeVisible(); }); test('should have proper ARIA labels', async ({ page }) => { await page.goto('/admin/files'); await page.waitForSelector('[data-testid="files-table"]'); // Check ARIA attributes await expect(page.locator('[data-testid="files-table"]')).toHaveAttribute('role', 'grid'); await expect(page.locator('th')).toHaveAttribute('role', 'columnheader'); await expect(page.locator('[data-testid="file-row"]')).toHaveAttribute('role', 'row'); // Check button accessibility const buttons = page.locator('button'); const buttonCount = await buttons.count(); for (let i = 0; i < Math.min(buttonCount, 5); i++) { const button = buttons.nth(i); const hasAriaLabel = await button.getAttribute('aria-label'); expect(hasAriaLabel).toBeTruthy(); } }); });