refactor: 重构页面导航和滚动逻辑,提升用户体验 test: 更新测试配置和用例,增加覆盖率和稳定性 perf: 优化性能指标和阈值,适应开发环境需求 ci: 添加Lighthouse CI工作流,集成性能测试 docs: 更新API文档和健康检查端点 fix: 修复登录页面和表单提交问题 style: 调整响应式布局和可访问性改进 chore: 更新依赖项和脚本配置
37 KiB
质量改进迭代实施计划
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: 分析现有实现
检查两个文件的实现差异:
# 查看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,添加完整的验证和邮件发送逻辑:
'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:
'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文件
rm src/app/api/contact/route.ts
rm src/app/api/contact/route.test.ts
Step 5: 更新测试
创建新的测试文件 src/app/(marketing)/contact/actions.test.ts:
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: 运行测试验证
npm run test:unit
Expected: 所有测试通过
Step 7: 手动测试
npm run dev
# 访问 http://localhost:3000/contact
# 填写表单并提交
# 验证邮件发送成功
Step 8: 提交更改
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: 安装依赖
npm install --save-dev @types/jest jest ts-jest
Step 2: 创建Jest配置文件
创建 jest.config.ts:
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:
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 中添加:
{
"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: 运行覆盖率测试
npm run test:unit:coverage
Expected: 生成覆盖率报告,显示覆盖率百分比
Step 6: 查看覆盖率报告
npm run test:coverage:open
Expected: 在浏览器中打开HTML覆盖率报告
Step 7: 添加覆盖率徽章(可选)
在 README.md 中添加:
[](https://codecov.io/gh/your-org/your-repo)
Step 8: 提交更改
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依赖
npm install --save-dev allure-playwright
Step 2: 更新Playwright配置
修改 e2e/playwright.config.ts,确保Allure reporter已配置:
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:
#!/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 中添加:
{
"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: 运行测试生成报告
cd e2e
npm test
cd ..
npm run test:report
Expected:
- 测试执行完成
- Allure报告在浏览器中打开
- 显示测试统计、趋势、详情
Step 6: 添加CI集成(可选)
创建 .github/workflows/test-report.yml:
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: 提交更改
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: 安装依赖
npm install swagger-jsdoc swagger-ui-react
npm install --save-dev @types/swagger-jsdoc
Step 2: 创建OpenAPI配置
创建 src/lib/openapi.ts:
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:
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:
'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:
/**
* @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脚本
{
"scripts": {
"docs:api": "next dev -p 3000",
"docs:generate": "ts-node scripts/generate-api-docs.ts"
}
}
Step 7: 测试API文档
npm run dev
# 访问 http://localhost:3000/api-docs
Expected:
- Swagger UI界面正常显示
- 所有API端点都有文档
- 可以在线测试API
Step 8: 提交更改
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: 创建版本化目录结构
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目录:
# 迁移管理后台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:
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:
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:
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调用:
# 查找所有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路径:
// 旧路径
await page.goto('/api/admin/content');
// 新路径
await page.goto('/api/v1/admin/content');
Step 8: 运行测试验证
npm run test:unit
npm run test:e2e
Expected: 所有测试通过
Step 9: 提交更改
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
npm install --save-dev @lhci/cli@0.10.x
Step 2: 创建Lighthouse CI配置
创建 .lighthouserc.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:
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脚本
{
"scripts": {
"lighthouse": "lhci autorun",
"lighthouse:collect": "lhci collect",
"lighthouse:assert": "lhci assert",
"lighthouse:upload": "lhci upload"
}
}
Step 5: 本地测试
# 启动应用
npm run build
npm start &
# 运行Lighthouse CI
npm run lighthouse
Expected:
- 生成性能报告
- 所有断言通过
- 报告保存在
.lighthouseci/目录
Step 6: 查看报告
# 打开最新的报告
open .lighthouseci/lhr-*.html
Step 7: 提交更改
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: 安装依赖
npm install --save-dev jest-openapi
Step 2: 创建契约测试配置
创建 tests/api-contracts/setup.ts:
import { matchers } from 'jest-openapi';
import { specs } from '@/lib/openapi';
expect.extend(matchers(specs));
Step 3: 创建契约测试
创建 tests/api-contracts/content.api.test.ts:
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: 运行契约测试
npm run test:api-contracts
Step 5: 提交更改
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:
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: 运行性能分析
ts-node scripts/analyze-test-performance.ts
Step 3: 优化慢速测试
根据分析结果优化测试:
- 减少等待时间:
// 不推荐
await page.waitForTimeout(5000);
// 推荐
await page.waitForSelector('[data-testid="result"]');
- 并行执行:
// playwright.config.ts
{
fullyParallel: true,
workers: '75%',
}
- 拆分大测试:
// 将一个大测试拆分为多个小测试
test.describe('User Registration', () => {
test('should fill registration form', async ({ page }) => {
// 只测试表单填写
});
test('should submit registration', async ({ page }) => {
// 只测试提交
});
});
Step 4: 提交更改
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 负责人: 张翔 状态: 待执行