diff --git a/docs/plans/2026-03-06-test-framework-refactoring-implementation.md b/docs/plans/2026-03-06-test-framework-refactoring-implementation.md new file mode 100644 index 0000000..f5c0839 --- /dev/null +++ b/docs/plans/2026-03-06-test-framework-refactoring-implementation.md @@ -0,0 +1,2353 @@ +# 测试框架重构实施计划 + +> **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: 创建共享层基础目录** + +```bash +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: 提交** + +```bash +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: 创建页面对象类型** + +```typescript +// 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: 创建测试类型** + +```typescript +// 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: 创建性能测试类型** + +```typescript +// 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: 创建可访问性测试类型** + +```typescript +// 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测试类型** + +```typescript +// 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: 创建类型导出文件** + +```typescript +// 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: 提交** + +```bash +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: 创建基础配置** + +```typescript +// 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: 创建环境配置** + +```typescript +// test-framework/shared/config/environments.ts +import { TestConfig } from '../types'; + +export const environments: Record = { + 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: 创建测试页面配置** + +```typescript +// test-framework/shared/config/test-pages.ts +import { PageConfig } from '../types'; + +export const testPages: Record = { + 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: 创建测试数据配置** + +```typescript +// 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: 提交** + +```bash +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: 创建基础页面对象类** + +```typescript +// 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 { + await this.page.goto(this.url, { waitUntil: 'networkidle', timeout: this.config.timeout }); + } + + async waitForLoadState(state: 'load' | 'domcontentloaded' | 'networkidle' = 'load'): Promise { + await this.page.waitForLoadState(state, { timeout: this.config.timeout }); + } + + async click(locator: Locator | string): Promise { + 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 { + const element = typeof locator === 'string' ? this.page.locator(locator) : locator; + await element.fill(value); + } + + async getText(locator: Locator | string): Promise { + const element = typeof locator === 'string' ? this.page.locator(locator) : locator; + return await element.textContent() || ''; + } + + async isVisible(locator: Locator | string): Promise { + const element = typeof locator === 'string' ? this.page.locator(locator) : locator; + return await element.isVisible(); + } + + async waitForElement(locator: Locator | string, timeout?: number): Promise { + 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 { + const element = typeof locator === 'string' ? this.page.locator(locator) : locator; + await element.scrollIntoViewIfNeeded(); + } + + async takeScreenshot(filename: string): Promise { + const screenshotDir = 'test-framework/reports/screenshots'; + await this.page.screenshot({ path: `${screenshotDir}/${filename}` }); + } + + async hover(locator: Locator | string): Promise { + const element = typeof locator === 'string' ? this.page.locator(locator) : locator; + await element.hover(); + } + + async getCurrentURL(): Promise { + return this.page.url(); + } + + async getTitle(): Promise { + return await this.page.title(); + } + + async getAttribute(locator: Locator | string, attribute: string): Promise { + 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: 提交** + +```bash +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: 创建首页页面对象** + +```typescript +// 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 { + return await this.getText('h1'); + } + + async getFeaturesSection(): Promise { + return await this.isVisible('.features-section'); + } + + async navigateToAbout(): Promise { + await this.click('a[href="/about"]'); + } + + async navigateToContact(): Promise { + await this.click('a[href="/contact"]'); + } + + async navigateToProducts(): Promise { + await this.click('a[href="/products"]'); + } +} +``` + +**Step 2: 创建关于页面页面对象** + +```typescript +// 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 { + return await this.getText('h1'); + } + + async getContent(): Promise { + return await this.getText('.about-content'); + } +} +``` + +**Step 3: 创建联系页面页面对象** + +```typescript +// 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 { + 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 { + await this.click('button[type="submit"]'); + } + + async getFormErrorMessage(): Promise { + return await this.getText('.error-message'); + } + + async getFormSuccessMessage(): Promise { + return await this.getText('.success-message'); + } +} +``` + +**Step 4: 创建产品页面页面对象** + +```typescript +// 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 { + return await this.page.locator('.product-card').count(); + } + + async getProductTitle(index: number): Promise { + return await this.getText(`.product-card:nth-child(${index + 1}) h3`); + } +} +``` + +**Step 5: 创建服务页面页面对象** + +```typescript +// 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 { + return await this.page.locator('.service-item').count(); + } +} +``` + +**Step 6: 创建案例页面页面对象** + +```typescript +// 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 { + return await this.page.locator('.case-card').count(); + } +} +``` + +**Step 7: 创建新闻页面页面对象** + +```typescript +// 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 { + return await this.page.locator('.news-item').count(); + } +} +``` + +**Step 8: 创建页面对象导出文件** + +```typescript +// 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: 提交** + +```bash +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: 创建性能监控器** + +```typescript +// 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 { + 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 { + 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 { + 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: 提交** + +```bash +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: 创建可访问性测试器** + +```typescript +// 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 { + 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 { + 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: 提交** + +```bash +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验证器** + +```typescript +// 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 { + 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 { + 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 { + 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 { + 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 { + 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: 提交** + +```bash +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** + +```typescript +// 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({ + 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** + +```typescript +// 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({ + performanceMonitor: async ({ page }, use) => { + const monitor = new PerformanceMonitor(page); + await use(monitor); + } +}); + +export { expect } from '@playwright/test'; +``` + +**Step 3: 创建可访问性测试fixture** + +```typescript +// 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({ + accessibilityTester: async ({ page }, use) => { + const tester = new AccessibilityTester(page); + await use(tester); + } +}); + +export { expect } from '@playwright/test'; +``` + +**Step 4: 提交** + +```bash +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: 创建共享层导出文件** + +```typescript +// 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: 提交** + +```bash +git add test-framework/shared/index.ts +git commit -m "feat: add shared layer export file" +``` + +### Task 11: 验证共享层完整性 + +**Files:** +- Test: 验证所有导出是否正常 + +**Step 1: 创建验证脚本** + +```typescript +// 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: 提交** + +```bash +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** + +```typescript +// 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** + +```typescript +// 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: 提交** + +```bash +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总结文档** + +```markdown +# 阶段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: 提交** + +```bash +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: 创建性能审计测试** + +```typescript +// 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检查测试** + +```typescript +// 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: 创建可访问性测试** + +```typescript +// 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: 创建表单验证测试** + +```typescript +// 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: 提交** + +```bash +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配置** + +```typescript +// 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: 提交** + +```bash +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** + +```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: 提交** + +```bash +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配置** + +```json +{ + "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: 提交** + +```bash +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: 提交** + +```bash +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总结文档** + +```markdown +# 阶段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: 提交** + +```bash +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: 提交** + +```bash +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: 创建测试报告生成器** + +```typescript +// 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 = new Map(); + + addResult(type: string, result: any): void { + this.results.set(type, result); + } + + generateHTMLReport(): string { + const timestamp = new Date().toLocaleString('zh-CN'); + + let html = ` + + + + + + 综合测试报告 - ${timestamp} + + + +
+

