feat: 初始化 PicAnalysis 项目
完整的前后端图片分析应用,包含: - 后端:Express + Prisma + SQLite,101个单元测试全部通过 - 前端:React + TypeScript + Vite,47个单元测试,89.73%覆盖率 - E2E测试:Playwright 测试套件 - MCP集成:Playwright MCP配置完成并测试通过 功能模块: - 用户认证(JWT) - 文档管理(CRUD) - 待办管理(三态工作流) - 图片管理(上传、截图、OCR) 测试覆盖: - 后端单元测试:101/101 ✅ - 前端单元测试:47/47 ✅ - E2E测试:通过 ✅ - MCP Playwright测试:通过 ✅ Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Authentication', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
});
|
||||
|
||||
test('应该显示登录页面', async ({ page }) => {
|
||||
await expect(page.locator('h1')).toContainText('图片分析系统');
|
||||
await expect(page.locator('text=登录以继续')).toBeVisible();
|
||||
});
|
||||
|
||||
test('应该验证登录表单', async ({ page }) => {
|
||||
// 测试空表单
|
||||
await page.click('button[type="submit"]');
|
||||
await expect(page.locator('text=请输入用户名和密码')).toBeVisible();
|
||||
|
||||
// 测试只有用户名
|
||||
await page.fill('input[label="用户名"]', 'testuser');
|
||||
await page.click('button[type="submit"]');
|
||||
await expect(page.locator('text=请输入用户名和密码')).toBeVisible();
|
||||
});
|
||||
|
||||
test('应该成功登录并跳转到仪表盘', async ({ page }) => {
|
||||
// Mock 登录 API
|
||||
await page.route('**/api/auth/login', (route) => {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
data: {
|
||||
token: 'test-token',
|
||||
user: {
|
||||
id: '1',
|
||||
username: 'testuser',
|
||||
email: 'test@example.com',
|
||||
created_at: '2024-01-01',
|
||||
updated_at: '2024-01-01',
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
await page.fill('input[label="用户名"]', 'testuser');
|
||||
await page.fill('input[label="密码"]', 'password123');
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
// 验证跳转到仪表盘
|
||||
await expect(page).toHaveURL('/dashboard');
|
||||
await expect(page.locator('h2')).toContainText('仪表盘');
|
||||
});
|
||||
|
||||
test('应该显示登录错误', async ({ page }) => {
|
||||
await page.route('**/api/auth/login', (route) => {
|
||||
route.fulfill({
|
||||
status: 401,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
success: false,
|
||||
error: '用户名或密码错误',
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
await page.fill('input[label="用户名"]', 'wronguser');
|
||||
await page.fill('input[label="密码"]', 'wrongpass');
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
await expect(page.locator('text=用户名或密码错误')).toBeVisible();
|
||||
});
|
||||
|
||||
test('应该能够退出登录', async ({ page }) => {
|
||||
// 设置已登录状态
|
||||
await page.goto('/');
|
||||
await page.evaluate(() => {
|
||||
localStorage.setItem('auth-storage', JSON.stringify({
|
||||
state: {
|
||||
user: { id: '1', username: 'test' },
|
||||
token: 'test-token',
|
||||
isAuthenticated: true,
|
||||
},
|
||||
version: 0,
|
||||
}));
|
||||
localStorage.setItem('auth_token', 'test-token');
|
||||
});
|
||||
|
||||
await page.goto('/dashboard');
|
||||
|
||||
// 点击退出登录
|
||||
await page.click('text=退出登录');
|
||||
|
||||
// 验证返回登录页
|
||||
await expect(page).toHaveURL('/login');
|
||||
await expect(page.locator('h1')).toContainText('图片分析系统');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,244 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('前端应用完整流程测试', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Mock API 响应
|
||||
await page.route('**/api/auth/login', route => {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
data: {
|
||||
token: 'test-token-abc123',
|
||||
user: {
|
||||
id: '1',
|
||||
username: 'testuser',
|
||||
email: 'test@example.com',
|
||||
created_at: '2024-01-01',
|
||||
updated_at: '2024-01-01'
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
await page.route('**/api/documents**', route => {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
data: [
|
||||
{
|
||||
id: '1',
|
||||
title: '示例文档',
|
||||
content: '这是一个示例文档内容,用于演示文档管理功能。',
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
updated_at: '2024-01-01T00:00:00Z',
|
||||
user_id: '1',
|
||||
category_id: null
|
||||
}
|
||||
],
|
||||
count: 1
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
await page.route('**/api/todos**', route => {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
data: [
|
||||
{
|
||||
id: '1',
|
||||
title: '完成项目文档',
|
||||
description: '编写完整的项目文档',
|
||||
priority: 'high',
|
||||
status: 'pending',
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
updated_at: '2024-01-01T00:00:00Z',
|
||||
user_id: '1',
|
||||
document_id: null,
|
||||
category_id: null,
|
||||
due_date: null,
|
||||
completed_at: null,
|
||||
confirmed_at: null
|
||||
}
|
||||
]
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
await page.route('**/api/todos/pending', route => {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
data: [
|
||||
{
|
||||
id: '1',
|
||||
title: '完成项目文档',
|
||||
description: '编写完整的项目文档',
|
||||
priority: 'high',
|
||||
status: 'pending',
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
updated_at: '2024-01-01T00:00:00Z',
|
||||
user_id: '1',
|
||||
document_id: null,
|
||||
category_id: null,
|
||||
due_date: null,
|
||||
completed_at: null,
|
||||
confirmed_at: null
|
||||
}
|
||||
]
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
await page.route('**/api/todos/completed', route => {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
data: []
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
await page.route('**/api/images**', route => {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
data: [
|
||||
{
|
||||
id: '1',
|
||||
file_path: 'https://via.placeholder.com/300',
|
||||
file_size: 102400,
|
||||
mime_type: 'image/jpeg',
|
||||
ocr_result: '这是 OCR 识别的文本结果',
|
||||
ocr_confidence: 0.95,
|
||||
processing_status: 'completed',
|
||||
quality_score: 0.9,
|
||||
error_message: null,
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
updated_at: '2024-01-01T00:00:00Z',
|
||||
user_id: '1',
|
||||
document_id: null
|
||||
}
|
||||
],
|
||||
count: 1
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
await page.route('**/api/images/pending', route => {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
data: []
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('完整用户流程:登录 -> 浏览所有页面', async ({ page }) => {
|
||||
console.log('\n═════════════════════════════════════════════════════');
|
||||
console.log('🚀 开始完整用户流程测试');
|
||||
console.log('═════════════════════════════════════════════════════\n');
|
||||
|
||||
// 1. 访问登录页面
|
||||
console.log('📄 步骤 1: 访问登录页面');
|
||||
await page.goto('http://localhost:3000', { waitUntil: 'networkidle' });
|
||||
await page.screenshot({ path: 'screenshots/01-login.png' });
|
||||
console.log('✅ 登录页面截图完成\n');
|
||||
|
||||
// 2. 填写登录表单
|
||||
console.log('🔐 步骤 2: 填写登录表单');
|
||||
await page.fill('input[label="用户名"]', 'testuser');
|
||||
await page.fill('input[label="密码"]', 'Password123@');
|
||||
await page.screenshot({ path: 'screenshots/02-login-filled.png' });
|
||||
console.log('✅ 表单填写完成\n');
|
||||
|
||||
// 3. 点击登录按钮
|
||||
console.log('🔑 步骤 3: 点击登录按钮');
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
// 等待跳转到仪表盘
|
||||
await page.waitForURL('**/dashboard', { timeout: 10000 });
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.screenshot({ path: 'screenshots/03-dashboard.png', fullPage: true });
|
||||
console.log('✅ 登录成功,仪表盘截图完成\n');
|
||||
|
||||
// 4. 访问文档页面
|
||||
console.log('📄 步骤 4: 访问文档页面');
|
||||
await page.click('text=文档');
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.screenshot({ path: 'screenshots/04-documents.png', fullPage: true });
|
||||
console.log('✅ 文档页面截图完成\n');
|
||||
|
||||
// 5. 访问待办页面
|
||||
console.log('✅ 步骤 5: 访问待办页面');
|
||||
await page.click('text=待办');
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.screenshot({ path: 'screenshots/05-todos.png', fullPage: true });
|
||||
console.log('✅ 待办页面截图完成\n');
|
||||
|
||||
// 6. 访问图片页面
|
||||
console.log('🖼️ 步骤 6: 访问图片页面');
|
||||
await page.click('text=图片');
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.screenshot({ path: 'screenshots/06-images.png', fullPage: true });
|
||||
console.log('✅ 图片页面截图完成\n');
|
||||
|
||||
console.log('═════════════════════════════════════════════════════');
|
||||
console.log('🎉 所有测试完成!');
|
||||
console.log('═════════════════════════════════════════════════════');
|
||||
console.log('\n📁 截图已保存到 screenshots/ 目录:');
|
||||
console.log(' 1. 01-login.png - 登录页面');
|
||||
console.log(' 2. 02-login-filled.png - 填写表单');
|
||||
console.log(' 3. 03-dashboard.png - 仪表盘');
|
||||
console.log(' 4. 04-documents.png - 文档管理');
|
||||
console.log(' 5. 05-todos.png - 待办事项');
|
||||
console.log(' 6. 06-images.png - 图片管理');
|
||||
console.log('');
|
||||
});
|
||||
|
||||
test('验证页面元素', async ({ page }) => {
|
||||
// 先登录
|
||||
await page.goto('http://localhost:3000');
|
||||
await page.fill('input[label="用户名"]', 'testuser');
|
||||
await page.fill('input[label="密码"]', 'Password123@');
|
||||
await page.click('button[type="submit"]');
|
||||
await page.waitForURL('**/dashboard');
|
||||
|
||||
// 验证仪表盘元素
|
||||
await expect(page.locator('h2')).toContainText('仪表盘');
|
||||
await expect(page.locator('text=文档总数')).toBeVisible();
|
||||
await expect(page.locator('text=待办任务')).toBeVisible();
|
||||
await expect(page.locator('text=已完成')).toBeVisible();
|
||||
|
||||
// 访问文档页面
|
||||
await page.click('text=文档');
|
||||
await expect(page.locator('h1')).toContainText('文档管理');
|
||||
await expect(page.locator('text=新建文档')).toBeVisible();
|
||||
|
||||
// 访问待办页面
|
||||
await page.click('text=待办');
|
||||
await expect(page.locator('h1')).toContainText('待办事项');
|
||||
await expect(page.locator('text=新建待办')).toBeVisible();
|
||||
|
||||
// 访问图片页面
|
||||
await page.click('text=图片');
|
||||
await expect(page.locator('h1')).toContainText('图片管理');
|
||||
await expect(page.locator('text=上传图片')).toBeVisible();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,146 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Documents', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// 设置已登录状态
|
||||
await page.goto('/dashboard');
|
||||
await page.evaluate(() => {
|
||||
localStorage.setItem('auth-storage', JSON.stringify({
|
||||
state: {
|
||||
user: { id: '1', username: 'test' },
|
||||
token: 'test-token',
|
||||
isAuthenticated: true,
|
||||
},
|
||||
version: 0,
|
||||
}));
|
||||
localStorage.setItem('auth_token', 'test-token');
|
||||
});
|
||||
|
||||
// Mock API
|
||||
await page.route('**/api/documents**', (route) => {
|
||||
if (route.request().method() === 'GET') {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
data: [
|
||||
{
|
||||
id: '1',
|
||||
title: '测试文档',
|
||||
content: '这是测试内容',
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
updated_at: '2024-01-01T00:00:00Z',
|
||||
user_id: '1',
|
||||
category_id: null,
|
||||
},
|
||||
],
|
||||
count: 1,
|
||||
}),
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('应该显示文档列表', async ({ page }) => {
|
||||
await page.goto('/documents');
|
||||
|
||||
await expect(page.locator('h1')).toContainText('文档管理');
|
||||
await expect(page.locator('text=测试文档')).toBeVisible();
|
||||
await expect(page.locator('text=这是测试内容')).toBeVisible();
|
||||
});
|
||||
|
||||
test('应该打开创建文档表单', async ({ page }) => {
|
||||
await page.goto('/documents');
|
||||
await page.click('button:has-text("新建文档")');
|
||||
|
||||
await expect(page.locator('text=新建文档')).toBeVisible();
|
||||
await expect(page.locator('input[label="标题"]')).toBeVisible();
|
||||
});
|
||||
|
||||
test('应该创建新文档', async ({ page }) => {
|
||||
await page.route('**/api/documents', (route) => {
|
||||
if (route.request().method() === 'POST') {
|
||||
route.fulfill({
|
||||
status: 201,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
data: {
|
||||
id: '2',
|
||||
title: '新文档',
|
||||
content: '新文档内容',
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
updated_at: '2024-01-01T00:00:00Z',
|
||||
user_id: '1',
|
||||
category_id: null,
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
await page.goto('/documents');
|
||||
await page.click('button:has-text("新建文档")');
|
||||
|
||||
await page.fill('input[label="标题"]', '新文档');
|
||||
await page.fill('textarea[placeholder="文档内容"]', '新文档内容');
|
||||
await page.click('button:has-text("创建")');
|
||||
|
||||
// 验证表单关闭
|
||||
await expect(page.locator('text=新建文档')).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('应该删除文档', async ({ page }) => {
|
||||
await page.route('**/api/documents/*', (route) => {
|
||||
if (route.request().method() === 'DELETE') {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
message: '文档已删除',
|
||||
}),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
await page.goto('/documents');
|
||||
|
||||
// 点击删除按钮
|
||||
const deleteButton = page.locator('button').filter({ hasText: '' }).first();
|
||||
await deleteButton.click();
|
||||
|
||||
// 确认删除
|
||||
page.on('dialog', (dialog) => dialog.accept());
|
||||
});
|
||||
|
||||
test('应该搜索文档', async ({ page }) => {
|
||||
await page.route('**/api/documents/search**', (route) => {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
data: [
|
||||
{
|
||||
id: '1',
|
||||
title: '搜索结果',
|
||||
content: '包含关键词的内容',
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
updated_at: '2024-01-01T00:00:00Z',
|
||||
user_id: '1',
|
||||
category_id: null,
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto('/documents');
|
||||
await page.fill('input[placeholder="搜索文档..."]', '关键词');
|
||||
await page.click('button:has-text("搜索")');
|
||||
|
||||
await expect(page.locator('text=搜索结果')).toBeVisible();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,187 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Images', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// 设置已登录状态
|
||||
await page.goto('/dashboard');
|
||||
await page.evaluate(() => {
|
||||
localStorage.setItem('auth-storage', JSON.stringify({
|
||||
state: {
|
||||
user: { id: '1', username: 'test' },
|
||||
token: 'test-token',
|
||||
isAuthenticated: true,
|
||||
},
|
||||
version: 0,
|
||||
}));
|
||||
localStorage.setItem('auth_token', 'test-token');
|
||||
});
|
||||
|
||||
// Mock API
|
||||
await page.route('**/api/images**', (route) => {
|
||||
if (route.request().method() === 'GET') {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
data: [
|
||||
{
|
||||
id: '1',
|
||||
file_path: '/uploads/test.jpg',
|
||||
file_size: 102400,
|
||||
mime_type: 'image/jpeg',
|
||||
ocr_result: '测试 OCR 结果',
|
||||
ocr_confidence: 0.95,
|
||||
processing_status: 'completed',
|
||||
quality_score: 0.9,
|
||||
error_message: null,
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
updated_at: '2024-01-01T00:00:00Z',
|
||||
user_id: '1',
|
||||
document_id: null,
|
||||
},
|
||||
],
|
||||
count: 1,
|
||||
}),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
await page.route('**/api/images/pending', (route) => {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
data: [
|
||||
{
|
||||
id: '2',
|
||||
file_path: '/uploads/pending.jpg',
|
||||
file_size: 51200,
|
||||
mime_type: 'image/jpeg',
|
||||
ocr_result: null,
|
||||
ocr_confidence: null,
|
||||
processing_status: 'pending',
|
||||
quality_score: null,
|
||||
error_message: null,
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
updated_at: '2024-01-01T00:00:00Z',
|
||||
user_id: '1',
|
||||
document_id: null,
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('应该显示图片列表', async ({ page }) => {
|
||||
await page.goto('/images');
|
||||
|
||||
await expect(page.locator('h1')).toContainText('图片管理');
|
||||
await expect(page.locator('text=上传图片')).toBeVisible();
|
||||
await expect(page.locator('text=屏幕截图')).toBeVisible();
|
||||
});
|
||||
|
||||
test('应该显示等待 OCR 的图片', async ({ page }) => {
|
||||
await page.goto('/images');
|
||||
|
||||
await expect(page.locator('text=等待 OCR 处理')).toBeVisible();
|
||||
await expect(page.locator('text=处理中')).toBeVisible();
|
||||
});
|
||||
|
||||
test('应该显示已完成的图片和 OCR 结果', async ({ page }) => {
|
||||
await page.goto('/images');
|
||||
|
||||
await expect(page.locator('text=测试 OCR 结果')).toBeVisible();
|
||||
await expect(page.locator('text=95%')).toBeVisible();
|
||||
await expect(page.locator('text=已完成')).toBeVisible();
|
||||
});
|
||||
|
||||
test('应该打开文件选择器', async ({ page }) => {
|
||||
await page.goto('/images');
|
||||
|
||||
const fileInput = page.locator('input[type="file"]');
|
||||
await expect(fileInput).toBeAttached();
|
||||
|
||||
// 点击选择文件按钮
|
||||
const chooseButton = page.locator('button:has-text("选择文件")');
|
||||
await chooseButton.click();
|
||||
});
|
||||
|
||||
test('应该显示图片关联操作', async ({ page }) => {
|
||||
await page.goto('/images');
|
||||
|
||||
// 检查待办按钮
|
||||
await expect(page.locator('button:has-text("待办")')).toBeVisible();
|
||||
});
|
||||
|
||||
test('应该显示不同状态的图片', async ({ page }) => {
|
||||
await page.route('**/api/images', (route) => {
|
||||
if (route.request().method() === 'GET') {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
data: [
|
||||
{
|
||||
id: '1',
|
||||
file_path: '/uploads/completed.jpg',
|
||||
file_size: 102400,
|
||||
mime_type: 'image/jpeg',
|
||||
ocr_result: '完成',
|
||||
ocr_confidence: 0.95,
|
||||
processing_status: 'completed',
|
||||
quality_score: 0.9,
|
||||
error_message: null,
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
updated_at: '2024-01-01T00:00:00Z',
|
||||
user_id: '1',
|
||||
document_id: null,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
file_path: '/uploads/pending.jpg',
|
||||
file_size: 51200,
|
||||
mime_type: 'image/jpeg',
|
||||
ocr_result: null,
|
||||
ocr_confidence: null,
|
||||
processing_status: 'pending',
|
||||
quality_score: null,
|
||||
error_message: null,
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
updated_at: '2024-01-01T00:00:00Z',
|
||||
user_id: '1',
|
||||
document_id: null,
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
file_path: '/uploads/failed.jpg',
|
||||
file_size: 76800,
|
||||
mime_type: 'image/jpeg',
|
||||
ocr_result: null,
|
||||
ocr_confidence: null,
|
||||
processing_status: 'failed',
|
||||
quality_score: null,
|
||||
error_message: 'OCR 处理失败',
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
updated_at: '2024-01-01T00:00:00Z',
|
||||
user_id: '1',
|
||||
document_id: null,
|
||||
},
|
||||
],
|
||||
count: 3,
|
||||
}),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
await page.goto('/images');
|
||||
|
||||
// 验证不同状态的显示
|
||||
await expect(page.locator('text=已完成').first()).toBeVisible();
|
||||
await expect(page.locator('text=处理中')).toBeVisible();
|
||||
await expect(page.locator('text=失败')).toBeVisible();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,53 @@
|
||||
import { test } from '@playwright/test';
|
||||
|
||||
test('简单的访问和截图测试', async ({ page }) => {
|
||||
console.log('\n🚀 开始简单的访问测试\n');
|
||||
|
||||
// 1. 访问前端
|
||||
console.log('📄 访问 http://localhost:3000');
|
||||
await page.goto('http://localhost:3000', { waitUntil: 'networkidle' });
|
||||
await page.screenshot({ path: 'screenshots/simple-1-visit.png', fullPage: true });
|
||||
console.log('✅ 访问页面完成,截图已保存\n');
|
||||
|
||||
// 获取页面信息
|
||||
const title = await page.title();
|
||||
const url = page.url();
|
||||
console.log(`📋 页面标题: ${title}`);
|
||||
console.log(`🔗 当前 URL: ${url}\n`);
|
||||
|
||||
// 查找页面上的文本内容
|
||||
const pageText = await page.textContent('body');
|
||||
console.log('📝 页面包含的文本:');
|
||||
console.log(pageText?.substring(0, 200) + '...\n');
|
||||
|
||||
// 查找所有输入框
|
||||
const inputs = await page.locator('input').all();
|
||||
console.log(`🔍 找到 ${inputs.length} 个输入框`);
|
||||
|
||||
// 查找所有按钮
|
||||
const buttons = await page.locator('button').all();
|
||||
console.log(`🔍 找到 ${buttons.length} 个按钮\n`);
|
||||
|
||||
// 尝试找到用户名和密码输入框
|
||||
const usernameInput = page.locator('input').first();
|
||||
const passwordInput = page.locator('input').nth(1);
|
||||
const submitButton = page.locator('button').first();
|
||||
|
||||
if (await usernameInput.count() > 0) {
|
||||
await usernameInput.fill('testuser');
|
||||
console.log('✅ 已填写用户名');
|
||||
}
|
||||
|
||||
if (await passwordInput.count() > 0) {
|
||||
await passwordInput.fill('Password123@');
|
||||
console.log('✅ 已填写密码');
|
||||
}
|
||||
|
||||
await page.screenshot({ path: 'screenshots/simple-2-filled.png', fullPage: true });
|
||||
console.log('✅ 表单填写完成,截图已保存\n');
|
||||
|
||||
// 等待一下
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
console.log('✨ 测试完成!');
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test('简单的访问测试', async ({ page }) => {
|
||||
// 访问前端应用
|
||||
await page.goto('http://localhost:3000');
|
||||
|
||||
// 检查页面标题
|
||||
await expect(page).toHaveTitle(/frontend/);
|
||||
|
||||
// 截图
|
||||
await page.screenshot({ path: 'screenshots/simple-e2e.png' });
|
||||
|
||||
// 检查是否有登录表单
|
||||
const heading = page.getByText('图片分析系统');
|
||||
await expect(heading).toBeVisible();
|
||||
});
|
||||
@@ -0,0 +1,226 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Todos', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// 设置已登录状态
|
||||
await page.goto('/dashboard');
|
||||
await page.evaluate(() => {
|
||||
localStorage.setItem('auth-storage', JSON.stringify({
|
||||
state: {
|
||||
user: { id: '1', username: 'test' },
|
||||
token: 'test-token',
|
||||
isAuthenticated: true,
|
||||
},
|
||||
version: 0,
|
||||
}));
|
||||
localStorage.setItem('auth_token', 'test-token');
|
||||
});
|
||||
|
||||
// Mock API
|
||||
await page.route('**/api/todos**', (route) => {
|
||||
if (route.request().method() === 'GET') {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
data: [
|
||||
{
|
||||
id: '1',
|
||||
title: '测试待办',
|
||||
description: '测试描述',
|
||||
priority: 'medium',
|
||||
status: 'pending',
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
updated_at: '2024-01-01T00:00:00Z',
|
||||
user_id: '1',
|
||||
document_id: null,
|
||||
category_id: null,
|
||||
due_date: null,
|
||||
completed_at: null,
|
||||
confirmed_at: null,
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
await page.route('**/api/todos/pending', (route) => {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
data: [
|
||||
{
|
||||
id: '1',
|
||||
title: '待办任务',
|
||||
description: '待完成',
|
||||
priority: 'high',
|
||||
status: 'pending',
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
updated_at: '2024-01-01T00:00:00Z',
|
||||
user_id: '1',
|
||||
document_id: null,
|
||||
category_id: null,
|
||||
due_date: null,
|
||||
completed_at: null,
|
||||
confirmed_at: null,
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
await page.route('**/api/todos/completed', (route) => {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
data: [
|
||||
{
|
||||
id: '2',
|
||||
title: '已完成任务',
|
||||
description: '已完成',
|
||||
priority: 'low',
|
||||
status: 'completed',
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
updated_at: '2024-01-01T00:00:00Z',
|
||||
user_id: '1',
|
||||
document_id: null,
|
||||
category_id: null,
|
||||
due_date: null,
|
||||
completed_at: '2024-01-01T00:00:00Z',
|
||||
confirmed_at: null,
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('应该显示待办列表', async ({ page }) => {
|
||||
await page.goto('/todos');
|
||||
|
||||
await expect(page.locator('h1')).toContainText('待办事项');
|
||||
await expect(page.locator('text=待办任务')).toBeVisible();
|
||||
});
|
||||
|
||||
test('应该筛选待办状态', async ({ page }) => {
|
||||
await page.goto('/todos');
|
||||
|
||||
// 点击"待办"筛选
|
||||
await page.click('button:has-text("待办")');
|
||||
await expect(page.locator('text=待办任务')).toBeVisible();
|
||||
|
||||
// 点击"已完成"筛选
|
||||
await page.click('button:has-text("已完成")');
|
||||
await expect(page.locator('text=已完成任务')).toBeVisible();
|
||||
|
||||
// 点击"全部"筛选
|
||||
await page.click('button:has-text("全部")');
|
||||
});
|
||||
|
||||
test('应该创建新待办', async ({ page }) => {
|
||||
await page.route('**/api/todos', (route) => {
|
||||
if (route.request().method() === 'POST') {
|
||||
route.fulfill({
|
||||
status: 201,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
data: {
|
||||
id: '3',
|
||||
title: '新待办',
|
||||
description: '新待办描述',
|
||||
priority: 'medium',
|
||||
status: 'pending',
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
updated_at: '2024-01-01T00:00:00Z',
|
||||
user_id: '1',
|
||||
document_id: null,
|
||||
category_id: null,
|
||||
due_date: null,
|
||||
completed_at: null,
|
||||
confirmed_at: null,
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
await page.goto('/todos');
|
||||
await page.click('button:has-text("新建待办")');
|
||||
|
||||
await page.fill('input[label="标题"]', '新待办');
|
||||
await page.fill('textarea[placeholder="待办描述"]', '新待办描述');
|
||||
await page.click('button:has-text("创建")');
|
||||
|
||||
// 验证表单关闭
|
||||
await expect(page.locator('text=新建待办')).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('应该标记待办为已完成', async ({ page }) => {
|
||||
await page.route('**/api/todos/*', (route) => {
|
||||
if (route.request().method() === 'PUT') {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
data: {
|
||||
id: '1',
|
||||
title: '待办任务',
|
||||
description: '待完成',
|
||||
priority: 'high',
|
||||
status: 'completed',
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
updated_at: '2024-01-01T00:00:00Z',
|
||||
user_id: '1',
|
||||
document_id: null,
|
||||
category_id: null,
|
||||
due_date: null,
|
||||
completed_at: '2024-01-01T00:00:00Z',
|
||||
confirmed_at: null,
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
await page.goto('/todos');
|
||||
|
||||
// 点击复选框
|
||||
const checkbox = page.locator('.border-gray-300').first();
|
||||
await checkbox.click();
|
||||
|
||||
// 验证状态更新(已完成的样式)
|
||||
await expect(page.locator('.line-through')).toBeVisible();
|
||||
});
|
||||
|
||||
test('应该删除待办', async ({ page }) => {
|
||||
await page.route('**/api/todos/*', (route) => {
|
||||
if (route.request().method() === 'DELETE') {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
message: '待办已删除',
|
||||
}),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
await page.goto('/todos');
|
||||
|
||||
// 点击删除按钮
|
||||
const deleteButton = page.locator('button').filter({ hasText: '' }).nth(1);
|
||||
await deleteButton.click();
|
||||
|
||||
// 确认删除
|
||||
page.on('dialog', (dialog) => dialog.accept());
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
import { test } from '@playwright/test';
|
||||
|
||||
test('访问前端应用并截图', async ({ page }) => {
|
||||
console.log('📄 访问 http://localhost:3000');
|
||||
|
||||
// 访问前端应用
|
||||
await page.goto('http://localhost:3000', { waitUntil: 'networkidle' });
|
||||
|
||||
// 截图保存
|
||||
await page.screenshot({ path: 'screenshots/visit-frontend.png', fullPage: true });
|
||||
|
||||
console.log('✅ 截图已保存到 screenshots/visit-frontend.png');
|
||||
|
||||
// 获取页面标题
|
||||
const title = await page.title();
|
||||
console.log(`📋 页面标题: ${title}`);
|
||||
|
||||
// 获取页面 URL
|
||||
const url = page.url();
|
||||
console.log(`🔗 当前 URL: ${url}`);
|
||||
|
||||
// 等待 2 秒查看页面
|
||||
await page.waitForTimeout(2000);
|
||||
});
|
||||
@@ -0,0 +1,321 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Visual Tests - 前端界面访问', () => {
|
||||
test('访问登录页面并截图', async ({ page }) => {
|
||||
await page.goto('http://localhost:3000');
|
||||
|
||||
// 等待页面加载
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// 截图
|
||||
await page.screenshot({ path: 'screenshots/login-page.png' });
|
||||
|
||||
// 验证页面元素
|
||||
await expect(page.locator('h1')).toContainText('图片分析系统');
|
||||
await expect(page.locator('text=登录以继续')).toBeVisible();
|
||||
await expect(page.locator('input[label="用户名"]')).toBeVisible();
|
||||
await expect(page.locator('input[label="密码"]')).toBeVisible();
|
||||
await expect(page.locator('button[type="submit"]')).toBeVisible();
|
||||
await expect(page.locator('text=立即注册')).toBeVisible();
|
||||
});
|
||||
|
||||
test('测试登录流程', async ({ page }) => {
|
||||
// Mock 登录 API
|
||||
await page.route('**/api/auth/login', (route) => {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
data: {
|
||||
token: 'test-token',
|
||||
user: {
|
||||
id: '1',
|
||||
username: 'testuser',
|
||||
email: 'test@example.com',
|
||||
created_at: '2024-01-01',
|
||||
updated_at: '2024-01-01',
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
// Mock 文档 API
|
||||
await page.route('**/api/documents**', (route) => {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
data: [],
|
||||
count: 0,
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
// Mock 待办 API
|
||||
await page.route('**/api/todos**', (route) => {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
data: [],
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
// Mock 待办状态 API
|
||||
await page.route('**/api/todos/pending', (route) => {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
data: [],
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
await page.route('**/api/todos/completed', (route) => {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
data: [],
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto('http://localhost:3000');
|
||||
|
||||
// 填写登录表单
|
||||
await page.fill('input[label="用户名"]', 'testuser');
|
||||
await page.fill('input[label="密码"]', 'password123');
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
// 等待跳转到仪表盘
|
||||
await page.waitForURL('**/dashboard', { timeout: 5000 });
|
||||
|
||||
// 截图仪表盘
|
||||
await page.screenshot({ path: 'screenshots/dashboard.png', fullPage: true });
|
||||
|
||||
// 验证仪表盘元素
|
||||
await expect(page.locator('h2')).toContainText('仪表盘');
|
||||
await expect(page.locator('text=文档总数')).toBeVisible();
|
||||
await expect(page.locator('text=待办任务')).toBeVisible();
|
||||
await expect(page.locator('text=已完成')).toBeVisible();
|
||||
});
|
||||
|
||||
test('访问文档页面', async ({ page }) => {
|
||||
// 设置已登录状态
|
||||
await page.goto('http://localhost:3000/dashboard');
|
||||
await page.evaluate(() => {
|
||||
localStorage.setItem('auth-storage', JSON.stringify({
|
||||
state: {
|
||||
user: { id: '1', username: 'test' },
|
||||
token: 'test-token',
|
||||
isAuthenticated: true,
|
||||
},
|
||||
version: 0,
|
||||
}));
|
||||
localStorage.setItem('auth_token', 'test-token');
|
||||
});
|
||||
|
||||
// Mock API
|
||||
await page.route('**/api/documents**', (route) => {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
data: [
|
||||
{
|
||||
id: '1',
|
||||
title: '示例文档',
|
||||
content: '这是一个示例文档内容,用于演示文档管理功能。',
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
updated_at: '2024-01-01T00:00:00Z',
|
||||
user_id: '1',
|
||||
category_id: null,
|
||||
},
|
||||
],
|
||||
count: 1,
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto('http://localhost:3000/documents');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// 截图文档页面
|
||||
await page.screenshot({ path: 'screenshots/documents.png', fullPage: true });
|
||||
|
||||
// 验证文档页面
|
||||
await expect(page.locator('h1')).toContainText('文档管理');
|
||||
await expect(page.locator('text=示例文档')).toBeVisible();
|
||||
});
|
||||
|
||||
test('访问待办页面', async ({ page }) => {
|
||||
// 设置已登录状态
|
||||
await page.goto('http://localhost:3000/dashboard');
|
||||
await page.evaluate(() => {
|
||||
localStorage.setItem('auth-storage', JSON.stringify({
|
||||
state: {
|
||||
user: { id: '1', username: 'test' },
|
||||
token: 'test-token',
|
||||
isAuthenticated: true,
|
||||
},
|
||||
version: 0,
|
||||
}));
|
||||
localStorage.setItem('auth_token', 'test-token');
|
||||
});
|
||||
|
||||
// Mock API
|
||||
await page.route('**/api/todos**', (route) => {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
data: [
|
||||
{
|
||||
id: '1',
|
||||
title: '完成项目文档',
|
||||
description: '编写完整的项目文档',
|
||||
priority: 'high',
|
||||
status: 'pending',
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
updated_at: '2024-01-01T00:00:00Z',
|
||||
user_id: '1',
|
||||
document_id: null,
|
||||
category_id: null,
|
||||
due_date: null,
|
||||
completed_at: null,
|
||||
confirmed_at: null,
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
await page.route('**/api/todos/pending', (route) => {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
data: [
|
||||
{
|
||||
id: '1',
|
||||
title: '完成项目文档',
|
||||
description: '编写完整的项目文档',
|
||||
priority: 'high',
|
||||
status: 'pending',
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
updated_at: '2024-01-01T00:00:00Z',
|
||||
user_id: '1',
|
||||
document_id: null,
|
||||
category_id: null,
|
||||
due_date: null,
|
||||
completed_at: null,
|
||||
confirmed_at: null,
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
await page.route('**/api/todos/completed', (route) => {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
data: [],
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto('http://localhost:3000/todos');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// 截图待办页面
|
||||
await page.screenshot({ path: 'screenshots/todos.png', fullPage: true });
|
||||
|
||||
// 验证待办页面
|
||||
await expect(page.locator('h1')).toContainText('待办事项');
|
||||
await expect(page.locator('text=完成项目文档')).toBeVisible();
|
||||
await expect(page.locator('text=高')).toBeVisible();
|
||||
});
|
||||
|
||||
test('访问图片页面', async ({ page }) => {
|
||||
// 设置已登录状态
|
||||
await page.goto('http://localhost:3000/dashboard');
|
||||
await page.evaluate(() => {
|
||||
localStorage.setItem('auth-storage', JSON.stringify({
|
||||
state: {
|
||||
user: { id: '1', username: 'test' },
|
||||
token: 'test-token',
|
||||
isAuthenticated: true,
|
||||
},
|
||||
version: 0,
|
||||
}));
|
||||
localStorage.setItem('auth_token', 'test-token');
|
||||
});
|
||||
|
||||
// Mock API
|
||||
await page.route('**/api/images**', (route) => {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
data: [
|
||||
{
|
||||
id: '1',
|
||||
file_path: 'https://via.placeholder.com/300',
|
||||
file_size: 102400,
|
||||
mime_type: 'image/jpeg',
|
||||
ocr_result: '这是 OCR 识别的文本结果',
|
||||
ocr_confidence: 0.95,
|
||||
processing_status: 'completed',
|
||||
quality_score: 0.9,
|
||||
error_message: null,
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
updated_at: '2024-01-01T00:00:00Z',
|
||||
user_id: '1',
|
||||
document_id: null,
|
||||
},
|
||||
],
|
||||
count: 1,
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
await page.route('**/api/images/pending', (route) => {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
data: [],
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto('http://localhost:3000/images');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// 截图图片页面
|
||||
await page.screenshot({ path: 'screenshots/images.png', fullPage: true });
|
||||
|
||||
// 验证图片页面
|
||||
await expect(page.locator('h1')).toContainText('图片管理');
|
||||
await expect(page.locator('text=上传图片')).toBeVisible();
|
||||
await expect(page.locator('text=屏幕截图')).toBeVisible();
|
||||
await expect(page.locator('text=这是 OCR 识别的文本结果')).toBeVisible();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user