Files
novalon-website/docs/plans/2026-03-05-mobile-testing-implementation-plan.md
T
2026-03-05 14:16:51 +08:00

1902 lines
44 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 移动端测试完善实施计划
> **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+ 次