Vue 3 端到端测试

概述

端到端(E2E)测试是验证Vue 3应用在真实浏览器环境中完整功能流程的重要手段。与单元测试和组件测试不同,E2E测试模拟真实用户的交互行为,从用户界面开始,验证整个应用的功能链路。本集将深入探讨Vue 3的E2E测试生态系统,包括Cypress和Playwright两个主流测试框架的使用方法、测试编写技巧、与CI/CD集成,以及最佳实践和常见问题解决方案。

核心知识点

1. E2E测试框架介绍

Cypress

Cypress是一个流行的E2E测试框架,具有以下特点:

  • 基于JavaScript/TypeScript
  • 内置断言库
  • 实时重新加载
  • 强大的调试功能
  • 支持网络请求拦截
  • 支持截图和视频录制
  • 支持CI/CD集成

Playwright

Playwright是微软开发的E2E测试框架,具有以下特点:

  • 支持多种浏览器(Chrome、Firefox、Safari)
  • 支持多种编程语言(JavaScript/TypeScript、Python、Java、C#)
  • 自动等待机制
  • 支持移动端测试
  • 支持并行测试
  • 支持网络请求拦截
  • 支持截图和视频录制

2. Cypress 安装与配置

安装

# 安装Cypress
npm install -D cypress

# 初始化Cypress(创建配置文件和测试目录)
npx cypress open

配置

Cypress的配置文件是cypress.config.ts

import { defineConfig } from 'cypress'

export default defineConfig({
  e2e: {
    baseUrl: 'http://localhost:5173', // Vue开发服务器地址
    setupNodeEvents(on, config) {
      // 自定义事件处理
    },
    specPattern: 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}',
    supportFile: 'cypress/support/e2e.{js,jsx,ts,tsx}'
  },
  video: true, // 启用视频录制
  screenshotsFolder: 'cypress/screenshots',
  videosFolder: 'cypress/videos'
})

package.json中添加脚本:

{
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview",
    "test:e2e": "cypress open",
    "test:e2e:run": "cypress run"
  }
}

3. Cypress 基本使用

编写第一个测试

创建cypress/e2e/home.cy.ts

describe('Home Page', () => {
  beforeEach(() => {
    // 访问应用首页
    cy.visit('/')
  })
  
  it('displays the home page title', () => {
    // 断言页面标题
    cy.title().should('include', 'Vue 3 App')
    
    // 断言页面内容
    cy.contains('h1', 'Welcome to Vue 3')
  })
  
  it('navigates to about page', () => {
    // 点击导航链接
    cy.contains('About').click()
    
    // 断言URL
    cy.url().should('include', '/about')
    
    // 断言页面内容
    cy.contains('h1', 'About Us')
  })
})

表单测试

describe('Login Form', () => {
  it('logs in successfully', () => {
    cy.visit('/login')
    
    // 填写表单
    cy.get('input[name="username"]').type('admin')
    cy.get('input[name="password"]').type('password123')
    
    // 提交表单
    cy.get('button[type="submit"]').click()
    
    // 断言登录成功
    cy.url().should('eq', 'http://localhost:5173/dashboard')
    cy.contains('Welcome, admin')
  })
  
  it('shows error for invalid credentials', () => {
    cy.visit('/login')
    
    // 填写无效凭据
    cy.get('input[name="username"]').type('invalid')
    cy.get('input[name="password"]').type('invalid123')
    
    // 提交表单
    cy.get('button[type="submit"]').click()
    
    // 断言错误信息
    cy.contains('.error', 'Invalid username or password')
  })
})

网络请求拦截

describe('API Testing', () => {
  it('fetches and displays users', () => {
    // 拦截API请求
    cy.intercept('GET', '/api/users', {
      statusCode: 200,
      body: [
        { id: 1, name: 'John' },
        { id: 2, name: 'Jane' }
      ]
    }).as('getUsers')
    
    cy.visit('/users')
    
    // 等待请求完成
    cy.wait('@getUsers')
    
    // 断言用户列表
    cy.contains('John')
    cy.contains('Jane')
  })
})

4. Playwright 安装与配置

安装

# 安装Playwright
npm install -D playwright

# 安装浏览器
npx playwright install

# 初始化Playwright(创建配置文件和测试目录)
npx playwright init

配置

Playwright的配置文件是playwright.config.ts

import { defineConfig, devices } from '@playwright/test'

