Files
novalon-website/docs/plans/2026-03-06-test-framework-refactoring-implementation.md
T

57 KiB

测试框架重构实施计划

For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

目标: 创建统一的测试框架,整合E2E测试和开发环境测试,提高代码复用性和可维护性

架构: 采用渐进式迁移策略,首先创建共享的基础层(页面对象、配置、工具类),然后迁移开发环境测试到TypeScript/Playwright,最后优化和清理

Tech Stack: Playwright, TypeScript, Node.js, Git Worktrees


阶段1:创建共享层

Task 1: 创建共享层目录结构

Files:

  • Create: test-framework/shared/config/
  • Create: test-framework/shared/pages/
  • Create: test-framework/shared/utils/
  • Create: test-framework/shared/fixtures/
  • Create: test-framework/shared/types/

Step 1: 创建共享层基础目录

mkdir -p test-framework/shared/{config,pages,utils,fixtures,types}
mkdir -p test-framework/shared/utils/{performance,seo,accessibility,forms,mobile,reporting,common}
mkdir -p test-framework/e2e
mkdir -p test-framework/dev-audit/{performance,seo,accessibility,forms}
mkdir -p test-framework/reports/{html,json,screenshots}

Step 2: 验证目录结构

Run: tree test-framework/ -L 3

Expected: 所有目录创建成功

Step 3: 提交

git add test-framework/
git commit -m "feat: create shared layer directory structure"

Task 2: 创建类型定义

Files:

  • Create: test-framework/shared/types/page.types.ts
  • Create: test-framework/shared/types/test.types.ts
  • Create: test-framework/shared/types/performance.types.ts
  • Create: test-framework/shared/types/accessibility.types.ts
  • Create: test-framework/shared/types/seo.types.ts
  • Create: test-framework/shared/types/index.ts

Step 1: 创建页面对象类型

// test-framework/shared/types/page.types.ts
import { Page, Locator } from '@playwright/test';

export interface PageConfig {
  name: string;
  url: string;
  selectors: {
    title: string;
    [key: string]: string;
  };
}

export interface PageSelectors {
  [key: string]: string;
}

export interface NavigationItem {
  name: string;
  url: string;
  selector: string;
}

Step 2: 创建测试类型

// test-framework/shared/types/test.types.ts
export interface TestConfig {
  baseURL: string;
  timeout: number;
  retries: number;
  environment: string;
  headless: boolean;
  slowMo?: number;
}

export interface TestResult {
  name: string;
  status: 'passed' | 'failed' | 'skipped';
  duration: number;
  errors?: Error[];
}

export interface TestSuite {
  name: string;
  tests: TestResult[];
  summary: {
    total: number;
    passed: number;
    failed: number;
    skipped: number;
    duration: number;
  };
}

Step 3: 创建性能测试类型

// test-framework/shared/types/performance.types.ts
export interface PerformanceMetrics {
  loadTime: number;
  domContentLoaded: number;
  firstPaint: number;
  firstContentfulPaint: number;
}

export interface CoreWebVitals {
  largestContentfulPaint: number;
  firstInputDelay: number;
  cumulativeLayoutShift: number;
}

export interface ResourceTiming {
  name: string;
  duration: number;
  size: number;
  type: string;
}

export interface NetworkTiming {
  dns: number;
  tcp: number;
  ssl: number;
  request: number;
  response: number;
  total: number;
}

export interface LighthouseResult {
  performance: number;
  accessibility: number;
  bestPractices: number;
  seo: number;
  pwa: number;
}

Step 4: 创建可访问性测试类型

// test-framework/shared/types/accessibility.types.ts
export interface AccessibilityResult {
  score: number;
  violations: Violation[];
  passes: number;
  incomplete: number;
  page: string;
  url: string;
}

export interface Violation {
  id: string;
  impact: string;
  description: string;
  help: string;
  helpUrl: string;
  nodes: number;
}

export interface WCAGCompliance {
  level: 'A' | 'AA' | 'AAA';
  passed: number;
  failed: number;
  total: number;
}

Step 5: 创建SEO测试类型

// test-framework/shared/types/seo.types.ts
export interface SEOResult {
  score: number;
  metaTags: MetaTagResult;
  headings: HeadingResult;
  links: LinkResult;
  images: ImageResult;
}

export interface MetaTagResult {
  title: boolean;
  description: boolean;
  keywords: boolean;
  ogTitle: boolean;
  ogDescription: boolean;
  canonical: boolean;
}

export interface HeadingResult {
  hasH1: boolean;
  headingStructure: boolean;
  multipleH1: boolean;
}

export interface LinkResult {
  total: number;
  broken: number;
  internal: number;
  external: number;
}

export interface ImageResult {
  total: number;
  withAlt: number;
  withoutAlt: number;
}

Step 6: 创建类型导出文件

// test-framework/shared/types/index.ts
export * from './page.types';
export * from './test.types';
export * from './performance.types';
export * from './accessibility.types';
export * from './seo.types';

Step 7: 提交

git add test-framework/shared/types/
git commit -m "feat: add type definitions for test framework"

Task 3: 创建配置管理

Files:

  • Create: test-framework/shared/config/base.config.ts
  • Create: test-framework/shared/config/environments.ts
  • Create: test-framework/shared/config/test-pages.ts
  • Create: test-framework/shared/config/test-data.ts

Step 1: 创建基础配置

// test-framework/shared/config/base.config.ts
import { TestConfig } from '../types';

export const defaultConfig: TestConfig = {
  baseURL: 'http://localhost:3000',
  timeout: 5000,
  retries: 3,
  environment: 'development',
  headless: true,
  slowMo: undefined
};

export const testTimeouts = {
  short: 2000,
  medium: 5000,
  long: 10000,
  veryLong: 30000
};

export const testThresholds = {
  performance: {
    good: 90,
    needsImprovement: 50,
    poor: 0
  },
  accessibility: {
    good: 95,
    needsImprovement: 80,
    poor: 0
  },
  seo: {
    good: 90,
    needsImprovement: 70,
    poor: 0
  }
};

Step 2: 创建环境配置

// test-framework/shared/config/environments.ts
import { TestConfig } from '../types';

export const environments: Record<string, TestConfig> = {
  development: {
    baseURL: 'http://localhost:3000',
    timeout: 5000,
    retries: 3,
    environment: 'development',
    headless: false
  },
  staging: {
    baseURL: 'https://staging.novalon.com',
    timeout: 10000,
    retries: 2,
    environment: 'staging',
    headless: true
  },
  production: {
    baseURL: 'https://www.novalon.com',
    timeout: 10000,
    retries: 1,
    environment: 'production',
    headless: true
  }
};

export function getEnvironmentConfig(env: string = 'development'): TestConfig {
  return environments[env] || environments.development;
}

Step 3: 创建测试页面配置

// test-framework/shared/config/test-pages.ts
import { PageConfig } from '../types';

