From acfb1ccadccddac979042b2f2c4f0b15cce481fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=BF=94?= Date: Thu, 5 Mar 2026 14:16:51 +0800 Subject: [PATCH] docs: add mobile testing implementation plan --- ...3-05-mobile-testing-implementation-plan.md | 1901 +++++++++++++++++ 1 file changed, 1901 insertions(+) create mode 100644 docs/plans/2026-03-05-mobile-testing-implementation-plan.md diff --git a/docs/plans/2026-03-05-mobile-testing-implementation-plan.md b/docs/plans/2026-03-05-mobile-testing-implementation-plan.md new file mode 100644 index 0000000..6dbcbbe --- /dev/null +++ b/docs/plans/2026-03-05-mobile-testing-implementation-plan.md @@ -0,0 +1,1901 @@ +# 移动端测试完善实施计划 + +> **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 = { + '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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + await this.setNetworkCondition(fromConfig); + await this.context.pages()[0]!.waitForTimeout(1000); + await this.setNetworkCondition(toConfig); +} +``` + +**Step 4: 实现重置网络条件** + +```typescript +async resetNetworkCondition(): Promise { + 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; +} + +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 { + 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 { + 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 ` + + + + + + 移动端测试报告 + + + +

移动端测试报告

+
+
+
${overview.total}
+
总测试数
+
+
+
${overview.passed}
+
通过
+
+
+
${overview.failed}
+
失败
+
+
+
${(overview.duration / 1000).toFixed(2)}s
+
执行时间
+
+
+ + + `; +} +``` + +**Step 2: 添加报告保存方法** + +```typescript +async saveReport(report: string, outputPath: string): Promise { + 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+ 次