44 KiB
移动端测试完善实施计划
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 对象中添加:
'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 对象中添加:
'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 对象中添加:
'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: 验证设备配置
运行测试验证新设备配置:
cd e2e
npm run test -- --grep "mobile-ux" --project=chromium
Step 5: Commit
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 对象中添加:
'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 对象中添加:
'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 设备配置
运行测试验证新设备配置:
cd e2e
npm run test -- --grep "mobile-ux" --project=chromium
Step 4: Commit
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 对象中添加:
'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 对象中添加:
'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 设备配置
运行测试验证新设备配置:
cd e2e
npm run test -- --grep "mobile-ux" --project=chromium
Step 4: Commit
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: 创建网络配置类型定义
创建文件并添加类型定义:
export interface NetworkConfig {
name: string;
offline: boolean;
downloadThroughput?: number;
uploadThroughput?: number;
latency?: number;
}
Step 2: 添加网络配置对象
添加完整的网络配置:
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: 添加辅助函数
export function getNetworkConfig(key: string): NetworkConfig {
return networkConfigs[key] || networkConfigs['wifi-fast'];
}
export function getAllNetworkConfigs(): NetworkConfig[] {
return Object.values(networkConfigs);
}
Step 4: 验证网络配置
创建简单的验证测试:
cd e2e
node -e "const { networkConfigs } = require('./src/config/network-configs.ts'); console.log(Object.keys(networkConfigs));"
Step 5: Commit
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: 创建基础类结构
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: 添加触摸事件数据生成
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: 添加性能基准数据生成
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: 验证测试数据生成器
创建简单的验证脚本:
cd e2e
node -e "const { MobileTestDataGenerator } = require('./src/utils/MobileTestDataGenerator.ts'); console.log(MobileTestDataGenerator.generateTouchEvent('tap'));"
Step 5: Commit
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: 导入新设备配置
在文件顶部添加导入:
import { getMobileDevices } from './src/utils/devices';
Step 2: 添加移动设备项目
在 projects 数组中添加移动设备项目:
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: 添加性能测试项目
{
name: 'performance-mobile',
use: {
...devices['Mobile Chrome'],
viewport: { width: 375, height: 667 },
isMobile: true,
},
testMatch: /.*\.perf\.spec\.ts/,
},
Step 4: 添加 PWA 测试项目
{
name: 'pwa-mobile',
use: {
...devices['Mobile Chrome'],
viewport: { width: 375, height: 667 },
isMobile: true,
serviceWorkers: 'allow',
},
testMatch: /.*\.pwa\.spec\.ts/,
},
Step 5: 验证配置
运行测试验证新配置:
cd e2e
npm run test -- --list
Step 6: Commit
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: 创建基础类结构
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: 验证基础结构
cd e2e
node -e "const { GestureSimulator } = require('./src/utils/GestureSimulator.ts'); console.log('GestureSimulator class created');"
Step 3: Commit
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 方法
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: 添加快速滑动方法
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:
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: 运行测试验证
cd e2e
npm run test -- e2e/src/tests/utils/gesture-simulator.spec.ts
Step 5: Commit
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 方法
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: 编写测试用例
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: 运行测试验证
cd e2e
npm run test -- e2e/src/tests/utils/gesture-simulator.spec.ts
Step 4: Commit
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: 实现长按方法
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: 实现双击方法
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: 编写测试用例
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: 运行测试验证
cd e2e
npm run test -- e2e/src/tests/utils/gesture-simulator.spec.ts
Step 5: Commit
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: 实现拖拽方法
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: 编写测试用例
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: 运行测试验证
cd e2e
npm run test -- e2e/src/tests/utils/gesture-simulator.spec.ts
Step 4: Commit
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: 创建基础类结构
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: 验证基础结构
cd e2e
node -e "const { NetworkSimulator } = require('./src/utils/NetworkSimulator.ts'); console.log('NetworkSimulator class created');"
Step 3: Commit
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: 实现网络条件设置方法
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: 实现离线模式切换
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: 实现网络切换模拟
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: 实现重置网络条件
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:
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: 运行测试验证
cd e2e
npm run test -- e2e/src/tests/utils/network-simulator.spec.ts
Step 7: Commit
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: 创建基础类结构
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: 验证基础结构
cd e2e
node -e "const { MobilePerformanceMonitor } = require('./src/utils/MobilePerformanceMonitor.ts'); console.log('MobilePerformanceMonitor class created');"
Step 3: Commit
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 监控
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:
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: 运行测试验证
cd e2e
npm run test -- e2e/src/tests/utils/mobile-performance-monitor.spec.ts
Step 4: Commit
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 依赖
cd e2e
npm install --save-dev lighthouse chrome-launcher
npm install --save-dev @types/lighthouse
Step 2: 实现 Lighthouse 审计方法
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: 编写测试用例
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: 运行测试验证
cd e2e
npm run test -- e2e/src/tests/utils/mobile-performance-monitor.spec.ts
Step 5: Commit
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: 创建测试套件基础结构
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: 添加首屏加载性能测试
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: 添加交互响应性能测试
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: 运行测试验证
cd e2e
npm run test -- e2e/src/tests/mobile/performance/mobile-performance.spec.ts
Step 5: Commit
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: 创建测试套件基础结构
import { test, expect } from '../../../fixtures/base.fixture';
import { getMobileDevices } from '../../../utils/devices';
test.describe('移动端兼容性测试 @mobile @compatibility', () => {
const devices = getMobileDevices();
});
Step 2: 添加布局适配测试
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: 添加导航功能测试
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: 运行测试验证
cd e2e
npm run test -- e2e/src/tests/mobile/compatibility/device-compatibility.spec.ts
Step 5: Commit
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: 创建测试套件基础结构
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: 添加单指滑动测试
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: 添加长按测试
test('长按 - 元素长按', async ({ page }) => {
await page.goto('/');
const card = page.locator('.card').first();
await gestureSimulator.longPress(card, 1000);
await expect(card).toBeVisible();
});
Step 4: 添加双击测试
test('双击 - 元素双击', async ({ page }) => {
await page.goto('/products');
const image = page.locator('.product-image').first();
await gestureSimulator.doubleTap(image);
await expect(image).toBeVisible();
});
Step 5: 运行测试验证
cd e2e
npm run test -- e2e/src/tests/mobile/gesture/gesture-interaction.spec.ts
Step 6: Commit
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: 创建测试套件基础结构
import { test, expect } from '../../../fixtures/base.fixture';
test.describe('移动端 PWA 功能测试 @mobile @pwa', () => {
test.use({ serviceWorkers: 'allow' });
});
Step 2: 添加 Service Worker 注册测试
test('Service Worker 注册成功', async ({ page }) => {
await page.goto('/');
const swRegistration = await page.evaluate(() => {
return navigator.serviceWorker.getRegistration();
});
expect(swRegistration).toBeTruthy();
});
Step 3: 添加离线缓存功能测试
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: 运行测试验证
cd e2e
npm run test -- e2e/src/tests/mobile/pwa/pwa-functionality.spec.ts
Step 5: Commit
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: 创建基础类结构
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: 验证基础结构
cd e2e
node -e "const { MobileTestReporter } = require('./src/utils/MobileTestReporter.ts'); console.log('MobileTestReporter class created');"
Step 3: Commit
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 报告生成
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: 添加报告保存方法
async saveReport(report: string, outputPath: string): Promise<void> {
const fs = await import('fs/promises');
await fs.writeFile(outputPath, report, 'utf-8');
}
Step 3: 编写测试用例
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: 运行测试验证
cd e2e
npm run test -- e2e/src/tests/utils/mobile-test-reporter.spec.ts
Step 5: Commit
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 配置
workers: process.env.CI ? 4 : '50%',
Step 2: 添加测试超时配置
timeout: 60000,
expect: {
timeout: 30000,
}
Step 3: 验证配置
cd e2e
npm run test -- --list
Step 4: Commit
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: 创建测试指南文档
# 移动端测试指南
## 概述
本文档介绍如何使用移动端测试框架进行测试。
## 手势模拟器
### 基本用法
\`\`\`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: 验证文档
cd e2e
cat docs/mobile-testing-guide.md
Step 3: Commit
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+ 次