export const testPages: Record<string, PageConfig> = {
  home: {
    name: '首页',
    url: '/',
    selectors: {
      title: 'h1',
      hero: '.hero-section',
      features: '.features-section'
    }
  },
  about: {
    name: '关于我们',
    url: '/about',
    selectors: {
      title: 'h1',
      content: '.about-content'
    }
  },
  contact: {
    name: '联系我们',
    url: '/contact',
    selectors: {
      title: 'h1',
      form: '#contact-form',
      submitButton: 'button[type="submit"]'
    }
  },
  products: {
    name: '产品',
    url: '/products',
    selectors: {
      title: 'h1',
      productGrid: '.products-grid',
      productCard: '.product-card'
    }
  },
  services: {
    name: '服务',
    url: '/services',
    selectors: {
      title: 'h1',
      servicesList: '.services-list',
      serviceItem: '.service-item'
    }
  },
  cases: {
    name: '案例',
    url: '/cases',
    selectors: {
      title: 'h1',
      casesGrid: '.cases-grid',
      caseCard: '.case-card'
    }
  },
  news: {
    name: '新闻',
    url: '/news',
    selectors: {
      title: 'h1',
      newsList: '.news-list',
      newsItem: '.news-item'
    }
  }
};

export function getPageConfig(pageKey: string): PageConfig {
  return testPages[pageKey] || testPages.home;
}

export function getAllPageConfigs(): PageConfig[] {
  return Object.values(testPages);
}

Step 4: 创建测试数据配置

// test-framework/shared/config/test-data.ts
export const formData = {
  valid: {
    name: '测试用户',
    email: 'test@example.com',
    phone: '13800138000',
    message: '这是一条测试消息'
  },
  invalid: {
    email: 'invalid-email',
    phone: '123',
    empty: ''
  }
};

export const performanceThresholds = {
  loadTime: 3000,
  domContentLoaded: 2000,
  firstContentfulPaint: 1500,
  largestContentfulPaint: 2500,
  cumulativeLayoutShift: 0.1,
  firstInputDelay: 100
};

export const accessibilityThresholds = {
  score: 80,
  maxViolations: 5
};

export const seoThresholds = {
  score: 80,
  minTitleLength: 10,
  maxTitleLength: 60,
  minDescriptionLength: 50,
  maxDescriptionLength: 160
};

Step 5: 提交

git add test-framework/shared/config/
git commit -m "feat: add configuration management system"

Task 4: 创建基础页面对象

Files:

  • Create: test-framework/shared/pages/BasePage.ts

Step 1: 创建基础页面对象类

// test-framework/shared/pages/BasePage.ts
import { Page, Locator } from '@playwright/test';
import { TestConfig } from '../types';
import { defaultConfig } from '../config/base.config';

export class BasePage {
  readonly page: Page;
  readonly config: TestConfig;
  readonly url: string;

  constructor(page: Page, url: string, config?: TestConfig) {
    this.page = page;
    this.url = url;
    this.config = config || defaultConfig;
  }

  async navigate(): Promise<void> {
    await this.page.goto(this.url, { waitUntil: 'networkidle', timeout: this.config.timeout });
  }

  async waitForLoadState(state: 'load' | 'domcontentloaded' | 'networkidle' = 'load'): Promise<void> {
    await this.page.waitForLoadState(state, { timeout: this.config.timeout });
  }

  async click(locator: Locator | string): Promise<void> {
    const element = typeof locator === 'string' ? this.page.locator(locator) : locator;
    await element.click({ timeout: this.config.timeout });
  }

  async fill(locator: Locator | string, value: string): Promise<void> {
    const element = typeof locator === 'string' ? this.page.locator(locator) : locator;
    await element.fill(value);
  }

  async getText(locator: Locator | string): Promise<string> {
    const element = typeof locator === 'string' ? this.page.locator(locator) : locator;
    return await element.textContent() || '';
  }

  async isVisible(locator: Locator | string): Promise<boolean> {
    const element = typeof locator === 'string' ? this.page.locator(locator) : locator;
    return await element.isVisible();
  }

  async waitForElement(locator: Locator | string, timeout?: number): Promise<void> {
    const element = typeof locator === 'string' ? this.page.locator(locator) : locator;
    await element.waitFor({ state: 'visible', timeout: timeout || this.config.timeout });
  }

  async scrollToElement(locator: Locator | string): Promise<void> {
    const element = typeof locator === 'string' ? this.page.locator(locator) : locator;
    await element.scrollIntoViewIfNeeded();
  }

  async takeScreenshot(filename: string): Promise<void> {
    const screenshotDir = 'test-framework/reports/screenshots';
    await this.page.screenshot({ path: `${screenshotDir}/${filename}` });
  }

  async hover(locator: Locator | string): Promise<void> {
    const element = typeof locator === 'string' ? this.page.locator(locator) : locator;
    await element.hover();
  }

  async getCurrentURL(): Promise<string> {
    return this.page.url();
  }

  async getTitle(): Promise<string> {
    return await this.page.title();
  }

  async getAttribute(locator: Locator | string, attribute: string): Promise<string | null> {
    const element = typeof locator === 'string' ? this.page.locator(locator) : locator;
    return await element.getAttribute(attribute);
  }

  async measurePerformance(): Promise<{
    loadTime: number;
    domContentLoaded: number;
    firstPaint: number;
    firstContentfulPaint: number;
  }> {
    const metrics = await this.page.evaluate(() => {
      const performance = window.performance;
      const timing = performance.timing;
      const navigation = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming;

      return {
        loadTime: timing.loadEventEnd - timing.navigationStart,
        domContentLoaded: timing.domContentLoadedEventEnd - timing.navigationStart,
        firstPaint: navigation ? navigation.loadEventEnd - navigation.fetchStart : 0,
        firstContentfulPaint: navigation ? navigation.domContentLoadedEventEnd - navigation.fetchStart : 0,
      };
    });

    return metrics;
  }
}

Step 2: 提交

git add test-framework/shared/pages/BasePage.ts
git commit -m "feat: add base page object with common methods"

Task 5: 创建具体页面对象

Files:

  • Create: test-framework/shared/pages/HomePage.ts
  • Create: test-framework/shared/pages/AboutPage.ts
  • Create: test-framework/shared/pages/ContactPage.ts
  • Create: test-framework/shared/pages/ProductsPage.ts
  • Create: test-framework/shared/pages/ServicesPage.ts
  • Create: test-framework/shared/pages/CasesPage.ts
  • Create: test-framework/shared/pages/NewsPage.ts

Step 1: 创建首页页面对象

// test-framework/shared/pages/HomePage.ts
import { Page } from '@playwright/test';
import { BasePage } from './BasePage';
import { getPageConfig } from '../config/test-pages';

export class HomePage extends BasePage {
  constructor(page: Page, config?) {
    const pageConfig = getPageConfig('home');
    super(page, pageConfig.url, config);
    this.pageConfig = pageConfig;
  }

  private pageConfig;

  async getHeroTitle(): Promise<string> {
    return await this.getText('h1');
  }

  async getFeaturesSection(): Promise<boolean> {
    return await this.isVisible('.features-section');
  }

  async navigateToAbout(): Promise<void> {
    await this.click('a[href="/about"]');
  }

  async navigateToContact(): Promise<void> {
    await this.click('a[href="/contact"]');
  }

  async navigateToProducts(): Promise<void> {
    await this.click('a[href="/products"]');
  }
}

Step 2: 创建关于页面页面对象

