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:
wjl
2026-02-22 20:10:11 +08:00
commit 1a0ebde95d
122 changed files with 30760 additions and 0 deletions
+98
View File
@@ -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('图片分析系统');
});
});
+244
View File
@@ -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();
});
});
+146
View File
@@ -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();
});
});
+187
View File
@@ -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();
});
});
+53
View File
@@ -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('✨ 测试完成!');
});
+16
View File
@@ -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();
});
+226
View File
@@ -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());
});
});
+24
View File
@@ -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);
});
+321
View File
@@ -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();
});
});