diff --git a/docs/mobile-test-evaluation-report.md b/docs/mobile-test-evaluation-report.md
deleted file mode 100644
index b2bc875..0000000
--- a/docs/mobile-test-evaluation-report.md
+++ /dev/null
@@ -1,464 +0,0 @@
-# 移动端自动化测试现状评估报告
-
-**评估时间**: 2026-03-05
-**评估者**: 张翔
-**测试框架**: Playwright + TypeScript
-**评估范围**: 移动端E2E测试
-
----
-
-## 一、执行摘要
-
-### 1.1 测试执行概况
-
-**测试环境**
-- 浏览器:Mobile Chrome (Pixel 5)、Mobile Safari (iPhone 12)
-- 基础URL:http://localhost:3001
-- 测试框架:Playwright 1.58.2 + TypeScript
-
-**总体统计**
-- 总测试数:470个(所有项目)
-- 移动端测试:60个
-- 通过:55个(91.7%)
-- 失败:5个(8.3%)
-- 执行时间:约4.8分钟
-
-### 1.2 关键发现
-
-**✅ 优势**
-1. 移动端测试通过率较高(91.7%)
-2. 基础功能测试覆盖较完整
-3. 测试框架结构清晰
-4. 已有页面对象模式基础
-
-**❌ 问题**
-1. 可访问性问题严重(58个颜色对比度违规)
-2. 面包屑导航在移动端缺失
-3. 冒烟测试在移动端失败率较高(33个失败)
-4. 性能测试通过率极低(9.1%)
-5. 响应式测试通过率低(26.7%)
-
----
-
-## 二、详细测试结果分析
-
-### 2.1 移动端测试结果
-
-**测试文件**: `src/tests/mobile/mobile-ux.spec.ts`
-
-**测试用例统计**
-- 总用例数:13个
-- 通过用例:12个
-- 失败用例:1个
-- 通过率:92.3%
-
-**失败用例详情**
-```
-测试名称: Mobile About page renders correctly
-失败原因: 面包屑导航元素未找到
-选择器: nav[aria-label="breadcrumb"]
-错误信息: element(s) not found
-影响设备: Chromium, Firefox, WebKit, Mobile Chrome, Mobile Safari
-```
-
-**通过用例列表**
-1. ✅ Mobile menu opens and closes correctly
-2. ✅ Mobile menu navigation works
-3. ✅ Mobile menu closes on outside click
-4. ✅ Mobile viewport renders correctly
-5. ✅ Touch targets are appropriately sized
-6. ✅ Mobile page scrolls smoothly
-7. ✅ Mobile images are responsive
-8. ✅ Mobile text is readable
-9. ✅ Mobile Products page cards stack vertically
-10. ✅ Mobile Contact page form is usable
-11. ✅ Mobile keyboard navigation works
-
-### 2.2 冒烟测试结果
-
-**测试文件**: `src/tests/smoke/*.spec.ts`
-
-**测试统计**
-- 总用例数:57个
-- 通过用例:24个
-- 失败用例:33个
-- 通过率:42.1%
-
-**失败用例分布**
-- 联系页面冒烟测试:20个失败
-- 首页冒烟测试:8个失败
-- 导航冒烟测试:5个失败
-
-**典型失败原因**
-1. 元素选择器在移动端不匹配
-2. 元素在移动端不可见(响应式隐藏)
-3. 移动端布局变化导致定位失败
-4. 超时问题(移动端加载较慢)
-
-### 2.3 可访问性测试结果
-
-**测试文件**: `src/tests/accessibility/wcag-compliance.spec.ts`
-
-**违规统计**
-- 总违规数:58个
-- 严重程度:serious
-- 主要类型:color-contrast(颜色对比度)
-
-**典型违规示例**
-```
-元素:
扫码关注获取最新资讯
-前景色: #718096
-背景色: #F5F5F5
-对比度: 3.68:1
-要求: 4.5:1 (WCAG 2.1 AA)
-影响: 页脚区域文本可读性
-```
-
-**影响范围**
-- 页脚区域:版权信息、社交媒体链接
-- 联系信息:辅助文本、提示信息
-- 表单标签:占位符文本、帮助文本
-
-### 2.4 性能测试结果
-
-**测试文件**: `src/tests/performance/*.spec.ts`
-
-**测试统计**
-- 总用例数:33个
-- 通过用例:3个
-- 失败用例:30个
-- 通过率:9.1%
-
-**失败原因分析**
-1. 页面加载时间超出阈值(LCP > 2.5s)
-2. 交互响应时间过长(FID > 100ms)
-3. 滚动性能不佳(帧率 < 60fps)
-4. 资源加载效率低(TTFB > 600ms)
-
-**性能指标现状**
-```
-LCP (最大内容绘制): 3.2s (目标: <2.5s)
-FID (首次输入延迟): 180ms (目标: <100ms)
-CLS (累积布局偏移): 0.15 (目标: <0.1)
-TTI (可交互时间): 4.5s (目标: <3.5s)
-TTFB (首字节时间): 800ms (目标: <600ms)
-```
-
-### 2.5 响应式测试结果
-
-**测试文件**: `src/tests/responsive/*.spec.ts`
-
-**测试统计**
-- 总用例数:60个
-- 通过用例:16个
-- 失败用例:44个
-- 通过率:26.7%
-
-**失败原因分析**
-1. 移动端布局适配不完善
-2. 平板端显示需要调整
-3. 大屏幕显示需要优化
-4. 移动端交互需要改进
-
----
-
-## 三、问题分类与优先级
-
-### 3.1 高优先级问题(P0)
-
-**1. 可访问性问题**
-- **问题**: 58个颜色对比度违规
-- **影响**: 影响视觉障碍用户,不符合WCAG 2.1 AA标准
-- **解决方案**:
- - 调整文本颜色或背景色
- - 增加字体大小或字重
- - 使用更深的文本颜色
-- **预计工作量**: 2天
-
-**2. 面包屑导航缺失**
-- **问题**: 移动端About页面缺少面包屑导航
-- **影响**: 用户无法了解当前位置,导航体验差
-- **解决方案**:
- - 在移动端添加面包屑导航
- - 或在移动端使用其他导航方式
-- **预计工作量**: 0.5天
-
-**3. 冒烟测试失败**
-- **问题**: 33个冒烟测试在移动端失败
-- **影响**: 基础功能验证不完整,无法保证质量
-- **解决方案**:
- - 修复元素选择器
- - 适配移动端布局变化
- - 优化超时设置
-- **预计工作量**: 3天
-
-### 3.2 中优先级问题(P1)
-
-**1. 性能问题**
-- **问题**: 性能测试通过率仅9.1%
-- **影响**: 用户体验差,影响SEO排名
-- **解决方案**:
- - 优化图片加载(懒加载、WebP格式)
- - 代码分割和按需加载
- - 优化CSS和JS
- - 改善缓存策略
-- **预计工作量**: 5天
-
-**2. 响应式适配问题**
-- **问题**: 响应式测试通过率仅26.7%
-- **影响**: 不同设备显示效果不一致
-- **解决方案**:
- - 完善移动端布局
- - 优化平板端显示
- - 改善大屏幕体验
-- **预计工作量**: 4天
-
-### 3.3 低优先级问题(P2)
-
-**1. 测试覆盖不足**
-- **问题**: 移动端测试覆盖率仅30%
-- **影响**: 无法全面保证移动端质量
-- **解决方案**:
- - 扩展移动端测试用例
- - 增加设备覆盖
- - 添加更多测试场景
-- **预计工作量**: 10天
-
----
-
-## 四、测试框架评估
-
-### 4.1 架构优势
-
-**1. 清晰的目录结构**
-```
-e2e/src/
-├── pages/ # 页面对象
-├── tests/ # 测试用例
-├── fixtures/ # 测试夹具
-├── utils/ # 工具类
-└── config/ # 配置文件
-```
-
-**2. 页面对象模式**
-- BasePage提供通用方法
-- 各页面继承BasePage
-- 代码复用性好
-
-**3. 测试分类清晰**
-- Smoke测试
-- Regression测试
-- Performance测试
-- Responsive测试
-- Mobile测试
-- Accessibility测试
-- Security测试
-- Visual测试
-
-### 4.2 架构不足
-
-**1. 缺少移动端专用页面对象**
-- 当前只有通用的BasePage
-- 没有MobilePage专门处理移动端交互
-- 移动端特定方法分散在各测试中
-
-**2. 设备矩阵不完善**
-- 只配置了Pixel 5和iPhone 12
-- 缺少其他主流设备
-- 没有响应式断点测试
-
-**3. 性能监控不完整**
-- PerformanceMonitor功能有限
-- 缺少Core Web Vitals完整监控
-- 没有移动端特定性能指标
-
-**4. 测试数据管理缺失**
-- 没有统一的测试数据管理
-- 测试数据硬编码在测试中
-- 缺少测试数据生成工具
-
----
-
-## 五、与设计方案的差距分析
-
-### 5.1 测试覆盖率差距
-
-| 测试类型 | 设计目标 | 当前状态 | 差距 |
-|---------|---------|---------|------|
-| 基础功能测试 | 90%+ | 42.1% | -47.9% |
-| 性能测试 | 90%+ | 9.1% | -80.9% |
-| 兼容性测试 | 95%+ | 26.7% | -68.3% |
-| 可访问性测试 | 90%+ | 0% | -90% |
-| 用户体验测试 | 90%+ | 92.3% | +2.3% |
-
-### 5.2 设备覆盖差距
-
-| 设备类型 | 设计目标 | 当前状态 | 差距 |
-|---------|---------|---------|------|
-| iPhone 12/14 | ✅ | ✅ | - |
-| Galaxy S21 | ✅ | ❌ | -1 |
-| iPad Pro | ✅ | ❌ | -1 |
-| iPad Mini | ✅ | ❌ | -1 |
-| 响应式断点 | ✅ | ❌ | -5 |
-
-### 5.3 技术实现差距
-
-| 功能模块 | 设计要求 | 当前状态 | 差距 |
-|---------|---------|---------|------|
-| MobilePage | ✅ | ❌ | 未创建 |
-| DeviceMatrix | ✅ | ❌ | 未创建 |
-| PerformanceMonitor增强 | ✅ | ⚠️ | 部分实现 |
-| TestDataGenerator | ✅ | ⚠️ | 部分实现 |
-| CI/CD集成 | ✅ | ⚠️ | 部分实现 |
-
----
-
-## 六、改进建议
-
-### 6.1 立即行动项(1周内)
-
-**1. 修复高优先级问题**
-- 修复可访问性问题(颜色对比度)
-- 添加移动端面包屑导航
-- 修复冒烟测试失败
-
-**2. 完善测试基础设施**
-- 创建MobilePage页面对象
-- 配置智能设备矩阵
-- 增强PerformanceMonitor
-
-**3. 优化测试执行**
-- 修复元素选择器
-- 优化超时设置
-- 改善测试稳定性
-
-### 6.2 短期改进项(2-4周)
-
-**1. 性能优化**
-- 优化图片加载
-- 实施代码分割
-- 改善缓存策略
-- 优化CSS和JS
-
-**2. 响应式改进**
-- 完善移动端布局
-- 优化平板端显示
-- 改善大屏幕体验
-
-**3. 测试扩展**
-- 扩展移动端测试用例
-- 增加设备覆盖
-- 添加更多测试场景
-
-### 6.3 长期优化项(1-3个月)
-
-**1. 建立持续优化机制**
-- 定期评估测试结果
-- 动态调整设备矩阵
-- 持续优化性能
-
-**2. 引入高级测试技术**
-- AI驱动的测试用例生成
-- 测试即代码(TaaC)
-- 智能测试调度
-
-**3. 建立质量度量体系**
-- 测试覆盖率监控
-- 缺陷逃逸率分析
-- 质量趋势分析
-
----
-
-## 七、风险评估
-
-### 7.1 技术风险
-
-**高风险**
-- 可访问性问题可能导致法律风险
-- 性能问题影响用户体验和SEO
-- 响应式问题影响多设备兼容性
-
-**中风险**
-- 测试覆盖不足可能导致缺陷逃逸
-- 设备覆盖不足可能导致兼容性问题
-- 测试稳定性不足可能导致误报
-
-### 7.2 项目风险
-
-**时间风险**
-- 修复高优先级问题需要5.5天
-- 完成短期改进项需要2-4周
-- 完成长期优化项需要1-3个月
-
-**资源风险**
-- 需要开发资源修复问题
-- 需要测试资源扩展测试
-- 需要运维资源优化性能
-
-### 7.3 应对措施
-
-**技术应对**
-- 优先修复高优先级问题
-- 建立持续优化机制
-- 定期评估和调整
-
-**项目应对**
-- 分阶段实施,降低风险
-- 合理分配资源
-- 建立应急预案
-
----
-
-## 八、总结
-
-### 8.1 现状总结
-
-**优势**
-1. 测试框架基础良好
-2. 移动端测试通过率较高
-3. 测试结构清晰
-4. 已有页面对象模式
-
-**不足**
-1. 可访问性问题严重
-2. 性能问题突出
-3. 响应式适配不完善
-4. 测试覆盖不足
-
-### 8.2 改进方向
-
-**短期(1-2周)**
-- 修复高优先级问题
-- 完善测试基础设施
-- 优化测试执行
-
-**中期(1-2个月)**
-- 性能优化
-- 响应式改进
-- 测试扩展
-
-**长期(3-6个月)**
-- 建立持续优化机制
-- 引入高级测试技术
-- 建立质量度量体系
-
-### 8.3 预期成果
-
-**量化指标**
-- 移动端测试覆盖率:从30%提升到90%+
-- 测试通过率:从91.7%提升到98%+
-- 性能测试通过率:从9.1%提升到90%+
-- 响应式测试通过率:从26.7%提升到90%+
-
-**质量提升**
-- 可访问性:符合WCAG 2.1 AA标准
-- 性能:Core Web Vitals达到优秀水平
-- 兼容性:覆盖95%用户
-- 用户体验:触摸交互和可访问性得到保障
-
----
-
-**报告版本**: v1.0
-**最后更新**: 2026-03-05
-**维护者**: 张翔
-**状态**: 已完成
diff --git a/docs/plans/2026-03-05-mobile-automation-test-design.md b/docs/plans/2026-03-05-mobile-automation-test-design.md
deleted file mode 100644
index be820d8..0000000
--- a/docs/plans/2026-03-05-mobile-automation-test-design.md
+++ /dev/null
@@ -1,557 +0,0 @@
-# 移动端自动化测试完善方案设计
-
-**创建时间**: 2026-03-05
-**设计者**: 张翔
-**状态**: 已确认
-**版本**: v1.0
-
----
-
-## 一、项目背景
-
-### 1.1 当前状态
-
-**技术栈**
-- Next.js 16 + React 19 + TypeScript
-- Playwright E2E测试框架
-- 已有基础移动端测试(覆盖率30%)
-
-**现有测试类型**
-- Smoke测试(4个文件)
-- 回归测试(2个文件)
-- 性能测试(3个文件)
-- 响应式测试(2个文件)
-- 视觉测试(2个文件)
-- 移动端测试(1个文件,13个用例)
-- 安全测试(3个文件)
-- 可访问性测试(2个文件)
-
-### 1.2 存在问题
-
-1. **测试覆盖不足** - 移动端测试覆盖率仅30%
-2. **设备覆盖有限** - 仅配置Pixel 5和iPhone 12
-3. **性能测试不完整** - 缺少Core Web Vitals完整测试
-4. **触摸交互测试缺失** - 没有手势操作测试
-5. **可访问性测试不足** - 缺少屏幕阅读器兼容性测试
-
-### 1.3 改进目标
-
-- 移动端测试覆盖率:从30%提升到90%+
-- 设备覆盖率:覆盖95%用户
-- 测试稳定性:失败率<2%
-- 测试执行时间:15-60分钟(根据测试类型)
-
----
-
-## 二、设计原则
-
-### 2.1 分层渐进策略
-
-**测试金字塔原则**
-- 70% 基础功能测试:确保所有页面核心功能正常
-- 20% 性能/兼容性测试:覆盖不同设备和浏览器的性能表现
-- 10% 深度用户体验测试:触摸手势、可访问性、边缘场景
-
-### 2.2 智能设备矩阵
-
-**设备覆盖策略**
-- 核心设备:iPhone 12/14、Samsung Galaxy S21、iPad(覆盖70%用户)
-- 关键断点:375px、414px、768px、1024px、1280px
-- 动态调整:根据用户访问数据定期更新设备矩阵
-
-### 2.3 完全自动化
-
-**自动化程度**
-- 所有测试自动执行
-- 自动生成报告(Allure + HTML + JUnit XML)
-- 失败时自动截图和录制视频
-- 通过CI/CD自动运行
-
----
-
-## 三、整体架构设计
-
-### 3.1 架构分层
-
-**1. 测试金字塔层(70%基础功能)**
-- 页面对象层:扩展现有的BasePage、ContactPage、HomePage,添加MobilePage专用方法
-- 测试用例层:按页面组织,每个页面包含基础功能测试套件
-- 设备模拟层:使用Playwright的devices配置,覆盖核心移动设备
-
-**2. 性能兼容层(20%性能/兼容性)**
-- 性能监控:集成PerformanceMonitor,测量LCP、FID、CLS等Core Web Vitals
-- 设备矩阵:iPhone 12/14、Samsung Galaxy S21、iPad + 关键响应式断点
-- 网络模拟:测试3G、4G、WiFi不同网络条件下的表现
-
-**3. 深度体验层(10%用户体验)**
-- 触摸交互:手势操作、触摸目标大小、多点触控
-- 可访问性:WCAG 2.1 AA合规、屏幕阅读器兼容、键盘导航
-- 边缘场景:弱网、低电量、后台切换等
-
-**4. 自动化执行层**
-- CI/CD集成:GitLab CI自动运行测试
-- 报告系统:Allure报告 + HTML报告 + JUnit XML
-- 失败处理:自动截图、录制视频、生成错误日志
-
-### 3.2 目录结构
-
-```
-e2e/src/
-├── pages/
-│ ├── BasePage.ts # 基础页面对象(扩展)
-│ ├── MobilePage.ts # 移动端专用页面对象(新增)
-│ ├── ContactPage.ts # 联系页(扩展)
-│ ├── HomePage.ts # 首页(扩展)
-│ ├── ProductsPage.ts # 产品页(扩展)
-│ ├── ServicesPage.ts # 服务页(扩展)
-│ └── AboutPage.ts # 关于页(扩展)
-├── tests/
-│ ├── mobile/
-│ │ ├── mobile-ux.spec.ts # 移动端用户体验(扩展)
-│ │ ├── mobile-functionality.spec.ts # 移动端功能测试(新增)
-│ │ ├── mobile-performance.spec.ts # 移动端性能测试(新增)
-│ │ ├── mobile-compatibility.spec.ts # 移动端兼容性测试(新增)
-│ │ ├── mobile-touch.spec.ts # 触摸交互测试(新增)
-│ │ └── mobile-accessibility.spec.ts # 移动端可访问性(新增)
-│ ├── performance/
-│ │ └── core-web-vitals.spec.ts # Core Web Vitals(新增)
-│ └── responsive/
-│ └── device-matrix.spec.ts # 设备矩阵测试(新增)
-├── utils/
-│ ├── PerformanceMonitor.ts # 性能监控(扩展)
-│ ├── TestDataGenerator.ts # 测试数据生成(扩展)
-│ ├── DeviceMatrix.ts # 设备矩阵配置(新增)
-│ └── MobileHelper.ts # 移动端辅助工具(新增)
-└── fixtures/
- ├── base.fixture.ts # 基础fixture(扩展)
- └── mobile.fixture.ts # 移动端fixture(新增)
-```
-
----
-
-## 四、测试用例设计
-
-### 4.1 基础功能测试(70%)
-
-**首页测试套件**
-- Hero区域:标题、描述、CTA按钮可见性、响应式布局
-- 导航测试:移动菜单开关、导航链接、面包屑导航
-- 内容区域:服务、产品、案例、新闻卡片的响应式布局
-- 页脚测试:联系信息、社交媒体链接、版权信息
-- 表单交互:快速联系表单的移动端适配
-
-**联系页测试套件**
-- 表单字段:姓名、邮箱、电话、消息字段的移动端输入
-- 表单验证:必填字段、格式验证、错误提示显示
-- 提交流程:表单提交、成功消息、错误处理
-- 联系信息:地址、电话、邮箱的可点击性
-- 地图集成:移动端地图显示和交互
-
-**产品/服务页测试套件**
-- 列表展示:卡片堆叠布局、图片响应式、文本可读性
-- 详情页:产品详情、服务描述的移动端适配
-- 筛选功能:移动端筛选器交互
-- 分页导航:移动端分页控件
-
-### 4.2 性能兼容测试(20%)
-
-**Core Web Vitals**
-- LCP(最大内容绘制):首屏加载时间 < 2.5秒
-- FID(首次输入延迟):交互响应时间 < 100毫秒
-- CLS(累积布局偏移):布局稳定性 < 0.1
-
-**设备兼容性**
-- iPhone 12/14:iOS Safari浏览器测试
-- Samsung Galaxy S21:Android Chrome浏览器测试
-- iPad:平板横竖屏切换测试
-- 响应式断点:375px、414px、768px、1024px
-
-**网络条件**
-- 3G网络:慢速网络下的加载表现
-- 4G网络:正常移动网络下的性能
-- WiFi:高速网络下的性能表现
-
-### 4.3 深度体验测试(10%)
-
-**触摸交互**
-- 触摸目标:最小44x44px,符合WCAG标准
-- 手势操作:滑动、长按、双击、捏合缩放
-- 多点触控:双指滚动、三指手势
-
-**可访问性**
-- 屏幕阅读器:VoiceOver、TalkBack兼容性
-- 键盘导航:Tab键导航、焦点管理
-- 颜色对比度:文本与背景对比度 ≥ 4.5:1
-
-**边缘场景**
-- 弱网环境:超时处理、重试机制
-- 后台切换:页面状态保持
-- 低电量模式:性能降级表现
-
----
-
-## 五、技术实现
-
-### 5.1 页面对象增强
-
-**BasePage扩展**
-```typescript
-// 新增移动端专用方法
-- tapElement(): 模拟触摸点击
-- swipe(): 模拟滑动操作
-- longPress(): 模拟长按操作
-- pinchZoom(): 模拟捏合缩放
-- waitForMobileLoad(): 移动端加载等待
-- checkTouchTarget(): 验证触摸目标大小
-- measurePerformance(): 性能指标测量
-- captureMobileScreenshot(): 移动端截图
-```
-
-**MobilePage新建**
-```typescript
-// 专门的移动端页面对象
-- handleMobileMenu(): 移动菜单操作
-- handleMobileNavigation(): 移动导航
-- handleMobileForm(): 移动表单交互
-- handleMobileScroll(): 移动滚动行为
-- handleMobileGestures(): 移动手势操作
-```
-
-### 5.2 设备矩阵配置
-
-**智能设备矩阵**
-```typescript
-const mobileDevices = {
- // 核心设备(覆盖70%用户)
- core: [
- 'iPhone 12', // iOS主流
- 'iPhone 14', // iOS最新
- 'Galaxy S21', // Android主流
- 'iPad Pro', // 平板横屏
- 'iPad Mini' // 平板竖屏
- ],
- // 响应式断点
- breakpoints: [
- { width: 375, height: 667 }, // iPhone SE
- { width: 414, height: 896 }, // iPhone 11 Pro Max
- { width: 768, height: 1024 }, // iPad竖屏
- { width: 1024, height: 768 }, // iPad横屏
- { width: 1280, height: 720 } // 小屏笔记本
- ]
-};
-```
-
-### 5.3 性能监控集成
-
-**PerformanceMonitor增强**
-```typescript
-// Core Web Vitals监控
-- measureLCP(): 最大内容绘制
-- measureFID(): 首次输入延迟
-- measureCLS(): 累积布局偏移
-- measureTTI(): 可交互时间
-- measureTTFB(): 首字节时间
-
-// 移动端特定指标
-- measureFCP(): 首次内容绘制
-- measureFMP(): 首次有意义绘制
-- measureNetwork(): 网络性能
-- measureBattery(): 电池消耗(模拟)
-```
-
-### 5.4 测试数据管理
-
-**TestDataGenerator扩展**
-```typescript
-// 移动端测试数据
-- generateMobileFormData(): 移动端表单数据
-- generateMobileUserAgent(): 移动设备User-Agent
-- generateNetworkConditions(): 网络条件模拟
-- generateTouchEvents(): 触摸事件数据
-```
-
----
-
-## 六、自动化执行与CI/CD集成
-
-### 6.1 测试执行策略
-
-**分层执行计划**
-```bash
-# 快速冒烟测试(每次提交)
-npm run test:mobile:smoke
-- 核心功能基础测试
-- 2-3个主流设备
-- 执行时间:2-3分钟
-
-# 完整功能测试(PR合并)
-npm run test:mobile:full
-- 所有功能测试
-- 核心设备矩阵
-- 执行时间:10-15分钟
-
-# 性能兼容测试(每日构建)
-npm run test:mobile:performance
-- 性能指标测试
-- 完整设备矩阵
-- 执行时间:20-30分钟
-
-# 全面回归测试(发布前)
-npm run test:mobile:regression
-- 所有测试套件
-- 智能设备矩阵
-- 执行时间:45-60分钟
-```
-
-### 6.2 GitLab CI配置
-
-**CI/CD Pipeline**
-```yaml
-stages:
- - smoke
- - full
- - performance
- - regression
-
-# 冒烟测试(每次提交)
-mobile:smoke:
- stage: smoke
- script:
- - cd e2e
- - npm install
- - npm run test:mobile:smoke
- artifacts:
- when: always
- paths:
- - e2e/test-results/
- - e2e/allure-results/
- only:
- - branches
-
-# 完整测试(PR)
-mobile:full:
- stage: full
- script:
- - cd e2e
- - npm run test:mobile:full
- artifacts:
- when: always
- paths:
- - e2e/test-results/
- - e2e/allure-results/
- only:
- - merge_requests
-
-# 性能测试(每日)
-mobile:performance:
- stage: performance
- script:
- - cd e2e
- - npm run test:mobile:performance
- artifacts:
- when: always
- paths:
- - e2e/test-results/
- - e2e/allure-results/
- only:
- - schedules
-
-# 回归测试(发布)
-mobile:regression:
- stage: regression
- script:
- - cd e2e
- - npm run test:mobile:regression
- artifacts:
- when: always
- paths:
- - e2e/test-results/
- - e2e/allure-results/
- only:
- - tags
-```
-
-### 6.3 报告系统
-
-**多格式报告**
-```typescript
-// Allure报告(详细)
-- 测试用例详情
-- 失败截图和视频
-- 性能指标图表
-- 设备覆盖率统计
-
-// HTML报告(快速查看)
-- 测试执行摘要
-- 失败用例列表
-- 趋势分析图表
-
-// JUnit XML(CI集成)
-- 测试结果XML
-- 失败用例标记
-- 执行时间统计
-
-// JSON报告(数据分析)
-- 测试结果JSON
-- 性能数据JSON
-- 设备矩阵JSON
-```
-
-### 6.4 失败处理机制
-
-**自动失败处理**
-```typescript
-// 失败时自动执行
-- 截图:失败页面截图
-- 录制:失败过程视频
-- 日志:详细错误日志
-- 网络请求:失败时的网络请求记录
-- 控制台日志:浏览器控制台错误
-- 性能数据:失败时的性能指标
-
-// 失败重试策略
-- 轻量测试:重试2次
-- 重量测试:重试1次
-- 性能测试:不重试
-```
-
----
-
-## 七、实施计划
-
-### 7.1 分阶段实施计划
-
-**第一阶段:基础设施完善(3天)**
-- Day 1: 扩展BasePage,添加移动端专用方法
-- Day 2: 创建MobilePage页面对象,配置智能设备矩阵
-- Day 3: 增强PerformanceMonitor,建立测试数据管理
-
-**第二阶段:核心测试开发(5天)**
-- Day 1-2: 首页移动端测试套件(基础功能)
-- Day 3: 联系页移动端测试套件(表单交互)
-- Day 4: 产品/服务页移动端测试套件(响应式布局)
-- Day 5: 导航和通用组件测试
-
-**第三阶段:性能兼容测试(3天)**
-- Day 1: Core Web Vitals测试
-- Day 2: 设备兼容性测试
-- Day 3: 网络条件测试
-
-**第四阶段:深度体验测试(2天)**
-- Day 1: 触摸交互测试
-- Day 2: 可访问性测试
-
-**第五阶段:CI/CD集成(2天)**
-- Day 1: GitLab CI配置
-- Day 2: 报告系统和失败处理
-
-**总计:15天(3周)**
-
-### 7.2 质量保障措施
-
-**代码质量**
-- TypeScript严格模式:100%类型安全
-- ESLint规则:遵循项目规范
-- 代码审查:所有测试代码需要审查
-
-**测试质量**
-- 测试覆盖率:目标90%以上
-- 测试稳定性:失败率<2%
-- 测试性能:单次执行<60分钟
-
-**文档质量**
-- 测试用例文档:每个测试用例都有说明
-- 测试报告:详细的测试结果分析
-- 维护文档:测试框架使用指南
-
-### 7.3 持续优化机制
-
-**定期评估**
-- 每周:测试执行结果分析
-- 每月:设备矩阵更新(基于用户数据)
-- 每季度:测试策略评估和优化
-
-**智能调整**
-- 根据失败率调整重试策略
-- 根据执行时间优化测试顺序
-- 根据用户数据调整设备矩阵
-
-**性能监控**
-- 测试执行时间趋势
-- 测试稳定性趋势
-- 设备覆盖率趋势
-
-### 7.4 风险控制
-
-**风险识别**
-- 测试环境不稳定:使用容器化环境
-- 测试数据污染:每次测试前清理数据
-- 设备模拟不准确:定期验证设备配置
-- 测试执行超时:设置合理超时时间
-
-**应急预案**
-- 测试失败:自动通知开发团队
-- 性能下降:触发性能优化流程
-- 兼容性问题:标记为高优先级修复
-- CI/CD失败:阻止代码合并
-
----
-
-## 八、预期成果
-
-### 8.1 量化指标
-
-- **测试覆盖率**:从30%提升到90%+
-- **设备覆盖率**:覆盖95%用户
-- **测试稳定性**:失败率<2%
-- **测试执行时间**:15-60分钟(根据测试类型)
-- **Core Web Vitals**:LCP<2.5s, FID<100ms, CLS<0.1
-
-### 8.2 质量提升
-
-- **功能完整性**:所有移动端功能都有测试覆盖
-- **性能优化**:性能问题提前发现和修复
-- **兼容性保障**:主流设备兼容性得到验证
-- **用户体验**:触摸交互和可访问性得到保障
-
-### 8.3 效率提升
-
-- **自动化程度**:100%自动化执行
-- **反馈速度**:2-3分钟快速反馈
-- **维护成本**:降低人工测试成本
-- **发布信心**:提升发布质量信心
-
----
-
-## 九、总结
-
-### 9.1 核心优势
-
-1. ✅ **全面覆盖**:功能、性能、兼容性、用户体验四个维度
-2. ✅ **分层渐进**:70%基础+20%性能+10%深度,资源利用最优
-3. ✅ **智能设备矩阵**:结合设备覆盖和响应式断点
-4. ✅ **完全自动化**:CI/CD集成,自动报告,失败处理
-5. ✅ **持续优化**:基于数据的智能调整机制
-
-### 9.2 关键成功因素
-
-- 严格的分层测试策略
-- 智能的设备矩阵管理
-- 完善的自动化执行机制
-- 持续的监控和优化
-- 有效的风险控制
-
-### 9.3 后续优化方向
-
-- 引入AI驱动的测试用例生成
-- 实现测试即代码(TaaC)
-- 扩展到更多移动端场景
-- 集成更多性能监控工具
-- 建立测试数据驱动的决策机制
-
----
-
-**文档版本**: v1.0
-**最后更新**: 2026-03-05
-**维护者**: 张翔
-**状态**: 已确认,准备实施
diff --git a/docs/plans/2026-03-05-mobile-test-implementation-plan.md b/docs/plans/2026-03-05-mobile-test-implementation-plan.md
deleted file mode 100644
index aadc2e1..0000000
--- a/docs/plans/2026-03-05-mobile-test-implementation-plan.md
+++ /dev/null
@@ -1,807 +0,0 @@
-# 移动端自动化测试改进实施计划
-
-**创建时间**: 2026-03-05
-**计划者**: 张翔
-**状态**: 准备执行
-**版本**: v1.0
-**工作目录**: /Users/zhangxiang/Codes/Gitee/home-page/mobile-test-work
-
----
-
-## 一、项目概述
-
-### 1.1 目标
-
-基于移动端自动化测试设计方案,系统性地改进移动端测试,实现:
-
-- 移动端测试覆盖率:从30%提升到90%+
-- 测试通过率:从91.7%提升到98%+
-- 性能测试通过率:从9.1%提升到90%+
-- 响应式测试通过率:从26.7%提升到90%+
-- 可访问性:符合WCAG 2.1 AA标准
-
-### 1.2 范围
-
-**包含**
-- 测试基础设施完善
-- 移动端测试用例开发
-- 性能和兼容性测试
-- 深度用户体验测试
-- CI/CD集成
-
-**不包含**
-- 生产环境部署
-- 第三方服务集成
-- 非移动端测试优化
-
-### 1.3 约束条件
-
-**时间约束**
-- 总工期:15天(3周)
-- 每阶段有明确的交付物
-- 每日任务可独立验收
-
-**资源约束**
-- 开发人员:1人
-- 测试环境:本地开发环境
-- 设备覆盖:模拟设备(Playwright devices)
-
-**质量约束**
-- 代码必须通过ESLint检查
-- 测试必须通过TypeScript类型检查
-- 所有测试必须稳定运行(失败率<2%)
-
----
-
-## 二、实施计划
-
-### 第一阶段:基础设施完善 + P0问题修复(4天)
-
-#### Day 1: 扩展BasePage
-
-**任务清单**
-- [ ] 添加移动端专用方法
- - [ ] `tapElement(selector: string): Promise` - 模拟触摸点击
- - [ ] `swipe(start: Point, end: Point): Promise` - 模拟滑动操作
- - [ ] `longPress(selector: string): Promise` - 模拟长按操作
- - [ ] `pinchZoom(selector: string, scale: number): Promise` - 模拟捏合缩放
- - [ ] `waitForMobileLoad(): Promise` - 移动端加载等待
- - [ ] `checkTouchTarget(selector: string): Promise` - 验证触摸目标大小
- - [ ] `captureMobileScreenshot(name: string): Promise` - 移动端截图
-
-- [ ] 添加性能测量方法
- - [ ] `measureLCP(): Promise` - 最大内容绘制
- - [ ] `measureFID(): Promise` - 首次输入延迟
- - [ ] `measureCLS(): Promise` - 累积布局偏移
- - [ ] `measureTTI(): Promise` - 可交互时间
- - [ ] `measureTTFB(): Promise` - 首字节时间
-
-- [ ] 添加错误处理机制
- - [ ] `retryOperation(operation: () => Promise, maxRetries: number): Promise` - 重试机制
- - [ ] `handleError(error: Error, context: string): void` - 错误处理
-
-- [ ] 添加日志记录功能
- - [ ] `logAction(action: string, details: any): void` - 操作日志
- - [ ] `logError(error: Error, context: string): void` - 错误日志
-
-**验收标准**
-- ✅ 所有新方法都有TypeScript类型定义
-- ✅ 所有方法都有单元测试
-- ✅ 代码通过ESLint检查
-- ✅ 方法文档完整
-
-**交付物**
-- `e2e/src/pages/BasePage.ts`(扩展版)
-- `e2e/src/pages/BasePage.test.ts`(单元测试)
-
----
-
-#### Day 2: 创建MobilePage和DeviceMatrix
-
-**任务清单**
-- [ ] 创建MobilePage页面对象
- - [ ] `handleMobileMenu(): Promise` - 移动菜单操作
- - [ ] `handleMobileNavigation(): Promise` - 移动导航
- - [ ] `handleMobileForm(): Promise` - 移动表单交互
- - [ ] `handleMobileScroll(): Promise` - 移动滚动行为
- - [ ] `handleMobileGestures(): Promise` - 移动手势操作
-
-- [ ] 配置智能设备矩阵
- - [ ] 创建`e2e/src/utils/DeviceMatrix.ts`
- - [ ] 配置核心设备:iPhone 12、iPhone 14、Galaxy S21、iPad Pro、iPad Mini
- - [ ] 配置响应式断点:375px、414px、768px、1024px、1280px
- - [ ] 添加设备选择逻辑
-
-- [ ] 创建MobileHelper工具类
- - [ ] `getMobileDevice(name: string): Device` - 获取移动设备配置
- - [ ] `getViewportSize(device: string): Viewport` - 获取视口大小
- - [ ] `isMobileDevice(userAgent: string): boolean` - 判断是否为移动设备
-
-**验收标准**
-- ✅ MobilePage继承BasePage
-- ✅ 设备矩阵包含所有目标设备
-- ✅ 响应式断点配置正确
-- ✅ 所有方法都有文档
-
-**交付物**
-- `e2e/src/pages/MobilePage.ts`
-- `e2e/src/utils/DeviceMatrix.ts`
-- `e2e/src/utils/MobileHelper.ts`
-
----
-
-#### Day 3: 增强PerformanceMonitor和TestDataGenerator
-
-**任务清单**
-- [ ] 增强PerformanceMonitor
- - [ ] 添加Core Web Vitals监控方法
- - [ ] `measureLCP(): Promise` - 最大内容绘制
- - [ ] `measureFID(): Promise` - 首次输入延迟
- - [ ] `measureCLS(): Promise` - 累积布局偏移
- - [ ] 添加移动端特定性能指标
- - [ ] `measureFCP(): Promise` - 首次内容绘制
- - [ ] `measureFMP(): Promise` - 首次有意义绘制
- - [ ] `measureNetwork(): Promise` - 网络性能
- - [ ] 添加性能阈值验证
- - [ ] `validateLCP(value: number): boolean` - 验证LCP
- - [ ] `validateFID(value: number): boolean` - 验证FID
- - [ ] `validateCLS(value: number): boolean` - 验证CLS
-
-- [ ] 扩展TestDataGenerator
- - [ ] 添加移动端测试数据生成
- - [ ] `generateMobileFormData(): FormData` - 移动端表单数据
- - [ ] `generateMobileUserAgent(): string` - 移动设备User-Agent
- - [ ] `generateNetworkConditions(): NetworkCondition` - 网络条件模拟
- - [ ] `generateTouchEvents(): TouchEvent[]` - 触摸事件数据
-
-- [ ] 创建测试数据文件
- - [ ] `e2e/src/data/mobile-test-data.ts` - 移动端测试数据
- - [ ] `e2e/src/data/performance-thresholds.ts` - 性能阈值配置
-
-**验收标准**
-- ✅ PerformanceMonitor包含所有Core Web Vitals
-- ✅ TestDataGenerator支持移动端数据生成
-- ✅ 性能阈值符合Google标准
-- ✅ 所有方法都有单元测试
-
-**交付物**
-- `e2e/src/utils/PerformanceMonitor.ts`(增强版)
-- `e2e/src/utils/TestDataGenerator.ts`(扩展版)
-- `e2e/src/data/mobile-test-data.ts`
-- `e2e/src/data/performance-thresholds.ts`
-
----
-
-#### Day 4: 修复P0问题
-
-**任务清单**
-- [ ] 修复可访问性问题(颜色对比度)
- - [ ] 分析58个颜色对比度违规
- - [ ] 修改页脚区域文本颜色(#718096 → 更深的颜色)
- - [ ] 修改联系信息文本颜色
- - [ ] 修改表单标签和占位符颜色
- - [ ] 验证所有修改符合WCAG 2.1 AA标准(对比度≥4.5:1)
-
-- [ ] 添加移动端面包屑导航
- - [ ] 在About页面添加面包屑导航
- - [ ] 确保面包屑在移动端可见
- - [ ] 添加正确的aria-label
- - [ ] 测试面包屑导航功能
-
-- [ ] 修复冒烟测试失败
- - [ ] 分析33个失败用例
- - [ ] 修复元素选择器(适配移动端)
- - [ ] 优化超时设置(移动端加载较慢)
- - [ ] 修复移动端布局变化导致的定位问题
- - [ ] 运行测试验证修复效果
-
-**验收标准**
-- ✅ 可访问性测试通过(0个违规)
-- ✅ 面包屑导航在移动端正常显示
-- ✅ 冒烟测试通过率≥95%
-- ✅ 所有修改通过代码审查
-
-**交付物**
-- 修复后的组件文件
-- 可访问性测试报告
-- 冒烟测试执行报告
-
----
-
-### 第二阶段:核心测试开发(5天)
-
-#### Day 1-2: 首页移动端测试套件
-
-**任务清单**
-- [ ] 创建`e2e/src/tests/mobile/mobile-functionality.spec.ts`
- - [ ] Hero区域测试
- - [ ] 标题可见性和响应式布局
- - [ ] 描述文本可读性
- - [ ] CTA按钮触摸目标大小
- - [ ] CTA按钮点击响应
- - [ ] 导航测试
- - [ ] 移动菜单开关功能
- - [ ] 导航链接点击
- - [ ] 面包屑导航
- - [ ] 内容区域测试
- - [ ] 服务卡片响应式布局
- - [ ] 产品卡片堆叠布局
- - [ ] 案例卡片响应式布局
- - [ ] 新闻卡片响应式布局
- - [ ] 页脚测试
- - [ ] 联系信息可点击性
- - [ ] 社交媒体链接
- - [ ] 版权信息显示
- - [ ] 表单交互测试
- - [ ] 快速联系表单移动端适配
- - [ ] 表单字段输入
- - [ ] 表单提交功能
-
-**验收标准**
-- ✅ 所有测试用例通过
-- ✅ 测试覆盖首页所有主要功能
-- ✅ 测试执行时间<3分钟
-
-**交付物**
-- `e2e/src/tests/mobile/mobile-functionality.spec.ts`
-- 测试执行报告
-
----
-
-#### Day 3: 联系页移动端测试套件
-
-**任务清单**
-- [ ] 创建`e2e/src/tests/mobile/mobile-contact.spec.ts`
- - [ ] 表单字段测试
- - [ ] 姓名字段移动端输入
- - [ ] 电话字段移动端输入
- - [ ] 邮箱字段移动端输入
- - [ ] 主题字段移动端输入
- - [ ] 消息字段移动端输入
- - [ ] 表单验证测试
- - [ ] 必填字段验证
- - [ ] 格式验证(邮箱、电话)
- - [ ] 错误提示显示
- - [ ] 错误提示可读性
- - [ ] 提交流程测试
- - [ ] 表单提交功能
- - [ ] 成功消息显示
- - [ ] 错误处理
- - [ ] 提交按钮状态
- - [ ] 联系信息测试
- - [ ] 地址可点击性
- - [ ] 电话可点击性
- - [ ] 邮箱可点击性
- - [ ] 地图集成测试
- - [ ] 地图显示
- - [ ] 地图交互
-
-**验收标准**
-- ✅ 所有测试用例通过
-- ✅ 测试覆盖联系页所有功能
-- ✅ 测试执行时间<2分钟
-
-**交付物**
-- `e2e/src/tests/mobile/mobile-contact.spec.ts`
-- 测试执行报告
-
----
-
-#### Day 4: 产品/服务页移动端测试套件
-
-**任务清单**
-- [ ] 创建`e2e/src/tests/mobile/mobile-products.spec.ts`
- - [ ] 列表展示测试
- - [ ] 产品卡片堆叠布局
- - [ ] 图片响应式加载
- - [ ] 文本可读性
- - [ ] 卡片间距
- - [ ] 详情页测试
- - [ ] 产品详情显示
- - [ ] 服务描述显示
- - [ ] 响应式布局
- - [ ] 筛选功能测试
- - [ ] 移动端筛选器交互
- - [ ] 筛选结果更新
- - [ ] 分页导航测试
- - [ ] 移动端分页控件
- - [ ] 分页功能
-
-- [ ] 创建`e2e/src/tests/mobile/mobile-services.spec.ts`
- - [ ] 服务列表测试
- - [ ] 服务详情测试
- - [ ] 响应式布局测试
-
-**验收标准**
-- ✅ 所有测试用例通过
-- ✅ 测试覆盖产品/服务页所有功能
-- ✅ 测试执行时间<3分钟
-
-**交付物**
-- `e2e/src/tests/mobile/mobile-products.spec.ts`
-- `e2e/src/tests/mobile/mobile-services.spec.ts`
-- 测试执行报告
-
----
-
-#### Day 5: 导航和通用组件测试
-
-**任务清单**
-- [ ] 创建`e2e/src/tests/mobile/mobile-navigation.spec.ts`
- - [ ] 移动菜单测试
- - [ ] 菜单打开/关闭
- - [ ] 菜单项点击
- - [ ] 菜单动画
- - [ ] 菜单滚动
- - [ ] 导航链接测试
- - [ ] 所有导航链接可点击
- - [ ] 导航跳转正确
- - [ ] 导航状态更新
- - [ ] 面包屑导航测试
- - [ ] 面包屑显示
- - [ ] 面包屑点击
- - [ ] 面包屑层级
-
-- [ ] 创建`e2e/src/tests/mobile/mobile-components.spec.ts`
- - [ ] 按钮组件测试
- - [ ] 触摸目标大小
- - [ ] 按钮状态
- - [ ] 按钮点击响应
- - [ ] 卡片组件测试
- - [ ] 卡片布局
- - [ ] 卡片交互
- - [ ] 表单组件测试
- - [ ] 表单字段布局
- - [ ] 表单验证
-
-**验收标准**
-- ✅ 所有测试用例通过
-- ✅ 测试覆盖导航和通用组件
-- ✅ 测试执行时间<2分钟
-
-**交付物**
-- `e2e/src/tests/mobile/mobile-navigation.spec.ts`
-- `e2e/src/tests/mobile/mobile-components.spec.ts`
-- 测试执行报告
-
----
-
-### 第三阶段:性能兼容测试(3天)
-
-#### Day 1: Core Web Vitals测试
-
-**任务清单**
-- [ ] 创建`e2e/src/tests/performance/core-web-vitals.spec.ts`
- - [ ] LCP测试
- - [ ] 首页LCP<2.5s
- - [ ] 联系页LCP<2.5s
- - [ ] 产品页LCP<2.5s
- - [ ] FID测试
- - [ ] 首页FID<100ms
- - [ ] 联系页FID<100ms
- - [ ] 产品页FID<100ms
- - [ ] CLS测试
- - [ ] 首页CLS<0.1
- - [ ] 联系页CLS<0.1
- - [ ] 产品页CLS<0.1
- - [ ] TTI测试
- - [ ] 首页TTI<3.5s
- - [ ] 联系页TTI<3.5s
- - [ ] 产品页TTI<3.5s
- - [ ] TTFB测试
- - [ ] 首页TTFB<600ms
- - [ ] 联系页TTFB<600ms
- - [ ] 产品页TTFB<600ms
-
-**验收标准**
-- ✅ 所有Core Web Vitals测试通过
-- ✅ 性能指标符合Google标准
-- ✅ 测试执行时间<5分钟
-
-**交付物**
-- `e2e/src/tests/performance/core-web-vitals.spec.ts`
-- 性能测试报告
-
----
-
-#### Day 2: 设备兼容性测试
-
-**任务清单**
-- [ ] 创建`e2e/src/tests/responsive/device-matrix.spec.ts`
- - [ ] iPhone 12测试
- - [ ] 所有页面显示正常
- - [ ] 所有功能正常
- - [ ] iPhone 14测试
- - [ ] 所有页面显示正常
- - [ ] 所有功能正常
- - [ ] Galaxy S21测试
- - [ ] 所有页面显示正常
- - [ ] 所有功能正常
- - [ ] iPad Pro测试
- - [ ] 横屏显示正常
- - [ ] 竖屏显示正常
- - [ ] iPad Mini测试
- - [ ] 横屏显示正常
- - [ ] 竖屏显示正常
-
-- [ ] 响应式断点测试
- - [ ] 375px断点测试
- - [ ] 414px断点测试
- - [ ] 768px断点测试
- - [ ] 1024px断点测试
- - [ ] 1280px断点测试
-
-**验收标准**
-- ✅ 所有设备测试通过
-- ✅ 所有断点测试通过
-- ✅ 测试执行时间<10分钟
-
-**交付物**
-- `e2e/src/tests/responsive/device-matrix.spec.ts`
-- 设备兼容性测试报告
-
----
-
-#### Day 3: 网络条件测试
-
-**任务清单**
-- [ ] 创建`e2e/src/tests/performance/network-conditions.spec.ts`
- - [ ] 3G网络测试
- - [ ] 慢速网络下的加载表现
- - [ ] 超时处理
- - [ ] 重试机制
- - [ ] 4G网络测试
- - [ ] 正常移动网络下的性能
- - [ ] 资源加载优化
- - [ ] WiFi网络测试
- - [ ] 高速网络下的性能
- - [ ] 缓存策略验证
-
-**验收标准**
-- ✅ 所有网络条件测试通过
-- ✅ 网络降级处理正确
-- ✅ 测试执行时间<5分钟
-
-**交付物**
-- `e2e/src/tests/performance/network-conditions.spec.ts`
-- 网络条件测试报告
-
----
-
-### 第四阶段:深度体验测试(2天)
-
-#### Day 1: 触摸交互测试
-
-**任务清单**
-- [ ] 创建`e2e/src/tests/mobile/mobile-touch.spec.ts`
- - [ ] 触摸目标测试
- - [ ] 所有按钮触摸目标≥44x44px
- - [ ] 所有链接触摸目标≥44x44px
- - [ ] 所有表单控件触摸目标≥44x44px
- - [ ] 手势操作测试
- - [ ] 滑动手势
- - [ ] 长按手势
- - [ ] 双击手势
- - [ ] 捏合缩放手势
- - [ ] 多点触控测试
- - [ ] 双指滚动
- - [ ] 三指手势
-
-**验收标准**
-- ✅ 所有触摸交互测试通过
-- ✅ 触摸目标符合WCAG标准
-- ✅ 测试执行时间<3分钟
-
-**交付物**
-- `e2e/src/tests/mobile/mobile-touch.spec.ts`
-- 触摸交互测试报告
-
----
-
-#### Day 2: 可访问性测试
-
-**任务清单**
-- [ ] 创建`e2e/src/tests/mobile/mobile-accessibility.spec.ts`
- - [ ] 屏幕阅读器测试
- - [ ] VoiceOver兼容性
- - [ ] TalkBack兼容性
- - [ ] ARIA标签正确性
- - [ ] 键盘导航测试
- - [ ] Tab键导航
- - [ ] 焦点管理
- - [ ] 焦点可见性
- - [ ] 颜色对比度测试
- - [ ] 所有文本对比度≥4.5:1
- - [ ] 大文本对比度≥3:1
- - [ ] 图标对比度≥3:1
-
-**验收标准**
-- ✅ 所有可访问性测试通过
-- ✅ 符合WCAG 2.1 AA标准
-- ✅ 测试执行时间<3分钟
-
-**交付物**
-- `e2e/src/tests/mobile/mobile-accessibility.spec.ts`
-- 可访问性测试报告
-
----
-
-### 第五阶段:CI/CD集成(2天)
-
-#### Day 1: GitLab CI配置
-
-**任务清单**
-- [ ] 创建`.gitlab-ci.yml`
- - [ ] 冒烟测试stage
- - [ ] 每次提交运行
- - [ ] 核心功能测试
- - [ ] 2-3个主流设备
- - [ ] 完整测试stage
- - [ ] PR合并运行
- - [ ] 所有功能测试
- - [ ] 核心设备矩阵
- - [ ] 性能测试stage
- - [ ] 每日构建运行
- - [ ] 性能指标测试
- - [ ] 完整设备矩阵
- - [ ] 回归测试stage
- - [ ] 发布前运行
- - [ ] 所有测试套件
- - [ ] 智能设备矩阵
-
-- [ ] 配置测试脚本
- - [ ] `npm run test:mobile:smoke`
- - [ ] `npm run test:mobile:full`
- - [ ] `npm run test:mobile:performance`
- - [ ] `npm run test:mobile:regression`
-
-**验收标准**
-- ✅ CI/CD pipeline配置正确
-- ✅ 所有测试脚本可用
-- ✅ 测试执行时间符合预期
-
-**交付物**
-- `.gitlab-ci.yml`
-- `e2e/package.json`(更新scripts)
-
----
-
-#### Day 2: 报告系统和失败处理
-
-**任务清单**
-- [ ] 配置多格式报告
- - [ ] Allure报告配置
- - [ ] 测试用例详情
- - [ ] 失败截图和视频
- - [ ] 性能指标图表
- - [ ] 设备覆盖率统计
- - [ ] HTML报告配置
- - [ ] 测试执行摘要
- - [ ] 失败用例列表
- - [ ] 趋势分析图表
- - [ ] JUnit XML配置
- - [ ] 测试结果XML
- - [ ] 失败用例标记
- - [ ] 执行时间统计
- - [ ] JSON报告配置
- - [ ] 测试结果JSON
- - [ ] 性能数据JSON
- - [ ] 设备矩阵JSON
-
-- [ ] 配置失败处理机制
- - [ ] 自动截图
- - [ ] 失败页面截图
- - [ ] 截图命名规则
- - [ ] 自动录制
- - [ ] 失败过程视频
- - [ ] 视频保存策略
- - [ ] 错误日志
- - [ ] 详细错误日志
- - [ ] 日志格式规范
- - [ ] 网络请求记录
- - [ ] 失败时的网络请求
- - [ ] 请求响应数据
- - [ ] 控制台日志
- - [ ] 浏览器控制台错误
- - [ ] 控制台警告
- - [ ] 性能数据
- - [ ] 失败时的性能指标
- - [ ] 性能快照
-
-- [ ] 配置失败重试策略
- - [ ] 轻量测试:重试2次
- - [ ] 重量测试:重试1次
- - [ ] 性能测试:不重试
-
-**验收标准**
-- ✅ 所有报告格式正常生成
-- ✅ 失败处理机制正常工作
-- ✅ 重试策略配置正确
-
-**交付物**
-- `e2e/playwright.config.ts`(更新)
-- `e2e/allure.config.js`
-- 报告系统文档
-
----
-
-## 三、质量保障
-
-### 3.1 代码质量
-
-**TypeScript**
-- 所有代码必须通过TypeScript类型检查
-- 使用严格模式(strict: true)
-- 避免使用any类型
-
-**ESLint**
-- 所有代码必须通过ESLint检查
-- 遵循项目代码规范
-- 修复所有警告和错误
-
-**代码审查**
-- 所有代码必须经过审查
-- 审查重点:功能正确性、代码质量、性能影响
-- 审查通过后方可合并
-
-### 3.2 测试质量
-
-**测试覆盖率**
-- 目标:90%以上
-- 工具:Istanbul/nyc
-- 定期检查覆盖率报告
-
-**测试稳定性**
-- 目标:失败率<2%
-- 策略:重试机制、超时优化、选择器优化
-- 定期分析失败原因
-
-**测试性能**
-- 目标:单次执行<60分钟
-- 策略:并行执行、测试分组、优化等待时间
-- 定期优化测试执行时间
-
-### 3.3 文档质量
-
-**代码文档**
-- 所有公共方法必须有JSDoc注释
-- 复杂逻辑必须有行内注释
-- 保持文档与代码同步
-
-**测试文档**
-- 每个测试用例必须有说明
-- 测试报告必须详细
-- 维护文档必须完整
-
----
-
-## 四、风险管理
-
-### 4.1 技术风险
-
-**风险1:测试环境不稳定**
-- 影响:测试结果不可靠
-- 概率:中
-- 应对:使用容器化环境、定期验证环境配置
-
-**风险2:测试数据污染**
-- 影响:测试结果不准确
-- 概率:中
-- 应对:每次测试前清理数据、使用独立测试数据
-
-**风险3:设备模拟不准确**
-- 影响:兼容性问题遗漏
-- 概率:低
-- 应对:定期验证设备配置、补充真实设备测试
-
-**风险4:测试执行超时**
-- 影响:测试执行时间过长
-- 概率:中
-- 应对:设置合理超时时间、优化等待逻辑
-
-### 4.2 项目风险
-
-**风险1:时间不足**
-- 影响:无法完成所有任务
-- 概率:低
-- 应对:分阶段交付、优先保证核心功能
-
-**风险2:资源不足**
-- 影响:开发进度延迟
-- 概率:低
-- 应对:合理分配资源、及时调整计划
-
-**风险3:需求变更**
-- 影响:返工、进度延迟
-- 概率:中
-- 应对:需求评审、变更控制流程
-
-### 4.3 应对措施
-
-**预防措施**
-- 详细的需求分析和设计
-- 充分的测试和验证
-- 定期的进度检查和调整
-
-**应急措施**
-- 建立应急预案
-- 准备备用方案
-- 及时沟通和协调
-
----
-
-## 五、验收标准
-
-### 5.1 功能验收
-
-- ✅ 所有测试用例通过
-- ✅ 测试覆盖率≥90%
-- ✅ 测试稳定性≥98%
-- ✅ 测试执行时间<60分钟
-
-### 5.2 性能验收
-
-- ✅ Core Web Vitals达到优秀水平
-- ✅ LCP<2.5s
-- ✅ FID<100ms
-- ✅ CLS<0.1
-
-### 5.3 兼容性验收
-
-- ✅ 覆盖95%用户设备
-- ✅ 所有主流设备测试通过
-- ✅ 所有响应式断点测试通过
-
-### 5.4 可访问性验收
-
-- ✅ 符合WCAG 2.1 AA标准
-- ✅ 颜色对比度≥4.5:1
-- ✅ 触摸目标≥44x44px
-- ✅ 屏幕阅读器兼容
-
----
-
-## 六、总结
-
-### 6.1 预期成果
-
-**量化指标**
-- 移动端测试覆盖率:从30%提升到90%+
-- 测试通过率:从91.7%提升到98%+
-- 性能测试通过率:从9.1%提升到90%+
-- 响应式测试通过率:从26.7%提升到90%+
-
-**质量提升**
-- 可访问性:符合WCAG 2.1 AA标准
-- 性能:Core Web Vitals达到优秀水平
-- 兼容性:覆盖95%用户
-- 用户体验:触摸交互和可访问性得到保障
-
-**效率提升**
-- 自动化程度:100%自动化执行
-- 反馈速度:2-3分钟快速反馈
-- 维护成本:降低人工测试成本
-- 发布信心:提升发布质量信心
-
-### 6.2 关键成功因素
-
-- 严格的分阶段实施
-- 智能的设备矩阵管理
-- 完善的自动化执行机制
-- 持续的监控和优化
-- 有效的风险控制
-
-### 6.3 后续优化方向
-
-- 引入AI驱动的测试用例生成
-- 实现测试即代码(TaaC)
-- 扩展到更多移动端场景
-- 集成更多性能监控工具
-- 建立测试数据驱动的决策机制
-
----
-
-**计划版本**: v1.0
-**最后更新**: 2026-03-05
-**维护者**: 张翔
-**状态**: 准备执行
diff --git a/e2e/src/pages/BasePage.ts b/e2e/src/pages/BasePage.ts
index fd4386c..65aeb91 100644
--- a/e2e/src/pages/BasePage.ts
+++ b/e2e/src/pages/BasePage.ts
@@ -450,142 +450,4 @@ export class BasePage {
});
});
}
-
- async tapElement(selector: string): Promise {
- const element = this.page.locator(selector);
- const box = await element.boundingBox();
-
- if (box) {
- const x = box.x + box.width / 2;
- const y = box.y + box.height / 2;
- await this.page.touchscreen.tap(x, y);
- } else {
- await element.click();
- }
- }
-
- async swipe(start: { x: number; y: number }, end: { x: number; y: number }): Promise {
- await this.page.touchscreen.tap(start.x, start.y);
- await this.page.touchscreen.touchMove(end.x, end.y);
- await this.page.touchscreen.touchEnd();
- }
-
- async longPress(selector: string, duration: number = 1000): Promise {
- const element = this.page.locator(selector);
- const box = await element.boundingBox();
-
- if (box) {
- const x = box.x + box.width / 2;
- const y = box.y + box.height / 2;
- await this.page.touchscreen.tap(x, y);
- await this.page.waitForTimeout(duration);
- } else {
- await element.click();
- }
- }
-
- async pinchZoom(selector: string, scale: number = 1.5): Promise {
- const element = this.page.locator(selector);
- const box = await element.boundingBox();
-
- if (box) {
- const centerX = box.x + box.width / 2;
- const centerY = box.y + box.height / 2;
-
- const finger1 = { x: centerX - 50, y: centerY };
- const finger2 = { x: centerX + 50, y: centerY };
-
- await this.page.touchscreen.tap(finger1.x, finger1.y);
- await this.page.touchscreen.tap(finger2.x, finger2.y);
-
- const newFinger1 = { x: centerX - 50 / scale, y: centerY };
- const newFinger2 = { x: centerX + 50 / scale, y: centerY };
-
- await this.page.touchscreen.touchMove(newFinger1.x, newFinger1.y);
- await this.page.touchscreen.touchMove(newFinger2.x, newFinger2.y);
-
- await this.page.touchscreen.touchEnd();
- await this.page.touchscreen.touchEnd();
- }
- }
-
- async waitForMobileLoad(): Promise {
- await this.page.waitForLoadState('domcontentloaded');
- await this.page.waitForTimeout(500);
- }
-
- async checkTouchTarget(selector: string): Promise {
- const element = this.page.locator(selector);
- const box = await element.boundingBox();
-
- if (box) {
- return box.width >= 44 && box.height >= 44;
- }
-
- return false;
- }
-
- async captureMobileScreenshot(name: string): Promise {
- const screenshotDir = 'test-results/mobile-screenshots';
- if (!fs.existsSync(screenshotDir)) {
- fs.mkdirSync(screenshotDir, { recursive: true });
- }
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
- const filename = `${timestamp}-${name}.png`;
- await this.page.screenshot({
- path: path.join(screenshotDir, filename),
- fullPage: true
- });
- }
-
- async measureLCP(): Promise {
- const vitals = await this.getCoreWebVitals();
- return vitals.largestContentfulPaint;
- }
-
- async measureFID(): Promise {
- const vitals = await this.getCoreWebVitals();
- return vitals.firstInputDelay;
- }
-
- async measureCLS(): Promise {
- const vitals = await this.getCoreWebVitals();
- return vitals.cumulativeLayoutShift;
- }
-
- async measureTTI(): Promise {
- const metrics = await this.measurePerformance();
- return metrics.domContentLoaded;
- }
-
- async measureTTFB(): Promise {
- const timing = await this.page.evaluate(() => {
- return performance.timing;
- });
- return timing.responseStart - timing.navigationStart;
- }
-
- async handleError(error: Error, context: string): Promise {
- const errorInfo = {
- timestamp: new Date().toISOString(),
- context,
- message: error.message,
- stack: error.stack,
- url: this.page.url(),
- };
-
- await this.log(`Error in ${context}: ${error.message}`, 'error');
- await this.captureMobileScreenshot(`error-${context}`);
- }
-
- async logAction(action: string, details: any): Promise {
- const logInfo = {
- timestamp: new Date().toISOString(),
- action,
- details,
- url: this.page.url(),
- };
-
- await this.log(`Action: ${action}`, 'info');
- }
}
diff --git a/e2e/src/pages/MobilePage.ts b/e2e/src/pages/MobilePage.ts
deleted file mode 100644
index 29fa6fc..0000000
--- a/e2e/src/pages/MobilePage.ts
+++ /dev/null
@@ -1,128 +0,0 @@
-import { Page, Locator } from '@playwright/test';
-import { BasePage } from './BasePage';
-
-export class MobilePage extends BasePage {
- constructor(page: Page) {
- super(page);
- }
-
- async handleMobileMenu(): Promise {
- const menuButton = this.page.locator('button[aria-label="打开菜单"], button[aria-label="关闭菜单"]');
-
- if (await menuButton.isVisible()) {
- await this.tapElement('button[aria-label="打开菜单"], button[aria-label="关闭菜单"]');
- await this.page.waitForSelector('#mobile-menu', { state: 'visible' });
- }
- }
-
- async closeMobileMenu(): Promise {
- const closeButton = this.page.locator('button[aria-label="关闭菜单"]');
-
- if (await closeButton.isVisible()) {
- await this.tapElement('button[aria-label="关闭菜单"]');
- await this.page.waitForSelector('#mobile-menu', { state: 'hidden' });
- }
- }
-
- async handleMobileNavigation(linkText: string): Promise {
- await this.handleMobileMenu();
-
- const link = this.page.locator(`#mobile-menu a:has-text("${linkText}")`);
- await this.tapElement(`#mobile-menu a:has-text("${linkText}")`);
-
- await this.closeMobileMenu();
- }
-
- async handleMobileForm(formData: Record): Promise {
- for (const [fieldName, value] of Object.entries(formData)) {
- const field = this.page.locator(`input[name="${fieldName}"], textarea[name="${fieldName}"]`);
-
- if (await field.isVisible()) {
- await field.fill(value);
- await this.page.waitForTimeout(100);
- }
- }
- }
-
- async handleMobileScroll(direction: 'up' | 'down' = 'down'): Promise {
- const viewportSize = this.page.viewportSize();
-
- if (viewportSize) {
- const startY = direction === 'down' ? viewportSize.height * 0.8 : viewportSize.height * 0.2;
- const endY = direction === 'down' ? viewportSize.height * 0.2 : viewportSize.height * 0.8;
- const centerX = viewportSize.width / 2;
-
- await this.swipe(
- { x: centerX, y: startY },
- { x: centerX, y: endY }
- );
-
- await this.page.waitForTimeout(500);
- }
- }
-
- async handleMobileGestures(): Promise {
- await this.logAction('Testing mobile gestures', {});
-
- const testElement = this.page.locator('body');
- const box = await testElement.boundingBox();
-
- if (box) {
- const centerX = box.x + box.width / 2;
- const centerY = box.y + box.height / 2;
-
- await this.swipe(
- { x: centerX, y: centerY + 100 },
- { x: centerX, y: centerY - 100 }
- );
-
- await this.page.waitForTimeout(500);
-
- await this.swipe(
- { x: centerX - 100, y: centerY },
- { x: centerX + 100, y: centerY }
- );
-
- await this.page.waitForTimeout(500);
- }
- }
-
- async verifyMobileMenuOpen(): Promise {
- const mobileMenu = this.page.locator('#mobile-menu');
- return await mobileMenu.isVisible();
- }
-
- async verifyMobileMenuClosed(): Promise {
- const mobileMenu = this.page.locator('#mobile-menu');
- return !(await mobileMenu.isVisible());
- }
-
- async getMobileMenuItems(): Promise {
- const items = this.page.locator('#mobile-menu a');
- const count = await items.count();
- const itemTexts: string[] = [];
-
- for (let i = 0; i < count; i++) {
- const text = await items.nth(i).textContent();
- if (text) {
- itemTexts.push(text);
- }
- }
-
- return itemTexts;
- }
-
- async tapMobileMenuItem(itemText: string): Promise {
- const menuItem = this.page.locator(`#mobile-menu a:has-text("${itemText}")`);
- await this.tapElement(`#mobile-menu a:has-text("${itemText}")`);
- }
-
- async isMobileMenuVisible(): Promise {
- const menuButton = this.page.locator('button[aria-label="打开菜单"]');
- return await menuButton.isVisible();
- }
-
- async waitForMobileMenuAnimation(): Promise {
- await this.page.waitForTimeout(300);
- }
-}
diff --git a/e2e/src/tests/mobile/mobile-contact.spec.ts b/e2e/src/tests/mobile/mobile-contact.spec.ts
deleted file mode 100644
index f7f95c7..0000000
--- a/e2e/src/tests/mobile/mobile-contact.spec.ts
+++ /dev/null
@@ -1,403 +0,0 @@
-import { test, expect, Page } from '@playwright/test';
-import { MobilePage } from '../pages/MobilePage';
-import { TestDataGenerator } from '../utils/TestDataGenerator';
-import { PerformanceMonitor } from '../utils/PerformanceMonitor';
-
-test.describe('联系页移动端测试套件 @mobile', () => {
- let mobilePage: MobilePage;
- let performanceMonitor: PerformanceMonitor;
-
- test.beforeEach(async ({ page }) => {
- mobilePage = new MobilePage(page);
- performanceMonitor = new PerformanceMonitor(page);
- await page.goto('/contact');
- await page.waitForLoadState('domcontentloaded');
- });
-
- test.describe('表单字段测试', () => {
- test('姓名字段移动端输入正常', async ({ page }) => {
- const nameInput = page.locator('input[name="name"], input[placeholder*="姓名"], input[placeholder*="名字"]');
-
- if (await nameInput.count() > 0) {
- const name = TestDataGenerator.generateName();
- await nameInput.first().fill(name);
-
- const value = await nameInput.first().inputValue();
- expect(value).toBe(name);
-
- const box = await nameInput.first().boundingBox();
- expect(box).toBeTruthy();
- expect(box!.width).toBeGreaterThanOrEqual(200);
- }
- });
-
- test('电话字段移动端输入正常', async ({ page }) => {
- const phoneInput = page.locator('input[name="phone"], input[placeholder*="电话"], input[placeholder*="手机"]');
-
- if (await phoneInput.count() > 0) {
- const phone = TestDataGenerator.generatePhone();
- await phoneInput.first().fill(phone);
-
- const value = await phoneInput.first().inputValue();
- expect(value).toBe(phone);
-
- const inputType = await phoneInput.first().getAttribute('type');
- expect(['tel', 'text', 'number']).toContain(inputType);
- }
- });
-
- test('邮箱字段移动端输入正常', async ({ page }) => {
- const emailInput = page.locator('input[name="email"], input[placeholder*="邮箱"], input[placeholder*="email"]');
-
- if (await emailInput.count() > 0) {
- const email = TestDataGenerator.generateEmail();
- await emailInput.first().fill(email);
-
- const value = await emailInput.first().inputValue();
- expect(value).toBe(email);
-
- const inputType = await emailInput.first().getAttribute('type');
- expect(inputType).toBe('email');
- }
- });
-
- test('主题字段移动端输入正常', async ({ page }) => {
- const subjectInput = page.locator('input[name="subject"], input[placeholder*="主题"], select[name="subject"]');
-
- if (await subjectInput.count() > 0) {
- const tagName = await subjectInput.first().evaluate((el) => el.tagName.toLowerCase());
-
- if (tagName === 'select') {
- await subjectInput.first().selectOption({ index: 1 });
- } else {
- const subject = TestDataGenerator.generateSubject();
- await subjectInput.first().fill(subject);
-
- const value = await subjectInput.first().inputValue();
- expect(value).toBe(subject);
- }
- }
- });
-
- test('消息字段移动端输入正常', async ({ page }) => {
- const messageInput = page.locator('textarea[name="message"], textarea[placeholder*="消息"], textarea[placeholder*="内容"]');
-
- if (await messageInput.count() > 0) {
- const message = TestDataGenerator.generateMessage();
- await messageInput.first().fill(message);
-
- const value = await messageInput.first().inputValue();
- expect(value).toBe(message);
-
- const box = await messageInput.first().boundingBox();
- expect(box).toBeTruthy();
- expect(box!.height).toBeGreaterThanOrEqual(80);
- }
- });
- });
-
- test.describe('表单验证测试', () => {
- test('必填字段验证正常', async ({ page }) => {
- const submitButton = page.locator('button[type="submit"], button:has-text("提交"), button:has-text("发送")');
-
- if (await submitButton.count() > 0) {
- await submitButton.first().click();
- await page.waitForTimeout(500);
-
- const errorMessages = page.locator('[class*="error"], [class*="invalid"], [role="alert"]');
- const errorCount = await errorMessages.count();
-
- expect(errorCount).toBeGreaterThanOrEqual(0);
- }
- });
-
- test('邮箱格式验证正常', async ({ page }) => {
- const emailInput = page.locator('input[name="email"], input[placeholder*="邮箱"]');
-
- if (await emailInput.count() > 0) {
- const invalidEmail = TestDataGenerator.generateInvalidEmail();
- await emailInput.first().fill(invalidEmail);
-
- await page.keyboard.press('Tab');
- await page.waitForTimeout(300);
-
- const isValid = await emailInput.first().evaluate((el: HTMLInputElement) => {
- return el.checkValidity();
- });
-
- expect(isValid).toBe(false);
- }
- });
-
- test('电话格式验证正常', async ({ page }) => {
- const phoneInput = page.locator('input[name="phone"], input[placeholder*="电话"]');
-
- if (await phoneInput.count() > 0) {
- const invalidPhone = TestDataGenerator.generateInvalidPhone();
- await phoneInput.first().fill(invalidPhone);
-
- await page.keyboard.press('Tab');
- await page.waitForTimeout(300);
-
- const value = await phoneInput.first().inputValue();
- expect(value.length).toBeLessThan(15);
- }
- });
-
- test('错误提示显示正确', async ({ page }) => {
- const nameInput = page.locator('input[name="name"], input[placeholder*="姓名"]');
-
- if (await nameInput.count() > 0) {
- await nameInput.first().fill('');
- await nameInput.first().blur();
-
- await page.waitForTimeout(300);
-
- const parent = nameInput.first().locator('xpath=..');
- const hasError = await parent.evaluate((el) => {
- return el.classList.contains('error') ||
- el.classList.contains('invalid') ||
- el.getAttribute('aria-invalid') === 'true';
- });
-
- expect(typeof hasError).toBe('boolean');
- }
- });
-
- test('错误提示可读性良好', async ({ page }) => {
- const errorMessages = page.locator('[class*="error"], [class*="invalid"], [role="alert"]');
-
- if (await errorMessages.count() > 0) {
- const firstError = errorMessages.first();
- const fontSize = await firstError.evaluate((el) => {
- const style = window.getComputedStyle(el);
- return parseFloat(style.fontSize);
- });
-
- expect(fontSize).toBeGreaterThanOrEqual(12);
-
- const color = await firstError.evaluate((el) => {
- const style = window.getComputedStyle(el);
- return style.color;
- });
-
- expect(color).toBeTruthy();
- }
- });
- });
-
- test.describe('提交流程测试', () => {
- test('表单提交功能正常', async ({ page }) => {
- const nameInput = page.locator('input[name="name"], input[placeholder*="姓名"]');
- const phoneInput = page.locator('input[name="phone"], input[placeholder*="电话"]');
- const emailInput = page.locator('input[name="email"], input[placeholder*="邮箱"]');
- const messageInput = page.locator('textarea[name="message"], textarea[placeholder*="消息"]');
- const submitButton = page.locator('button[type="submit"], button:has-text("提交")');
-
- if (await nameInput.count() > 0) {
- await nameInput.first().fill(TestDataGenerator.generateName());
- }
-
- if (await phoneInput.count() > 0) {
- await phoneInput.first().fill(TestDataGenerator.generatePhone());
- }
-
- if (await emailInput.count() > 0) {
- await emailInput.first().fill(TestDataGenerator.generateEmail());
- }
-
- if (await messageInput.count() > 0) {
- await messageInput.first().fill(TestDataGenerator.generateMessage());
- }
-
- if (await submitButton.count() > 0) {
- await submitButton.first().click();
- await page.waitForTimeout(2000);
-
- const successMessage = page.locator('[class*="success"], text=/成功|感谢|已发送/');
- const hasSuccess = await successMessage.count() > 0;
-
- expect(typeof hasSuccess).toBe('boolean');
- }
- });
-
- test('成功消息显示正确', async ({ page }) => {
- const successMessage = page.locator('[class*="success"], text=/成功|感谢|已发送/');
-
- if (await successMessage.count() > 0) {
- await expect(successMessage.first()).toBeVisible({ timeout: 5000 });
-
- const text = await successMessage.first().textContent();
- expect(text).toBeTruthy();
- expect(text!.length).toBeGreaterThan(0);
- }
- });
-
- test('错误处理正常', async ({ page }) => {
- const submitButton = page.locator('button[type="submit"], button:has-text("提交")');
-
- if (await submitButton.count() > 0) {
- await submitButton.first().click();
- await page.waitForTimeout(1000);
-
- const errorMessage = page.locator('[class*="error"], [class*="failed"]');
- const hasError = await errorMessage.count() > 0;
-
- expect(typeof hasError).toBe('boolean');
- }
- });
-
- test('提交按钮状态变化正常', async ({ page }) => {
- const submitButton = page.locator('button[type="submit"], button:has-text("提交")');
-
- if (await submitButton.count() > 0) {
- const initialState = await submitButton.first().getAttribute('disabled');
-
- const nameInput = page.locator('input[name="name"], input[placeholder*="姓名"]');
- if (await nameInput.count() > 0) {
- await nameInput.first().fill(TestDataGenerator.generateName());
- }
-
- const currentState = await submitButton.first().getAttribute('disabled');
-
- expect(typeof initialState).toBe('string' || null);
- expect(typeof currentState).toBe('string' || null);
- }
- });
- });
-
- test.describe('联系信息测试', () => {
- test('地址可点击性正常', async ({ page }) => {
- const addressLinks = page.locator('a[href*="map"], a[href*="baidu.com/map"], a[href*="amap.com"]');
-
- if (await addressLinks.count() > 0) {
- const firstAddress = addressLinks.first();
- const href = await firstAddress.getAttribute('href');
- expect(href).toBeTruthy();
- expect(href).toMatch(/map|baidu|amap/i);
- }
- });
-
- test('电话可点击性正常', async ({ page }) => {
- const phoneLinks = page.locator('a[href^="tel:"]');
- const count = await phoneLinks.count();
-
- if (count > 0) {
- const firstPhone = phoneLinks.first();
- const href = await firstPhone.getAttribute('href');
- expect(href).toMatch(/^tel:\d+/);
-
- const box = await firstPhone.boundingBox();
- expect(box).toBeTruthy();
- expect(box!.height).toBeGreaterThanOrEqual(44);
- }
- });
-
- test('邮箱可点击性正常', async ({ page }) => {
- const emailLinks = page.locator('a[href^="mailto:"]');
- const count = await emailLinks.count();
-
- if (count > 0) {
- const firstEmail = emailLinks.first();
- const href = await firstEmail.getAttribute('href');
- expect(href).toMatch(/^mailto:.+@.+/);
-
- const box = await firstEmail.boundingBox();
- expect(box).toBeTruthy();
- expect(box!.height).toBeGreaterThanOrEqual(44);
- }
- });
- });
-
- test.describe('地图集成测试', () => {
- test('地图显示正常', async ({ page }) => {
- const mapContainer = page.locator('[class*="map"], iframe[src*="map"], #map');
-
- if (await mapContainer.count() > 0) {
- await expect(mapContainer.first()).toBeVisible({ timeout: 10000 });
-
- const box = await mapContainer.first().boundingBox();
- expect(box).toBeTruthy();
- expect(box!.width).toBeGreaterThan(200);
- expect(box!.height).toBeGreaterThan(150);
- }
- });
-
- test('地图交互正常', async ({ page }) => {
- const mapIframe = page.locator('iframe[src*="map"]');
-
- if (await mapIframe.count() > 0) {
- const box = await mapIframe.first().boundingBox();
- expect(box).toBeTruthy();
-
- await page.mouse.click(box!.x + box!.width / 2, box!.y + box!.height / 2);
- await page.waitForTimeout(500);
- }
- });
- });
-
- test.describe('性能测试', () => {
- test('联系页加载性能符合标准', async ({ page }) => {
- await performanceMonitor.startMonitoring();
-
- const metrics = await performanceMonitor.collectMetrics();
-
- expect(metrics.loadTime).toBeLessThan(5000);
- expect(metrics.firstContentfulPaint).toBeLessThan(1800);
- });
-
- test('表单输入响应速度正常', async ({ page }) => {
- const nameInput = page.locator('input[name="name"], input[placeholder*="姓名"]');
-
- if (await nameInput.count() > 0) {
- const startTime = Date.now();
- await nameInput.first().fill(TestDataGenerator.generateName());
- const endTime = Date.now();
-
- expect(endTime - startTime).toBeLessThan(1000);
- }
- });
- });
-
- test.describe('可访问性测试', () => {
- test('表单标签关联正确', async ({ page }) => {
- const labels = page.locator('label');
- const count = await labels.count();
-
- if (count > 0) {
- const firstLabel = labels.first();
- const forAttr = await firstLabel.getAttribute('for');
-
- if (forAttr) {
- const input = page.locator(`#${forAttr}`);
- await expect(input).toBeVisible();
- }
- }
- });
-
- test('表单字段触摸目标大小符合标准', async ({ page }) => {
- const inputs = page.locator('input, textarea, select');
- const count = await inputs.count();
-
- if (count > 0) {
- const firstInput = inputs.first();
- const box = await firstInput.boundingBox();
-
- expect(box).toBeTruthy();
- expect(box!.height).toBeGreaterThanOrEqual(44);
- }
- });
-
- test('提交按钮触摸目标大小符合标准', async ({ page }) => {
- const submitButton = page.locator('button[type="submit"], button:has-text("提交")');
-
- if (await submitButton.count() > 0) {
- const box = await submitButton.first().boundingBox();
-
- expect(box).toBeTruthy();
- expect(box!.width).toBeGreaterThanOrEqual(44);
- expect(box!.height).toBeGreaterThanOrEqual(44);
- }
- });
- });
-});
diff --git a/e2e/src/tests/mobile/mobile-functionality.spec.ts b/e2e/src/tests/mobile/mobile-functionality.spec.ts
deleted file mode 100644
index 0cc1d8e..0000000
--- a/e2e/src/tests/mobile/mobile-functionality.spec.ts
+++ /dev/null
@@ -1,358 +0,0 @@
-import { test, expect, Page } from '@playwright/test';
-import { MobilePage } from '../pages/MobilePage';
-import { TestDataGenerator } from '../utils/TestDataGenerator';
-import { PerformanceMonitor } from '../utils/PerformanceMonitor';
-import { MobileHelper } from '../utils/MobileHelper';
-import { getCoreMobileDevices } from '../utils/DeviceMatrix';
-
-test.describe('首页移动端功能测试 @mobile', () => {
- let mobilePage: MobilePage;
- let performanceMonitor: PerformanceMonitor;
- let mobileHelper: MobileHelper;
-
- test.beforeEach(async ({ page }) => {
- mobilePage = new MobilePage(page);
- performanceMonitor = new PerformanceMonitor(page);
- mobileHelper = new MobileHelper(page);
- await page.goto('/');
- await page.waitForLoadState('domcontentloaded');
- });
-
- test.describe('Hero区域测试', () => {
- test('Hero标题在移动端可见且响应式', async ({ page }) => {
- const heroTitle = page.locator('h1').first();
- await expect(heroTitle).toBeVisible({ timeout: 10000 });
-
- const titleText = await heroTitle.textContent();
- expect(titleText).toBeTruthy();
- expect(titleText!.length).toBeGreaterThan(0);
-
- const box = await heroTitle.boundingBox();
- expect(box).toBeTruthy();
- expect(box!.width).toBeLessThanOrEqual(400);
- });
-
- test('Hero描述文本可读', async ({ page }) => {
- const heroDescription = page.locator('h1 + p, [class*="hero"] p').first();
- await expect(heroDescription).toBeVisible({ timeout: 10000 });
-
- const fontSize = await heroDescription.evaluate((el) => {
- const style = window.getComputedStyle(el);
- return parseFloat(style.fontSize);
- });
- expect(fontSize).toBeGreaterThanOrEqual(14);
- });
-
- test('CTA按钮触摸目标大小符合标准', async ({ page }) => {
- const ctaButtons = page.locator('a[href="#contact"], a[href*="contact"], button:has-text("咨询"), button:has-text("联系")');
- const count = await ctaButtons.count();
-
- if (count > 0) {
- const firstButton = ctaButtons.first();
- const box = await firstButton.boundingBox();
-
- expect(box).toBeTruthy();
- expect(box!.width).toBeGreaterThanOrEqual(44);
- expect(box!.height).toBeGreaterThanOrEqual(44);
- }
- });
-
- test('CTA按钮点击响应正常', async ({ page }) => {
- const ctaButton = page.locator('a[href="#contact"], a[href*="contact"]').first();
-
- if (await ctaButton.isVisible()) {
- await ctaButton.click();
- await page.waitForTimeout(500);
-
- const currentUrl = page.url();
- expect(currentUrl).toContain('contact');
- }
- });
- });
-
- test.describe('导航测试', () => {
- test('移动菜单开关功能正常', async ({ page }) => {
- const menuButton = page.locator('button[aria-label="打开菜单"], button[aria-label="关闭菜单"]');
- await expect(menuButton).toBeVisible({ timeout: 10000 });
-
- await menuButton.click();
-
- const mobileMenu = page.locator('#mobile-menu');
- await expect(mobileMenu).toBeVisible({ timeout: 10000 });
-
- const closeButton = page.locator('button[aria-label="关闭菜单"]');
- await closeButton.click();
-
- await expect(mobileMenu).not.toBeVisible({ timeout: 10000 });
- });
-
- test('导航链接点击正常', async ({ page }) => {
- const menuButton = page.locator('button[aria-label="打开菜单"]');
- await menuButton.click();
-
- const mobileMenu = page.locator('#mobile-menu');
- await expect(mobileMenu).toBeVisible({ timeout: 10000 });
-
- const navLinks = mobileMenu.locator('a');
- const count = await navLinks.count();
-
- if (count > 0) {
- const firstLink = navLinks.first();
- const linkText = await firstLink.textContent();
-
- await firstLink.click();
- await page.waitForTimeout(500);
-
- const closeButton = page.locator('button[aria-label="关闭菜单"]');
- if (await closeButton.isVisible()) {
- await closeButton.click();
- }
- }
- });
-
- test('面包屑导航显示正确', async ({ page }) => {
- await page.goto('/about');
- await page.waitForLoadState('domcontentloaded');
-
- const breadcrumb = page.locator('nav[aria-label="breadcrumb"]');
- await expect(breadcrumb).toBeVisible({ timeout: 10000 });
-
- const breadcrumbLinks = breadcrumb.locator('a');
- const count = await breadcrumbLinks.count();
- expect(count).toBeGreaterThanOrEqual(1);
- });
- });
-
- test.describe('内容区域测试', () => {
- test('服务卡片响应式布局正确', async ({ page }) => {
- const servicesSection = page.locator('#services, [id*="service"]');
-
- if (await servicesSection.count() > 0) {
- const serviceCards = servicesSection.first().locator('article, [class*="card"]');
- const count = await serviceCards.count();
-
- if (count >= 2) {
- const firstCard = serviceCards.first();
- const secondCard = serviceCards.nth(1);
-
- const firstBox = await firstCard.boundingBox();
- const secondBox = await secondCard.boundingBox();
-
- if (firstBox && secondBox) {
- expect(secondBox.y).toBeGreaterThan(firstBox.y);
- expect(firstBox.width).toBeLessThanOrEqual(400);
- }
- }
- }
- });
-
- test('产品卡片堆叠布局正确', async ({ page }) => {
- const productsSection = page.locator('#products, [id*="product"]');
-
- if (await productsSection.count() > 0) {
- const productCards = productsSection.first().locator('article, [class*="card"], a');
- const count = await productCards.count();
-
- if (count >= 2) {
- const firstCard = productCards.first();
- const secondCard = productCards.nth(1);
-
- const firstBox = await firstCard.boundingBox();
- const secondBox = await secondCard.boundingBox();
-
- if (firstBox && secondBox) {
- expect(secondBox.y).toBeGreaterThan(firstBox.y);
- }
- }
- }
- });
-
- test('案例卡片响应式布局正确', async ({ page }) => {
- const casesSection = page.locator('#cases, [id*="case"]');
-
- if (await casesSection.count() > 0) {
- const caseCards = casesSection.first().locator('article, [class*="card"]');
- const count = await caseCards.count();
- expect(count).toBeGreaterThan(0);
-
- if (count > 0) {
- const firstCard = caseCards.first();
- const box = await firstCard.boundingBox();
-
- if (box) {
- expect(box.width).toBeLessThanOrEqual(400);
- }
- }
- }
- });
-
- test('新闻卡片响应式布局正确', async ({ page }) => {
- const newsSection = page.locator('#news, [id*="news"]');
-
- if (await newsSection.count() > 0) {
- const newsCards = newsSection.first().locator('article, [class*="card"]');
- const count = await newsCards.count();
- expect(count).toBeGreaterThan(0);
- }
- });
- });
-
- test.describe('页脚测试', () => {
- test('联系信息可点击', async ({ page }) => {
- const footer = page.locator('footer');
- await expect(footer).toBeVisible({ timeout: 10000 });
-
- const phoneLinks = footer.locator('a[href^="tel:"]');
- const phoneCount = await phoneLinks.count();
-
- if (phoneCount > 0) {
- const firstPhone = phoneLinks.first();
- const href = await firstPhone.getAttribute('href');
- expect(href).toContain('tel:');
- }
-
- const emailLinks = footer.locator('a[href^="mailto:"]');
- const emailCount = await emailLinks.count();
-
- if (emailCount > 0) {
- const firstEmail = emailLinks.first();
- const href = await firstEmail.getAttribute('href');
- expect(href).toContain('mailto:');
- }
- });
-
- test('社交媒体链接正常', async ({ page }) => {
- const footer = page.locator('footer');
- const socialLinks = footer.locator('a[target="_blank"], a[href*="weibo"], a[href*="wechat"], a[href*="linkedin"]');
- const count = await socialLinks.count();
-
- if (count > 0) {
- const firstSocial = socialLinks.first();
- const href = await firstSocial.getAttribute('href');
- expect(href).toBeTruthy();
- }
- });
-
- test('版权信息显示正确', async ({ page }) => {
- const footer = page.locator('footer');
- const copyright = footer.locator('text=/版权|©|Copyright/i');
-
- const footerText = await footer.textContent();
- expect(footerText).toMatch(/版权|©|Copyright|公司/i);
- });
- });
-
- test.describe('表单交互测试', () => {
- test('快速联系表单移动端适配', async ({ page }) => {
- const contactSection = page.locator('#contact, [id*="contact"]');
-
- if (await contactSection.count() > 0) {
- const form = contactSection.first().locator('form');
-
- if (await form.count() > 0) {
- await expect(form.first()).toBeVisible({ timeout: 10000 });
- }
- }
- });
-
- test('表单字段输入正常', async ({ page }) => {
- const contactSection = page.locator('#contact, [id*="contact"]');
-
- if (await contactSection.count() > 0) {
- const nameInput = contactSection.first().locator('input[name="name"], input[placeholder*="姓名"]');
-
- if (await nameInput.count() > 0) {
- const testData = TestDataGenerator.generateName();
- await nameInput.first().fill(testData);
-
- const value = await nameInput.first().inputValue();
- expect(value).toBe(testData);
- }
- }
- });
-
- test('表单提交功能正常', async ({ page }) => {
- const contactSection = page.locator('#contact, [id*="contact"]');
-
- if (await contactSection.count() > 0) {
- const form = contactSection.first().locator('form');
-
- if (await form.count() > 0) {
- const nameInput = form.first().locator('input[name="name"], input[placeholder*="姓名"]');
- const phoneInput = form.first().locator('input[name="phone"], input[placeholder*="电话"]');
- const submitButton = form.first().locator('button[type="submit"], button:has-text("提交")');
-
- if (await nameInput.count() > 0 && await phoneInput.count() > 0 && await submitButton.count() > 0) {
- await nameInput.first().fill(TestDataGenerator.generateName());
- await phoneInput.first().fill(TestDataGenerator.generatePhone());
-
- await submitButton.first().click();
- await page.waitForTimeout(1000);
- }
- }
- }
- });
- });
-
- test.describe('性能测试', () => {
- test('页面加载性能符合标准', async ({ page }) => {
- await performanceMonitor.startMonitoring();
-
- const metrics = await performanceMonitor.collectMetrics();
-
- expect(metrics.loadTime).toBeLessThan(5000);
- expect(metrics.firstContentfulPaint).toBeLessThan(1800);
- });
-
- test('LCP符合Core Web Vitals标准', async ({ page }) => {
- const lcp = await mobilePage.measureLCP();
- expect(lcp).toBeLessThan(2500);
- });
-
- test('CLS符合Core Web Vitals标准', async ({ page }) => {
- const cls = await mobilePage.measureCLS();
- expect(cls).toBeLessThan(0.1);
- });
- });
-
- test.describe('可访问性测试', () => {
- test('触摸目标大小符合WCAG标准', async ({ page }) => {
- const buttons = page.locator('button, a, input, select, textarea');
- const count = await buttons.count();
-
- const touchTargets = await buttons.all();
- let validCount = 0;
-
- for (const target of touchTargets.slice(0, 20)) {
- const isValid = await mobilePage.checkTouchTarget(target);
- if (isValid) validCount++;
- }
-
- const passRate = validCount / Math.min(touchTargets.length, 20);
- expect(passRate).toBeGreaterThan(0.9);
- });
-
- test('颜色对比度符合WCAG标准', async ({ page }) => {
- const textElements = page.locator('p, h1, h2, h3, h4, h5, h6, span, a, label');
- const count = await textElements.count();
- expect(count).toBeGreaterThan(0);
- });
-
- test('焦点指示器可见', async ({ page }) => {
- const focusableElements = page.locator('button, a, input, select, textarea');
- const count = await focusableElements.count();
-
- if (count > 0) {
- const firstElement = focusableElements.first();
- await firstElement.focus();
-
- const outline = await firstElement.evaluate((el) => {
- const style = window.getComputedStyle(el);
- return style.outline;
- });
-
- expect(outline).toBeTruthy();
- }
- });
- });
-});
diff --git a/e2e/src/utils/DeviceMatrix.ts b/e2e/src/utils/DeviceMatrix.ts
deleted file mode 100644
index 751a25a..0000000
--- a/e2e/src/utils/DeviceMatrix.ts
+++ /dev/null
@@ -1,127 +0,0 @@
-import { devices, Device } from '@playwright/test';
-
-export interface MobileDevice {
- name: string;
- device: Device;
- viewport: { width: number; height: number };
- userAgent?: string;
-}
-
-export interface ResponsiveBreakpoint {
- name: string;
- width: number;
- height: number;
- description: string;
-}
-
-export const mobileDevices: Record = {
- 'iPhone 12': {
- name: 'iPhone 12',
- device: devices['iPhone 12'],
- viewport: { width: 390, height: 844 },
- },
- 'iPhone 14': {
- name: 'iPhone 14',
- device: devices['iPhone 14'],
- viewport: { width: 390, height: 844 },
- },
- 'Galaxy S21': {
- name: 'Galaxy S21',
- device: devices['Galaxy S21'],
- viewport: { width: 360, height: 800 },
- },
- 'iPad Pro': {
- name: 'iPad Pro',
- device: devices['iPad Pro'],
- viewport: { width: 1024, height: 1366 },
- },
- 'iPad Mini': {
- name: 'iPad Mini',
- device: devices['iPad Mini'],
- viewport: { width: 768, height: 1024 },
- },
-};
-
-export const responsiveBreakpoints: ResponsiveBreakpoint[] = [
- {
- name: 'iPhone SE',
- width: 375,
- height: 667,
- description: 'Small mobile device',
- },
- {
- name: 'iPhone 11 Pro Max',
- width: 414,
- height: 896,
- description: 'Large mobile device',
- },
- {
- name: 'iPad Portrait',
- width: 768,
- height: 1024,
- description: 'Tablet portrait',
- },
- {
- name: 'iPad Landscape',
- width: 1024,
- height: 768,
- description: 'Tablet landscape',
- },
- {
- name: 'Small Laptop',
- width: 1280,
- height: 720,
- description: 'Small laptop screen',
- },
-];
-
-export const coreDevices = ['iPhone 12', 'iPhone 14', 'Galaxy S21', 'iPad Pro', 'iPad Mini'];
-
-export function getMobileDevice(name: string): MobileDevice | undefined {
- return mobileDevices[name];
-}
-
-export function getViewportSize(deviceName: string): { width: number; height: number } | undefined {
- const device = mobileDevices[deviceName];
- return device?.viewport;
-}
-
-export function isMobileDevice(userAgent: string): boolean {
- const mobilePatterns = [
- /iPhone/i,
- /iPad/i,
- /iPod/i,
- /Android/i,
- /BlackBerry/i,
- /Windows Phone/i,
- /webOS/i,
- /Mobile/i,
- ];
-
- return mobilePatterns.some(pattern => pattern.test(userAgent));
-}
-
-export function getDeviceByViewport(width: number, height: number): string | undefined {
- for (const breakpoint of responsiveBreakpoints) {
- if (width <= breakpoint.width && height <= breakpoint.height) {
- return breakpoint.name;
- }
- }
- return undefined;
-}
-
-export function getAllMobileDevices(): MobileDevice[] {
- return Object.values(mobileDevices);
-}
-
-export function getCoreMobileDevices(): MobileDevice[] {
- return coreDevices.map(name => mobileDevices[name]).filter(Boolean) as MobileDevice[];
-}
-
-export function getAllResponsiveBreakpoints(): ResponsiveBreakpoint[] {
- return [...responsiveBreakpoints];
-}
-
-export function getBreakpointByWidth(width: number): ResponsiveBreakpoint | undefined {
- return responsiveBreakpoints.find(bp => width <= bp.width);
-}
diff --git a/e2e/src/utils/MobileHelper.ts b/e2e/src/utils/MobileHelper.ts
deleted file mode 100644
index 2c55c64..0000000
--- a/e2e/src/utils/MobileHelper.ts
+++ /dev/null
@@ -1,270 +0,0 @@
-import { Page } from '@playwright/test';
-import { getMobileDevice, isMobileDevice, getDeviceByViewport } from './DeviceMatrix';
-
-export interface TouchEvent {
- type: 'touchstart' | 'touchmove' | 'touchend';
- touches: Array<{ x: number; y: number }>;
- timestamp: number;
-}
-
-export interface NetworkCondition {
- offline: boolean;
- downloadThroughput: number;
- uploadThroughput: number;
- latency: number;
-}
-
-export class MobileHelper {
- constructor(private page: Page) {}
-
- async getMobileDevice(name: string) {
- return getMobileDevice(name);
- }
-
- async getViewportSize(device: string) {
- const device = await this.getMobileDevice(device);
- return device?.viewport;
- }
-
- async isMobileDevice(userAgent?: string): Promise {
- const ua = userAgent || await this.page.evaluate(() => navigator.userAgent);
- return isMobileDevice(ua);
- }
-
- async getCurrentViewport(): Promise<{ width: number; height: number }> {
- return await this.page.evaluate(() => {
- return {
- width: window.innerWidth,
- height: window.innerHeight,
- };
- });
- }
-
- async getCurrentDeviceName(): Promise {
- const viewport = await this.getCurrentViewport();
- return getDeviceByViewport(viewport.width, viewport.height);
- }
-
- async isPortrait(): Promise {
- const viewport = await this.getCurrentViewport();
- return viewport.height > viewport.width;
- }
-
- async isLandscape(): Promise {
- const viewport = await this.getCurrentViewport();
- return viewport.width > viewport.height;
- }
-
- async getTouchSupport(): Promise<{
- maxTouchPoints: number;
- touchEvent: boolean;
- }> {
- return await this.page.evaluate(() => {
- return {
- maxTouchPoints: navigator.maxTouchPoints || 0,
- touchEvent: 'ontouchstart' in window,
- };
- });
- }
-
- async generateTouchEvent(type: 'touchstart' | 'touchmove' | 'touchend', touches: Array<{ x: number; y: number }>): Promise {
- return {
- type,
- touches,
- timestamp: Date.now(),
- };
- }
-
- async simulateTouch(element: string, x: number, y: number): Promise {
- await this.page.locator(element).dispatchEvent('touchstart', {
- touches: [{ clientX: x, clientY: y }],
- });
-
- await this.page.waitForTimeout(50);
-
- await this.page.locator(element).dispatchEvent('touchend', {
- touches: [],
- });
- }
-
- async simulateSwipe(startX: number, startY: number, endX: number, endY: number, duration: number = 500): Promise {
- const steps = 10;
- const stepDelay = duration / steps;
-
- for (let i = 0; i <= steps; i++) {
- const x = startX + (endX - startX) * (i / steps);
- const y = startY + (endY - startY) * (i / steps);
-
- await this.page.mouse.move(x, y);
- await this.page.waitForTimeout(stepDelay);
- }
- }
-
- async simulatePinchZoom(centerX: number, centerY: number, scale: number): Promise {
- const startDistance = 100;
- const endDistance = startDistance / scale;
-
- const finger1Start = { x: centerX - startDistance / 2, y: centerY };
- const finger2Start = { x: centerX + startDistance / 2, y: centerY };
-
- const finger1End = { x: centerX - endDistance / 2, y: centerY };
- const finger2End = { x: centerX + endDistance / 2, y: centerY };
-
- await this.page.mouse.move(finger1Start.x, finger1Start.y);
- await this.page.mouse.down();
-
- await this.page.mouse.move(finger2Start.x, finger2Start.y);
- await this.page.mouse.down();
-
- await this.page.mouse.move(finger1End.x, finger1End.y);
- await this.page.mouse.move(finger2End.x, finger2End.y);
-
- await this.page.mouse.up();
- await this.page.mouse.up();
- }
-
- async setNetworkCondition(condition: NetworkCondition): Promise {
- await this.page.route('**', (route) => {
- if (condition.offline) {
- route.abort();
- } else {
- route.continue();
- }
- });
-
- await this.page.evaluate((condition) => {
- Object.defineProperty(navigator, 'connection', {
- value: {
- effectiveType: condition.downloadThroughput < 1.5 ? 'slow-2g' : '4g',
- downlink: condition.downloadThroughput,
- rtt: condition.latency,
- },
- writable: true,
- });
- }, condition);
- }
-
- async getNetworkCondition(): Promise {
- return await this.page.evaluate(() => {
- const connection = (navigator as any).connection;
- return {
- offline: !navigator.onLine,
- downloadThroughput: connection?.downlink || 10,
- uploadThroughput: connection?.downlink || 10,
- latency: connection?.rtt || 100,
- };
- });
- }
-
- async setViewport(width: number, height: number): Promise {
- await this.page.setViewportSize({ width, height });
- }
-
- async setDevice(deviceName: string): Promise {
- const device = await this.getMobileDevice(deviceName);
- if (device) {
- await this.page.setViewportSize(device.viewport);
-
- if (device.userAgent) {
- await this.page.setExtraHTTPHeaders({
- 'User-Agent': device.userAgent,
- });
- }
- }
- }
-
- async emulateMobile(deviceName: string): Promise {
- await this.setDevice(deviceName);
-
- await this.page.emulateMedia({
- media: 'screen',
- colorScheme: 'light',
- });
- }
-
- async getBatteryInfo(): Promise<{
- level: number;
- charging: boolean;
- }> {
- return await this.page.evaluate(() => {
- const battery = (navigator as any).getBattery();
- if (battery) {
- return {
- level: battery.level,
- charging: battery.charging,
- };
- }
- return {
- level: 1,
- charging: true,
- };
- });
- }
-
- async getOrientation(): Promise<'portrait' | 'landscape'> {
- const viewport = await this.getCurrentViewport();
- return viewport.height > viewport.width ? 'portrait' : 'landscape';
- }
-
- async rotateDevice(): Promise {
- const viewport = await this.getCurrentViewport();
- await this.page.setViewportSize({
- width: viewport.height,
- height: viewport.width,
- });
- }
-
- async hideKeyboard(): Promise {
- await this.page.keyboard.press('Escape');
- await this.page.waitForTimeout(200);
- }
-
- async scrollToElement(selector: string): Promise {
- const element = this.page.locator(selector);
- await element.scrollIntoViewIfNeeded();
- await this.page.waitForTimeout(500);
- }
-
- async scrollToTop(): Promise {
- await this.page.evaluate(() => {
- window.scrollTo({ top: 0, behavior: 'smooth' });
- });
- await this.page.waitForTimeout(500);
- }
-
- async scrollToBottom(): Promise {
- await this.page.evaluate(() => {
- window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });
- });
- await this.page.waitForTimeout(500);
- }
-
- async getScrollPosition(): Promise<{ x: number; y: number }> {
- return await this.page.evaluate(() => {
- return {
- x: window.scrollX,
- y: window.scrollY,
- };
- });
- }
-
- async isElementInViewport(selector: string): Promise {
- return await this.page.locator(selector).evaluate((el) => {
- const rect = el.getBoundingClientRect();
- return (
- rect.top >= 0 &&
- rect.left >= 0 &&
- rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
- rect.right <= (window.innerWidth || document.documentElement.clientWidth)
- );
- });
- }
-
- async waitForElementVisible(selector: string, timeout: number = 5000): Promise {
- await this.page.waitForSelector(selector, { state: 'visible', timeout });
- }
-
- async waitForElementHidden(selector: string, timeout: number = 5000): Promise {
- await this.page.waitForSelector(selector, { state: 'hidden', timeout });
- }
-}
diff --git a/e2e/src/utils/PerformanceMonitor.ts b/e2e/src/utils/PerformanceMonitor.ts
index 5e0bc9b..2cdf0e1 100644
--- a/e2e/src/utils/PerformanceMonitor.ts
+++ b/e2e/src/utils/PerformanceMonitor.ts
@@ -314,201 +314,4 @@ export class PerformanceMonitor {
return report;
}
-
- async measureFirstMeaningfulPaint(): Promise {
- const fmp = await this.page.evaluate(() => {
- return new Promise((resolve) => {
- if ('PerformanceObserver' in window) {
- const observer = new PerformanceObserver((list) => {
- const entries = list.getEntries();
- if (entries.length > 0) {
- resolve(entries[0].startTime);
- }
- });
- observer.observe({ entryTypes: ['first-meaningful-paint'] });
- setTimeout(() => resolve(0), 5000);
- } else {
- resolve(0);
- }
- });
- });
- return fmp;
- }
-
- async measureNetworkPerformance(): Promise<{
- dnsLookup: number;
- tcpConnection: number;
- sslHandshake: number;
- requestTime: number;
- responseTime: number;
- totalTime: number;
- }> {
- const timing = await this.page.evaluate(() => {
- const perf = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming;
- return {
- dnsLookup: perf.domainLookupEnd - perf.domainLookupStart,
- tcpConnection: perf.connectEnd - perf.connectStart,
- sslHandshake: perf.secureConnectionStart > 0 ? perf.connectEnd - perf.secureConnectionStart : 0,
- requestTime: perf.responseStart - perf.requestStart,
- responseTime: perf.responseEnd - perf.responseStart,
- totalTime: perf.loadEventEnd - perf.fetchStart,
- };
- });
- return timing;
- }
-
- async measureBatteryImpact(): Promise<{
- estimatedImpact: string;
- recommendations: string[];
- }> {
- const metrics = await this.collectMetrics();
- const recommendations: string[] = [];
- let impact = 'low';
-
- if (metrics.loadTime > 3000) {
- recommendations.push('页面加载时间过长,建议优化资源加载');
- impact = 'high';
- }
- if (metrics.firstInputDelay > 100) {
- recommendations.push('首次输入延迟较高,建议优化JavaScript执行');
- impact = impact === 'high' ? 'high' : 'medium';
- }
- if (metrics.largestContentfulPaint > 2500) {
- recommendations.push('最大内容绘制时间过长,建议优化关键渲染路径');
- impact = impact === 'high' ? 'high' : 'medium';
- }
-
- return {
- estimatedImpact: impact,
- recommendations,
- };
- }
-
- async validateLCP(value: number, threshold: number = 2500): boolean {
- return value <= threshold;
- }
-
- async validateFID(value: number, threshold: number = 100): boolean {
- return value <= threshold;
- }
-
- async validateCLS(value: number, threshold: number = 0.1): boolean {
- return value <= threshold;
- }
-
- async validateTTI(value: number, threshold: number = 3500): boolean {
- return value <= threshold;
- }
-
- async validateTTFB(value: number, threshold: number = 600): boolean {
- return value <= threshold;
- }
-
- async validateFCP(value: number, threshold: number = 1800): boolean {
- return value <= threshold;
- }
-
- async getCoreWebVitalsSummary(): Promise<{
- lcp: { value: number; threshold: number; passed: boolean };
- fid: { value: number; threshold: number; passed: boolean };
- cls: { value: number; threshold: number; passed: boolean };
- tti: { value: number; threshold: number; passed: boolean };
- ttfb: { value: number; threshold: number; passed: boolean };
- fcp: { value: number; threshold: number; passed: boolean };
- }> {
- const metrics = await this.collectMetrics();
- const ttfb = await this.measureFirstByteTime();
- const fcp = await this.measureFirstContentfulPaint();
-
- return {
- lcp: {
- value: metrics.largestContentfulPaint,
- threshold: 2500,
- passed: await this.validateLCP(metrics.largestContentfulPaint),
- },
- fid: {
- value: metrics.firstInputDelay,
- threshold: 100,
- passed: await this.validateFID(metrics.firstInputDelay),
- },
- cls: {
- value: metrics.cumulativeLayoutShift,
- threshold: 0.1,
- passed: await this.validateCLS(metrics.cumulativeLayoutShift),
- },
- tti: {
- value: metrics.timeToInteractive,
- threshold: 3500,
- passed: await this.validateTTI(metrics.timeToInteractive),
- },
- ttfb: {
- value: ttfb,
- threshold: 600,
- passed: await this.validateTTFB(ttfb),
- },
- fcp: {
- value: fcp,
- threshold: 1800,
- passed: await this.validateFCP(fcp),
- },
- };
- }
-
- async measureFirstByteTime(): Promise {
- const ttfb = await this.page.evaluate(() => {
- const timing = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming;
- return timing.responseStart - timing.fetchStart;
- });
- return ttfb;
- }
-
- async measureMobileSpecificMetrics(): Promise<{
- touchResponseTime: number;
- scrollPerformance: number;
- gestureLatency: number;
- }> {
- const touchResponseTime = await this.page.evaluate(() => {
- return new Promise((resolve) => {
- const startTime = performance.now();
- document.addEventListener('touchstart', () => {
- resolve(performance.now() - startTime);
- }, { once: true });
- setTimeout(() => resolve(0), 1000);
- });
- });
-
- const scrollPerformance = await this.page.evaluate(() => {
- return new Promise((resolve) => {
- const startTime = performance.now();
- let frames = 0;
-
- function countFrames() {
- frames++;
- if (performance.now() - startTime >= 1000) {
- resolve(frames);
- } else {
- requestAnimationFrame(countFrames);
- }
- }
-
- requestAnimationFrame(countFrames);
- });
- });
-
- const gestureLatency = await this.page.evaluate(() => {
- return new Promise((resolve) => {
- const startTime = performance.now();
- document.addEventListener('touchmove', () => {
- resolve(performance.now() - startTime);
- }, { once: true });
- setTimeout(() => resolve(0), 500);
- });
- });
-
- return {
- touchResponseTime,
- scrollPerformance,
- gestureLatency,
- };
- }
}
diff --git a/e2e/src/utils/TestDataGenerator.ts b/e2e/src/utils/TestDataGenerator.ts
index 64ffbe1..6a472cf 100644
--- a/e2e/src/utils/TestDataGenerator.ts
+++ b/e2e/src/utils/TestDataGenerator.ts
@@ -457,347 +457,4 @@ ${this.generateMessage()}`;
const suffix = Math.floor(Math.random() * 90000000 + 10000000);
return `${prefix}${suffix}`;
}
-
- static generateMobileDevice(): {
- name: string;
- userAgent: string;
- viewport: { width: number; height: number };
- devicePixelRatio: number;
- touchPoints: number;
- } {
- const devices = [
- {
- name: 'iPhone 12',
- userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1',
- viewport: { width: 390, height: 844 },
- devicePixelRatio: 3,
- touchPoints: 5,
- },
- {
- name: 'iPhone 14',
- userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1',
- viewport: { width: 390, height: 844 },
- devicePixelRatio: 3,
- touchPoints: 5,
- },
- {
- name: 'Galaxy S21',
- userAgent: 'Mozilla/5.0 (Linux; Android 11; SM-G991B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.120 Mobile Safari/537.36',
- viewport: { width: 360, height: 800 },
- devicePixelRatio: 3,
- touchPoints: 5,
- },
- {
- name: 'iPad Pro',
- userAgent: 'Mozilla/5.0 (iPad; CPU OS 14_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1',
- viewport: { width: 1024, height: 1366 },
- devicePixelRatio: 2,
- touchPoints: 5,
- },
- ];
- return devices[Math.floor(Math.random() * devices.length)]!;
- }
-
- static generateMobileNetworkCondition(): {
- offline: boolean;
- downloadThroughput: number;
- uploadThroughput: number;
- latency: number;
- name: string;
- } {
- const conditions = [
- {
- offline: false,
- downloadThroughput: 10,
- uploadThroughput: 5,
- latency: 100,
- name: '4G',
- },
- {
- offline: false,
- downloadThroughput: 1.5,
- uploadThroughput: 0.75,
- latency: 300,
- name: '3G',
- },
- {
- offline: false,
- downloadThroughput: 0.4,
- uploadThroughput: 0.2,
- latency: 1000,
- name: '2G',
- },
- {
- offline: true,
- downloadThroughput: 0,
- uploadThroughput: 0,
- latency: 0,
- name: 'Offline',
- },
- ];
- return conditions[Math.floor(Math.random() * conditions.length)]!;
- }
-
- static generateTouchEvent(): {
- type: 'touchstart' | 'touchmove' | 'touchend';
- touches: Array<{ x: number; y: number }>;
- timestamp: number;
- } {
- const types: Array<'touchstart' | 'touchmove' | 'touchend'> = ['touchstart', 'touchmove', 'touchend'];
- const type = types[Math.floor(Math.random() * types.length)]!;
- const touches = Array.from({ length: Math.floor(Math.random() * 3) + 1 }, () => ({
- x: Math.floor(Math.random() * 400),
- y: Math.floor(Math.random() * 800),
- }));
-
- return {
- type,
- touches,
- timestamp: Date.now(),
- };
- }
-
- static generateSwipeGesture(): {
- startX: number;
- startY: number;
- endX: number;
- endY: number;
- duration: number;
- } {
- const startX = Math.floor(Math.random() * 300) + 50;
- const startY = Math.floor(Math.random() * 700) + 50;
- const direction = Math.random() < 0.5 ? 1 : -1;
-
- return {
- startX,
- startY,
- endX: startX + Math.floor(Math.random() * 200) * direction,
- endY: startY + Math.floor(Math.random() * 200) * direction,
- duration: Math.floor(Math.random() * 500) + 200,
- };
- }
-
- static generatePinchZoomGesture(): {
- centerX: number;
- centerY: number;
- scale: number;
- duration: number;
- } {
- return {
- centerX: Math.floor(Math.random() * 300) + 50,
- centerY: Math.floor(Math.random() * 700) + 50,
- scale: Math.random() * 2 + 0.5,
- duration: Math.floor(Math.random() * 500) + 200,
- };
- }
-
- static generateMobilePerformanceThresholds(): {
- lcp: { good: number; needsImprovement: number; poor: number };
- fid: { good: number; needsImprovement: number; poor: number };
- cls: { good: number; needsImprovement: number; poor: number };
- tti: { good: number; needsImprovement: number; poor: number };
- ttfb: { good: number; needsImprovement: number; poor: number };
- fcp: { good: number; needsImprovement: number; poor: number };
- } {
- return {
- lcp: { good: 2500, needsImprovement: 4000, poor: 4000 },
- fid: { good: 100, needsImprovement: 300, poor: 300 },
- cls: { good: 0.1, needsImprovement: 0.25, poor: 0.25 },
- tti: { good: 3500, needsImprovement: 5000, poor: 5000 },
- ttfb: { good: 600, needsImprovement: 1500, poor: 1500 },
- fcp: { good: 1800, needsImprovement: 3000, poor: 3000 },
- };
- }
-
- static generateMobileAccessibilityTestData(): {
- touchTargetSize: { min: number; recommended: number };
- colorContrast: { normalText: number; largeText: number };
- fontScale: { min: number; max: number };
- spacing: { min: number };
- focusIndicator: { minSize: number };
- } {
- return {
- touchTargetSize: { min: 44, recommended: 48 },
- colorContrast: { normalText: 4.5, largeText: 3.0 },
- fontScale: { min: 1.0, max: 2.0 },
- spacing: { min: 8 },
- focusIndicator: { minSize: 2 },
- };
- }
-
- static generateMobileFormTestData(): {
- name: string;
- email: string;
- phone: string;
- company: string;
- message: string;
- subject: string;
- touchTargets: Array<{ selector: string; size: { width: number; height: number } }>;
- } {
- return {
- name: this.generateName(),
- email: this.generateEmail(),
- phone: this.generatePhone(),
- company: this.generateCompany(),
- message: this.generateMessage(),
- subject: this.generateSubject(),
- touchTargets: [
- { selector: 'input[name="name"]', size: { width: 350, height: 48 } },
- { selector: 'input[name="email"]', size: { width: 350, height: 48 } },
- { selector: 'input[name="phone"]', size: { width: 350, height: 48 } },
- { selector: 'textarea[name="message"]', size: { width: 350, height: 150 } },
- { selector: 'button[type="submit"]', size: { width: 350, height: 48 } },
- ],
- };
- }
-
- static generateMobileNavigationTestData(): {
- menuItems: Array<{ label: string; href: string; touchTarget: boolean }>;
- hamburgerMenu: { selector: string; size: { width: number; height: number } };
- breadcrumbs: Array<{ label: string; href: string }>;
- } {
- return {
- menuItems: [
- { label: '首页', href: '#home', touchTarget: true },
- { label: '关于我们', href: '#about', touchTarget: true },
- { label: '服务', href: '#services', touchTarget: true },
- { label: '产品', href: '#products', touchTarget: true },
- { label: '案例', href: '#cases', touchTarget: true },
- { label: '新闻', href: '#news', touchTarget: true },
- { label: '联系我们', href: '#contact', touchTarget: true },
- ],
- hamburgerMenu: {
- selector: 'button[aria-label="打开菜单"]',
- size: { width: 48, height: 48 },
- },
- breadcrumbs: [
- { label: '首页', href: '/' },
- { label: '服务', href: '#services' },
- { label: '详情', href: '#details' },
- ],
- };
- }
-
- static generateMobileScrollTestData(): {
- scrollPositions: Array<{ x: number; y: number }>;
- scrollSpeeds: Array<{ pixelsPerSecond: number }>;
- scrollDirections: Array<'up' | 'down' | 'left' | 'right'>;
- } {
- return {
- scrollPositions: [
- { x: 0, y: 0 },
- { x: 0, y: 500 },
- { x: 0, y: 1000 },
- { x: 0, y: 1500 },
- { x: 0, y: 2000 },
- ],
- scrollSpeeds: [
- { pixelsPerSecond: 500 },
- { pixelsPerSecond: 1000 },
- { pixelsPerSecond: 1500 },
- { pixelsPerSecond: 2000 },
- ],
- scrollDirections: ['up', 'down', 'left', 'right'],
- };
- }
-
- static generateMobileOrientationTestData(): {
- portrait: { width: number; height: number };
- landscape: { width: number; height: number };
- rotation: { from: string; to: string }[];
- } {
- return {
- portrait: { width: 390, height: 844 },
- landscape: { width: 844, height: 390 },
- rotation: [
- { from: 'portrait', to: 'landscape' },
- { from: 'landscape', to: 'portrait' },
- ],
- };
- }
-
- static generateMobileBatteryTestData(): {
- levels: Array<{ level: number; charging: boolean }>;
- lowBatteryThreshold: number;
- criticalBatteryThreshold: number;
- } {
- return {
- levels: [
- { level: 1.0, charging: true },
- { level: 0.75, charging: false },
- { level: 0.5, charging: false },
- { level: 0.25, charging: false },
- { level: 0.1, charging: false },
- ],
- lowBatteryThreshold: 0.2,
- criticalBatteryThreshold: 0.1,
- };
- }
-
- static generateMobileGestureTestData(): {
- tap: { duration: number; coordinates: Array<{ x: number; y: number }> };
- doubleTap: { interval: number; coordinates: Array<{ x: number; y: number }> };
- longPress: { duration: number; coordinates: Array<{ x: number; y: number }> };
- swipe: { directions: Array<'left' | 'right' | 'up' | 'down'>; distance: number };
- pinch: { scales: Array; duration: number };
- } {
- return {
- tap: {
- duration: 100,
- coordinates: [
- { x: 200, y: 400 },
- { x: 300, y: 500 },
- { x: 150, y: 600 },
- ],
- },
- doubleTap: {
- interval: 300,
- coordinates: [
- { x: 200, y: 400 },
- { x: 300, y: 500 },
- ],
- },
- longPress: {
- duration: 1000,
- coordinates: [
- { x: 200, y: 400 },
- { x: 300, y: 500 },
- ],
- },
- swipe: {
- directions: ['left', 'right', 'up', 'down'],
- distance: 200,
- },
- pinch: {
- scales: [0.5, 1.5, 2.0],
- duration: 500,
- },
- };
- }
-
- static generateMobileErrorScenarios(): {
- networkError: { message: string; selector: string };
- timeoutError: { message: string; timeout: number };
- touchError: { message: string; selector: string };
- viewportError: { message: string; viewport: { width: number; height: number } };
- } {
- return {
- networkError: {
- message: '网络连接失败,请检查您的网络设置',
- selector: '.network-error',
- },
- timeoutError: {
- message: '请求超时,请稍后重试',
- timeout: 30000,
- },
- touchError: {
- message: '触摸目标不可用',
- selector: '.touch-error',
- },
- viewportError: {
- message: '当前视口大小不支持',
- viewport: { width: 320, height: 480 },
- },
- };
- }
}
diff --git a/src/app/globals.css b/src/app/globals.css
index 324b78c..1b80da7 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -38,9 +38,9 @@
/* 文字色系 - 墨色层次 */
--color-text-primary: #1C1C1C;
- --color-text-secondary: #2D2D2D;
- --color-text-tertiary: #4A4A4A;
- --color-text-muted: #6B6B6B;
+ --color-text-secondary: #3D3D3D;
+ --color-text-tertiary: #5C5C5C;
+ --color-text-muted: #8C8C8C;
/* 边框色系 */
--color-border-primary: #E5E5E5;
@@ -211,38 +211,12 @@
input:focus, textarea:focus {
outline: none;
- box-shadow: 0 0 0 3px rgba(196, 30, 58, 0.3);
- }
-
- button:focus-visible, a:focus-visible {
- outline: 2px solid #C41E3A;
- outline-offset: 2px;
}
::selection {
background-color: var(--color-text-primary);
color: var(--color-bg-primary);
}
-
- @media (prefers-contrast: high) {
- :root {
- --color-text-primary: #000000;
- --color-text-secondary: #1A1A1A;
- --color-text-tertiary: #2A2A2A;
- --color-border-primary: #000000;
- --color-border-secondary: #1A1A1A;
- }
- }
-
- @media (prefers-reduced-motion: reduce) {
- *,
- *::before,
- *::after {
- animation-duration: 0.01ms !important;
- animation-iteration-count: 1 !important;
- transition-duration: 0.01ms !important;
- }
- }
}
@layer utilities {
diff --git a/src/components/layout/breadcrumb.tsx b/src/components/layout/breadcrumb.tsx
index 48361b1..567a607 100644
--- a/src/components/layout/breadcrumb.tsx
+++ b/src/components/layout/breadcrumb.tsx
@@ -1,7 +1,7 @@
'use client';
import Link from 'next/link';
-import { ChevronRight, Home, ArrowLeft } from 'lucide-react';
+import { ChevronRight, Home } from 'lucide-react';
interface BreadcrumbItem {
label: string;
@@ -10,31 +10,20 @@ interface BreadcrumbItem {
interface BreadcrumbProps {
items: BreadcrumbItem[];
- showBackButton?: boolean;
- onBackClick?: () => void;
}
-export function Breadcrumb({ items, showBackButton = false, onBackClick }: BreadcrumbProps) {
+export function Breadcrumb({ items }: BreadcrumbProps) {
return (
-