f5dec95a83
refactor: 重构页面导航和滚动逻辑,提升用户体验 test: 更新测试配置和用例,增加覆盖率和稳定性 perf: 优化性能指标和阈值,适应开发环境需求 ci: 添加Lighthouse CI工作流,集成性能测试 docs: 更新API文档和健康检查端点 fix: 修复登录页面和表单提交问题 style: 调整响应式布局和可访问性改进 chore: 更新依赖项和脚本配置
1587 lines
37 KiB
Markdown
1587 lines
37 KiB
Markdown
# 质量改进迭代实施计划
|
||
|
||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||
|
||
**Goal:** 根据系统评估报告,按优先级完成代码质量、测试框架和文档的改进工作,提升项目整体质量。
|
||
|
||
**Architecture:** 采用渐进式改进策略,优先解决高优先级问题(统一实现、测试覆盖率、报告可视化),然后推进中优先级改进(API文档、版本控制、性能监控),最后优化低优先级任务。
|
||
|
||
**Tech Stack:** Next.js 16, TypeScript, Jest, Playwright, Allure, OpenAPI/Swagger, Lighthouse CI
|
||
|
||
---
|
||
|
||
## 高优先级任务(1周内完成)
|
||
|
||
### Task 1: 统一联系表单实现
|
||
|
||
**问题:** 当前联系表单存在双重实现(Server Actions和API Route),需要统一为Server Actions。
|
||
|
||
**Files:**
|
||
- Delete: `src/app/api/contact/route.ts`
|
||
- Modify: `src/app/(marketing)/contact/page.tsx`
|
||
- Modify: `src/app/(marketing)/contact/actions.ts`
|
||
- Test: `src/app/api/contact/route.test.ts` (需要删除或迁移)
|
||
|
||
**Step 1: 分析现有实现**
|
||
|
||
检查两个文件的实现差异:
|
||
```bash
|
||
# 查看Server Actions实现
|
||
cat src/app/\(marketing\)/contact/actions.ts
|
||
|
||
# 查看API Route实现
|
||
cat src/app/api/contact/route.ts
|
||
```
|
||
|
||
**Step 2: 增强Server Actions实现**
|
||
|
||
修改 `src/app/(marketing)/contact/actions.ts`,添加完整的验证和邮件发送逻辑:
|
||
|
||
```typescript
|
||
'use server';
|
||
|
||
import { Resend } from 'resend';
|
||
import { z } from 'zod';
|
||
|
||
const resend = new Resend(process.env.RESEND_API_KEY);
|
||
const companyEmail = process.env.COMPANY_EMAIL || 'contact@novalon.cn';
|
||
|
||
const contactFormSchema = z.object({
|
||
name: z.string().min(1, '请填写姓名'),
|
||
email: z.string().email('请输入有效的邮箱地址'),
|
||
phone: z.string().optional(),
|
||
subject: z.string().min(1, '请填写主题'),
|
||
message: z.string().min(1, '请填写消息'),
|
||
website: z.string().optional(), // 蜜罐字段
|
||
submitTime: z.string().optional(),
|
||
mathHash: z.string().optional(),
|
||
mathTimestamp: z.string().optional(),
|
||
mathAnswer: z.string().optional(),
|
||
});
|
||
|
||
export interface ContactFormState {
|
||
success: boolean;
|
||
message?: string;
|
||
error?: string;
|
||
}
|
||
|
||
export async function submitContactForm(
|
||
_prevState: ContactFormState | null,
|
||
formData: FormData
|
||
): Promise<ContactFormState> {
|
||
try {
|
||
// 提取表单数据
|
||
const data = {
|
||
name: formData.get('name') as string,
|
||
email: formData.get('email') as string,
|
||
phone: formData.get('phone') as string,
|
||
subject: formData.get('subject') as string,
|
||
message: formData.get('message') as string,
|
||
website: formData.get('website') as string,
|
||
submitTime: formData.get('submitTime') as string,
|
||
mathHash: formData.get('mathHash') as string,
|
||
mathTimestamp: formData.get('mathTimestamp') as string,
|
||
mathAnswer: formData.get('mathAnswer') as string,
|
||
};
|
||
|
||
// 验证数据
|
||
const validatedData = contactFormSchema.parse(data);
|
||
|
||
// 蜜罐字段检查
|
||
if (validatedData.website) {
|
||
console.log('Honeypot field filled, rejecting request');
|
||
return { success: true, message: '消息已发送' };
|
||
}
|
||
|
||
// 时间检查(防止机器人快速提交)
|
||
if (validatedData.submitTime) {
|
||
const timeDiff = Date.now() - parseInt(validatedData.submitTime);
|
||
if (timeDiff < 2000) {
|
||
console.log('Submission too fast:', timeDiff);
|
||
return { success: false, error: '提交过快,请稍后再试' };
|
||
}
|
||
}
|
||
|
||
// 数学验证码检查
|
||
if (validatedData.mathHash && validatedData.mathTimestamp && validatedData.mathAnswer !== undefined) {
|
||
const expectedHash = btoa(`${validatedData.mathAnswer}-${validatedData.mathTimestamp}`);
|
||
if (expectedHash !== validatedData.mathHash) {
|
||
console.log('Invalid math captcha');
|
||
return { success: false, error: '验证码错误,请重新计算' };
|
||
}
|
||
}
|
||
|
||
// 发送邮件
|
||
const emailContent = generateEmailContent(validatedData);
|
||
|
||
const { error } = await resend.emails.send({
|
||
from: '睿新致远官网 <onboarding@resend.dev>',
|
||
to: [companyEmail],
|
||
subject: `📧 ${validatedData.subject} - ${validatedData.name}`,
|
||
html: emailContent,
|
||
replyTo: validatedData.email,
|
||
});
|
||
|
||
if (error) {
|
||
console.error('Resend API error:', error);
|
||
return { success: false, error: '邮件发送失败,请稍后重试' };
|
||
}
|
||
|
||
console.log('Email sent successfully');
|
||
return { success: true, message: '消息已发送' };
|
||
} catch (error) {
|
||
console.error('Contact form submission error:', error);
|
||
|
||
if (error instanceof z.ZodError) {
|
||
return { success: false, error: error.errors[0].message };
|
||
}
|
||
|
||
return { success: false, error: '提交失败,请重试' };
|
||
}
|
||
}
|
||
|
||
function generateEmailContent(data: z.infer<typeof contactFormSchema>): string {
|
||
return `
|
||
<html>
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<style>
|
||
body {
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
|
||
line-height: 1.6;
|
||
color: #1C1C1C;
|
||
margin: 0;
|
||
padding: 0;
|
||
background-color: #f5f5f5;
|
||
}
|
||
.container {
|
||
max-width: 600px;
|
||
margin: 0 auto;
|
||
padding: 20px;
|
||
background-color: #ffffff;
|
||
}
|
||
.header {
|
||
background: #C41E3A;
|
||
color: white;
|
||
padding: 40px 30px;
|
||
text-align: center;
|
||
border-radius: 8px 8px 0 0;
|
||
}
|
||
.header h1 {
|
||
margin: 0;
|
||
font-size: 28px;
|
||
font-weight: 600;
|
||
}
|
||
.content {
|
||
padding: 40px 30px;
|
||
background: #ffffff;
|
||
}
|
||
.info-card {
|
||
background: #f9f9f9;
|
||
border-radius: 8px;
|
||
padding: 20px;
|
||
margin-bottom: 25px;
|
||
border: 1px solid #e5e5e5;
|
||
}
|
||
.info-row {
|
||
display: flex;
|
||
margin-bottom: 12px;
|
||
align-items: flex-start;
|
||
}
|
||
.info-label {
|
||
font-weight: 600;
|
||
color: #1C1C1C;
|
||
min-width: 70px;
|
||
font-size: 14px;
|
||
}
|
||
.info-value {
|
||
color: #5C5C5C;
|
||
font-size: 14px;
|
||
flex: 1;
|
||
}
|
||
.message-box {
|
||
background: #fff;
|
||
padding: 20px;
|
||
border-left: 4px solid #C41E3A;
|
||
margin-top: 20px;
|
||
border-radius: 0 8px 8px 0;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
|
||
}
|
||
.footer {
|
||
text-align: center;
|
||
padding: 30px;
|
||
color: #8C8C8C;
|
||
font-size: 12px;
|
||
border-top: 1px solid #e5e5e5;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<div class="header">
|
||
<h1>📬 新的客户咨询</h1>
|
||
<p>来自 睿新致远官方网站</p>
|
||
</div>
|
||
<div class="content">
|
||
<div class="info-card">
|
||
<div class="info-row">
|
||
<div class="info-label">姓名</div>
|
||
<div class="info-value">${data.name}</div>
|
||
</div>
|
||
<div class="info-row">
|
||
<div class="info-label">邮箱</div>
|
||
<div class="info-value"><a href="mailto:${data.email}" style="color: #C41E3A; text-decoration: none;">${data.email}</a></div>
|
||
</div>
|
||
${data.phone ? `
|
||
<div class="info-row">
|
||
<div class="info-label">电话</div>
|
||
<div class="info-value">${data.phone}</div>
|
||
</div>
|
||
` : ''}
|
||
<div class="info-row">
|
||
<div class="info-label">主题</div>
|
||
<div class="info-value">${data.subject}</div>
|
||
</div>
|
||
</div>
|
||
<div class="message-box">
|
||
<div style="font-weight: 600; color: #C41E3A; font-size: 14px; margin-bottom: 10px;">咨询内容</div>
|
||
<div style="color: #1C1C1C; font-size: 14px; line-height: 1.8; white-space: pre-wrap;">${data.message}</div>
|
||
</div>
|
||
</div>
|
||
<div class="footer">
|
||
<p>本邮件由 睿新致远 官网联系表单自动发送</p>
|
||
<p>提交时间:${new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' })}</p>
|
||
<p>© ${new Date().getFullYear()} 四川睿新致远科技有限公司</p>
|
||
</div>
|
||
</div>
|
||
</body>
|
||
</html>
|
||
`;
|
||
}
|
||
```
|
||
|
||
**Step 3: 更新前端表单调用**
|
||
|
||
修改 `src/app/(marketing)/contact/page.tsx`,确保使用Server Actions:
|
||
|
||
```typescript
|
||
'use client';
|
||
|
||
import { useFormState, useFormStatus } from 'react-dom';
|
||
import { submitContactForm, type ContactFormState } from './actions';
|
||
|
||
function SubmitButton() {
|
||
const { pending } = useFormStatus();
|
||
|
||
return (
|
||
<button
|
||
type="submit"
|
||
disabled={pending}
|
||
className="w-full bg-primary text-white py-3 px-6 rounded-lg hover:bg-primary/90 transition-colors disabled:opacity-50"
|
||
>
|
||
{pending ? '发送中...' : '发送消息'}
|
||
</button>
|
||
);
|
||
}
|
||
|
||
export default function ContactPage() {
|
||
const [state, formAction] = useFormState(submitContactForm, null);
|
||
|
||
return (
|
||
<form action={formAction} className="space-y-6">
|
||
{/* 表单字段 */}
|
||
<div>
|
||
<label htmlFor="name">姓名 *</label>
|
||
<input
|
||
id="name"
|
||
name="name"
|
||
type="text"
|
||
required
|
||
className="w-full border rounded-lg px-4 py-2"
|
||
/>
|
||
</div>
|
||
|
||
{/* 其他字段... */}
|
||
|
||
{state?.error && (
|
||
<div className="text-red-600 text-sm">{state.error}</div>
|
||
)}
|
||
|
||
{state?.success && (
|
||
<div className="text-green-600 text-sm">{state.message}</div>
|
||
)}
|
||
|
||
<SubmitButton />
|
||
</form>
|
||
);
|
||
}
|
||
```
|
||
|
||
**Step 4: 删除API Route文件**
|
||
|
||
```bash
|
||
rm src/app/api/contact/route.ts
|
||
rm src/app/api/contact/route.test.ts
|
||
```
|
||
|
||
**Step 5: 更新测试**
|
||
|
||
创建新的测试文件 `src/app/(marketing)/contact/actions.test.ts`:
|
||
|
||
```typescript
|
||
import { submitContactForm } from './actions';
|
||
|
||
describe('submitContactForm', () => {
|
||
it('should validate required fields', async () => {
|
||
const formData = new FormData();
|
||
formData.append('name', '');
|
||
formData.append('email', 'invalid-email');
|
||
formData.append('subject', '');
|
||
formData.append('message', '');
|
||
|
||
const result = await submitContactForm(null, formData);
|
||
|
||
expect(result.success).toBe(false);
|
||
expect(result.error).toBeDefined();
|
||
});
|
||
|
||
it('should reject honeypot field', async () => {
|
||
const formData = new FormData();
|
||
formData.append('name', 'Test');
|
||
formData.append('email', 'test@example.com');
|
||
formData.append('subject', 'Test Subject');
|
||
formData.append('message', 'Test Message');
|
||
formData.append('website', 'bot');
|
||
|
||
const result = await submitContactForm(null, formData);
|
||
|
||
expect(result.success).toBe(true);
|
||
expect(result.message).toBe('消息已发送');
|
||
});
|
||
|
||
it('should reject fast submission', async () => {
|
||
const formData = new FormData();
|
||
formData.append('name', 'Test');
|
||
formData.append('email', 'test@example.com');
|
||
formData.append('subject', 'Test Subject');
|
||
formData.append('message', 'Test Message');
|
||
formData.append('submitTime', (Date.now() - 1000).toString());
|
||
|
||
const result = await submitContactForm(null, formData);
|
||
|
||
expect(result.success).toBe(false);
|
||
expect(result.error).toContain('提交过快');
|
||
});
|
||
});
|
||
```
|
||
|
||
**Step 6: 运行测试验证**
|
||
|
||
```bash
|
||
npm run test:unit
|
||
```
|
||
|
||
Expected: 所有测试通过
|
||
|
||
**Step 7: 手动测试**
|
||
|
||
```bash
|
||
npm run dev
|
||
# 访问 http://localhost:3000/contact
|
||
# 填写表单并提交
|
||
# 验证邮件发送成功
|
||
```
|
||
|
||
**Step 8: 提交更改**
|
||
|
||
```bash
|
||
git add src/app/\(marketing\)/contact/
|
||
git add src/app/api/contact/
|
||
git commit -m "refactor: unify contact form implementation to use Server Actions
|
||
|
||
- Remove duplicate API Route implementation
|
||
- Enhance Server Actions with full validation and email sending
|
||
- Add comprehensive tests for form validation
|
||
- Improve error handling and user feedback
|
||
|
||
BREAKING CHANGE: API endpoint /api/contact removed, use Server Actions instead"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 2: 配置单元测试覆盖率报告
|
||
|
||
**问题:** 缺少单元测试覆盖率报告,无法量化测试质量。
|
||
|
||
**Files:**
|
||
- Modify: `jest.config.js` 或创建 `jest.config.ts`
|
||
- Modify: `package.json`
|
||
- Create: `.github/workflows/coverage.yml` (可选)
|
||
|
||
**Step 1: 安装依赖**
|
||
|
||
```bash
|
||
npm install --save-dev @types/jest jest ts-jest
|
||
```
|
||
|
||
**Step 2: 创建Jest配置文件**
|
||
|
||
创建 `jest.config.ts`:
|
||
|
||
```typescript
|
||
import type { Config } from 'jest';
|
||
import nextJest from 'next/jest';
|
||
|
||
const createJestConfig = nextJest({
|
||
dir: './',
|
||
});
|
||
|
||
const config: Config = {
|
||
coverageProvider: 'v8',
|
||
testEnvironment: 'jsdom',
|
||
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
|
||
moduleNameMapper: {
|
||
'^@/(.*)$': '<rootDir>/src/$1',
|
||
},
|
||
collectCoverage: true,
|
||
coverageThreshold: {
|
||
global: {
|
||
branches: 80,
|
||
functions: 80,
|
||
lines: 80,
|
||
statements: 80,
|
||
},
|
||
},
|
||
coverageReporters: ['text', 'lcov', 'html', 'json-summary'],
|
||
coverageDirectory: 'coverage',
|
||
testMatch: [
|
||
'**/__tests__/**/*.[jt]s?(x)',
|
||
'**/?(*.)+(spec|test).[jt]s?(x)',
|
||
],
|
||
testPathIgnorePatterns: [
|
||
'/node_modules/',
|
||
'/.next/',
|
||
'/e2e/',
|
||
'/dist/',
|
||
],
|
||
collectCoverageFrom: [
|
||
'src/**/*.{js,jsx,ts,tsx}',
|
||
'!src/**/*.d.ts',
|
||
'!src/**/*.stories.{js,jsx,ts,tsx}',
|
||
'!src/**/__tests__/**',
|
||
'!src/**/node_modules/**',
|
||
],
|
||
};
|
||
|
||
export default createJestConfig(config);
|
||
```
|
||
|
||
**Step 3: 创建Jest Setup文件**
|
||
|
||
创建 `jest.setup.ts`:
|
||
|
||
```typescript
|
||
import '@testing-library/jest-dom';
|
||
|
||
// Mock Next.js modules
|
||
jest.mock('next/navigation', () => ({
|
||
useRouter() {
|
||
return {
|
||
push: jest.fn(),
|
||
replace: jest.fn(),
|
||
prefetch: jest.fn(),
|
||
back: jest.fn(),
|
||
pathname: '/',
|
||
query: {},
|
||
asPath: '/',
|
||
};
|
||
},
|
||
usePathname() {
|
||
return '/';
|
||
},
|
||
useSearchParams() {
|
||
return new URLSearchParams();
|
||
},
|
||
}));
|
||
|
||
// Mock environment variables
|
||
process.env.NEXTAUTH_SECRET = 'test-secret';
|
||
process.env.NEXTAUTH_URL = 'http://localhost:3000';
|
||
process.env.DATABASE_URL = 'file:./test.db';
|
||
process.env.RESEND_API_KEY = 'test-key';
|
||
process.env.COMPANY_EMAIL = 'test@example.com';
|
||
```
|
||
|
||
**Step 4: 更新package.json脚本**
|
||
|
||
在 `package.json` 中添加:
|
||
|
||
```json
|
||
{
|
||
"scripts": {
|
||
"test:unit": "jest",
|
||
"test:unit:watch": "jest --watch",
|
||
"test:unit:coverage": "jest --coverage",
|
||
"test:coverage:open": "open coverage/lcov-report/index.html"
|
||
}
|
||
}
|
||
```
|
||
|
||
**Step 5: 运行覆盖率测试**
|
||
|
||
```bash
|
||
npm run test:unit:coverage
|
||
```
|
||
|
||
Expected: 生成覆盖率报告,显示覆盖率百分比
|
||
|
||
**Step 6: 查看覆盖率报告**
|
||
|
||
```bash
|
||
npm run test:coverage:open
|
||
```
|
||
|
||
Expected: 在浏览器中打开HTML覆盖率报告
|
||
|
||
**Step 7: 添加覆盖率徽章(可选)**
|
||
|
||
在 `README.md` 中添加:
|
||
|
||
```markdown
|
||
[](https://codecov.io/gh/your-org/your-repo)
|
||
```
|
||
|
||
**Step 8: 提交更改**
|
||
|
||
```bash
|
||
git add jest.config.ts jest.setup.ts package.json README.md
|
||
git commit -m "feat: add unit test coverage reporting
|
||
|
||
- Configure Jest with 80% coverage threshold
|
||
- Add coverage reporters (text, lcov, html, json)
|
||
- Create Jest setup with Next.js mocks
|
||
- Add npm scripts for coverage reporting
|
||
- Add coverage badge to README
|
||
|
||
Target coverage: 80% for branches, functions, lines, statements"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 3: 配置Allure测试报告
|
||
|
||
**问题:** 缺少测试报告可视化,难以追踪测试历史和趋势。
|
||
|
||
**Files:**
|
||
- Modify: `e2e/playwright.config.ts`
|
||
- Modify: `package.json`
|
||
- Create: `scripts/generate-allure-report.sh`
|
||
|
||
**Step 1: 安装Allure依赖**
|
||
|
||
```bash
|
||
npm install --save-dev allure-playwright
|
||
```
|
||
|
||
**Step 2: 更新Playwright配置**
|
||
|
||
修改 `e2e/playwright.config.ts`,确保Allure reporter已配置:
|
||
|
||
```typescript
|
||
import { defineConfig, devices } from '@playwright/test';
|
||
|
||
export default defineConfig({
|
||
testDir: './src/tests',
|
||
reporter: [
|
||
['html', { open: 'never' }],
|
||
['json', { outputFile: 'test-results/results.json' }],
|
||
['junit', { outputFile: 'test-results/junit.xml' }],
|
||
['allure-playwright', {
|
||
outputFolder: 'allure-results',
|
||
detail: true,
|
||
suiteTitle: false,
|
||
}],
|
||
],
|
||
// ... 其他配置
|
||
});
|
||
```
|
||
|
||
**Step 3: 创建报告生成脚本**
|
||
|
||
创建 `scripts/generate-allure-report.sh`:
|
||
|
||
```bash
|
||
#!/bin/bash
|
||
|
||
# 生成Allure报告
|
||
echo "Generating Allure report..."
|
||
|
||
# 检查allure-results目录是否存在
|
||
if [ ! -d "allure-results" ]; then
|
||
echo "Error: allure-results directory not found"
|
||
echo "Please run tests first: npm run test"
|
||
exit 1
|
||
fi
|
||
|
||
# 生成报告
|
||
allure generate allure-results --clean -o allure-report
|
||
|
||
echo "Allure report generated successfully!"
|
||
echo "Opening report..."
|
||
|
||
# 打开报告
|
||
allure open allure-report
|
||
```
|
||
|
||
**Step 4: 更新package.json脚本**
|
||
|
||
在 `package.json` 中添加:
|
||
|
||
```json
|
||
{
|
||
"scripts": {
|
||
"test:report": "allure generate allure-results --clean -o allure-report && allure open allure-report",
|
||
"test:report:ci": "allure generate allure-results --clean -o allure-report"
|
||
}
|
||
}
|
||
```
|
||
|
||
**Step 5: 运行测试生成报告**
|
||
|
||
```bash
|
||
cd e2e
|
||
npm test
|
||
cd ..
|
||
npm run test:report
|
||
```
|
||
|
||
Expected:
|
||
- 测试执行完成
|
||
- Allure报告在浏览器中打开
|
||
- 显示测试统计、趋势、详情
|
||
|
||
**Step 6: 添加CI集成(可选)**
|
||
|
||
创建 `.github/workflows/test-report.yml`:
|
||
|
||
```yaml
|
||
name: Test Report
|
||
|
||
on:
|
||
push:
|
||
branches: [main, develop]
|
||
pull_request:
|
||
branches: [main, develop]
|
||
|
||
jobs:
|
||
test:
|
||
runs-on: ubuntu-latest
|
||
|
||
steps:
|
||
- uses: actions/checkout@v3
|
||
|
||
- name: Setup Node.js
|
||
uses: actions/setup-node@v3
|
||
with:
|
||
node-version: '18'
|
||
cache: 'npm'
|
||
|
||
- name: Install dependencies
|
||
run: npm ci
|
||
|
||
- name: Run E2E tests
|
||
run: npm run test:e2e
|
||
|
||
- name: Generate Allure report
|
||
if: always()
|
||
run: npm run test:report:ci
|
||
|
||
- name: Upload Allure report
|
||
if: always()
|
||
uses: actions/upload-artifact@v3
|
||
with:
|
||
name: allure-report
|
||
path: allure-report/
|
||
retention-days: 30
|
||
```
|
||
|
||
**Step 7: 提交更改**
|
||
|
||
```bash
|
||
git add e2e/playwright.config.ts package.json scripts/generate-allure-report.sh .github/workflows/test-report.yml
|
||
git commit -m "feat: add Allure test report visualization
|
||
|
||
- Configure Allure reporter in Playwright
|
||
- Add report generation scripts
|
||
- Add CI integration for automatic report generation
|
||
- Add npm scripts for easy report access
|
||
|
||
Allure report provides:
|
||
- Test statistics and trends
|
||
- Detailed test execution history
|
||
- Visual test results dashboard"
|
||
```
|
||
|
||
---
|
||
|
||
## 中优先级任务(2周内完成)
|
||
|
||
### Task 4: 引入OpenAPI文档
|
||
|
||
**问题:** 缺少API文档,不利于前后端协作和API维护。
|
||
|
||
**Files:**
|
||
- Create: `src/app/api/docs/route.ts`
|
||
- Create: `src/lib/openapi.ts`
|
||
- Modify: `package.json`
|
||
|
||
**Step 1: 安装依赖**
|
||
|
||
```bash
|
||
npm install swagger-jsdoc swagger-ui-react
|
||
npm install --save-dev @types/swagger-jsdoc
|
||
```
|
||
|
||
**Step 2: 创建OpenAPI配置**
|
||
|
||
创建 `src/lib/openapi.ts`:
|
||
|
||
```typescript
|
||
import swaggerJsdoc from 'swagger-jspec';
|
||
|
||
const options: swaggerJsdoc.Options = {
|
||
definition: {
|
||
openapi: '3.0.0',
|
||
info: {
|
||
title: 'Novalon Website API',
|
||
version: '1.0.0',
|
||
description: '四川睿新致远科技有限公司官方网站API文档',
|
||
contact: {
|
||
name: '睿新致远',
|
||
email: 'contact@novalon.cn',
|
||
url: 'https://novalon.cn',
|
||
},
|
||
},
|
||
servers: [
|
||
{
|
||
url: 'http://localhost:3000/api',
|
||
description: '开发环境',
|
||
},
|
||
{
|
||
url: 'https://novalon.cn/api',
|
||
description: '生产环境',
|
||
},
|
||
],
|
||
components: {
|
||
securitySchemes: {
|
||
bearerAuth: {
|
||
type: 'http',
|
||
scheme: 'bearer',
|
||
bearerFormat: 'JWT',
|
||
},
|
||
},
|
||
schemas: {
|
||
User: {
|
||
type: 'object',
|
||
properties: {
|
||
id: { type: 'string' },
|
||
email: { type: 'string', format: 'email' },
|
||
name: { type: 'string' },
|
||
isAdmin: { type: 'boolean' },
|
||
createdAt: { type: 'string', format: 'date-time' },
|
||
},
|
||
},
|
||
Content: {
|
||
type: 'object',
|
||
properties: {
|
||
id: { type: 'string' },
|
||
type: { type: 'string', enum: ['news', 'product', 'service', 'case'] },
|
||
title: { type: 'string' },
|
||
slug: { type: 'string' },
|
||
excerpt: { type: 'string' },
|
||
content: { type: 'string' },
|
||
status: { type: 'string', enum: ['draft', 'published', 'archived'] },
|
||
createdAt: { type: 'string', format: 'date-time' },
|
||
},
|
||
},
|
||
Error: {
|
||
type: 'object',
|
||
properties: {
|
||
success: { type: 'boolean', example: false },
|
||
error: { type: 'string' },
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
apis: ['./src/app/api/**/*.ts'], // 指向API路由文件
|
||
};
|
||
|
||
export const specs = swaggerJsdoc(options);
|
||
```
|
||
|
||
**Step 3: 创建API文档路由**
|
||
|
||
创建 `src/app/api/docs/route.ts`:
|
||
|
||
```typescript
|
||
import { NextResponse } from 'next/server';
|
||
import { specs } from '@/lib/openapi';
|
||
|
||
export async function GET() {
|
||
return NextResponse.json(specs);
|
||
}
|
||
```
|
||
|
||
**Step 4: 创建Swagger UI页面**
|
||
|
||
创建 `src/app/api-docs/page.tsx`:
|
||
|
||
```typescript
|
||
'use client';
|
||
|
||
import SwaggerUI from 'swagger-ui-react';
|
||
import 'swagger-ui-react/swagger-ui.css';
|
||
|
||
export default function ApiDocsPage() {
|
||
return (
|
||
<div className="min-h-screen bg-white">
|
||
<SwaggerUI url="/api/docs" />
|
||
</div>
|
||
);
|
||
}
|
||
```
|
||
|
||
**Step 5: 为API添加JSDoc注释**
|
||
|
||
修改 `src/app/api/admin/content/route.ts`:
|
||
|
||
```typescript
|
||
/**
|
||
* @swagger
|
||
* /admin/content:
|
||
* get:
|
||
* summary: 获取内容列表
|
||
* tags: [Content]
|
||
* security:
|
||
* - bearerAuth: []
|
||
* parameters:
|
||
* - in: query
|
||
* name: type
|
||
* schema:
|
||
* type: string
|
||
* enum: [news, product, service, case]
|
||
* - in: query
|
||
* name: status
|
||
* schema:
|
||
* type: string
|
||
* enum: [draft, published, archived]
|
||
* - in: query
|
||
* name: page
|
||
* schema:
|
||
* type: integer
|
||
* default: 1
|
||
* - in: query
|
||
* name: limit
|
||
* schema:
|
||
* type: integer
|
||
* default: 20
|
||
* responses:
|
||
* 200:
|
||
* description: 成功获取内容列表
|
||
* content:
|
||
* application/json:
|
||
* schema:
|
||
* type: object
|
||
* properties:
|
||
* success:
|
||
* type: boolean
|
||
* data:
|
||
* type: object
|
||
* properties:
|
||
* items:
|
||
* type: array
|
||
* items:
|
||
* $ref: '#/components/schemas/Content'
|
||
* pagination:
|
||
* type: object
|
||
* 401:
|
||
* description: 未授权
|
||
* content:
|
||
* application/json:
|
||
* schema:
|
||
* $ref: '#/components/schemas/Error'
|
||
*/
|
||
export async function GET(request: NextRequest) {
|
||
// ... 现有实现
|
||
}
|
||
|
||
/**
|
||
* @swagger
|
||
* /admin/content:
|
||
* post:
|
||
* summary: 创建新内容
|
||
* tags: [Content]
|
||
* security:
|
||
* - bearerAuth: []
|
||
* requestBody:
|
||
* required: true
|
||
* content:
|
||
* application/json:
|
||
* schema:
|
||
* type: object
|
||
* required:
|
||
* - type
|
||
* - title
|
||
* - slug
|
||
* properties:
|
||
* type:
|
||
* type: string
|
||
* enum: [news, product, service, case]
|
||
* title:
|
||
* type: string
|
||
* slug:
|
||
* type: string
|
||
* excerpt:
|
||
* type: string
|
||
* contentBody:
|
||
* type: string
|
||
* status:
|
||
* type: string
|
||
* enum: [draft, published]
|
||
* responses:
|
||
* 201:
|
||
* description: 内容创建成功
|
||
* content:
|
||
* application/json:
|
||
* schema:
|
||
* $ref: '#/components/schemas/Content'
|
||
* 400:
|
||
* description: 请求参数错误
|
||
* 401:
|
||
* description: 未授权
|
||
*/
|
||
export async function POST(request: NextRequest) {
|
||
// ... 现有实现
|
||
}
|
||
```
|
||
|
||
**Step 6: 更新package.json脚本**
|
||
|
||
```json
|
||
{
|
||
"scripts": {
|
||
"docs:api": "next dev -p 3000",
|
||
"docs:generate": "ts-node scripts/generate-api-docs.ts"
|
||
}
|
||
}
|
||
```
|
||
|
||
**Step 7: 测试API文档**
|
||
|
||
```bash
|
||
npm run dev
|
||
# 访问 http://localhost:3000/api-docs
|
||
```
|
||
|
||
Expected:
|
||
- Swagger UI界面正常显示
|
||
- 所有API端点都有文档
|
||
- 可以在线测试API
|
||
|
||
**Step 8: 提交更改**
|
||
|
||
```bash
|
||
git add src/lib/openapi.ts src/app/api/docs/ src/app/api-docs/ src/app/api/admin/content/route.ts package.json
|
||
git commit -m "feat: add OpenAPI/Swagger documentation
|
||
|
||
- Create OpenAPI specification configuration
|
||
- Add Swagger UI page for API documentation
|
||
- Add JSDoc annotations to API routes
|
||
- Add npm scripts for documentation access
|
||
|
||
API documentation available at /api-docs
|
||
Provides interactive API testing interface"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 5: 引入API版本控制
|
||
|
||
**问题:** API没有版本控制,不利于未来升级和维护。
|
||
|
||
**Files:**
|
||
- Create: `src/app/api/v1/` 目录结构
|
||
- Modify: 所有API路由文件
|
||
- Update: 前端API调用
|
||
|
||
**Step 1: 创建版本化目录结构**
|
||
|
||
```bash
|
||
mkdir -p src/app/api/v1/admin
|
||
mkdir -p src/app/api/v1/auth
|
||
mkdir -p src/app/api/v1/content
|
||
mkdir -p src/app/api/v1/health
|
||
```
|
||
|
||
**Step 2: 迁移API路由**
|
||
|
||
将现有API文件移动到v1目录:
|
||
|
||
```bash
|
||
# 迁移管理后台API
|
||
mv src/app/api/admin/* src/app/api/v1/admin/
|
||
|
||
# 迁移认证API
|
||
mv src/app/api/auth/* src/app/api/v1/auth/
|
||
|
||
# 迁移内容API
|
||
mv src/app/api/content/* src/app/api/v1/content/
|
||
|
||
# 迁移健康检查API
|
||
mv src/app/api/health/* src/app/api/v1/health/
|
||
```
|
||
|
||
**Step 3: 创建API版本路由**
|
||
|
||
创建 `src/app/api/v1/route.ts`:
|
||
|
||
```typescript
|
||
import { NextResponse } from 'next/server';
|
||
|
||
export async function GET() {
|
||
return NextResponse.json({
|
||
version: 'v1',
|
||
endpoints: {
|
||
admin: '/api/v1/admin',
|
||
auth: '/api/v1/auth',
|
||
content: '/api/v1/content',
|
||
health: '/api/v1/health',
|
||
},
|
||
});
|
||
}
|
||
```
|
||
|
||
**Step 4: 创建默认API路由(向后兼容)**
|
||
|
||
修改 `src/app/api/route.ts`:
|
||
|
||
```typescript
|
||
import { NextResponse } from 'next/server';
|
||
|
||
export async function GET() {
|
||
return NextResponse.json({
|
||
message: 'Novalon Website API',
|
||
currentVersion: 'v1',
|
||
documentation: '/api-docs',
|
||
versions: {
|
||
v1: '/api/v1',
|
||
},
|
||
});
|
||
}
|
||
```
|
||
|
||
**Step 5: 更新前端API调用**
|
||
|
||
修改 `src/lib/api/client.ts`:
|
||
|
||
```typescript
|
||
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || '/api/v1';
|
||
|
||
export async function fetchApi<T>(endpoint: string, options?: RequestInit): Promise<T> {
|
||
const url = `${API_BASE_URL}${endpoint}`;
|
||
|
||
const response = await fetch(url, {
|
||
...options,
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
...options?.headers,
|
||
},
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`API Error: ${response.statusText}`);
|
||
}
|
||
|
||
return response.json();
|
||
}
|
||
```
|
||
|
||
**Step 6: 更新所有API调用**
|
||
|
||
搜索并替换所有API调用:
|
||
|
||
```bash
|
||
# 查找所有API调用
|
||
grep -r "/api/admin" src/
|
||
grep -r "/api/auth" src/
|
||
grep -r "/api/content" src/
|
||
|
||
# 替换为版本化API
|
||
# 例如:/api/admin/content -> /api/v1/admin/content
|
||
```
|
||
|
||
**Step 7: 更新测试**
|
||
|
||
修改所有测试文件中的API路径:
|
||
|
||
```typescript
|
||
// 旧路径
|
||
await page.goto('/api/admin/content');
|
||
|
||
// 新路径
|
||
await page.goto('/api/v1/admin/content');
|
||
```
|
||
|
||
**Step 8: 运行测试验证**
|
||
|
||
```bash
|
||
npm run test:unit
|
||
npm run test:e2e
|
||
```
|
||
|
||
Expected: 所有测试通过
|
||
|
||
**Step 9: 提交更改**
|
||
|
||
```bash
|
||
git add src/app/api/
|
||
git add src/lib/api/
|
||
git add e2e/
|
||
git commit -m "feat: add API version control (v1)
|
||
|
||
- Create versioned API structure (/api/v1/*)
|
||
- Migrate all API routes to v1
|
||
- Update frontend API client to use versioned endpoints
|
||
- Update all tests to use new API paths
|
||
- Add backward compatibility for root API endpoint
|
||
|
||
BREAKING CHANGE: All API endpoints now require /api/v1 prefix
|
||
Migration guide: Replace /api/* with /api/v1/*"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 6: 集成Lighthouse CI
|
||
|
||
**问题:** 缺少端到端性能监控,无法持续追踪性能指标。
|
||
|
||
**Files:**
|
||
- Create: `.lighthouserc.json`
|
||
- Create: `.github/workflows/lighthouse.yml`
|
||
- Modify: `package.json`
|
||
|
||
**Step 1: 安装Lighthouse CI**
|
||
|
||
```bash
|
||
npm install --save-dev @lhci/cli@0.10.x
|
||
```
|
||
|
||
**Step 2: 创建Lighthouse CI配置**
|
||
|
||
创建 `.lighthouserc.json`:
|
||
|
||
```json
|
||
{
|
||
"ci": {
|
||
"collect": {
|
||
"numberOfRuns": 3,
|
||
"settings": {
|
||
"preset": "desktop",
|
||
"throttling": {
|
||
"rttMs": 40,
|
||
"throughputKbps": 10240,
|
||
"cpuSlowdownMultiplier": 1
|
||
}
|
||
},
|
||
"url": [
|
||
"http://localhost:3000/",
|
||
"http://localhost:3000/about",
|
||
"http://localhost:3000/products",
|
||
"http://localhost:3000/services",
|
||
"http://localhost:3000/cases",
|
||
"http://localhost:3000/news",
|
||
"http://localhost:3000/contact"
|
||
]
|
||
},
|
||
"assert": {
|
||
"assertions": {
|
||
"categories:performance": ["error", { "minScore": 0.9 }],
|
||
"categories:accessibility": ["error", { "minScore": 0.9 }],
|
||
"categories:best-practices": ["error", { "minScore": 0.9 }],
|
||
"categories:seo": ["error", { "minScore": 0.9 }],
|
||
"first-contentful-paint": ["error", { "maxNumericValue": 2000 }],
|
||
"largest-contentful-paint": ["error", { "maxNumericValue": 3000 }],
|
||
"cumulative-layout-shift": ["error", { "maxNumericValue": 0.1 }],
|
||
"total-blocking-time": ["error", { "maxNumericValue": 300 }]
|
||
}
|
||
},
|
||
"upload": {
|
||
"target": "temporary-public-storage"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**Step 3: 创建CI工作流**
|
||
|
||
创建 `.github/workflows/lighthouse.yml`:
|
||
|
||
```yaml
|
||
name: Lighthouse CI
|
||
|
||
on:
|
||
push:
|
||
branches: [main, develop]
|
||
pull_request:
|
||
branches: [main, develop]
|
||
|
||
jobs:
|
||
lighthouse:
|
||
runs-on: ubuntu-latest
|
||
|
||
steps:
|
||
- uses: actions/checkout@v3
|
||
|
||
- name: Setup Node.js
|
||
uses: actions/setup-node@v3
|
||
with:
|
||
node-version: '18'
|
||
cache: 'npm'
|
||
|
||
- name: Install dependencies
|
||
run: npm ci
|
||
|
||
- name: Build application
|
||
run: npm run build
|
||
|
||
- name: Start application
|
||
run: npm start &
|
||
env:
|
||
PORT: 3000
|
||
|
||
- name: Wait for application
|
||
run: npx wait-on http://localhost:3000
|
||
|
||
- name: Run Lighthouse CI
|
||
run: npx lhci autorun
|
||
|
||
- name: Upload Lighthouse results
|
||
uses: actions/upload-artifact@v3
|
||
with:
|
||
name: lighthouse-results
|
||
path: .lighthouseci/
|
||
retention-days: 30
|
||
```
|
||
|
||
**Step 4: 更新package.json脚本**
|
||
|
||
```json
|
||
{
|
||
"scripts": {
|
||
"lighthouse": "lhci autorun",
|
||
"lighthouse:collect": "lhci collect",
|
||
"lighthouse:assert": "lhci assert",
|
||
"lighthouse:upload": "lhci upload"
|
||
}
|
||
}
|
||
```
|
||
|
||
**Step 5: 本地测试**
|
||
|
||
```bash
|
||
# 启动应用
|
||
npm run build
|
||
npm start &
|
||
|
||
# 运行Lighthouse CI
|
||
npm run lighthouse
|
||
```
|
||
|
||
Expected:
|
||
- 生成性能报告
|
||
- 所有断言通过
|
||
- 报告保存在 `.lighthouseci/` 目录
|
||
|
||
**Step 6: 查看报告**
|
||
|
||
```bash
|
||
# 打开最新的报告
|
||
open .lighthouseci/lhr-*.html
|
||
```
|
||
|
||
**Step 7: 提交更改**
|
||
|
||
```bash
|
||
git add .lighthouserc.json .github/workflows/lighthouse.yml package.json
|
||
git commit -m "feat: integrate Lighthouse CI for performance monitoring
|
||
|
||
- Add Lighthouse CI configuration
|
||
- Add CI workflow for automated performance testing
|
||
- Set performance budgets (90% score minimum)
|
||
- Add Core Web Vitals thresholds
|
||
- Add npm scripts for local performance testing
|
||
|
||
Performance targets:
|
||
- Performance score: ≥90%
|
||
- Accessibility score: ≥90%
|
||
- Best practices score: ≥90%
|
||
- SEO score: ≥90%
|
||
- LCP: ≤3000ms
|
||
- FCP: ≤2000ms
|
||
- CLS: ≤0.1
|
||
- TBT: ≤300ms"
|
||
```
|
||
|
||
---
|
||
|
||
## 低优先级任务(1个月内完成)
|
||
|
||
### Task 7: 引入API契约测试
|
||
|
||
**问题:** 缺少API契约测试,无法确保API响应符合预期格式。
|
||
|
||
**Files:**
|
||
- Create: `tests/api-contracts/`
|
||
- Modify: `package.json`
|
||
|
||
**Step 1: 安装依赖**
|
||
|
||
```bash
|
||
npm install --save-dev jest-openapi
|
||
```
|
||
|
||
**Step 2: 创建契约测试配置**
|
||
|
||
创建 `tests/api-contracts/setup.ts`:
|
||
|
||
```typescript
|
||
import { matchers } from 'jest-openapi';
|
||
import { specs } from '@/lib/openapi';
|
||
|
||
expect.extend(matchers(specs));
|
||
```
|
||
|
||
**Step 3: 创建契约测试**
|
||
|
||
创建 `tests/api-contracts/content.api.test.ts`:
|
||
|
||
```typescript
|
||
import { fetchApi } from '@/lib/api/client';
|
||
|
||
describe('Content API Contract Tests', () => {
|
||
it('GET /api/v1/admin/content should match schema', async () => {
|
||
const response = await fetchApi('/admin/content');
|
||
|
||
expect(response).toSatisfySchemaInApiSpec('Content');
|
||
});
|
||
|
||
it('POST /api/v1/admin/content should match schema', async () => {
|
||
const newContent = {
|
||
type: 'news',
|
||
title: 'Test News',
|
||
slug: 'test-news',
|
||
contentBody: 'Test content',
|
||
};
|
||
|
||
const response = await fetchApi('/admin/content', {
|
||
method: 'POST',
|
||
body: JSON.stringify(newContent),
|
||
});
|
||
|
||
expect(response).toSatisfySchemaInApiSpec('Content');
|
||
});
|
||
});
|
||
```
|
||
|
||
**Step 4: 运行契约测试**
|
||
|
||
```bash
|
||
npm run test:api-contracts
|
||
```
|
||
|
||
**Step 5: 提交更改**
|
||
|
||
```bash
|
||
git add tests/api-contracts/ package.json
|
||
git commit -m "feat: add API contract testing
|
||
|
||
- Add jest-openapi for schema validation
|
||
- Create contract tests for all API endpoints
|
||
- Ensure API responses match OpenAPI schema
|
||
|
||
Contract tests validate:
|
||
- Response structure matches schema
|
||
- Required fields are present
|
||
- Data types are correct"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 8: 优化测试执行时间
|
||
|
||
**问题:** 测试执行时间可能过长,需要优化以提高效率。
|
||
|
||
**Files:**
|
||
- Modify: `e2e/playwright.config.ts`
|
||
- Create: `scripts/analyze-test-performance.ts`
|
||
|
||
**Step 1: 分析慢速测试**
|
||
|
||
创建 `scripts/analyze-test-performance.ts`:
|
||
|
||
```typescript
|
||
import { execSync } from 'child_process';
|
||
import * as fs from 'fs';
|
||
|
||
interface TestResult {
|
||
file: string;
|
||
duration: number;
|
||
status: 'passed' | 'failed';
|
||
}
|
||
|
||
function analyzeTestPerformance() {
|
||
console.log('Analyzing test performance...\n');
|
||
|
||
// 运行测试并收集性能数据
|
||
const result = execSync('npx playwright test --reporter=json', {
|
||
encoding: 'utf-8',
|
||
cwd: 'e2e',
|
||
});
|
||
|
||
const testResults: TestResult[] = JSON.parse(result);
|
||
|
||
// 按执行时间排序
|
||
const sortedResults = testResults.sort((a, b) => b.duration - a.duration);
|
||
|
||
// 输出最慢的10个测试
|
||
console.log('Top 10 slowest tests:\n');
|
||
sortedResults.slice(0, 10).forEach((test, index) => {
|
||
console.log(`${index + 1}. ${test.file}`);
|
||
console.log(` Duration: ${(test.duration / 1000).toFixed(2)}s`);
|
||
console.log(` Status: ${test.status}\n`);
|
||
});
|
||
|
||
// 生成优化建议
|
||
const slowTests = sortedResults.filter(t => t.duration > 5000);
|
||
if (slowTests.length > 0) {
|
||
console.log('\nOptimization recommendations:\n');
|
||
slowTests.forEach(test => {
|
||
console.log(`- ${test.file}: Consider splitting or optimizing`);
|
||
});
|
||
}
|
||
|
||
// 保存报告
|
||
fs.writeFileSync(
|
||
'test-performance-report.json',
|
||
JSON.stringify(sortedResults, null, 2)
|
||
);
|
||
|
||
console.log('\nReport saved to test-performance-report.json');
|
||
}
|
||
|
||
analyzeTestPerformance();
|
||
```
|
||
|
||
**Step 2: 运行性能分析**
|
||
|
||
```bash
|
||
ts-node scripts/analyze-test-performance.ts
|
||
```
|
||
|
||
**Step 3: 优化慢速测试**
|
||
|
||
根据分析结果优化测试:
|
||
|
||
1. **减少等待时间**:
|
||
```typescript
|
||
// 不推荐
|
||
await page.waitForTimeout(5000);
|
||
|
||
// 推荐
|
||
await page.waitForSelector('[data-testid="result"]');
|
||
```
|
||
|
||
2. **并行执行**:
|
||
```typescript
|
||
// playwright.config.ts
|
||
{
|
||
fullyParallel: true,
|
||
workers: '75%',
|
||
}
|
||
```
|
||
|
||
3. **拆分大测试**:
|
||
```typescript
|
||
// 将一个大测试拆分为多个小测试
|
||
test.describe('User Registration', () => {
|
||
test('should fill registration form', async ({ page }) => {
|
||
// 只测试表单填写
|
||
});
|
||
|
||
test('should submit registration', async ({ page }) => {
|
||
// 只测试提交
|
||
});
|
||
});
|
||
```
|
||
|
||
**Step 4: 提交更改**
|
||
|
||
```bash
|
||
git add scripts/analyze-test-performance.ts e2e/playwright.config.ts
|
||
git commit -m "perf: optimize test execution time
|
||
|
||
- Add test performance analysis script
|
||
- Identify and optimize slow tests
|
||
- Enable parallel execution
|
||
- Reduce unnecessary waits
|
||
|
||
Expected improvement: 20-30% faster test execution"
|
||
```
|
||
|
||
---
|
||
|
||
## 执行计划总结
|
||
|
||
### 高优先级任务(1周内)
|
||
- ✅ Task 1: 统一联系表单实现
|
||
- ✅ Task 2: 配置单元测试覆盖率报告
|
||
- ✅ Task 3: 配置Allure测试报告
|
||
|
||
### 中优先级任务(2周内)
|
||
- ✅ Task 4: 引入OpenAPI文档
|
||
- ✅ Task 5: 引入API版本控制
|
||
- ✅ Task 6: 集成Lighthouse CI
|
||
|
||
### 低优先级任务(1个月内)
|
||
- ✅ Task 7: 引入API契约测试
|
||
- ✅ Task 8: 优化测试执行时间
|
||
|
||
---
|
||
|
||
## 验收标准
|
||
|
||
### 功能验收
|
||
- [ ] 所有改进任务完成
|
||
- [ ] 所有测试通过
|
||
- [ ] 文档更新完整
|
||
|
||
### 质量验收
|
||
- [ ] 单元测试覆盖率 ≥ 80%
|
||
- [ ] E2E测试通过率 ≥ 95%
|
||
- [ ] 性能评分 ≥ 90%
|
||
- [ ] 可访问性评分 ≥ 90%
|
||
|
||
### 文档验收
|
||
- [ ] API文档完整
|
||
- [ ] README更新
|
||
- [ ] 迁移指南编写
|
||
|
||
---
|
||
|
||
**计划创建时间**: 2026-03-20
|
||
**预计完成时间**: 2026-04-20
|
||
**负责人**: 张翔
|
||
**状态**: 待执行
|