// test-framework/shared/pages/AboutPage.ts
import { Page } from '@playwright/test';
import { BasePage } from './BasePage';
import { getPageConfig } from '../config/test-pages';

export class AboutPage extends BasePage {
  constructor(page: Page, config?) {
    const pageConfig = getPageConfig('about');
    super(page, pageConfig.url, config);
  }

  async getPageTitle(): Promise<string> {
    return await this.getText('h1');
  }

  async getContent(): Promise<string> {
    return await this.getText('.about-content');
  }
}

Step 3: 创建联系页面页面对象

// test-framework/shared/pages/ContactPage.ts
import { Page } from '@playwright/test';
import { BasePage } from './BasePage';
import { getPageConfig } from '../config/test-pages';

export class ContactPage extends BasePage {
  constructor(page: Page, config?) {
    const pageConfig = getPageConfig('contact');
    super(page, pageConfig.url, config);
  }

  async fillContactForm(data: { name: string; email: string; phone: string; message: string }): Promise<void> {
    await this.fill('#name', data.name);
    await this.fill('#email', data.email);
    await this.fill('#phone', data.phone);
    await this.fill('#message', data.message);
  }

  async submitForm(): Promise<void> {
    await this.click('button[type="submit"]');
  }

  async getFormErrorMessage(): Promise<string> {
    return await this.getText('.error-message');
  }

  async getFormSuccessMessage(): Promise<string> {
    return await this.getText('.success-message');
  }
}

Step 4: 创建产品页面页面对象

// test-framework/shared/pages/ProductsPage.ts
import { Page } from '@playwright/test';
import { BasePage } from './BasePage';
import { getPageConfig } from '../config/test-pages';

export class ProductsPage extends BasePage {
  constructor(page: Page, config?) {
    const pageConfig = getPageConfig('products');
    super(page, pageConfig.url, config);
  }

  async getProductCount(): Promise<number> {
    return await this.page.locator('.product-card').count();
  }

  async getProductTitle(index: number): Promise<string> {
    return await this.getText(`.product-card:nth-child(${index + 1}) h3`);
  }
}

Step 5: 创建服务页面页面对象

// test-framework/shared/pages/ServicesPage.ts
import { Page } from '@playwright/test';
import { BasePage } from './BasePage';
import { getPageConfig } from '../config/test-pages';

export class ServicesPage extends BasePage {
  constructor(page: Page, config?) {
    const pageConfig = getPageConfig('services');
    super(page, pageConfig.url, config);
  }

  async getServiceCount(): Promise<number> {
    return await this.page.locator('.service-item').count();
  }
}

Step 6: 创建案例页面页面对象

// test-framework/shared/pages/CasesPage.ts
import { Page } from '@playwright/test';
import { BasePage } from './BasePage';
import { getPageConfig } from '../config/test-pages';

export class CasesPage extends BasePage {
  constructor(page: Page, config?) {
    const pageConfig = getPageConfig('cases');
    super(page, pageConfig.url, config);
  }

  async getCaseCount(): Promise<number> {
    return await this.page.locator('.case-card').count();
  }
}

Step 7: 创建新闻页面页面对象

// test-framework/shared/pages/NewsPage.ts
import { Page } from '@playwright/test';
import { BasePage } from './BasePage';
import { getPageConfig } from '../config/test-pages';

export class NewsPage extends BasePage {
  constructor(page: Page, config?) {
    const pageConfig = getPageConfig('news');
    super(page, pageConfig.url, config);
  }

  async getNewsCount(): Promise<number> {
    return await this.page.locator('.news-item').count();
  }
}

Step 8: 创建页面对象导出文件

// test-framework/shared/pages/index.ts
export { BasePage } from './BasePage';
export { HomePage } from './HomePage';
export { AboutPage } from './AboutPage';
export { ContactPage } from './ContactPage';
export { ProductsPage } from './ProductsPage';
export { ServicesPage } from './ServicesPage';
export { CasesPage } from './CasesPage';
export { NewsPage } from './NewsPage';

Step 9: 提交

git add test-framework/shared/pages/
git commit -m "feat: add page objects for all pages"

Task 6: 创建性能测试工具

Files:

  • Create: test-framework/shared/utils/performance/PerformanceMonitor.ts
  • Create: test-framework/shared/utils/performance/LighthouseRunner.ts
  • Create: test-framework/shared/utils/performance/CoreWebVitals.ts

Step 1: 创建性能监控器

// test-framework/shared/utils/performance/PerformanceMonitor.ts
import { Page } from '@playwright/test';
import { PerformanceMetrics, NetworkTiming, ResourceTiming } from '../../types';

export class PerformanceMonitor {
  constructor(private page: Page) {}

  async measurePageLoad(): Promise<PerformanceMetrics> {
    const metrics = await this.page.evaluate(() => {
      const performance = window.performance;
      const timing = performance.timing;
      const navigation = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming;

      return {
        loadTime: timing.loadEventEnd - timing.navigationStart,
        domContentLoaded: timing.domContentLoadedEventEnd - timing.navigationStart,
        firstPaint: navigation ? navigation.loadEventEnd - navigation.fetchStart : 0,
        firstContentfulPaint: navigation ? navigation.domContentLoadedEventEnd - navigation.fetchStart : 0,
      };
    });

    return metrics;
  }

  async measureNetworkTiming(): Promise<NetworkTiming> {
    return await this.page.evaluate(() => {
      const timing = performance.timing;
      return {
        dns: timing.domainLookupEnd - timing.domainLookupStart,
        tcp: timing.connectEnd - timing.connectStart,
        ssl: timing.connectEnd - timing.secureConnectionStart,
        request: timing.responseStart - timing.requestStart,
        response: timing.responseEnd - timing.responseStart,
        total: timing.loadEventEnd - timing.navigationStart,
      };
    });
  }

  async measureResourceTiming(): Promise<ResourceTiming[]> {
    return await this.page.evaluate(() => {
      const resources = performance.getEntriesByType('resource') as PerformanceResourceTiming[];
      return resources.map(r => ({
        name: r.name,
        duration: r.duration,
        size: r.transferSize,
        type: r.initiatorType
      }));
    });
  }

  async measureMemoryUsage(): Promise<{ usedJSHeapSize: number; totalJSHeapSize: number }> {
    return await this.page.evaluate(() => {
      const memory = (performance as any).memory;
      return {
        usedJSHeapSize: memory.usedJSHeapSize,
        totalJSHeapSize: memory.totalJSHeapSize
      };
    });
  }
}

Step 2: 提交

git add test-framework/shared/utils/performance/PerformanceMonitor.ts
git commit -m "feat: add performance monitoring utility"

Task 7: 创建可访问性测试工具

Files:

  • Create: test-framework/shared/utils/accessibility/AccessibilityTester.ts

Step 1: 创建可访问性测试器

// test-framework/shared/utils/accessibility/AccessibilityTester.ts
import { Page } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
import { AccessibilityResult, Violation } from '../../types';

export class AccessibilityTester {
  constructor(private page: Page) {}

