1902 lines
44 KiB
Markdown
1902 lines
44 KiB
Markdown
# 移动端测试完善实施计划
|
||
|
||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||
|
||
**Goal:** 建立全面完善的移动端测试体系,覆盖性能、兼容性、手势交互、PWA 功能等方面
|
||
|
||
**Architecture:** 采用分层架构设计,包括测试数据层、测试工具层、测试用例层、测试执行层。基于现有 Playwright 框架,渐进式扩展移动端测试能力。
|
||
|
||
**Tech Stack:** Playwright, TypeScript, Lighthouse, Chrome DevTools Protocol, Allure
|
||
|
||
---
|
||
|
||
## Phase 1: 基础设施扩展(1-2 周)
|
||
|
||
### Task 1.1: 扩展设备配置 - 添加 iPhone 系列
|
||
|
||
**Files:**
|
||
- Modify: `e2e/src/utils/devices.ts`
|
||
|
||
**Step 1: 添加 iPhone 13 Pro 配置**
|
||
|
||
在 `devices` 对象中添加:
|
||
|
||
```typescript
|
||
'iphone-13-pro': {
|
||
name: 'iPhone 13 Pro',
|
||
viewport: { width: 390, height: 844 },
|
||
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1',
|
||
isMobile: true,
|
||
deviceScaleFactor: 3,
|
||
},
|
||
```
|
||
|
||
**Step 2: 添加 iPhone 14 Pro 配置**
|
||
|
||
在 `devices` 对象中添加:
|
||
|
||
```typescript
|
||
'iphone-14-pro': {
|
||
name: 'iPhone 14 Pro',
|
||
viewport: { width: 393, height: 852 },
|
||
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',
|
||
isMobile: true,
|
||
deviceScaleFactor: 3,
|
||
},
|
||
```
|
||
|
||
**Step 3: 添加 iPhone 15 Pro 配置**
|
||
|
||
在 `devices` 对象中添加:
|
||
|
||
```typescript
|
||
'iphone-15-pro': {
|
||
name: 'iPhone 15 Pro',
|
||
viewport: { width: 393, height: 852 },
|
||
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1',
|
||
isMobile: true,
|
||
deviceScaleFactor: 3,
|
||
},
|
||
```
|
||
|
||
**Step 4: 验证设备配置**
|
||
|
||
运行测试验证新设备配置:
|
||
|
||
```bash
|
||
cd e2e
|
||
npm run test -- --grep "mobile-ux" --project=chromium
|
||
```
|
||
|
||
**Step 5: Commit**
|
||
|
||
```bash
|
||
git add e2e/src/utils/devices.ts
|
||
git commit -m "feat: add iPhone 13/14/15 Pro device configurations"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 1.2: 扩展设备配置 - 添加 Android 系列
|
||
|
||
**Files:**
|
||
- Modify: `e2e/src/utils/devices.ts`
|
||
|
||
**Step 1: 添加 Google Pixel 7 配置**
|
||
|
||
在 `devices` 对象中添加:
|
||
|
||
```typescript
|
||
'pixel-7': {
|
||
name: 'Google Pixel 7',
|
||
viewport: { width: 412, height: 915 },
|
||
userAgent: 'Mozilla/5.0 (Linux; Android 13; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36',
|
||
isMobile: true,
|
||
deviceScaleFactor: 2.625,
|
||
},
|
||
```
|
||
|
||
**Step 2: 添加 Samsung Galaxy S23 配置**
|
||
|
||
在 `devices` 对象中添加:
|
||
|
||
```typescript
|
||
'samsung-galaxy-s23': {
|
||
name: 'Samsung Galaxy S23',
|
||
viewport: { width: 360, height: 780 },
|
||
userAgent: 'Mozilla/5.0 (Linux; Android 13; SM-S911B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36',
|
||
isMobile: true,
|
||
deviceScaleFactor: 3,
|
||
},
|
||
```
|
||
|
||
**Step 3: 验证 Android 设备配置**
|
||
|
||
运行测试验证新设备配置:
|
||
|
||
```bash
|
||
cd e2e
|
||
npm run test -- --grep "mobile-ux" --project=chromium
|
||
```
|
||
|
||
**Step 4: Commit**
|
||
|
||
```bash
|
||
git add e2e/src/utils/devices.ts
|
||
git commit -m "feat: add Android device configurations (Pixel 7, Galaxy S23)"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 1.3: 扩展设备配置 - 添加 iPad 系列
|
||
|
||
**Files:**
|
||
- Modify: `e2e/src/utils/devices.ts`
|
||
|
||
**Step 1: 添加 iPad Air 配置**
|
||
|
||
在 `devices` 对象中添加:
|
||
|
||
```typescript
|
||
'ipad-air': {
|
||
name: 'iPad Air',
|
||
viewport: { width: 820, height: 1180 },
|
||
userAgent: 'Mozilla/5.0 (iPad; CPU OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1',
|
||
isMobile: true,
|
||
deviceScaleFactor: 2,
|
||
},
|
||
```
|
||
|
||
**Step 2: 添加 iPad Pro 12.9" 配置**
|
||
|
||
在 `devices` 对象中添加:
|
||
|
||
```typescript
|
||
'ipad-pro-12-9': {
|
||
name: 'iPad Pro 12.9"',
|
||
viewport: { width: 1024, height: 1366 },
|
||
userAgent: 'Mozilla/5.0 (iPad; CPU OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1',
|
||
isMobile: true,
|
||
deviceScaleFactor: 2,
|
||
},
|
||
```
|
||
|
||
**Step 3: 验证 iPad 设备配置**
|
||
|
||
运行测试验证新设备配置:
|
||
|
||
```bash
|
||
cd e2e
|
||
npm run test -- --grep "mobile-ux" --project=chromium
|
||
```
|
||
|
||
**Step 4: Commit**
|
||
|
||
```bash
|
||
git add e2e/src/utils/devices.ts
|
||
git commit -m "feat: add iPad device configurations (iPad Air, iPad Pro 12.9)"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 1.4: 创建网络配置文件
|
||
|
||
**Files:**
|
||
- Create: `e2e/src/config/network-configs.ts`
|
||
|
||
**Step 1: 创建网络配置类型定义**
|
||
|
||
创建文件并添加类型定义:
|
||
|
||
```typescript
|
||
export interface NetworkConfig {
|
||
name: string;
|
||
offline: boolean;
|
||
downloadThroughput?: number;
|
||
uploadThroughput?: number;
|
||
latency?: number;
|
||
}
|
||
```
|
||
|
||
**Step 2: 添加网络配置对象**
|
||
|
||
添加完整的网络配置:
|
||
|
||
```typescript
|
||
export const networkConfigs: Record<string, NetworkConfig> = {
|
||
'2g-slow': {
|
||
name: '2G Slow',
|
||
offline: false,
|
||
downloadThroughput: 250 * 1024, // 250 Kbps
|
||
uploadThroughput: 50 * 1024, // 50 Kbps
|
||
latency: 2000, // 2000ms
|
||
},
|
||
'3g-fast': {
|
||
name: '3G Fast',
|
||
offline: false,
|
||
downloadThroughput: 1.6 * 1024 * 1024, // 1.6 Mbps
|
||
uploadThroughput: 750 * 1024, // 750 Kbps
|
||
latency: 100, // 100ms
|
||
},
|
||
'4g-lte': {
|
||
name: '4G LTE',
|
||
offline: false,
|
||
downloadThroughput: 4 * 1024 * 1024, // 4 Mbps
|
||
uploadThroughput: 3 * 1024 * 1024, // 3 Mbps
|
||
latency: 20, // 20ms
|
||
},
|
||
'wifi-fast': {
|
||
name: 'WiFi Fast',
|
||
offline: false,
|
||
downloadThroughput: 30 * 1024 * 1024, // 30 Mbps
|
||
uploadThroughput: 15 * 1024 * 1024, // 15 Mbps
|
||
latency: 2, // 2ms
|
||
},
|
||
'offline': {
|
||
name: 'Offline',
|
||
offline: true,
|
||
},
|
||
};
|
||
```
|
||
|
||
**Step 3: 添加辅助函数**
|
||
|
||
```typescript
|
||
export function getNetworkConfig(key: string): NetworkConfig {
|
||
return networkConfigs[key] || networkConfigs['wifi-fast'];
|
||
}
|
||
|
||
export function getAllNetworkConfigs(): NetworkConfig[] {
|
||
return Object.values(networkConfigs);
|
||
}
|
||
```
|
||
|
||
**Step 4: 验证网络配置**
|
||
|
||
创建简单的验证测试:
|
||
|
||
```bash
|
||
cd e2e
|
||
node -e "const { networkConfigs } = require('./src/config/network-configs.ts'); console.log(Object.keys(networkConfigs));"
|
||
```
|
||
|
||
**Step 5: Commit**
|
||
|
||
```bash
|
||
git add e2e/src/config/network-configs.ts
|
||
git commit -m "feat: add network configuration for mobile testing"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 1.5: 创建移动端测试数据生成器
|
||
|
||
**Files:**
|
||
- Create: `e2e/src/utils/MobileTestDataGenerator.ts`
|
||
|
||
**Step 1: 创建基础类结构**
|
||
|
||
```typescript
|
||
import { Page } from '@playwright/test';
|
||
import { getNetworkConfig, NetworkConfig } from '../config/network-configs';
|
||
import { getDevice, DeviceConfig } from './devices';
|
||
|
||
export class MobileTestDataGenerator {
|
||
static generateUserAgent(device: string): string {
|
||
const deviceConfig = getDevice(device);
|
||
return deviceConfig.userAgent || '';
|
||
}
|
||
|
||
static generateNetworkConfig(type: '2G' | '3G' | '4G' | 'WiFi' | 'offline'): NetworkConfig {
|
||
const configMap = {
|
||
'2G': '2g-slow',
|
||
'3G': '3g-fast',
|
||
'4G': '4g-lte',
|
||
'WiFi': 'wifi-fast',
|
||
'offline': 'offline',
|
||
};
|
||
return getNetworkConfig(configMap[type]);
|
||
}
|
||
}
|
||
```
|
||
|
||
**Step 2: 添加触摸事件数据生成**
|
||
|
||
```typescript
|
||
export interface TouchEventData {
|
||
type: 'tap' | 'swipe' | 'pinch' | 'longPress';
|
||
x: number;
|
||
y: number;
|
||
duration?: number;
|
||
distance?: number;
|
||
}
|
||
|
||
static generateTouchEvent(type: 'tap' | 'swipe' | 'pinch' | 'longPress'): TouchEventData {
|
||
const baseData = {
|
||
x: Math.floor(Math.random() * 375),
|
||
y: Math.floor(Math.random() * 667),
|
||
};
|
||
|
||
switch (type) {
|
||
case 'tap':
|
||
return { ...baseData, type: 'tap' };
|
||
case 'swipe':
|
||
return { ...baseData, type: 'swipe', duration: 500 };
|
||
case 'pinch':
|
||
return { ...baseData, type: 'pinch', distance: 100 };
|
||
case 'longPress':
|
||
return { ...baseData, type: 'longPress', duration: 1000 };
|
||
default:
|
||
return { ...baseData, type: 'tap' };
|
||
}
|
||
}
|
||
```
|
||
|
||
**Step 3: 添加性能基准数据生成**
|
||
|
||
```typescript
|
||
export interface PerformanceBaseline {
|
||
device: string;
|
||
network: string;
|
||
LCP: number;
|
||
FCP: number;
|
||
CLS: number;
|
||
FID: number;
|
||
TTI: number;
|
||
}
|
||
|
||
static generatePerformanceBaseline(device: string, network: string): PerformanceBaseline {
|
||
const deviceMultiplier = {
|
||
'mobile-375x667': 1.2,
|
||
'mobile-414x896': 1.1,
|
||
'tablet-768x1024': 0.9,
|
||
'desktop-1920x1080': 0.8,
|
||
}[device] || 1;
|
||
|
||
const networkMultiplier = {
|
||
'2g-slow': 3,
|
||
'3g-fast': 2,
|
||
'4g-lte': 1.5,
|
||
'wifi-fast': 1,
|
||
}[network] || 1;
|
||
|
||
const multiplier = deviceMultiplier * networkMultiplier;
|
||
|
||
return {
|
||
device,
|
||
network,
|
||
LCP: 2500 * multiplier,
|
||
FCP: 1800 * multiplier,
|
||
CLS: 0.1,
|
||
FID: 100 * multiplier,
|
||
TTI: 3800 * multiplier,
|
||
};
|
||
}
|
||
```
|
||
|
||
**Step 4: 验证测试数据生成器**
|
||
|
||
创建简单的验证脚本:
|
||
|
||
```bash
|
||
cd e2e
|
||
node -e "const { MobileTestDataGenerator } = require('./src/utils/MobileTestDataGenerator.ts'); console.log(MobileTestDataGenerator.generateTouchEvent('tap'));"
|
||
```
|
||
|
||
**Step 5: Commit**
|
||
|
||
```bash
|
||
git add e2e/src/utils/MobileTestDataGenerator.ts
|
||
git commit -m "feat: add mobile test data generator"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 1.6: 扩展 Playwright 配置
|
||
|
||
**Files:**
|
||
- Modify: `e2e/playwright.config.ts`
|
||
|
||
**Step 1: 导入新设备配置**
|
||
|
||
在文件顶部添加导入:
|
||
|
||
```typescript
|
||
import { getMobileDevices } from './src/utils/devices';
|
||
```
|
||
|
||
**Step 2: 添加移动设备项目**
|
||
|
||
在 `projects` 数组中添加移动设备项目:
|
||
|
||
```typescript
|
||
const mobileDevices = getMobileDevices();
|
||
|
||
// 在现有项目后添加
|
||
...mobileDevices.map(device => ({
|
||
name: `mobile-${device.name.replace(/\s+/g, '-').toLowerCase()}`,
|
||
use: {
|
||
...devices['Mobile Chrome'],
|
||
viewport: device.viewport,
|
||
userAgent: device.userAgent,
|
||
deviceScaleFactor: device.deviceScaleFactor,
|
||
isMobile: true,
|
||
},
|
||
})),
|
||
```
|
||
|
||
**Step 3: 添加性能测试项目**
|
||
|
||
```typescript
|
||
{
|
||
name: 'performance-mobile',
|
||
use: {
|
||
...devices['Mobile Chrome'],
|
||
viewport: { width: 375, height: 667 },
|
||
isMobile: true,
|
||
},
|
||
testMatch: /.*\.perf\.spec\.ts/,
|
||
},
|
||
```
|
||
|
||
**Step 4: 添加 PWA 测试项目**
|
||
|
||
```typescript
|
||
{
|
||
name: 'pwa-mobile',
|
||
use: {
|
||
...devices['Mobile Chrome'],
|
||
viewport: { width: 375, height: 667 },
|
||
isMobile: true,
|
||
serviceWorkers: 'allow',
|
||
},
|
||
testMatch: /.*\.pwa\.spec\.ts/,
|
||
},
|
||
```
|
||
|
||
**Step 5: 验证配置**
|
||
|
||
运行测试验证新配置:
|
||
|
||
```bash
|
||
cd e2e
|
||
npm run test -- --list
|
||
```
|
||
|
||
**Step 6: Commit**
|
||
|
||
```bash
|
||
git add e2e/playwright.config.ts
|
||
git commit -m "feat: extend Playwright config for mobile testing"
|
||
```
|
||
|
||
---
|
||
|
||
## Phase 2: 核心测试工具开发(2-3 周)
|
||
|
||
### Task 2.1: 创建手势模拟器 - 基础结构
|
||
|
||
**Files:**
|
||
- Create: `e2e/src/utils/GestureSimulator.ts`
|
||
|
||
**Step 1: 创建基础类结构**
|
||
|
||
```typescript
|
||
import { Page, Locator } from '@playwright/test';
|
||
|
||
export interface SwipeOptions {
|
||
startX: number;
|
||
startY: number;
|
||
endX: number;
|
||
endY: number;
|
||
duration: number;
|
||
}
|
||
|
||
export interface PinchOptions {
|
||
centerX: number;
|
||
centerY: number;
|
||
startDistance: number;
|
||
endDistance: number;
|
||
duration: number;
|
||
}
|
||
|
||
export interface DragOptions {
|
||
source: Locator;
|
||
target: Locator;
|
||
duration: number;
|
||
}
|
||
|
||
export class GestureSimulator {
|
||
constructor(private page: Page) {}
|
||
}
|
||
```
|
||
|
||
**Step 2: 验证基础结构**
|
||
|
||
```bash
|
||
cd e2e
|
||
node -e "const { GestureSimulator } = require('./src/utils/GestureSimulator.ts'); console.log('GestureSimulator class created');"
|
||
```
|
||
|
||
**Step 3: Commit**
|
||
|
||
```bash
|
||
git add e2e/src/utils/GestureSimulator.ts
|
||
git commit -m "feat: create GestureSimulator base structure"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 2.2: 实现手势模拟器 - 单指滑动
|
||
|
||
**Files:**
|
||
- Modify: `e2e/src/utils/GestureSimulator.ts`
|
||
|
||
**Step 1: 实现 swipe 方法**
|
||
|
||
```typescript
|
||
async swipe(options: SwipeOptions): Promise<void> {
|
||
const { startX, startY, endX, endY, duration } = options;
|
||
|
||
await this.page.touchscreen.tap(startX, startY);
|
||
|
||
const steps = 10;
|
||
const stepDuration = duration / steps;
|
||
|
||
for (let i = 1; i <= steps; i++) {
|
||
const x = startX + (endX - startX) * (i / steps);
|
||
const y = startY + (endY - startY) * (i / steps);
|
||
|
||
await this.page.touchscreen.touchMove(x, y);
|
||
await this.page.waitForTimeout(stepDuration);
|
||
}
|
||
|
||
await this.page.touchscreen.touchEnd();
|
||
}
|
||
```
|
||
|
||
**Step 2: 添加快速滑动方法**
|
||
|
||
```typescript
|
||
async quickSwipeDown(): Promise<void> {
|
||
const viewport = this.page.viewportSize();
|
||
if (!viewport) return;
|
||
|
||
await this.swipe({
|
||
startX: viewport.width / 2,
|
||
startY: viewport.height * 0.3,
|
||
endX: viewport.width / 2,
|
||
endY: viewport.height * 0.7,
|
||
duration: 300,
|
||
});
|
||
}
|
||
|
||
async slowSwipeUp(): Promise<void> {
|
||
const viewport = this.page.viewportSize();
|
||
if (!viewport) return;
|
||
|
||
await this.swipe({
|
||
startX: viewport.width / 2,
|
||
startY: viewport.height * 0.7,
|
||
endX: viewport.width / 2,
|
||
endY: viewport.height * 0.3,
|
||
duration: 800,
|
||
});
|
||
}
|
||
```
|
||
|
||
**Step 3: 编写测试用例**
|
||
|
||
创建测试文件 `e2e/src/tests/utils/gesture-simulator.spec.ts`:
|
||
|
||
```typescript
|
||
import { test, expect } from '@playwright/test';
|
||
import { GestureSimulator } from '../../utils/GestureSimulator';
|
||
|
||
test.describe('GestureSimulator - Swipe', () => {
|
||
test('should perform swipe up', async ({ page }) => {
|
||
await page.goto('/');
|
||
const simulator = new GestureSimulator(page);
|
||
|
||
const initialScrollY = await page.evaluate(() => window.scrollY);
|
||
expect(initialScrollY).toBe(0);
|
||
|
||
await simulator.slowSwipeUp();
|
||
|
||
const afterScrollY = await page.evaluate(() => window.scrollY);
|
||
expect(afterScrollY).toBeGreaterThan(0);
|
||
});
|
||
});
|
||
```
|
||
|
||
**Step 4: 运行测试验证**
|
||
|
||
```bash
|
||
cd e2e
|
||
npm run test -- e2e/src/tests/utils/gesture-simulator.spec.ts
|
||
```
|
||
|
||
**Step 5: Commit**
|
||
|
||
```bash
|
||
git add e2e/src/utils/GestureSimulator.ts e2e/src/tests/utils/gesture-simulator.spec.ts
|
||
git commit -m "feat: implement swipe gesture in GestureSimulator"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 2.3: 实现手势模拟器 - 双指捏合
|
||
|
||
**Files:**
|
||
- Modify: `e2e/src/utils/GestureSimulator.ts`
|
||
|
||
**Step 1: 实现 pinch 方法**
|
||
|
||
```typescript
|
||
async pinch(options: PinchOptions): Promise<void> {
|
||
const { centerX, centerY, startDistance, endDistance, duration } = options;
|
||
|
||
const startDistanceX = startDistance / 2;
|
||
const startDistanceY = startDistance / 2;
|
||
const endDistanceX = endDistance / 2;
|
||
const endDistanceY = endDistance / 2;
|
||
|
||
const steps = 10;
|
||
const stepDuration = duration / steps;
|
||
|
||
// Start pinch
|
||
await this.page.touchscreen.tap(centerX - startDistanceX, centerY - startDistanceY);
|
||
await this.page.touchscreen.tap(centerX + startDistanceX, centerY + startDistanceY);
|
||
|
||
for (let i = 1; i <= steps; i++) {
|
||
const progress = i / steps;
|
||
const currentDistanceX = startDistanceX + (endDistanceX - startDistanceX) * progress;
|
||
const currentDistanceY = startDistanceY + (endDistanceY - startDistanceY) * progress;
|
||
|
||
await this.page.touchscreen.touchMove(centerX - currentDistanceX, centerY - currentDistanceY);
|
||
await this.page.touchscreen.touchMove(centerX + currentDistanceX, centerY + currentDistanceY);
|
||
await this.page.waitForTimeout(stepDuration);
|
||
}
|
||
|
||
await this.page.touchscreen.touchEnd();
|
||
await this.page.touchscreen.touchEnd();
|
||
}
|
||
```
|
||
|
||
**Step 2: 编写测试用例**
|
||
|
||
```typescript
|
||
test('should perform pinch zoom', async ({ page }) => {
|
||
await page.goto('/products');
|
||
const simulator = new GestureSimulator(page);
|
||
|
||
const image = page.locator('.product-image').first();
|
||
await image.click();
|
||
|
||
await simulator.pinch({
|
||
centerX: 200,
|
||
centerY: 300,
|
||
startDistance: 100,
|
||
endDistance: 50,
|
||
duration: 300,
|
||
});
|
||
|
||
const transform = await image.evaluate((el) => {
|
||
const style = window.getComputedStyle(el);
|
||
return style.transform;
|
||
});
|
||
|
||
expect(transform).toBeTruthy();
|
||
});
|
||
```
|
||
|
||
**Step 3: 运行测试验证**
|
||
|
||
```bash
|
||
cd e2e
|
||
npm run test -- e2e/src/tests/utils/gesture-simulator.spec.ts
|
||
```
|
||
|
||
**Step 4: Commit**
|
||
|
||
```bash
|
||
git add e2e/src/utils/GestureSimulator.ts e2e/src/tests/utils/gesture-simulator.spec.ts
|
||
git commit -m "feat: implement pinch gesture in GestureSimulator"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 2.4: 实现手势模拟器 - 长按和双击
|
||
|
||
**Files:**
|
||
- Modify: `e2e/src/utils/GestureSimulator.ts`
|
||
|
||
**Step 1: 实现长按方法**
|
||
|
||
```typescript
|
||
async longPress(element: Locator, duration: number = 1000): Promise<void> {
|
||
const box = await element.boundingBox();
|
||
if (!box) throw new Error('Element not visible');
|
||
|
||
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);
|
||
await this.page.touchscreen.touchEnd();
|
||
}
|
||
```
|
||
|
||
**Step 2: 实现双击方法**
|
||
|
||
```typescript
|
||
async doubleTap(element: Locator): Promise<void> {
|
||
const box = await element.boundingBox();
|
||
if (!box) throw new Error('Element not visible');
|
||
|
||
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(100);
|
||
await this.page.touchscreen.tap(x, y);
|
||
}
|
||
```
|
||
|
||
**Step 3: 编写测试用例**
|
||
|
||
```typescript
|
||
test('should perform long press', async ({ page }) => {
|
||
await page.goto('/');
|
||
const simulator = new GestureSimulator(page);
|
||
|
||
const card = page.locator('.card').first();
|
||
await simulator.longPress(card, 1000);
|
||
|
||
await expect(card).toBeVisible();
|
||
});
|
||
|
||
test('should perform double tap', async ({ page }) => {
|
||
await page.goto('/products');
|
||
const simulator = new GestureSimulator(page);
|
||
|
||
const image = page.locator('.product-image').first();
|
||
await simulator.doubleTap(image);
|
||
|
||
await expect(image).toBeVisible();
|
||
});
|
||
```
|
||
|
||
**Step 4: 运行测试验证**
|
||
|
||
```bash
|
||
cd e2e
|
||
npm run test -- e2e/src/tests/utils/gesture-simulator.spec.ts
|
||
```
|
||
|
||
**Step 5: Commit**
|
||
|
||
```bash
|
||
git add e2e/src/utils/GestureSimulator.ts e2e/src/tests/utils/gesture-simulator.spec.ts
|
||
git commit -m "feat: implement long press and double tap gestures"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 2.5: 实现手势模拟器 - 拖拽
|
||
|
||
**Files:**
|
||
- Modify: `e2e/src/utils/GestureSimulator.ts`
|
||
|
||
**Step 1: 实现拖拽方法**
|
||
|
||
```typescript
|
||
async drag(options: DragOptions): Promise<void> {
|
||
const { source, target, duration } = options;
|
||
|
||
const sourceBox = await source.boundingBox();
|
||
const targetBox = await target.boundingBox();
|
||
|
||
if (!sourceBox || !targetBox) {
|
||
throw new Error('Source or target element not visible');
|
||
}
|
||
|
||
const startX = sourceBox.x + sourceBox.width / 2;
|
||
const startY = sourceBox.y + sourceBox.height / 2;
|
||
const endX = targetBox.x + targetBox.width / 2;
|
||
const endY = targetBox.y + targetBox.height / 2;
|
||
|
||
await this.swipe({
|
||
startX,
|
||
startY,
|
||
endX,
|
||
endY,
|
||
duration,
|
||
});
|
||
}
|
||
```
|
||
|
||
**Step 2: 编写测试用例**
|
||
|
||
```typescript
|
||
test('should perform drag', async ({ page }) => {
|
||
await page.goto('/products');
|
||
const simulator = new GestureSimulator(page);
|
||
|
||
const firstCard = page.locator('.card').first();
|
||
const secondCard = page.locator('.card').nth(1);
|
||
|
||
const firstCardInitialPosition = await firstCard.boundingBox();
|
||
|
||
await simulator.drag({
|
||
source: firstCard,
|
||
target: secondCard,
|
||
duration: 500,
|
||
});
|
||
|
||
const firstCardFinalPosition = await firstCard.boundingBox();
|
||
|
||
if (firstCardInitialPosition && firstCardFinalPosition) {
|
||
expect(firstCardFinalPosition.y).toBeGreaterThan(firstCardInitialPosition.y);
|
||
}
|
||
});
|
||
```
|
||
|
||
**Step 3: 运行测试验证**
|
||
|
||
```bash
|
||
cd e2e
|
||
npm run test -- e2e/src/tests/utils/gesture-simulator.spec.ts
|
||
```
|
||
|
||
**Step 4: Commit**
|
||
|
||
```bash
|
||
git add e2e/src/utils/GestureSimulator.ts e2e/src/tests/utils/gesture-simulator.spec.ts
|
||
git commit -m "feat: implement drag gesture in GestureSimulator"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 2.6: 创建网络环境模拟器 - 基础结构
|
||
|
||
**Files:**
|
||
- Create: `e2e/src/utils/NetworkSimulator.ts`
|
||
|
||
**Step 1: 创建基础类结构**
|
||
|
||
```typescript
|
||
import { BrowserContext, Page } from '@playwright/test';
|
||
import { NetworkConfig } from '../config/network-configs';
|
||
|
||
export interface NetworkRequest {
|
||
url: string;
|
||
method: string;
|
||
status: number;
|
||
duration: number;
|
||
}
|
||
|
||
export class NetworkSimulator {
|
||
private requests: NetworkRequest[] = [];
|
||
|
||
constructor(private context: BrowserContext) {
|
||
this.setupRequestMonitoring();
|
||
}
|
||
|
||
private setupRequestMonitoring(): void {
|
||
this.context.on('request', (request) => {
|
||
const startTime = Date.now();
|
||
|
||
request.on('response', (response) => {
|
||
this.requests.push({
|
||
url: request.url(),
|
||
method: request.method(),
|
||
status: response.status(),
|
||
duration: Date.now() - startTime,
|
||
});
|
||
});
|
||
});
|
||
}
|
||
}
|
||
```
|
||
|
||
**Step 2: 验证基础结构**
|
||
|
||
```bash
|
||
cd e2e
|
||
node -e "const { NetworkSimulator } = require('./src/utils/NetworkSimulator.ts'); console.log('NetworkSimulator class created');"
|
||
```
|
||
|
||
**Step 3: Commit**
|
||
|
||
```bash
|
||
git add e2e/src/utils/NetworkSimulator.ts
|
||
git commit -m "feat: create NetworkSimulator base structure"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 2.7: 实现网络环境模拟器 - 网络条件设置
|
||
|
||
**Files:**
|
||
- Modify: `e2e/src/utils/NetworkSimulator.ts`
|
||
|
||
**Step 1: 实现网络条件设置方法**
|
||
|
||
```typescript
|
||
async setNetworkCondition(config: NetworkConfig): Promise<void> {
|
||
const cdpSession = await this.context.newCDPSession(this.context.pages()[0]!);
|
||
|
||
if (config.offline) {
|
||
await cdpSession.send('Network.emulateNetworkConditions', {
|
||
offline: true,
|
||
downloadThroughput: 0,
|
||
uploadThroughput: 0,
|
||
latency: 0,
|
||
});
|
||
} else {
|
||
await cdpSession.send('Network.emulateNetworkConditions', {
|
||
offline: false,
|
||
downloadThroughput: config.downloadThroughput,
|
||
uploadThroughput: config.uploadThroughput,
|
||
latency: config.latency,
|
||
});
|
||
}
|
||
}
|
||
```
|
||
|
||
**Step 2: 实现离线模式切换**
|
||
|
||
```typescript
|
||
async goOffline(): Promise<void> {
|
||
const cdpSession = await this.context.newCDPSession(this.context.pages()[0]!);
|
||
await cdpSession.send('Network.emulateNetworkConditions', {
|
||
offline: true,
|
||
downloadThroughput: 0,
|
||
uploadThroughput: 0,
|
||
latency: 0,
|
||
});
|
||
}
|
||
|
||
async goOnline(): Promise<void> {
|
||
const cdpSession = await this.context.newCDPSession(this.context.pages()[0]!);
|
||
await cdpSession.send('Network.emulateNetworkConditions', {
|
||
offline: false,
|
||
downloadThroughput: -1,
|
||
uploadThroughput: -1,
|
||
latency: 0,
|
||
});
|
||
}
|
||
```
|
||
|
||
**Step 3: 实现网络切换模拟**
|
||
|
||
```typescript
|
||
async simulateNetworkSwitch(fromConfig: NetworkConfig, toConfig: NetworkConfig): Promise<void> {
|
||
await this.setNetworkCondition(fromConfig);
|
||
await this.context.pages()[0]!.waitForTimeout(1000);
|
||
await this.setNetworkCondition(toConfig);
|
||
}
|
||
```
|
||
|
||
**Step 4: 实现重置网络条件**
|
||
|
||
```typescript
|
||
async resetNetworkCondition(): Promise<void> {
|
||
const cdpSession = await this.context.newCDPSession(this.context.pages()[0]!);
|
||
await cdpSession.send('Network.emulateNetworkConditions', {
|
||
offline: false,
|
||
downloadThroughput: -1,
|
||
uploadThroughput: -1,
|
||
latency: 0,
|
||
});
|
||
}
|
||
```
|
||
|
||
**Step 5: 编写测试用例**
|
||
|
||
创建测试文件 `e2e/src/tests/utils/network-simulator.spec.ts`:
|
||
|
||
```typescript
|
||
import { test, expect } from '@playwright/test';
|
||
import { NetworkSimulator } from '../../utils/NetworkSimulator';
|
||
import { networkConfigs } from '../../config/network-configs';
|
||
|
||
test.describe('NetworkSimulator', () => {
|
||
test('should set 3G network condition', async ({ page, context }) => {
|
||
const simulator = new NetworkSimulator(context);
|
||
await simulator.setNetworkCondition(networkConfigs['3g-fast']);
|
||
|
||
await page.goto('/');
|
||
await expect(page.locator('header')).toBeVisible();
|
||
});
|
||
|
||
test('should switch to offline mode', async ({ page, context }) => {
|
||
const simulator = new NetworkSimulator(context);
|
||
await page.goto('/');
|
||
|
||
await simulator.goOffline();
|
||
await page.reload();
|
||
|
||
await expect(page.locator('header')).toBeVisible();
|
||
});
|
||
});
|
||
```
|
||
|
||
**Step 6: 运行测试验证**
|
||
|
||
```bash
|
||
cd e2e
|
||
npm run test -- e2e/src/tests/utils/network-simulator.spec.ts
|
||
```
|
||
|
||
**Step 7: Commit**
|
||
|
||
```bash
|
||
git add e2e/src/utils/NetworkSimulator.ts e2e/src/tests/utils/network-simulator.spec.ts
|
||
git commit -m "feat: implement network condition simulation in NetworkSimulator"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 2.8: 创建移动端性能监控器 - 基础结构
|
||
|
||
**Files:**
|
||
- Create: `e2e/src/utils/MobilePerformanceMonitor.ts`
|
||
|
||
**Step 1: 创建基础类结构**
|
||
|
||
```typescript
|
||
import { Page } from '@playwright/test';
|
||
|
||
export interface CoreWebVitals {
|
||
FCP: number; // First Contentful Paint
|
||
LCP: number; // Largest Contentful Paint
|
||
CLS: number; // Cumulative Layout Shift
|
||
FID: number; // First Input Delay
|
||
TTI: number; // Time to Interactive
|
||
}
|
||
|
||
export interface LighthouseResult {
|
||
score: number;
|
||
audits: Record<string, any>;
|
||
}
|
||
|
||
export class MobilePerformanceMonitor {
|
||
constructor(private page: Page) {}
|
||
}
|
||
```
|
||
|
||
**Step 2: 验证基础结构**
|
||
|
||
```bash
|
||
cd e2e
|
||
node -e "const { MobilePerformanceMonitor } = require('./src/utils/MobilePerformanceMonitor.ts'); console.log('MobilePerformanceMonitor class created');"
|
||
```
|
||
|
||
**Step 3: Commit**
|
||
|
||
```bash
|
||
git add e2e/src/utils/MobilePerformanceMonitor.ts
|
||
git commit -m "feat: create MobilePerformanceMonitor base structure"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 2.9: 实现移动端性能监控器 - Core Web Vitals
|
||
|
||
**Files:**
|
||
- Modify: `e2e/src/utils/MobilePerformanceMonitor.ts`
|
||
|
||
**Step 1: 实现 Core Web Vitals 监控**
|
||
|
||
```typescript
|
||
async getCoreWebVitals(): Promise<CoreWebVitals> {
|
||
const vitals = await this.page.evaluate(() => {
|
||
return new Promise((resolve) => {
|
||
const metrics: any = {};
|
||
|
||
const observer = new PerformanceObserver((list) => {
|
||
for (const entry of list.getEntries()) {
|
||
if (entry.entryType === 'paint') {
|
||
if (entry.name === 'first-contentful-paint') {
|
||
metrics.FCP = entry.startTime;
|
||
}
|
||
} else if (entry.entryType === 'largest-contentful-paint') {
|
||
metrics.LCP = entry.startTime;
|
||
} else if (entry.entryType === 'layout-shift') {
|
||
if (!metrics.CLS) metrics.CLS = 0;
|
||
metrics.CLS += (entry as any).value;
|
||
}
|
||
}
|
||
});
|
||
|
||
observer.observe({ entryTypes: ['paint', 'largest-contentful-paint', 'layout-shift'] });
|
||
|
||
setTimeout(() => {
|
||
observer.disconnect();
|
||
resolve(metrics);
|
||
}, 5000);
|
||
});
|
||
});
|
||
|
||
return {
|
||
FCP: vitals.FCP || 0,
|
||
LCP: vitals.LCP || 0,
|
||
CLS: vitals.CLS || 0,
|
||
FID: 0,
|
||
TTI: 0,
|
||
};
|
||
}
|
||
```
|
||
|
||
**Step 2: 编写测试用例**
|
||
|
||
创建测试文件 `e2e/src/tests/utils/mobile-performance-monitor.spec.ts`:
|
||
|
||
```typescript
|
||
import { test, expect } from '@playwright/test';
|
||
import { MobilePerformanceMonitor } from '../../utils/MobilePerformanceMonitor';
|
||
|
||
test.describe('MobilePerformanceMonitor', () => {
|
||
test('should get Core Web Vitals', async ({ page }) => {
|
||
await page.goto('/');
|
||
const monitor = new MobilePerformanceMonitor(page);
|
||
|
||
const vitals = await monitor.getCoreWebVitals();
|
||
|
||
expect(vitals.FCP).toBeGreaterThan(0);
|
||
expect(vitals.LCP).toBeGreaterThan(0);
|
||
expect(vitals.CLS).toBeGreaterThanOrEqual(0);
|
||
});
|
||
});
|
||
```
|
||
|
||
**Step 3: 运行测试验证**
|
||
|
||
```bash
|
||
cd e2e
|
||
npm run test -- e2e/src/tests/utils/mobile-performance-monitor.spec.ts
|
||
```
|
||
|
||
**Step 4: Commit**
|
||
|
||
```bash
|
||
git add e2e/src/utils/MobilePerformanceMonitor.ts e2e/src/tests/utils/mobile-performance-monitor.spec.ts
|
||
git commit -m "feat: implement Core Web Vitals monitoring in MobilePerformanceMonitor"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 2.10: 实现移动端性能监控器 - Lighthouse 集成
|
||
|
||
**Files:**
|
||
- Modify: `e2e/src/utils/MobilePerformanceMonitor.ts`
|
||
|
||
**Step 1: 安装 Lighthouse 依赖**
|
||
|
||
```bash
|
||
cd e2e
|
||
npm install --save-dev lighthouse chrome-launcher
|
||
npm install --save-dev @types/lighthouse
|
||
```
|
||
|
||
**Step 2: 实现 Lighthouse 审计方法**
|
||
|
||
```typescript
|
||
import lighthouse from 'lighthouse';
|
||
import * as chromeLauncher from 'chrome-launcher';
|
||
|
||
async runLighthouseAudit(url: string): Promise<LighthouseResult> {
|
||
const chrome = await chromeLauncher.launch({ chromeFlags: ['--headless'] });
|
||
const options = {
|
||
logLevel: 'info' as const,
|
||
output: 'json' as const,
|
||
onlyCategories: ['performance'],
|
||
port: chrome.port,
|
||
};
|
||
|
||
const runnerResult = await lighthouse(url, options);
|
||
await chrome.kill();
|
||
|
||
if (!runnerResult) {
|
||
throw new Error('Lighthouse audit failed');
|
||
}
|
||
|
||
return {
|
||
score: runnerResult.lhr.categories.performance.score * 100,
|
||
audits: runnerResult.lhr.audits,
|
||
};
|
||
}
|
||
```
|
||
|
||
**Step 3: 编写测试用例**
|
||
|
||
```typescript
|
||
test('should run Lighthouse audit', async ({ page }) => {
|
||
await page.goto('/');
|
||
const monitor = new MobilePerformanceMonitor(page);
|
||
|
||
const result = await monitor.runLighthouseAudit(page.url());
|
||
|
||
expect(result.score).toBeGreaterThan(0);
|
||
expect(result.audits).toBeDefined();
|
||
});
|
||
```
|
||
|
||
**Step 4: 运行测试验证**
|
||
|
||
```bash
|
||
cd e2e
|
||
npm run test -- e2e/src/tests/utils/mobile-performance-monitor.spec.ts
|
||
```
|
||
|
||
**Step 5: Commit**
|
||
|
||
```bash
|
||
git add e2e/src/utils/MobilePerformanceMonitor.ts e2e/src/tests/utils/mobile-performance-monitor.spec.ts e2e/package.json e2e/package-lock.json
|
||
git commit -m "feat: integrate Lighthouse in MobilePerformanceMonitor"
|
||
```
|
||
|
||
---
|
||
|
||
## Phase 3: 测试用例开发(3-4 周)
|
||
|
||
### Task 3.1: 创建移动端性能测试套件
|
||
|
||
**Files:**
|
||
- Create: `e2e/src/tests/mobile/performance/mobile-performance.spec.ts`
|
||
|
||
**Step 1: 创建测试套件基础结构**
|
||
|
||
```typescript
|
||
import { test, expect } from '../../../fixtures/base.fixture';
|
||
import { getMobileDevices } from '../../../utils/devices';
|
||
import { networkConfigs } from '../../../config/network-configs';
|
||
import { MobilePerformanceMonitor } from '../../../utils/MobilePerformanceMonitor';
|
||
import { MobileTestDataGenerator } from '../../../utils/MobileTestDataGenerator';
|
||
|
||
test.describe('移动端性能测试 @mobile @performance', () => {
|
||
const devices = getMobileDevices();
|
||
const networkTypes = ['wifi-fast', '4g-lte', '3g-fast', '2g-slow'] as const;
|
||
});
|
||
```
|
||
|
||
**Step 2: 添加首屏加载性能测试**
|
||
|
||
```typescript
|
||
for (const device of devices.slice(0, 3)) {
|
||
for (const network of networkTypes) {
|
||
test(`${device.name} - ${networkConfigs[network].name} - 首屏加载性能`, async ({ page, context }) => {
|
||
await page.setViewportSize(device.viewport);
|
||
|
||
const networkSimulator = new (await import('../../../utils/NetworkSimulator')).NetworkSimulator(context);
|
||
await networkSimulator.setNetworkCondition(networkConfigs[network]);
|
||
|
||
await page.goto('/');
|
||
await page.waitForLoadState('networkidle');
|
||
|
||
const monitor = new MobilePerformanceMonitor(page);
|
||
const vitals = await monitor.getCoreWebVitals();
|
||
|
||
const baseline = MobileTestDataGenerator.generatePerformanceBaseline(device.name, network);
|
||
|
||
expect(vitals.LCP).toBeLessThan(baseline.LCP);
|
||
expect(vitals.FCP).toBeLessThan(baseline.FCP);
|
||
expect(vitals.CLS).toBeLessThan(baseline.CLS);
|
||
});
|
||
}
|
||
}
|
||
```
|
||
|
||
**Step 3: 添加交互响应性能测试**
|
||
|
||
```typescript
|
||
test('移动端 - 交互响应性能', async ({ page }) => {
|
||
await page.setViewportSize({ width: 375, height: 667 });
|
||
await page.goto('/');
|
||
|
||
const monitor = new MobilePerformanceMonitor(page);
|
||
|
||
const button = page.locator('button').first();
|
||
const startTime = Date.now();
|
||
await button.click();
|
||
const responseTime = Date.now() - startTime;
|
||
|
||
expect(responseTime).toBeLessThan(100);
|
||
});
|
||
```
|
||
|
||
**Step 4: 运行测试验证**
|
||
|
||
```bash
|
||
cd e2e
|
||
npm run test -- e2e/src/tests/mobile/performance/mobile-performance.spec.ts
|
||
```
|
||
|
||
**Step 5: Commit**
|
||
|
||
```bash
|
||
git add e2e/src/tests/mobile/performance/mobile-performance.spec.ts
|
||
git commit -m "feat: add mobile performance test suite"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 3.2: 创建移动端兼容性测试套件
|
||
|
||
**Files:**
|
||
- Create: `e2e/src/tests/mobile/compatibility/device-compatibility.spec.ts`
|
||
|
||
**Step 1: 创建测试套件基础结构**
|
||
|
||
```typescript
|
||
import { test, expect } from '../../../fixtures/base.fixture';
|
||
import { getMobileDevices } from '../../../utils/devices';
|
||
|
||
test.describe('移动端兼容性测试 @mobile @compatibility', () => {
|
||
const devices = getMobileDevices();
|
||
});
|
||
```
|
||
|
||
**Step 2: 添加布局适配测试**
|
||
|
||
```typescript
|
||
for (const device of devices) {
|
||
test(`${device.name} - 布局适配正确`, async ({ page }) => {
|
||
await page.setViewportSize(device.viewport);
|
||
await page.goto('/');
|
||
|
||
await expect(page.locator('header')).toBeVisible();
|
||
await expect(page.locator('main')).toBeVisible();
|
||
await expect(page.locator('footer')).toBeVisible();
|
||
|
||
const bodyWidth = await page.evaluate(() => document.body.scrollWidth);
|
||
expect(bodyWidth).toBeLessThanOrEqual(device.viewport.width);
|
||
});
|
||
}
|
||
```
|
||
|
||
**Step 3: 添加导航功能测试**
|
||
|
||
```typescript
|
||
for (const device of devices.slice(0, 3)) {
|
||
test(`${device.name} - 导航功能正常`, async ({ page }) => {
|
||
await page.setViewportSize(device.viewport);
|
||
await page.goto('/');
|
||
|
||
const menuButton = page.locator('button[aria-label="打开菜单"]');
|
||
if (await menuButton.isVisible()) {
|
||
await menuButton.click();
|
||
await expect(page.locator('#mobile-menu')).toBeVisible();
|
||
}
|
||
|
||
await page.goto('/about');
|
||
await expect(page).toHaveURL(/.*about.*/);
|
||
});
|
||
}
|
||
```
|
||
|
||
**Step 4: 运行测试验证**
|
||
|
||
```bash
|
||
cd e2e
|
||
npm run test -- e2e/src/tests/mobile/compatibility/device-compatibility.spec.ts
|
||
```
|
||
|
||
**Step 5: Commit**
|
||
|
||
```bash
|
||
git add e2e/src/tests/mobile/compatibility/device-compatibility.spec.ts
|
||
git commit -m "feat: add mobile compatibility test suite"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 3.3: 创建移动端手势交互测试套件
|
||
|
||
**Files:**
|
||
- Create: `e2e/src/tests/mobile/gesture/gesture-interaction.spec.ts`
|
||
|
||
**Step 1: 创建测试套件基础结构**
|
||
|
||
```typescript
|
||
import { test, expect } from '../../../fixtures/base.fixture';
|
||
import { GestureSimulator } from '../../../utils/GestureSimulator';
|
||
|
||
test.describe('移动端手势交互测试 @mobile @gesture', () => {
|
||
let gestureSimulator: GestureSimulator;
|
||
|
||
test.beforeEach(async ({ page }) => {
|
||
await page.setViewportSize({ width: 375, height: 667 });
|
||
gestureSimulator = new GestureSimulator(page);
|
||
});
|
||
});
|
||
```
|
||
|
||
**Step 2: 添加单指滑动测试**
|
||
|
||
```typescript
|
||
test('单指滑动 - 页面滚动', async ({ page }) => {
|
||
await page.goto('/');
|
||
|
||
const initialScrollY = await page.evaluate(() => window.scrollY);
|
||
expect(initialScrollY).toBe(0);
|
||
|
||
await gestureSimulator.slowSwipeUp();
|
||
|
||
const afterScrollY = await page.evaluate(() => window.scrollY);
|
||
expect(afterScrollY).toBeGreaterThan(0);
|
||
});
|
||
```
|
||
|
||
**Step 3: 添加长按测试**
|
||
|
||
```typescript
|
||
test('长按 - 元素长按', async ({ page }) => {
|
||
await page.goto('/');
|
||
|
||
const card = page.locator('.card').first();
|
||
await gestureSimulator.longPress(card, 1000);
|
||
|
||
await expect(card).toBeVisible();
|
||
});
|
||
```
|
||
|
||
**Step 4: 添加双击测试**
|
||
|
||
```typescript
|
||
test('双击 - 元素双击', async ({ page }) => {
|
||
await page.goto('/products');
|
||
|
||
const image = page.locator('.product-image').first();
|
||
await gestureSimulator.doubleTap(image);
|
||
|
||
await expect(image).toBeVisible();
|
||
});
|
||
```
|
||
|
||
**Step 5: 运行测试验证**
|
||
|
||
```bash
|
||
cd e2e
|
||
npm run test -- e2e/src/tests/mobile/gesture/gesture-interaction.spec.ts
|
||
```
|
||
|
||
**Step 6: Commit**
|
||
|
||
```bash
|
||
git add e2e/src/tests/mobile/gesture/gesture-interaction.spec.ts
|
||
git commit -m "feat: add mobile gesture interaction test suite"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 3.4: 创建移动端 PWA 功能测试套件
|
||
|
||
**Files:**
|
||
- Create: `e2e/src/tests/mobile/pwa/pwa-functionality.spec.ts`
|
||
|
||
**Step 1: 创建测试套件基础结构**
|
||
|
||
```typescript
|
||
import { test, expect } from '../../../fixtures/base.fixture';
|
||
|
||
test.describe('移动端 PWA 功能测试 @mobile @pwa', () => {
|
||
test.use({ serviceWorkers: 'allow' });
|
||
});
|
||
```
|
||
|
||
**Step 2: 添加 Service Worker 注册测试**
|
||
|
||
```typescript
|
||
test('Service Worker 注册成功', async ({ page }) => {
|
||
await page.goto('/');
|
||
|
||
const swRegistration = await page.evaluate(() => {
|
||
return navigator.serviceWorker.getRegistration();
|
||
});
|
||
|
||
expect(swRegistration).toBeTruthy();
|
||
});
|
||
```
|
||
|
||
**Step 3: 添加离线缓存功能测试**
|
||
|
||
```typescript
|
||
test('离线缓存功能正常', async ({ page, context }) => {
|
||
await page.goto('/');
|
||
await page.waitForLoadState('networkidle');
|
||
|
||
await context.setOffline(true);
|
||
|
||
await page.reload();
|
||
await expect(page.locator('header')).toBeVisible();
|
||
await expect(page.locator('main')).toBeVisible();
|
||
|
||
await context.setOffline(false);
|
||
});
|
||
```
|
||
|
||
**Step 4: 运行测试验证**
|
||
|
||
```bash
|
||
cd e2e
|
||
npm run test -- e2e/src/tests/mobile/pwa/pwa-functionality.spec.ts
|
||
```
|
||
|
||
**Step 5: Commit**
|
||
|
||
```bash
|
||
git add e2e/src/tests/mobile/pwa/pwa-functionality.spec.ts
|
||
git commit -m "feat: add mobile PWA functionality test suite"
|
||
```
|
||
|
||
---
|
||
|
||
## Phase 4: 高级功能和优化(2-3 周)
|
||
|
||
### Task 4.1: 创建移动端测试报告系统
|
||
|
||
**Files:**
|
||
- Create: `e2e/src/utils/MobileTestReporter.ts`
|
||
|
||
**Step 1: 创建基础类结构**
|
||
|
||
```typescript
|
||
import { FullConfig, FullResult, Suite, TestCase, TestResult } from '@playwright/test';
|
||
|
||
export interface TestOverview {
|
||
total: number;
|
||
passed: number;
|
||
failed: number;
|
||
skipped: number;
|
||
duration: number;
|
||
}
|
||
|
||
export interface DeviceTestResult {
|
||
device: string;
|
||
passed: number;
|
||
failed: number;
|
||
duration: number;
|
||
}
|
||
|
||
export class MobileTestReporter {
|
||
constructor(private config: FullConfig) {}
|
||
|
||
generateOverview(results: FullResult): TestOverview {
|
||
const total = results.suites.reduce((sum, suite) => {
|
||
return sum + suite.suites.reduce((suiteSum, subSuite) => {
|
||
return suiteSum + subSuite.cases.length;
|
||
}, 0);
|
||
}, 0);
|
||
|
||
const passed = results.suites.reduce((sum, suite) => {
|
||
return sum + suite.suites.reduce((suiteSum, subSuite) => {
|
||
return suiteSum + subSuite.cases.filter(c => c.results[0]?.status === 'passed').length;
|
||
}, 0);
|
||
}, 0);
|
||
|
||
const failed = results.suites.reduce((sum, suite) => {
|
||
return sum + suite.suites.reduce((suiteSum, subSuite) => {
|
||
return suiteSum + subSuite.cases.filter(c => c.results[0]?.status === 'failed').length;
|
||
}, 0);
|
||
}, 0);
|
||
|
||
return {
|
||
total,
|
||
passed,
|
||
failed,
|
||
skipped: total - passed - failed,
|
||
duration: results.duration,
|
||
};
|
||
}
|
||
}
|
||
```
|
||
|
||
**Step 2: 验证基础结构**
|
||
|
||
```bash
|
||
cd e2e
|
||
node -e "const { MobileTestReporter } = require('./src/utils/MobileTestReporter.ts'); console.log('MobileTestReporter class created');"
|
||
```
|
||
|
||
**Step 3: Commit**
|
||
|
||
```bash
|
||
git add e2e/src/utils/MobileTestReporter.ts
|
||
git commit -m "feat: create MobileTestReporter base structure"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 4.2: 实现移动端测试报告 - HTML 报告
|
||
|
||
**Files:**
|
||
- Modify: `e2e/src/utils/MobileTestReporter.ts`
|
||
|
||
**Step 1: 实现 HTML 报告生成**
|
||
|
||
```typescript
|
||
generateHtmlReport(results: FullResult): string {
|
||
const overview = this.generateOverview(results);
|
||
|
||
return `
|
||
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>移动端测试报告</title>
|
||
<style>
|
||
body { font-family: Arial, sans-serif; margin: 20px; }
|
||
.overview { background: #f5f5f5; padding: 20px; border-radius: 8px; margin-bottom: 20px; }
|
||
.stat { display: inline-block; margin: 0 20px 10px 0; }
|
||
.stat-value { font-size: 24px; font-weight: bold; }
|
||
.stat-label { color: #666; }
|
||
.passed { color: #4caf50; }
|
||
.failed { color: #f44336; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<h1>移动端测试报告</h1>
|
||
<div class="overview">
|
||
<div class="stat">
|
||
<div class="stat-value">${overview.total}</div>
|
||
<div class="stat-label">总测试数</div>
|
||
</div>
|
||
<div class="stat">
|
||
<div class="stat-value passed">${overview.passed}</div>
|
||
<div class="stat-label">通过</div>
|
||
</div>
|
||
<div class="stat">
|
||
<div class="stat-value failed">${overview.failed}</div>
|
||
<div class="stat-label">失败</div>
|
||
</div>
|
||
<div class="stat">
|
||
<div class="stat-value">${(overview.duration / 1000).toFixed(2)}s</div>
|
||
<div class="stat-label">执行时间</div>
|
||
</div>
|
||
</div>
|
||
</body>
|
||
</html>
|
||
`;
|
||
}
|
||
```
|
||
|
||
**Step 2: 添加报告保存方法**
|
||
|
||
```typescript
|
||
async saveReport(report: string, outputPath: string): Promise<void> {
|
||
const fs = await import('fs/promises');
|
||
await fs.writeFile(outputPath, report, 'utf-8');
|
||
}
|
||
```
|
||
|
||
**Step 3: 编写测试用例**
|
||
|
||
```typescript
|
||
import { test, expect } from '@playwright/test';
|
||
import { MobileTestReporter } from '../../utils/MobileTestReporter';
|
||
|
||
test('MobileTestReporter should generate HTML report', async () => {
|
||
const reporter = new MobileTestReporter({} as any);
|
||
const html = reporter.generateHtmlReport({
|
||
suites: [],
|
||
duration: 10000,
|
||
status: 'passed',
|
||
} as any);
|
||
|
||
expect(html).toContain('移动端测试报告');
|
||
expect(html).toContain('总测试数');
|
||
});
|
||
```
|
||
|
||
**Step 4: 运行测试验证**
|
||
|
||
```bash
|
||
cd e2e
|
||
npm run test -- e2e/src/tests/utils/mobile-test-reporter.spec.ts
|
||
```
|
||
|
||
**Step 5: Commit**
|
||
|
||
```bash
|
||
git add e2e/src/utils/MobileTestReporter.ts e2e/src/tests/utils/mobile-test-reporter.spec.ts
|
||
git commit -m "feat: implement HTML report generation in MobileTestReporter"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 4.3: 优化测试执行效率 - 并行测试
|
||
|
||
**Files:**
|
||
- Modify: `e2e/playwright.config.ts`
|
||
|
||
**Step 1: 优化 workers 配置**
|
||
|
||
```typescript
|
||
workers: process.env.CI ? 4 : '50%',
|
||
```
|
||
|
||
**Step 2: 添加测试超时配置**
|
||
|
||
```typescript
|
||
timeout: 60000,
|
||
expect: {
|
||
timeout: 30000,
|
||
}
|
||
```
|
||
|
||
**Step 3: 验证配置**
|
||
|
||
```bash
|
||
cd e2e
|
||
npm run test -- --list
|
||
```
|
||
|
||
**Step 4: Commit**
|
||
|
||
```bash
|
||
git add e2e/playwright.config.ts
|
||
git commit -m "feat: optimize parallel test execution"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 4.4: 创建测试文档
|
||
|
||
**Files:**
|
||
- Create: `e2e/docs/mobile-testing-guide.md`
|
||
|
||
**Step 1: 创建测试指南文档**
|
||
|
||
```markdown
|
||
# 移动端测试指南
|
||
|
||
## 概述
|
||
|
||
本文档介绍如何使用移动端测试框架进行测试。
|
||
|
||
## 手势模拟器
|
||
|
||
### 基本用法
|
||
|
||
\`\`\`typescript
|
||
import { GestureSimulator } from '../utils/GestureSimulator';
|
||
|
||
const simulator = new GestureSimulator(page);
|
||
|
||
// 单指滑动
|
||
await simulator.swipe({
|
||
startX: 200,
|
||
startY: 600,
|
||
endX: 200,
|
||
endY: 200,
|
||
duration: 500,
|
||
});
|
||
|
||
// 长按
|
||
await simulator.longPress(element, 1000);
|
||
|
||
// 双击
|
||
await simulator.doubleTap(element);
|
||
\`\`\`
|
||
|
||
## 网络模拟器
|
||
|
||
### 基本用法
|
||
|
||
\`\`\`typescript
|
||
import { NetworkSimulator } from '../utils/NetworkSimulator';
|
||
import { networkConfigs } from '../config/network-configs';
|
||
|
||
const simulator = new NetworkSimulator(context);
|
||
|
||
// 设置网络条件
|
||
await simulator.setNetworkCondition(networkConfigs['3g-fast']);
|
||
|
||
// 切换到离线模式
|
||
await simulator.goOffline();
|
||
|
||
// 恢复在线
|
||
await simulator.goOnline();
|
||
\`\`\`
|
||
|
||
## 性能监控器
|
||
|
||
### 基本用法
|
||
|
||
\`\`\`typescript
|
||
import { MobilePerformanceMonitor } from '../utils/MobilePerformanceMonitor';
|
||
|
||
const monitor = new MobilePerformanceMonitor(page);
|
||
|
||
// 获取 Core Web Vitals
|
||
const vitals = await monitor.getCoreWebVitals();
|
||
|
||
// 运行 Lighthouse 审计
|
||
const result = await monitor.runLighthouseAudit(page.url());
|
||
\`\`\`
|
||
|
||
## 运行测试
|
||
|
||
### 运行所有移动端测试
|
||
|
||
\`\`\`bash
|
||
npm run test -- --grep "@mobile"
|
||
\`\`\`
|
||
|
||
### 运行特定设备测试
|
||
|
||
\`\`\`bash
|
||
npm run test -- --project=mobile-iphone-13-pro
|
||
\`\`\`
|
||
|
||
### 运行性能测试
|
||
|
||
\`\`\`bash
|
||
npm run test -- --grep "@performance"
|
||
\`\`\`
|
||
|
||
## 查看报告
|
||
|
||
测试完成后,可以在以下位置查看报告:
|
||
|
||
- HTML 报告: `e2e/test-results/index.html`
|
||
- Allure 报告: `e2e/allure-results/`
|
||
\`\`\`
|
||
```
|
||
|
||
**Step 2: 验证文档**
|
||
|
||
```bash
|
||
cd e2e
|
||
cat docs/mobile-testing-guide.md
|
||
```
|
||
|
||
**Step 3: Commit**
|
||
|
||
```bash
|
||
git add e2e/docs/mobile-testing-guide.md
|
||
git commit -m "docs: add mobile testing guide"
|
||
```
|
||
|
||
---
|
||
|
||
## 验收标准
|
||
|
||
### Phase 1 验收
|
||
|
||
- [ ] 所有新设备配置正确
|
||
- [ ] 测试数据生成器功能完整
|
||
- [ ] Playwright 配置支持多设备并行测试
|
||
|
||
### Phase 2 验收
|
||
|
||
- [ ] 手势模拟器支持所有基本手势
|
||
- [ ] 网络模拟器支持所有网络场景
|
||
- [ ] 性能监控器能准确测量 Core Web Vitals
|
||
|
||
### Phase 3 验收
|
||
|
||
- [ ] 所有测试用例通过
|
||
- [ ] 测试覆盖率达到预期目标
|
||
- [ ] 测试执行时间在可接受范围内
|
||
|
||
### Phase 4 验收
|
||
|
||
- [ ] 测试报告系统生成完整的移动端报告
|
||
- [ ] 测试执行效率提升 30% 以上
|
||
- [ ] 文档完整且易于理解
|
||
|
||
---
|
||
|
||
## 总结
|
||
|
||
本实施计划详细描述了移动端测试完善的四个阶段,每个阶段都包含具体的任务、步骤和验收标准。通过按照此计划执行,我们将建立一个全面完善的移动端测试体系。
|
||
|
||
**预计总时间**: 8-12 周
|
||
**预计总任务数**: 40+ 个任务
|
||
**预计提交次数**: 40+ 次
|