|
|
@@ -1,310 +0,0 @@
|
|
|
-import { test, expect } from '@playwright/test';
|
|
|
-
|
|
|
-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 randomString = Math.random().toString(36).substring(2, 10);
|
|
|
- const testFileName = `test-${randomString}.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();
|
|
|
- }
|
|
|
- });
|
|
|
-});
|