  async runAxeScan(pageName: string, url: string): Promise<AccessibilityResult> {
    const accessibilityScanResults = await new AxeBuilder({ this.page })
      .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
      .analyze();

    const violations: Violation[] = accessibilityScanResults.violations.map(v => ({
      id: v.id,
      impact: v.impact || 'unknown',
      description: v.description,
      help: v.help,
      helpUrl: v.helpUrl,
      nodes: v.nodes.length
    }));

    const passes = accessibilityScanResults.passes.length;
    const incomplete = accessibilityScanResults.incomplete.length;
    const score = this.calculateScore(violations, passes, incomplete);

    return {
      score,
      violations,
      passes,
      incomplete,
      page: pageName,
      url
    };
  }

  private calculateScore(violations: Violation[], passes: number, incomplete: number): number {
    const total = violations.length + passes + incomplete;
    if (total === 0) return 100;
    return parseFloat(((passes / total) * 100).toFixed(1));
  }

  async checkColorContrast(): Promise<boolean> {
    const results = await new AxeBuilder({ this.page })
      .withTags(['wcag2aa'])
      .include('#content')
      .analyze();

    return results.violations.filter(v => v.id === 'color-contrast').length === 0;
  }

  async checkAltText(): Promise<{ total: number; withAlt: number; withoutAlt: number }> {
    const images = await this.page.locator('img').all();
    let withAlt = 0;
    let withoutAlt = 0;

    for (const image of images) {
      const alt = await image.getAttribute('alt');
      if (alt && alt.trim() !== '') {
        withAlt++;
      } else {
        withoutAlt++;
      }
    }

    return {
      total: images.length,
      withAlt,
      withoutAlt
    };
  }
}

Step 2: 提交

git add test-framework/shared/utils/accessibility/AccessibilityTester.ts
git commit -m "feat: add accessibility testing utility"

Task 8: 创建SEO测试工具

Files:

  • Create: test-framework/shared/utils/seo/SEOValidator.ts

Step 1: 创建SEO验证器

// test-framework/shared/utils/seo/SEOValidator.ts
import { Page } from '@playwright/test';
import { SEOResult, MetaTagResult, HeadingResult, LinkResult, ImageResult } from '../../types';

export class SEOValidator {
  constructor(private page: Page) {}

  async validateSEO(): Promise<SEOResult> {
    const metaTags = await this.validateMetaTags();
    const headings = await this.validateHeadings();
    const links = await this.validateLinks();
    const images = await this.validateImages();

    const score = this.calculateScore(metaTags, headings, links, images);

    return {
      score,
      metaTags,
      headings,
      links,
      images
    };
  }

  async validateMetaTags(): Promise<MetaTagResult> {
    const title = await this.page.title();
    const description = await this.page.getAttribute('meta[name="description"]', 'content');
    const keywords = await this.page.getAttribute('meta[name="keywords"]', 'content');
    const ogTitle = await this.page.getAttribute('meta[property="og:title"]', 'content');
    const ogDescription = await this.page.getAttribute('meta[property="og:description"]', 'content');
    const canonical = await this.page.getAttribute('link[rel="canonical"]', 'href');

    return {
      title: !!title && title.length >= 10 && title.length <= 60,
      description: !!description && description.length >= 50 && description.length <= 160,
      keywords: !!keywords,
      ogTitle: !!ogTitle,
      ogDescription: !!ogDescription,
      canonical: !!canonical
    };
  }

  async validateHeadings(): Promise<HeadingResult> {
    const h1Count = await this.page.locator('h1').count();
    const hasH1 = h1Count > 0;
    const multipleH1 = h1Count > 1;

    const headings = await this.page.evaluate(() => {
      const elements = document.querySelectorAll('h1, h2, h3, h4, h5, h6');
      return Array.from(elements).map(el => el.tagName);
    });

    let headingStructure = true;
    let previousLevel = 0;
    for (const heading of headings) {
      const level = parseInt(heading.charAt(1));
      if (level > previousLevel + 1) {
        headingStructure = false;
        break;
      }
      previousLevel = level;
    }

    return {
      hasH1,
      headingStructure,
      multipleH1
    };
  }

  async validateLinks(): Promise<LinkResult> {
    const links = await this.page.locator('a').all();
    let internal = 0;
    let external = 0;
    let broken = 0;

    for (const link of links) {
      const href = await link.getAttribute('href');
      if (!href) continue;

      if (href.startsWith('http')) {
        external++;
      } else {
        internal++;
      }
    }

    return {
      total: links.length,
      broken,
      internal,
      external
    };
  }

  async validateImages(): Promise<ImageResult> {
    const images = await this.page.locator('img').all();
    let withAlt = 0;
    let withoutAlt = 0;

    for (const image of images) {
      const alt = await image.getAttribute('alt');
      if (alt && alt.trim() !== '') {
        withAlt++;
      } else {
        withoutAlt++;
      }
    }

    return {
      total: images.length,
      withAlt,
      withoutAlt
    };
  }

  private calculateScore(metaTags: MetaTagResult, headings: HeadingResult, links: LinkResult, images: ImageResult): number {
    let score = 0;
    let total = 0;

    const metaTagValues = Object.values(metaTags);
    score += metaTagValues.filter(v => v).length;
    total += metaTagValues.length;

    if (headings.hasH1) score++;
    if (headings.headingStructure) score++;
    if (!headings.multipleH1) score++;
    total += 3;

    if (images.withoutAlt === 0) score++;
    total++;

    return Math.round((score / total) * 100);
  }
}

Step 2: 提交

git add test-framework/shared/utils/seo/SEOValidator.ts
git commit -m "feat: add SEO validation utility"

Task 9: 创建共享Fixtures

Files:

  • Create: test-framework/shared/fixtures/base.fixture.ts
  • Create: test-framework/shared/fixtures/performance.fixture.ts
  • Create: test-framework/shared/fixtures/accessibility.fixture.ts

Step 1: 创建基础fixture

// test-framework/shared/fixtures/base.fixture.ts
import { test as base } from '@playwright/test';
import { BasePage, HomePage, AboutPage, ContactPage } from '../pages';
import { getEnvironmentConfig } from '../config/environments';

type MyFixtures = {
  basePage: BasePage;
  homePage: HomePage;
  aboutPage: AboutPage;
  contactPage: ContactPage;
  config: any;
};

export const test = base.extend<MyFixtures>({
  config: async ({}, use) => {
    const env = process.env.TEST_ENV || 'development';
    const config = getEnvironmentConfig(env);
    await use(config);
  },

  basePage: async ({ page }, use) => {
    const basePage = new BasePage(page, '/');
    await use(basePage);
  },

  homePage: async ({ page, config }, use) => {
    const homePage = new HomePage(page, config);
    await use(homePage);
  },

  aboutPage: async ({ page, config }, use) => {
    const aboutPage = new AboutPage(page, config);
    await use(aboutPage);
  },

  contactPage: async ({ page, config }, use) => {
    const contactPage = new ContactPage(page, config);
    await use(contactPage);
  }
});

export { expect } from '@playwright/test';

Step 2: 创建性能测试fixture

// test-framework/shared/fixtures/performance.fixture.ts
import { test as base } from '@playwright/test';
import { PerformanceMonitor } from '../utils/performance/PerformanceMonitor';

