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

44 KiB
Raw Blame History

移动端测试完善实施计划

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+ 次