import { test, expect } from '@playwright/test' const BASE_URL = 'http://localhost:5173' const API_URL = 'http://localhost:8080' test.describe('文件管理模块', () => { test.beforeEach(async ({ page }) => { // Navigate to file manager page await page.goto(`${BASE_URL}/#/files`) await page.waitForLoadState('networkidle') }) test('侧边栏有文件管理菜单项', async ({ page }) => { // Check sidebar menu item exists const fileMenuItem = page.locator('.el-menu-item').filter({ hasText: '文件管理' }) await expect(fileMenuItem).toBeVisible() // Verify FolderOpened icon is present const icon = fileMenuItem.locator('.el-icon') await expect(icon).toBeVisible() }) test('文件管理页面基本结构', async ({ page }) => { // Page title await expect(page.locator('h2', { hasText: '文件管理' })).toBeVisible() // Breadcrumb navigation await expect(page.locator('.el-breadcrumb')).toBeVisible() await expect(page.locator('.el-breadcrumb__item').first()).toContainText('全部文件') // Upload button await expect(page.locator('button', { hasText: '上传文件' })).toBeVisible() // Create folder button await expect(page.locator('button', { hasText: '新建文件夹' })).toBeVisible() // Search input await expect(page.locator('input[placeholder="搜索文件"]')).toBeVisible() // File table (may be empty) await expect(page.locator('.el-table')).toBeVisible() }) test('API 连接正常 - 空文件列表', async ({ page }) => { // Table exists (even if empty - API already responded during navigation) const table = page.locator('.el-table') await expect(table).toBeVisible() }) test('创建文件夹', async ({ page }) => { // Click create folder button await page.locator('button', { hasText: '新建文件夹' }).click() // Dialog should appear await expect(page.locator('.el-dialog__header', { hasText: '新建文件夹' })).toBeVisible() // Type folder name const dialog = page.locator('.el-dialog') await dialog.locator('input').fill('test-folder-' + Date.now()) // Click create button await dialog.locator('button', { hasText: '创建' }).click() // Wait for success message await expect(page.locator('.el-message--success')).toBeVisible({ timeout: 5000 }) // Folder should appear in the list await page.waitForTimeout(500) }) test('搜索框交互', async ({ page }) => { const searchInput = page.locator('input[placeholder="搜索文件"]') await expect(searchInput).toBeVisible() // Type search query await searchInput.fill('test') // Press enter to trigger search await searchInput.press('Enter') // Should make API call with search parameter const response = await page.waitForResponse( resp => resp.url().includes('/api/v1/files') && resp.url().includes('search=test'), { timeout: 5000 } ).catch(() => null) // Response might be null if server already responded, that's ok // The important thing is the search input still has the value await expect(searchInput).toHaveValue('test') // Clear search await searchInput.fill('') await searchInput.press('Enter') }) test('面包屑导航 - 初始状态', async ({ page }) => { // Initial breadcrumb should show "全部文件" const breadcrumbs = page.locator('.el-breadcrumb__item') await expect(breadcrumbs.first()).toContainText('全部文件') }) }) test.describe('任务提交 - 文件选择器集成', () => { test('提交任务页面有文件选择功能', async ({ page }) => { await page.goto(`${BASE_URL}/#/tasks/create`) await page.waitForLoadState('networkidle') // Wait for applications to load await page.waitForResponse( resp => resp.url().includes('/api/v1/applications'), { timeout: 5000 } ).catch(() => null) // Check "关联文件" form item exists await expect(page.locator('.el-form-item', { hasText: '关联文件' })).toBeVisible() // Check "选择文件" button exists await expect(page.locator('button', { hasText: '选择文件' })).toBeVisible() }) test('点击选择文件打开 FilePicker 弹窗', async ({ page }) => { await page.goto(`${BASE_URL}/#/tasks/create`) await page.waitForLoadState('networkidle') // Wait for applications to load await page.waitForResponse( resp => resp.url().includes('/api/v1/applications'), { timeout: 5000 } ).catch(() => null) // Click "选择文件" button await page.locator('button', { hasText: '选择文件' }).click() // FilePicker dialog should appear await expect(page.locator('.el-dialog__header', { hasText: '选择文件' })).toBeVisible({ timeout: 3000 }) // Should have breadcrumb navigation await expect(page.locator('.el-dialog .el-breadcrumb')).toBeVisible() // Should have confirm and cancel buttons await expect(page.locator('.el-dialog button', { hasText: '取消' })).toBeVisible() await expect(page.locator('.el-dialog button', { hasText: '确认' })).toBeVisible() // Close dialog await page.locator('.el-dialog button', { hasText: '取消' }).click() }) }) test.describe('文件上传流程', () => { test('上传按钮触发文件选择', async ({ page }) => { await page.goto(`${BASE_URL}/#/files`) await page.waitForLoadState('networkidle') // Upload button should be visible and enabled const uploadBtn = page.locator('button', { hasText: '上传文件' }) await expect(uploadBtn).toBeVisible() await expect(uploadBtn).toBeEnabled() // Click should trigger file input const fileInput = page.locator('input[type="file"]') await expect(fileInput).toBeAttached() }) }) test.describe('API 端点验证', () => { test('GET /api/v1/files 返回正确格式', async ({ request }) => { const resp = await request.get(`${API_URL}/api/v1/files`) expect(resp.status()).toBe(200) const body = await resp.json() expect(body.success).toBe(true) expect(body.data).toBeDefined() expect(body.data.files).toBeInstanceOf(Array) expect(typeof body.data.total).toBe('number') }) test('GET /api/v1/files/folders 返回正确格式', async ({ request }) => { const resp = await request.get(`${API_URL}/api/v1/files/folders`) expect(resp.status()).toBe(200) const body = await resp.json() expect(body.success).toBe(true) expect(body.data).toBeInstanceOf(Array) }) test('POST /api/v1/files/folders 创建文件夹', async ({ request }) => { const folderName = `test-folder-${Date.now()}` const resp = await request.post(`${API_URL}/api/v1/files/folders`, { data: { name: folderName } }) expect([200, 201]).toContain(resp.status()) const body = await resp.json() expect(body.success).toBe(true) expect(body.data.name).toBe(folderName) expect(body.data.id).toBeDefined() expect(typeof body.data.file_count).toBe('number') expect(typeof body.data.subfolder_count).toBe('number') }) test('POST /api/v1/files/uploads/init - 秒传测试', async ({ request }) => { // Use a known SHA256 to test instant upload behavior const resp = await request.post(`${API_URL}/api/v1/files/uploads`, { data: { file_name: 'test.txt', file_size: 5, sha256: 'abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890', chunk_size: 16777216, } }) // Will be either 200 (new upload session) or some error // Just verify the endpoint is reachable expect([200, 201, 400, 500]).toContain(resp.status()) }) test('创建子文件夹并在列表中看到', async ({ request }) => { // Create parent folder const parentResp = await request.post(`${API_URL}/api/v1/files/folders`, { data: { name: `parent-${Date.now()}` } }) const parent = await parentResp.json() const parentId = parent.data.id // Create child folder const childResp = await request.post(`${API_URL}/api/v1/files/folders`, { data: { name: `child-${Date.now()}`, parent_id: parentId } }) expect([200, 201]).toContain(childResp.status()) const child = await childResp.json() expect(child.data.parent_id).toBe(parentId) // List folders under parent const listResp = await request.get(`${API_URL}/api/v1/files/folders?parent_id=${parentId}`) expect(listResp.status()).toBe(200) const list = await listResp.json() expect(list.data.length).toBeGreaterThanOrEqual(1) }) test('删除空文件夹成功', async ({ request }) => { // Create a folder const createResp = await request.post(`${API_URL}/api/v1/files/folders`, { data: { name: `delete-me-${Date.now()}` } }) const folder = await createResp.json() const folderId = folder.data.id // Delete it (should succeed - empty folder) const deleteResp = await request.delete(`${API_URL}/api/v1/files/folders/${folderId}`) expect(deleteResp.status()).toBe(200) const deleteBody = await deleteResp.json() expect(deleteBody.success).toBe(true) }) })