type MyFixtures = {
  performanceMonitor: PerformanceMonitor;
};

export const test = base.extend<MyFixtures>({
  performanceMonitor: async ({ page }, use) => {
    const monitor = new PerformanceMonitor(page);
    await use(monitor);
  }
});

export { expect } from '@playwright/test';

Step 3: 创建可访问性测试fixture

// test-framework/shared/fixtures/accessibility.fixture.ts
import { test as base } from '@playwright/test';
import { AccessibilityTester } from '../utils/accessibility/AccessibilityTester';

type MyFixtures = {
  accessibilityTester: AccessibilityTester;
};

export const test = base.extend<MyFixtures>({
  accessibilityTester: async ({ page }, use) => {
    const tester = new AccessibilityTester(page);
    await use(tester);
  }
});

export { expect } from '@playwright/test';

Step 4: 提交

git add test-framework/shared/fixtures/
git commit -m "feat: add shared fixtures for testing"

Task 10: 创建共享层导出文件

Files:

  • Create: test-framework/shared/index.ts

Step 1: 创建共享层导出文件

// test-framework/shared/index.ts
export * from './config';
export * from './pages';
export * from './types';
export * from './fixtures';
export * from './utils/performance/PerformanceMonitor';
export * from './utils/accessibility/AccessibilityTester';
export * from './utils/seo/SEOValidator';

Step 2: 提交

git add test-framework/shared/index.ts
git commit -m "feat: add shared layer export file"

Task 11: 验证共享层完整性

Files:

  • Test: 验证所有导出是否正常

Step 1: 创建验证脚本

// test-framework/verify-shared-layer.ts
import { BasePage, HomePage } from './shared/pages';
import { getEnvironmentConfig } from './shared/config/environments';
import { PerformanceMonitor } from './shared/utils/performance/PerformanceMonitor';
import { AccessibilityTester } from './shared/utils/accessibility/AccessibilityTester';
import { SEOValidator } from './shared/utils/seo/SEOValidator';

console.log('✅ Shared layer imports verified successfully!');
console.log('- BasePage: OK');
console.log('- HomePage: OK');
console.log('- getEnvironmentConfig: OK');
console.log('- PerformanceMonitor: OK');
console.log('- AccessibilityTester: OK');
console.log('- SEOValidator: OK');

Step 2: 运行验证脚本

Run: npx ts-node test-framework/verify-shared-layer.ts

Expected: 所有导入成功,无错误

Step 3: 提交

git add test-framework/verify-shared-layer.ts
git commit -m "test: add shared layer verification script"

Task 12: 更新E2E测试以使用共享层

Files:

  • Modify: e2e/src/pages/BasePage.ts
  • Modify: e2e/src/pages/HomePage.ts
  • Modify: e2e/src/pages/AboutPage.ts
  • Modify: e2e/src/pages/ContactPage.ts

Step 1: 更新E2E测试的BasePage

// e2e/src/pages/BasePage.ts
import { Page, Locator } from '@playwright/test';
import { BasePage as SharedBasePage } from '../../test-framework/shared/pages';

export class BasePage extends SharedBasePage {
  constructor(page: Page, url: string, config?) {
    super(page, url, config);
  }

  // E2E测试特有的方法可以在这里添加
}

Step 2: 更新E2E测试的HomePage

// e2e/src/pages/HomePage.ts
import { Page } from '@playwright/test';
import { HomePage as SharedHomePage } from '../../test-framework/shared/pages';

export class HomePage extends SharedHomePage {
  constructor(page: Page, config?) {
    super(page, config);
  }

  // E2E测试特有的方法可以在这里添加
}

Step 3: 运行E2E测试验证

Run: cd e2e && npm run test:smoke

Expected: 所有测试通过

Step 4: 提交

git add e2e/src/pages/
git commit -m "refactor: update E2E pages to use shared layer"

Task 13: 创建阶段1总结文档

Files:

  • Create: test-framework/docs/phase1-summary.md

Step 1: 创建阶段1总结文档

# 阶段1:创建共享层 - 完成总结

## 完成的任务

1. ✅ 创建共享层目录结构
2. ✅ 创建类型定义
3. ✅ 创建配置管理
4. ✅ 创建基础页面对象
5. ✅ 创建具体页面对象
6. ✅ 创建性能测试工具
7. ✅ 创建可访问性测试工具
8. ✅ 创建SEO测试工具
9. ✅ 创建共享Fixtures
10. ✅ 创建共享层导出文件
11. ✅ 验证共享层完整性
12. ✅ 更新E2E测试以使用共享层

## 创建的文件

### 配置文件
- test-framework/shared/config/base.config.ts
- test-framework/shared/config/environments.ts
- test-framework/shared/config/test-pages.ts
- test-framework/shared/config/test-data.ts

### 页面对象
- test-framework/shared/pages/BasePage.ts
- test-framework/shared/pages/HomePage.ts
- test-framework/shared/pages/AboutPage.ts
- test-framework/shared/pages/ContactPage.ts
- test-framework/shared/pages/ProductsPage.ts
- test-framework/shared/pages/ServicesPage.ts
- test-framework/shared/pages/CasesPage.ts
- test-framework/shared/pages/NewsPage.ts

### 工具类
- test-framework/shared/utils/performance/PerformanceMonitor.ts
- test-framework/shared/utils/accessibility/AccessibilityTester.ts
- test-framework/shared/utils/seo/SEOValidator.ts

### Fixtures
- test-framework/shared/fixtures/base.fixture.ts
- test-framework/shared/fixtures/performance.fixture.ts
- test-framework/shared/fixtures/accessibility.fixture.ts

### 类型定义
- test-framework/shared/types/page.types.ts
- test-framework/shared/types/test.types.ts
- test-framework/shared/types/performance.types.ts
- test-framework/shared/types/accessibility.types.ts
- test-framework/shared/types/seo.types.ts

## 验证结果

- ✅ 所有TypeScript编译无错误
- ✅ 所有导入正常工作
- ✅ E2E测试仍然正常运行
- ✅ 共享层结构完整

## 下一步

进入阶段2:迁移开发环境测试

Step 2: 提交

git add test-framework/docs/phase1-summary.md
git commit -m "docs: add phase 1 completion summary"

阶段2:迁移开发环境测试

Task 14: 创建开发环境测试基础结构

Files:

  • Create: test-framework/dev-audit/performance/performance.spec.ts
  • Create: test-framework/dev-audit/seo/seo.spec.ts
  • Create: test-framework/dev-audit/accessibility/accessibility.spec.ts
  • Create: test-framework/dev-audit/forms/forms.spec.ts

Step 1: 创建性能审计测试

// test-framework/dev-audit/performance/performance.spec.ts
import { test, expect } from '@playwright/test';
import { HomePage, AboutPage, ContactPage, ProductsPage, ServicesPage, CasesPage, NewsPage } from '../../shared/pages';
import { PerformanceMonitor } from '../../shared/utils/performance/PerformanceMonitor';
import { performanceThresholds } from '../../shared/config/test-data';

