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