export default defineConfig({
  testDir: './tests',
  timeout: 30 * 1000,
  expect: {
    timeout: 5000
  },
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: 'html',
  use: {
    baseURL: 'http://localhost:5173',
    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'] },
    }
  ],
  webServer: {
    command: 'npm run dev',
    url: 'http://localhost:5173',
    reuseExistingServer: !process.env.CI,
  },
})

package.json中添加脚本:

{
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview",
    "test:e2e": "playwright test",
    "test:e2e:ui": "playwright test --ui",
    "test:e2e:report": "playwright show-report"
  }
}

5. Playwright 基本使用

编写第一个测试

创建tests/home.spec.ts

import { test, expect } from '@playwright/test'

test.describe('Home Page', () => {
  test.beforeEach(async ({ page }) => {
    // 访问应用首页
    await page.goto('/')
  })
  
  test('displays the home page title', async ({ page }) => {
    // 断言页面标题
    await expect(page).toHaveTitle(/Vue 3 App/)
    
    // 断言页面内容
    await expect(page.locator('h1')).toContainText('Welcome to Vue 3')
  })
  
  test('navigates to about page', async ({ page }) => {
    // 点击导航链接
    await page.click('text=About')
    
    // 断言URL
    await expect(page).toHaveURL(/about/)
    
    // 断言页面内容
    await expect(page.locator('h1')).toContainText('About Us')
  })
})

表单测试

import { test, expect } from '@playwright/test'

test.describe('Login Form', () => {
  test('logs in successfully', async ({ page }) => {
    await page.goto('/login')
    
    // 填写表单
    await page.fill('input[name="username"]', 'admin')
    await page.fill('input[name="password"]', 'password123')
    
    // 提交表单
    await page.click('button[type="submit"]')
    
    // 断言登录成功
    await expect(page).toHaveURL('http://localhost:5173/dashboard')
    await expect(page).toContainText('Welcome, admin')
  })
  
  test('shows error for invalid credentials', async ({ page }) => {
    await page.goto('/login')
    
    // 填写无效凭据
    await page.fill('input[name="username"]', 'invalid')
    await page.fill('input[name="password"]', 'invalid123')
    
    // 提交表单
    await page.click('button[type="submit"]')
    
    // 断言错误信息
    await expect(page.locator('.error')).toContainText('Invalid username or password')
  })
})

网络请求拦截

import { test, expect } from '@playwright/test'

test.describe('API Testing', () => {
  test('fetches and displays users', async ({ page }) => {
    // 拦截API请求
    await page.route('/api/users', async route => {
      await route.fulfill({
        status: 200,
        body: JSON.stringify([
          { id: 1, name: 'John' },
          { id: 2, name: 'Jane' }
        ])
      })
    })
    
    await page.goto('/users')
    
    // 断言用户列表
    await expect(page).toContainText('John')
    await expect(page).toContainText('Jane')
  })
})

6. E2E 测试最佳实践

1. 测试结构设计

  • 按功能模块组织测试文件
  • 每个测试文件测试一个功能模块
  • 使用describeit组织测试用例
  • 使用beforeEachafterEach设置测试环境

2. 测试编写原则

  • 测试用户实际使用场景
  • 避免测试实现细节
  • 保持测试的独立性
  • 测试边界情况
  • 使用有意义的测试名称

3. 选择器策略

  • 优先使用data-testid属性
  • 避免使用CSS选择器(容易受样式变化影响)
  • 避免使用XPath(可读性差)
  • 避免使用文本内容(容易受翻译影响)
<!-- 推荐 -->
<button data-testid="login-button">登录</button>

<!-- 不推荐 -->
<button class="btn-primary">登录</button>

4. 性能优化

  • 减少测试的依赖项
  • 并行运行测试
  • 合理设置测试超时时间
  • 避免在测试中使用真实的API(使用Mock或Stub)
  • 清理测试数据

5. 与CI/CD集成

  • 在CI/CD流程中运行E2E测试
  • 配置测试结果报告
  • 保存测试截图和视频
  • 失败时自动通知

7. Cypress CI/CD 配置

GitHub Actions 配置

创建.github/workflows/cypress.yml

name: Cypress E2E Tests

on: [push, pull_request]

jobs:
  cypress-run:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v3
      
      - name: Set up Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Start development server
        run: npm run dev &
        
      - name: Wait for server to start
        run: npx wait-on http://localhost:5173
      
      - name: Run Cypress tests
        uses: cypress-io/github-action@v5
        with:
          command: npx cypress run
      
      - name: Upload screenshots
        uses: actions/upload-artifact@v3
        if: failure()
        with:
          name: cypress-screenshots
          path: cypress/screenshots
      
      - name: Upload videos
        uses: actions/upload-artifact@v3
        if: failure()
        with:
          name: cypress-videos
          path: cypress/videos