test.describe('性能审计测试', () => {
  const pages = [
    { name: '首页', PageClass: HomePage },
    { name: '关于我们', PageClass: AboutPage },
    { name: '联系我们', PageClass: ContactPage },
    { name: '产品', PageClass: ProductsPage },
    { name: '服务', PageClass: ServicesPage },
    { name: '案例', PageClass: CasesPage },
    { name: '新闻', PageClass: NewsPage }
  ];

  pages.forEach(({ name, PageClass }) => {
    test(`${name} - 页面加载性能`, async ({ page }) => {
      const pageObj = new PageClass(page);
      const monitor = new PerformanceMonitor(page);

      await pageObj.navigate();
      const metrics = await monitor.measurePageLoad();

      console.log(`${name} 性能指标:`, metrics);

      expect(metrics.loadTime).toBeLessThan(performanceThresholds.loadTime);
      expect(metrics.domContentLoaded).toBeLessThan(performanceThresholds.domContentLoaded);
    });
  });
});

Step 2: 创建SEO检查测试

// test-framework/dev-audit/seo/seo.spec.ts
import { test, expect } from '@playwright/test';
import { HomePage, AboutPage, ContactPage } from '../../shared/pages';
import { SEOValidator } from '../../shared/utils/seo/SEOValidator';
import { seoThresholds } from '../../shared/config/test-data';

test.describe('SEO检查测试', () => {
  const pages = [
    { name: '首页', PageClass: HomePage },
    { name: '关于我们', PageClass: AboutPage },
    { name: '联系我们', PageClass: ContactPage }
  ];

  pages.forEach(({ name, PageClass }) => {
    test(`${name} - SEO验证`, async ({ page }) => {
      const pageObj = new PageClass(page);
      const validator = new SEOValidator(page);

      await pageObj.navigate();
      const result = await validator.validateSEO();

      console.log(`${name} SEO结果:`, result);

      expect(result.score).toBeGreaterThanOrEqual(seoThresholds.score);
      expect(result.metaTags.title).toBe(true);
      expect(result.metaTags.description).toBe(true);
    });
  });
});

Step 3: 创建可访问性测试

// test-framework/dev-audit/accessibility/accessibility.spec.ts
import { test, expect } from '@playwright/test';
import { HomePage, AboutPage, ContactPage } from '../../shared/pages';
import { AccessibilityTester } from '../../shared/utils/accessibility/AccessibilityTester';
import { accessibilityThresholds } from '../../shared/config/test-data';
import { getPageConfig } from '../../shared/config/test-pages';

test.describe('可访问性测试', () => {
  const pages = [
    { name: '首页', PageClass: HomePage },
    { name: '关于我们', PageClass: AboutPage },
    { name: '联系我们', PageClass: ContactPage }
  ];

  pages.forEach(({ name, PageClass }) => {
    test(`${name} - 可访问性验证`, async ({ page }) => {
      const pageConfig = getPageConfig(name === '首页' ? 'home' : name === '关于我们' ? 'about' : 'contact');
      const pageObj = new PageClass(page);
      const tester = new AccessibilityTester(page);

      await pageObj.navigate();
      const result = await tester.runAxeScan(pageConfig.name, pageConfig.url);

      console.log(`${name} 可访问性结果:`, result);

      expect(result.score).toBeGreaterThanOrEqual(accessibilityThresholds.score);
      expect(result.violations.length).toBeLessThanOrEqual(accessibilityThresholds.maxViolations);
    });
  });
});

Step 4: 创建表单验证测试

// test-framework/dev-audit/forms/forms.spec.ts
import { test, expect } from '@playwright/test';
import { ContactPage } from '../../shared/pages';
import { formData } from '../../shared/config/test-data';

test.describe('表单验证测试', () => {
  test('联系表单 - 有效数据提交', async ({ page }) => {
    const contactPage = new ContactPage(page);

    await contactPage.navigate();
    await contactPage.fillContactForm(formData.valid);
    await contactPage.submitForm();

    const successMessage = await contactPage.getFormSuccessMessage();
    expect(successMessage).toContain('成功');
  });

  test('联系表单 - 必填字段验证', async ({ page }) => {
    const contactPage = new ContactPage(page);

    await contactPage.navigate();
    await contactPage.fillContactForm({
      name: '',
      email: '',
      phone: '',
      message: ''
    });
    await contactPage.submitForm();

    const errorMessage = await contactPage.getFormErrorMessage();
    expect(errorMessage).toBeTruthy();
  });

  test('联系表单 - 邮箱格式验证', async ({ page }) => {
    const contactPage = new ContactPage(page);

    await contactPage.navigate();
    await contactPage.fillContactForm({
      name: '测试用户',
      email: formData.invalid.email,
      phone: '13800138000',
      message: '测试消息'
    });
    await contactPage.submitForm();

    const errorMessage = await contactPage.getFormErrorMessage();
    expect(errorMessage).toContain('邮箱');
  });
});

Step 5: 提交

git add test-framework/dev-audit/
git commit -m "feat: add dev-audit test suites"

Task 15: 创建Playwright配置文件

Files:

  • Create: test-framework/playwright.config.ts

Step 1: 创建Playwright配置

// test-framework/playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
import { getEnvironmentConfig } from './shared/config/environments';

const config = defineConfig({
  testDir: './dev-audit',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: [
    ['html', { outputFolder: 'test-framework/reports/html' }],
    ['json', { outputFile: 'test-framework/reports/results.json' }],
    ['list']
  ],
  use: {
    baseURL: getEnvironmentConfig(process.env.TEST_ENV || 'development').baseURL,
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure'
  },
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },
    },
    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'] },
    },
    {
      name: 'Mobile Chrome',
      use: { ...devices['Pixel 5'] },
    },
    {
      name: 'Mobile Safari',
      use: { ...devices['iPhone 12'] },
    }
  ]
});

export default config;

Step 2: 提交

git add test-framework/playwright.config.ts
git commit -m "feat: add Playwright configuration for dev-audit"

Task 16: 创建package.json和脚本

Files:

  • Create: test-framework/package.json

Step 1: 创建package.json

{
  "name": "test-framework",
  "version": "1.0.0",
  "description": "Unified test framework for Novalon website",
  "scripts": {
    "test": "playwright test",
    "test:dev-audit": "playwright test",
    "test:dev-audit:performance": "playwright test dev-audit/performance",
    "test:dev-audit:seo": "playwright test dev-audit/seo",
    "test:dev-audit:accessibility": "playwright test dev-audit/accessibility",
    "test:dev-audit:forms": "playwright test dev-audit/forms",
    "test:report": "playwright show-report",
    "test:install": "playwright install"
  },
  "devDependencies": {
    "@playwright/test": "^1.40.0",
    "@axe-core/playwright": "^4.8.0",
    "typescript": "^5.3.0"
  },
  "dependencies": {
    "lighthouse": "^11.0.0",
    "chrome-launcher": "^1.0.0"
  }
}

Step 2: 提交

git add test-framework/package.json
git commit -m "feat: add package.json with test scripts"

Task 17: 创建TypeScript配置

Files:

  • Create: test-framework/tsconfig.json

Step 1: 创建TypeScript配置

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "lib": ["ES2020"],
    "outDir": "./dist",
    "rootDir": "./",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "moduleResolution": "node",
    "types": ["node", "@playwright/test"]
  },
  "include": [
    "shared/**/*",
    "dev-audit/**/*"
  ],
  "exclude": [
    "node_modules",
    "dist",
    "reports"
  ]
}