综合测试报告

+

生成时间: ${timestamp}

+
+`; + + for (const [type, result] of this.results.entries()) { + html += this.generateSection(type, result); + } + + 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 `

${type}

${JSON.stringify(result, null, 2)}
`; + } + } + + 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 ` +
+

可访问性测试

+
+ 平均分数: ${avgScore.toFixed(1)} + 总违规数: ${totalViolations} +
+ + + + + + + + + + ${results.map(r => ` + + + + + + `).join('')} + +
页面分数违规数
${r.page}${r.score}${r.violations.length}
+
`; + } + + private generateSEOSection(results: SEOResult[]): string { + const avgScore = results.reduce((sum, r) => sum + r.score, 0) / results.length; + + return ` +
+

SEO检查

+
+ 平均分数: ${avgScore.toFixed(1)} +
+ + + + + + + + + + + ${results.map(r => ` + + + + + + + `).join('')} + +
页面分数Meta标签标题
${r.page}${r.score}${r.metaTags.title && r.metaTags.description ? '✅' : '❌'}${r.headings.hasH1 ? '✅' : '❌'}
+
`; + } + + private generatePerformanceSection(results: PerformanceMetrics[]): string { + const avgLoadTime = results.reduce((sum, r) => sum + r.loadTime, 0) / results.length; + + return ` +
+

性能测试

+
+ 平均加载时间: ${avgLoadTime.toFixed(0)}ms +
+ + + + + + + + + + ${results.map(r => ` + + + + + + `).join('')} + +
页面加载时间DOM加载
${r.page}${r.loadTime}ms${r.domContentLoaded}ms
+
`; + } + + 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: 提交** + +```bash +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: 创建一键测试脚本** + +```bash +#!/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: 提交** + +```bash +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文档** + +```markdown +# 测试框架 + +统一的测试框架,整合了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 +``` + +### 运行测试 + +```bash +# 运行所有测试 +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 +``` + +### 查看报告 + +```bash +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: 提交** + +```bash +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总结文档** + +```markdown +# 阶段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: 提交** + +```bash +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流程 +- 添加更多测试覆盖 +- 优化测试执行性能 +- 添加测试覆盖率统计 \ No newline at end of file