feb646efe5
feat: 为主导航菜单和页面区块添加ARIA属性 fix: 解决工作时间信息获取问题 perf: 优化页面滚动功能实现 fix: 修正联系页面标题显示问题 test: 运行完整测试套件验证修复效果 docs: 添加修复完成报告
545 lines
14 KiB
Markdown
545 lines
14 KiB
Markdown
# 测试套件修复计划
|
||
|
||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||
|
||
**目标:** 修复表单测试中的元素定位、CSS选择器和Toast组件处理问题,使所有9个失败的表单测试通过。
|
||
|
||
**架构:** 通过在系统代码中添加data-testid属性、修正测试代码的CSS选择器、更新Toast组件处理逻辑,实现测试与实际UI的同步。
|
||
|
||
**技术栈:** Playwright测试框架、React组件、TypeScript、CSS选择器
|
||
|
||
---
|
||
|
||
## 问题分析
|
||
|
||
当前9个表单测试失败的根本原因:
|
||
|
||
1. **元素定位器不匹配** - 测试使用了不存在的`data-testid`属性
|
||
2. **CSS选择器转义错误** - 错误消息选择器使用了双转义
|
||
3. **Toast组件处理不当** - 测试没有正确处理动态Toast显示
|
||
4. **缺少等待机制** - 没有等待异步UI更新完成
|
||
|
||
---
|
||
|
||
### Task 1: 在系统代码中添加data-testid属性
|
||
|
||
**目标:** 为表单元素添加data-testid属性,使测试能够正确定位元素。
|
||
|
||
**文件:**
|
||
- 修改: `/Users/zhangxiang/Codes/Gitee/home-page/novalon-website/src/components/sections/contact-section.tsx`
|
||
|
||
**Step 1: 为姓名输入框添加data-testid**
|
||
|
||
```tsx
|
||
<Input
|
||
label="姓名"
|
||
id="name"
|
||
placeholder="请输入您的姓名"
|
||
required
|
||
data-testid="name-input"
|
||
value={formData.name}
|
||
onChange={(e) => handleChange('name', e.target.value)}
|
||
onBlur={(e) => handleBlur('name', e.target.value)}
|
||
error={errors.name}
|
||
/>
|
||
```
|
||
|
||
**Step 2: 为电话输入框添加data-testid**
|
||
|
||
```tsx
|
||
<Input
|
||
label="电话"
|
||
id="phone"
|
||
type="tel"
|
||
placeholder="请输入您的电话"
|
||
required
|
||
data-testid="phone-input"
|
||
value={formData.phone}
|
||
onChange={(e) => handleChange('phone', e.target.value)}
|
||
onBlur={(e) => handleBlur('phone', e.target.value)}
|
||
error={errors.phone}
|
||
/>
|
||
```
|
||
|
||
**Step 3: 为邮箱输入框添加data-testid**
|
||
|
||
```tsx
|
||
<Input
|
||
label="邮箱"
|
||
id="email"
|
||
type="email"
|
||
placeholder="请输入您的邮箱"
|
||
required
|
||
data-testid="email-input"
|
||
value={formData.email}
|
||
onChange={(e) => handleChange('email', e.target.value)}
|
||
onBlur={(e) => handleBlur('email', e.target.value)}
|
||
error={errors.email}
|
||
/>
|
||
```
|
||
|
||
**Step 4: 为留言输入框添加data-testid**
|
||
|
||
```tsx
|
||
<Textarea
|
||
label="留言内容"
|
||
id="message"
|
||
placeholder="请输入您想咨询的内容"
|
||
rows={5}
|
||
required
|
||
data-testid="message-input"
|
||
value={formData.message}
|
||
onChange={(e) => handleChange('message', e.target.value)}
|
||
onBlur={(e) => handleBlur('message', e.target.value)}
|
||
error={errors.message}
|
||
/>
|
||
```
|
||
|
||
**Step 5: 为提交按钮添加data-testid**
|
||
|
||
```tsx
|
||
<Button
|
||
type="submit"
|
||
size="lg"
|
||
className="w-full group mt-auto min-h-[52px] md:min-h-0"
|
||
disabled={isSubmitting}
|
||
data-testid="submit-button"
|
||
>
|
||
```
|
||
|
||
**Step 6: 为成功消息添加data-testid**
|
||
|
||
```tsx
|
||
{isSubmitted ? (
|
||
<div className="text-center py-12 flex-1 flex items-center justify-center" data-testid="success-message">
|
||
<div className="w-16 h-16 bg-[#C41E3A] rounded-full flex items-center justify-center mx-auto mb-4 animate-stamp-in">
|
||
<CheckCircle2 className="w-8 h-8 text-white" />
|
||
</div>
|
||
<h4 className="text-xl font-semibold text-[#1A1A2E] mb-2">消息已发送</h4>
|
||
<p className="text-[#718096]">感谢您的留言,我们会尽快与您联系!</p>
|
||
</div>
|
||
) : (
|
||
```
|
||
|
||
**Step 7: 为Toast组件添加data-testid**
|
||
|
||
```tsx
|
||
{showToast && (
|
||
<Toast
|
||
message={toastMessage}
|
||
type={toastType}
|
||
onClose={() => setShowToast(false)}
|
||
data-testid="toast-notification"
|
||
/>
|
||
)}
|
||
```
|
||
|
||
**Step 8: 为错误消息容器添加data-testid**
|
||
|
||
在Input组件中,为错误消息添加data-testid:
|
||
|
||
```tsx
|
||
{error && (
|
||
<p id={errorId} className="mt-1 text-sm text-[#C41E3A]" role="alert" data-testid="error-message">
|
||
{error}
|
||
</p>
|
||
)}
|
||
```
|
||
|
||
**Step 9: 验证data-testid属性已正确添加**
|
||
|
||
运行: `cd /Users/zhangxiang/Codes/Gitee/home-page/novalon-website && npm run dev`
|
||
|
||
预期: 开发服务器正常启动,无编译错误
|
||
|
||
**Step 10: 提交更改**
|
||
|
||
```bash
|
||
git add src/components/sections/contact-section.tsx src/components/ui/input.tsx src/components/ui/textarea.tsx
|
||
git commit -m "feat: add data-testid attributes for form elements to improve testability"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 2: 修正测试代码中的CSS选择器
|
||
|
||
**目标:** 修正ContactPage中的CSS选择器,使用正确的转义和data-testid定位。
|
||
|
||
**文件:**
|
||
- 修改: `/Users/zhangxiang/Codes/Gitee/home-page/novalon-website/test-framework/shared/pages/ContactPage.ts`
|
||
|
||
**Step 1: 修正getFormErrorMessage方法**
|
||
|
||
```typescript
|
||
async getFormErrorMessage(): Promise<string> {
|
||
const errorElement = await this.page.locator('[data-testid="error-message"]').first();
|
||
return await errorElement.textContent() || '';
|
||
}
|
||
```
|
||
|
||
**Step 2: 修正getFormSuccessMessage方法**
|
||
|
||
```typescript
|
||
async getFormSuccessMessage(): Promise<string> {
|
||
await this.page.waitForTimeout(1000);
|
||
const successElement = await this.page.locator('[data-testid="success-message"]');
|
||
if (await successElement.count() > 0) {
|
||
return await successElement.textContent() || '';
|
||
}
|
||
return '';
|
||
}
|
||
```
|
||
|
||
**Step 3: 添加getToastMessage方法**
|
||
|
||
```typescript
|
||
async getToastMessage(): Promise<{ message: string; type: 'success' | 'error' }> {
|
||
const toastElement = await this.page.locator('[data-testid="toast-notification"]');
|
||
if (await toastElement.count() > 0) {
|
||
const message = await toastElement.textContent() || '';
|
||
const type = await toastElement.getAttribute('data-type') as 'success' | 'error';
|
||
return { message, type };
|
||
}
|
||
return { message: '', type: 'success' };
|
||
}
|
||
```
|
||
|
||
**Step 4: 添加waitForToast方法**
|
||
|
||
```typescript
|
||
async waitForToast(timeout: number = 5000): Promise<boolean> {
|
||
try {
|
||
await this.page.waitForSelector('[data-testid="toast-notification"]', { timeout });
|
||
return true;
|
||
} catch {
|
||
return false;
|
||
}
|
||
}
|
||
```
|
||
|
||
**Step 5: 添加waitForToastHidden方法**
|
||
|
||
```typescript
|
||
async waitForToastHidden(timeout: number = 5000): Promise<boolean> {
|
||
try {
|
||
await this.page.waitForSelector('[data-testid="toast-notification"]', { state: 'hidden', timeout });
|
||
return true;
|
||
} catch {
|
||
return false;
|
||
}
|
||
}
|
||
```
|
||
|
||
**Step 6: 运行表单测试验证选择器修复**
|
||
|
||
运行: `cd test-framework && npm run test:dev-audit:forms`
|
||
|
||
预期: 部分测试通过,元素定位错误减少
|
||
|
||
**Step 7: 提交更改**
|
||
|
||
```bash
|
||
git add test-framework/shared/pages/ContactPage.ts
|
||
git commit -m "fix: correct CSS selectors and add toast handling methods in ContactPage"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 3: 更新表单测试以正确处理Toast组件
|
||
|
||
**目标:** 修改测试用例,正确处理Toast组件的异步显示和消失。
|
||
|
||
**文件:**
|
||
- 修改: `/Users/zhangxiang/Codes/Gitee/home-page/novalon-website/test-framework/dev-audit/forms/forms.spec.ts`
|
||
|
||
**Step 1: 修改"联系表单 - 有效数据提交"测试**
|
||
|
||
```typescript
|
||
test('联系表单 - 有效数据提交', async ({ page }) => {
|
||
const contactPage = new ContactPage(page);
|
||
|
||
await page.route('**/api/contact', async route => {
|
||
await route.fulfill({
|
||
status: 200,
|
||
contentType: 'application/json',
|
||
body: JSON.stringify({ success: true, message: '消息已发送' })
|
||
});
|
||
});
|
||
|
||
await contactPage.navigate();
|
||
await contactPage.fillContactForm({
|
||
name: formData.valid.name,
|
||
email: formData.valid.email,
|
||
phone: formData.valid.phone,
|
||
message: formData.valid.message,
|
||
subject: '测试主题'
|
||
});
|
||
await contactPage.submitForm();
|
||
|
||
await contactPage.waitForToast();
|
||
const toastMessage = await contactPage.getToastMessage();
|
||
expect(toastMessage.message).toContain('成功');
|
||
expect(toastMessage.type).toBe('success');
|
||
|
||
await contactPage.waitForToastHidden();
|
||
});
|
||
```
|
||
|
||
**Step 2: 修改"联系表单 - 必填字段验证"测试**
|
||
|
||
```typescript
|
||
test('联系表单 - 必填字段验证', async ({ page }) => {
|
||
const contactPage = new ContactPage(page);
|
||
|
||
await contactPage.navigate();
|
||
await contactPage.fillContactForm({
|
||
name: '',
|
||
email: '',
|
||
phone: '',
|
||
message: '',
|
||
subject: ''
|
||
});
|
||
await contactPage.submitForm();
|
||
|
||
await page.waitForTimeout(500);
|
||
const errorMessage = await contactPage.getFormErrorMessage();
|
||
expect(errorMessage).toBeTruthy();
|
||
});
|
||
```
|
||
|
||
**Step 3: 修改"联系表单 - 邮箱格式验证"测试**
|
||
|
||
```typescript
|
||
test('联系表单 - 邮箱格式验证', async ({ page }) => {
|
||
const contactPage = new ContactPage(page);
|
||
|
||
await contactPage.navigate();
|
||
await contactPage.fillContactForm({
|
||
name: '测试用户',
|
||
email: formData.invalid.email,
|
||
phone: '13800138000',
|
||
message: '测试消息',
|
||
subject: '测试主题'
|
||
});
|
||
await contactPage.submitForm();
|
||
|
||
await page.waitForTimeout(500);
|
||
const errorMessage = await contactPage.getFormErrorMessage();
|
||
expect(errorMessage).toContain('邮箱');
|
||
});
|
||
```
|
||
|
||
**Step 4: 修改"联系表单 - API错误处理"测试**
|
||
|
||
```typescript
|
||
test('联系表单 - API错误处理', async ({ page }) => {
|
||
const contactPage = new ContactPage(page);
|
||
|
||
await page.route('**/api/contact', async route => {
|
||
await route.fulfill({
|
||
status: 500,
|
||
contentType: 'application/json',
|
||
body: JSON.stringify({ success: false, message: '服务器错误' })
|
||
});
|
||
});
|
||
|
||
await contactPage.navigate();
|
||
await contactPage.fillContactForm({
|
||
name: formData.valid.name,
|
||
email: formData.valid.email,
|
||
phone: formData.valid.phone,
|
||
message: formData.valid.message,
|
||
subject: '测试主题'
|
||
});
|
||
await contactPage.submitForm();
|
||
|
||
await contactPage.waitForToast();
|
||
const toastMessage = await contactPage.getToastMessage();
|
||
expect(toastMessage.message).toContain('失败');
|
||
expect(toastMessage.type).toBe('error');
|
||
|
||
await contactPage.waitForToastHidden();
|
||
});
|
||
```
|
||
|
||
**Step 5: 运行表单测试验证Toast处理**
|
||
|
||
运行: `cd test-framework && npm run test:dev-audit:forms`
|
||
|
||
预期: 所有表单测试通过,Toast组件正确处理
|
||
|
||
**Step 6: 提交更改**
|
||
|
||
```bash
|
||
git add test-framework/dev-audit/forms/forms.spec.ts
|
||
git commit -m "fix: update form tests to properly handle toast component and async UI updates"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 4: 更新Input和Textarea组件以支持data-testid
|
||
|
||
**目标:** 确保UI组件正确传递data-testid属性。
|
||
|
||
**文件:**
|
||
- 修改: `/Users/zhangxiang/Codes/Gitee/home-page/novalon-website/src/components/ui/input.tsx`
|
||
- 修改: `/Users/zhangxiang/Codes/Gitee/home-page/novalon-website/src/components/ui/textarea.tsx`
|
||
|
||
**Step 1: 更新Input组件接口**
|
||
|
||
```typescript
|
||
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
||
label?: string
|
||
error?: string
|
||
'data-testid'?: string
|
||
}
|
||
```
|
||
|
||
**Step 2: 在Input组件中使用data-testid**
|
||
|
||
```typescript
|
||
<input
|
||
type={type}
|
||
id={inputId}
|
||
data-slot="input"
|
||
data-testid={props['data-testid']}
|
||
className={cn(
|
||
// ... existing className
|
||
)}
|
||
ref={ref}
|
||
aria-required={props.required ? "true" : undefined}
|
||
aria-invalid={error ? "true" : "false"}
|
||
aria-describedby={error ? errorId : undefined}
|
||
{...props}
|
||
/>
|
||
```
|
||
|
||
**Step 3: 更新Textarea组件接口**
|
||
|
||
```typescript
|
||
export interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
|
||
label?: string
|
||
error?: string
|
||
'data-testid'?: string
|
||
}
|
||
```
|
||
|
||
**Step 4: 在Textarea组件中使用data-testid**
|
||
|
||
```typescript
|
||
<textarea
|
||
id={textareaId}
|
||
data-slot="textarea"
|
||
data-testid={props['data-testid']}
|
||
className={cn(
|
||
// ... existing className
|
||
)}
|
||
ref={ref}
|
||
aria-required={props.required ? "true" : undefined}
|
||
aria-invalid={error ? "true" : "false"}
|
||
aria-describedby={error ? errorId : undefined}
|
||
{...props}
|
||
/>
|
||
```
|
||
|
||
**Step 5: 验证组件正确传递data-testid**
|
||
|
||
运行: `cd /Users/zhangxiang/Codes/Gitee/home-page/novalon-website && npm run dev`
|
||
|
||
预期: 开发服务器正常启动,组件正确编译
|
||
|
||
**Step 6: 提交更改**
|
||
|
||
```bash
|
||
git add src/components/ui/input.tsx src/components/ui/textarea.tsx
|
||
git commit -m "feat: add data-testid support to Input and Textarea components"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 5: 运行完整测试套件验证修复
|
||
|
||
**目标:** 运行所有测试验证修复效果。
|
||
|
||
**文件:**
|
||
- 无需修改
|
||
|
||
**Step 1: 运行表单测试**
|
||
|
||
运行: `cd test-framework && npm run test:dev-audit:forms`
|
||
|
||
预期: 所有表单测试通过(20/20)
|
||
|
||
**Step 2: 运行性能测试**
|
||
|
||
运行: `cd test-framework && npm run test:dev-audit:performance`
|
||
|
||
预期: 所有性能测试通过(35/35)
|
||
|
||
**Step 3: 运行完整测试套件**
|
||
|
||
运行: `cd test-framework && npm run test:dev-audit`
|
||
|
||
预期: 所有测试通过(85/85),通过率100%
|
||
|
||
**Step 4: 生成测试报告**
|
||
|
||
运行: `cd test-framework && npx ts-node scripts/generate-report.ts`
|
||
|
||
预期: 生成完整的测试报告,显示所有测试通过
|
||
|
||
**Step 5: 查看测试结果**
|
||
|
||
运行: `cat test-framework/reports/results.json | jq '.stats'`
|
||
|
||
预期输出:
|
||
```json
|
||
{
|
||
"startTime": "2026-03-06T...",
|
||
"duration": ...,
|
||
"expected": 85,
|
||
"skipped": 0,
|
||
"unexpected": 0,
|
||
"flaky": 0
|
||
}
|
||
```
|
||
|
||
**Step 6: 提交最终验证**
|
||
|
||
```bash
|
||
git add test-framework/reports/
|
||
git commit -m "test: verify all tests passing after test suite fixes"
|
||
```
|
||
|
||
---
|
||
|
||
## 验收标准
|
||
|
||
- [ ] 所有表单测试通过(20/20)
|
||
- [ ] 所有性能测试通过(35/35)
|
||
- [ ] 所有SEO测试通过
|
||
- [ ] 所有可访问性测试通过
|
||
- [ ] 完整测试套件通过率100%(85/85)
|
||
- [ ] 测试报告显示所有测试通过
|
||
- [ ] 无测试超时错误
|
||
- [ ] 无元素定位错误
|
||
|
||
---
|
||
|
||
## 风险与注意事项
|
||
|
||
1. **Toast组件时序问题** - 需要确保测试等待足够时间让Toast显示和消失
|
||
2. **移动端兼容性** - 某些测试在移动设备上可能需要额外的等待时间
|
||
3. **并发测试冲突** - 多个测试同时运行可能导致状态污染,确保每个测试独立运行
|
||
4. **API模拟准确性** - 确保API模拟响应与真实API响应格式一致
|
||
|
||
---
|
||
|
||
## 后续优化建议
|
||
|
||
1. **添加视觉回归测试** - 使用Percy或Applitools进行UI视觉测试
|
||
2. **增加测试覆盖率** - 添加更多边界条件和异常场景测试
|
||
3. **性能基准测试** - 建立性能基准,监控性能回归
|
||
4. **测试数据管理** - 使用测试数据工厂生成更多样化的测试数据
|
||
5. **CI/CD集成** - 将测试集成到持续集成流程中
|