Step 2: 提交

git add test-framework/tsconfig.json
git commit -m "feat: add TypeScript configuration"

Task 18: 安装依赖并验证

Files:

  • Test: 验证依赖安装和测试运行

Step 1: 安装依赖

Run: cd test-framework && npm install

Expected: 所有依赖安装成功

Step 2: 安装Playwright浏览器

Run: cd test-framework && npm run test:install

Expected: Playwright浏览器安装成功

Step 3: 运行测试验证

Run: cd test-framework && npm run test:dev-audit:performance

Expected: 性能测试运行成功

Step 4: 提交

git add test-framework/package-lock.json
git commit -m "chore: install dependencies and verify tests"

Task 19: 创建阶段2总结文档

Files:

  • Create: test-framework/docs/phase2-summary.md

Step 1: 创建阶段2总结文档

# 阶段2:迁移开发环境测试 - 完成总结

## 完成的任务

1. ✅ 创建开发环境测试基础结构
2. ✅ 创建性能审计测试
3. ✅ 创建SEO检查测试
4. ✅ 创建可访问性测试
5. ✅ 创建表单验证测试
6. ✅ 创建Playwright配置文件
7. ✅ 创建package.json和脚本
8. ✅ 创建TypeScript配置
9. ✅ 安装依赖并验证

## 创建的文件

### 测试文件
- test-framework/dev-audit/performance/performance.spec.ts
- test-framework/dev-audit/seo/seo.spec.ts
- test-framework/dev-audit/accessibility/accessibility.spec.ts
- test-framework/dev-audit/forms/forms.spec.ts

### 配置文件
- test-framework/playwright.config.ts
- test-framework/package.json
- test-framework/tsconfig.json

## 验证结果

- ✅ 所有TypeScript编译无错误
- ✅ 所有依赖安装成功
- ✅ Playwright浏览器安装成功
- ✅ 测试可以正常运行
- ✅ 测试结果与原有脚本一致

## 下一步

进入阶段3:优化和清理

Step 2: 提交

git add test-framework/docs/phase2-summary.md
git commit -m "docs: add phase 2 completion summary"

阶段3:优化和清理

Task 20: 移除旧的scripts目录

Files:

  • Delete: scripts/ directory

Step 1: 备份scripts目录

Run: cp -r scripts scripts.backup

Expected: 备份创建成功

Step 2: 删除scripts目录

Run: rm -rf scripts/

Expected: scripts目录删除成功

Step 3: 提交

git add -A
git commit -m "chore: remove old scripts directory (backed up to scripts.backup)"

Task 21: 创建综合测试报告生成器

Files:

  • Create: test-framework/shared/utils/reporting/TestReporter.ts

Step 1: 创建测试报告生成器

// test-framework/shared/utils/reporting/TestReporter.ts
import * as fs from 'fs';
import * as path from 'path';
import { TestSuite, AccessibilityResult, SEOResult, PerformanceMetrics } from '../../types';

export class TestReporter {
  private results: Map<string, any> = new Map();

  addResult(type: string, result: any): void {
    this.results.set(type, result);
  }

  generateHTMLReport(): string {
    const timestamp = new Date().toLocaleString('zh-CN');

    let html = `
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>综合测试报告 - ${timestamp}</title>
  <style>
    body {
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
      max-width: 1200px;
      margin: 0 auto;
      padding: 20px;
      background: #f5f5f5;
    }
    .header {
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      color: white;
      padding: 30px;
      border-radius: 10px;
      margin-bottom: 30px;
    }
    .section {
      background: white;
      padding: 25px;
      border-radius: 10px;
      margin-bottom: 20px;
      box-shadow: 0 2px 8px rgba(0,0,0,0.1);
    }
    .metric {
      display: inline-block;
      padding: 10px 20px;
      margin: 5px;
      border-radius: 5px;
      font-weight: bold;
    }
    .metric.success { background: #10b981; color: white; }
    .metric.warning { background: #f59e0b; color: white; }
    .metric.danger { background: #ef4444; color: white; }
    table {
      width: 100%;
      border-collapse: collapse;
      margin-top: 15px;
    }
    th, td {
      padding: 12px;
      text-align: left;
      border-bottom: 1px solid #e5e7eb;
    }
    th { background: #f9fafb; font-weight: bold; }
  </style>
</head>
<body>
  <div class="header">
    <h1>综合测试报告</h1>
    <p>生成时间: ${timestamp}</p>
  </div>
`;

    for (const [type, result] of this.results.entries()) {
      html += this.generateSection(type, result);
    }

    html += `
</body>
</html>`;

    return html;
  }

  private generateSection(type: string, result: any): string {
    switch (type) {
      case 'accessibility':
        return this.generateAccessibilitySection(result);
      case 'seo':
        return this.generateSEOSection(result);
      case 'performance':
        return this.generatePerformanceSection(result);
      default:
        return `<div class="section"><h2>${type}</h2><pre>${JSON.stringify(result, null, 2)}</pre></div>`;
    }
  }

  private generateAccessibilitySection(results: AccessibilityResult[]): string {
    const totalViolations = results.reduce((sum, r) => sum + r.violations.length, 0);
    const avgScore = results.reduce((sum, r) => sum + r.score, 0) / results.length;

    return `
<div class="section">
  <h2>可访问性测试</h2>
  <div>
    <span class="metric ${avgScore >= 80 ? 'success' : 'warning'}">平均分数: ${avgScore.toFixed(1)}</span>
    <span class="metric ${totalViolations <= 5 ? 'success' : 'danger'}">总违规数: ${totalViolations}</span>
  </div>
  <table>
    <thead>
      <tr>
        <th>页面</th>
        <th>分数</th>
        <th>违规数</th>
      </tr>
    </thead>
    <tbody>
      ${results.map(r => `
        <tr>
          <td>${r.page}</td>
          <td>${r.score}</td>
          <td>${r.violations.length}</td>
        </tr>
      `).join('')}
    </tbody>
  </table>
</div>`;
  }

  private generateSEOSection(results: SEOResult[]): string {
    const avgScore = results.reduce((sum, r) => sum + r.score, 0) / results.length;

    return `
<div class="section">
  <h2>SEO检查</h2>
  <div>
    <span class="metric ${avgScore >= 80 ? 'success' : 'warning'}">平均分数: ${avgScore.toFixed(1)}</span>
  </div>
  <table>
    <thead>
      <tr>
        <th>页面</th>
        <th>分数</th>
        <th>Meta标签</th>
        <th>标题</th>
      </tr>
    </thead>
    <tbody>
      ${results.map(r => `
        <tr>
          <td>${r.page}</td>
          <td>${r.score}</td>
          <td>${r.metaTags.title && r.metaTags.description ? '✅' : '❌'}</td>
          <td>${r.headings.hasH1 ? '✅' : '❌'}</td>
        </tr>
      `).join('')}
    </tbody>
  </table>
</div>`;
  }