8. Playwright CI/CD 配置

GitHub Actions 配置

创建.github/workflows/playwright.yml

name: Playwright E2E Tests

on: [push, pull_request]

jobs:
  playwright-run:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v3
      
      - name: Set up Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Install Playwright browsers
        run: npx playwright install --with-deps
      
      - name: Run Playwright tests
        run: npm run test:e2e
      
      - name: Upload test report
        uses: actions/upload-artifact@v3
        if: always()
        with:
          name: playwright-report
          path: playwright-report
          retention-days: 30

最佳实践

  1. 测试覆盖范围

    • 测试核心业务流程
    • 测试用户最常用的功能
    • 测试关键路径
    • 测试边界情况
    • 测试错误处理
  2. 测试环境管理

    • 使用专门的测试环境
    • 确保测试环境的一致性
    • 自动清理测试数据
    • 使用Mock或Stub替代真实API
  3. 测试维护

    • 定期更新测试用例
    • 修复失败的测试
    • 删除过时的测试
    • 优化测试性能
  4. 测试报告

    • 生成清晰的测试报告
    • 包含测试结果、截图和视频
    • 定期分析测试覆盖率
    • 持续改进测试策略
  5. 团队协作

    • 建立测试规范
    • 培训团队成员
    • 定期审查测试用例
    • 分享测试经验

常见问题与解决方案

1. 测试速度问题

问题:E2E测试运行速度慢

解决方案

  • 并行运行测试
  • 减少测试的依赖项
  • 使用Mock或Stub替代真实API
  • 合理设置测试超时时间
  • 避免在测试中使用sleep

2. 测试稳定性问题

问题:测试经常失败,出现不稳定情况

解决方案

  • 使用合适的选择器(优先使用data-testid)
  • 避免测试实现细节
  • 使用自动等待机制
  • 增加适当的断言
  • 清理测试数据

3. 测试环境配置问题

问题:测试在本地运行正常,但在CI/CD环境中失败

解决方案

  • 确保测试环境的一致性
  • 使用Docker容器化测试环境
  • 配置正确的环境变量
  • 确保测试服务器正常启动

4. 测试数据管理问题

问题:测试数据难以管理,导致测试之间相互影响

解决方案

  • 每个测试使用独立的测试数据
  • 使用测试数据工厂生成测试数据
  • 测试完成后清理测试数据
  • 使用事务回滚(适用于数据库测试)

5. 测试报告问题

问题:测试报告不清晰,难以分析失败原因

解决方案

  • 配置详细的测试报告
  • 启用截图和视频录制
  • 记录详细的日志
  • 使用可视化测试报告

进一步学习资源

  1. Cypress 官方文档
  2. Playwright 官方文档
  3. Vue 3 测试指南
  4. Cypress 最佳实践
  5. Playwright 最佳实践
  6. E2E 测试策略

课后练习

  1. 练习1:Cypress 基础测试

    • 安装并配置Cypress
    • 编写测试用例测试首页和导航功能
    • 运行测试并查看结果
  2. 练习2:Cypress 表单测试

    • 编写测试用例测试登录表单
    • 测试成功登录和失败登录场景
    • 测试表单验证功能
  3. 练习3:Cypress API 测试

    • 编写测试用例测试API请求
    • 使用cy.intercept拦截网络请求
    • 测试不同的API响应情况
  4. 练习4:Playwright 基础测试

    • 安装并配置Playwright
    • 编写测试用例测试首页和导航功能
    • 运行测试并查看结果
  5. 练习5:Playwright 多浏览器测试

    • 配置Playwright支持多种浏览器
    • 运行测试并查看不同浏览器的测试结果
    • 分析测试报告
  6. 练习6:CI/CD 集成

    • 配置GitHub Actions运行E2E测试
    • 测试推送代码时自动运行测试
    • 查看测试报告和日志

通过本集的学习,你应该对Vue 3的E2E测试有了全面的了解。E2E测试是确保Vue应用质量的重要手段,合理使用E2E测试框架和编写高质量的测试用例,将有助于提高Vue应用的可靠性和稳定性。

« 上一篇 Vue 3组件测试与单元测试 - 确保应用质量的测试策略 下一篇 » Vue 3性能优化与调试 - 构建高性能前端应用