  private generatePerformanceSection(results: PerformanceMetrics[]): string {
    const avgLoadTime = results.reduce((sum, r) => sum + r.loadTime, 0) / results.length;

    return `
<div class="section">
  <h2>性能测试</h2>
  <div>
    <span class="metric ${avgLoadTime <= 3000 ? 'success' : 'warning'}">平均加载时间: ${avgLoadTime.toFixed(0)}ms</span>
  </div>
  <table>
    <thead>
      <tr>
        <th>页面</th>
        <th>加载时间</th>
        <th>DOM加载</th>
      </tr>
    </thead>
    <tbody>
      ${results.map(r => `
        <tr>
          <td>${r.page}</td>
          <td>${r.loadTime}ms</td>
          <td>${r.domContentLoaded}ms</td>
        </tr>
      `).join('')}
    </tbody>
  </table>
</div>`;
  }

  saveHTMLReport(outputPath: string): void {
    const html = this.generateHTMLReport();
    const dir = path.dirname(outputPath);
    if (!fs.existsSync(dir)) {
      fs.mkdirSync(dir, { recursive: true });
    }
    fs.writeFileSync(outputPath, html, 'utf-8');
  }

  generateJSONReport(): any {
    return {
      timestamp: new Date().toISOString(),
      results: Object.fromEntries(this.results)
    };
  }

  saveJSONReport(outputPath: string): void {
    const json = this.generateJSONReport();
    const dir = path.dirname(outputPath);
    if (!fs.existsSync(dir)) {
      fs.mkdirSync(dir, { recursive: true });
    }
    fs.writeFileSync(outputPath, JSON.stringify(json, null, 2), 'utf-8');
  }
}

Step 2: 提交

git add test-framework/shared/utils/reporting/TestReporter.ts
git commit -m "feat: add comprehensive test report generator"

Task 22: 创建一键测试脚本

Files:

  • Create: test-framework/run-all-tests.sh

Step 1: 创建一键测试脚本

#!/bin/bash

echo "🚀 开始运行所有测试..."

echo "📊 运行性能审计..."
npm run test:dev-audit:performance

echo "🔍 运行SEO检查..."
npm run test:dev-audit:seo

echo "♿ 运行可访问性测试..."
npm run test:dev-audit:accessibility

echo "📝 运行表单验证..."
npm run test:dev-audit:forms

echo "📈 生成综合报告..."
npm run test:report

echo "✅ 所有测试完成!"
echo "📄 报告位置: test-framework/reports/html/index.html"

Step 2: 设置脚本权限

Run: chmod +x test-framework/run-all-tests.sh

Expected: 脚本权限设置成功

Step 3: 提交

git add test-framework/run-all-tests.sh
git commit -m "feat: add one-click test runner script"

Task 23: 创建README文档

Files:

  • Create: test-framework/README.md

Step 1: 创建README文档

# 测试框架

统一的测试框架,整合了E2E测试和开发环境测试。

## 目录结构

test-framework/ ├── shared/ # 共享层 │ ├── config/ # 配置管理 │ ├── pages/ # 页面对象 │ ├── utils/ # 工具类 │ ├── fixtures/ # 测试fixtures │ └── types/ # 类型定义 ├── dev-audit/ # 开发环境测试 │ ├── performance/ # 性能测试 │ ├── seo/ # SEO测试 │ ├── accessibility/ # 可访问性测试 │ └── forms/ # 表单测试 └── reports/ # 测试报告


## 快速开始

### 安装依赖

```bash
npm install
npm run test:install

运行测试

# 运行所有测试
npm run test

# 运行特定类型的测试
npm run test:dev-audit:performance
npm run test:dev-audit:seo
npm run test:dev-audit:accessibility
npm run test:dev-audit:forms

# 一键运行所有测试
./run-all-tests.sh

查看报告

npm run test:report

测试类型

性能测试

  • 页面加载性能
  • Core Web Vitals
  • 资源加载时间

SEO测试

  • Meta标签验证
  • 标题结构检查
  • 链接验证

可访问性测试

  • WCAG 2.1 AA合规性
  • 颜色对比度检查
  • Alt文本验证

表单测试

  • 必填字段验证
  • 格式验证
  • 提交功能测试

配置

测试配置位于 shared/config/ 目录:

  • base.config.ts - 基础配置
  • environments.ts - 环境配置
  • test-pages.ts - 测试页面配置
  • test-data.ts - 测试数据配置

页面对象

所有页面对象位于 shared/pages/ 目录,继承自 BasePage

工具类

  • PerformanceMonitor - 性能监控
  • AccessibilityTester - 可访问性测试
  • SEOValidator - SEO验证

贡献

请遵循项目的代码规范和测试最佳实践。


**Step 2: 提交**

```bash
git add test-framework/README.md
git commit -m "docs: add comprehensive README documentation"

Task 24: 最终验证和测试

Files:

  • Test: 验证所有功能正常工作

Step 1: 运行完整测试套件

Run: cd test-framework && ./run-all-tests.sh

Expected: 所有测试通过

Step 2: 验证报告生成

Run: ls -la test-framework/reports/html/

Expected: HTML报告生成成功

Step 3: 验证JSON报告

Run: ls -la test-framework/reports/json/

Expected: JSON报告生成成功

Step 4: 提交

git add test-framework/reports/
git commit -m "test: verify all tests and reports"

Task 25: 创建阶段3总结文档

Files:

  • Create: test-framework/docs/phase3-summary.md

Step 1: 创建阶段3总结文档

# 阶段3:优化和清理 - 完成总结

## 完成的任务

1. ✅ 移除旧的scripts目录
2. ✅ 创建综合测试报告生成器
3. ✅ 创建一键测试脚本
4. ✅ 创建README文档
5. ✅ 最终验证和测试

## 创建的文件

### 工具类
- test-framework/shared/utils/reporting/TestReporter.ts

### 脚本
- test-framework/run-all-tests.sh

### 文档
- test-framework/README.md

### 报告
- test-framework/reports/html/index.html
- test-framework/reports/json/results.json

## 验证结果

- ✅ 所有测试通过
- ✅ 报告生成正常
- ✅ 文档完整
- ✅ 脚本可执行

## 最终状态

测试框架重构完成!所有功能正常工作,代码结构清晰,易于维护和扩展。

Step 2: 提交

git add test-framework/docs/phase3-summary.md
git commit -m "docs: add phase 3 completion summary"

总结

完成的所有阶段

  1. 阶段1:创建共享层

    • 创建了完整的共享基础层
    • 包括类型定义、配置管理、页面对象、工具类
    • E2E测试成功迁移到使用共享层
  2. 阶段2:迁移开发环境测试

    • 将所有开发环境测试迁移到TypeScript/Playwright
    • 使用共享的页面对象和工具类
    • 测试结果与原有脚本一致
  3. 阶段3:优化和清理

    • 移除旧的scripts目录
    • 创建综合测试报告生成器
    • 创建一键测试脚本
    • 完善文档

最终成果

  • 统一的测试框架
  • 完整的类型安全
  • 高代码复用性
  • 易于维护和扩展
  • 完善的文档

下一步

  • 将测试框架集成到CI/CD流程
  • 添加更多测试覆盖
  • 优化测试执行性能
  • 添加测试覆盖率统计