PLAYWRIGHT.md 11.2 KB

Playwright E2E 测试指南

✅ 配置完成

你的项目现在已经配置了 Playwright E2E 测试框架!

📦 已安装的包

{
  "devDependencies": {
    "@playwright/test": "^1.58.0"
  }
}

已安装的浏览器:

  • Chromium (Chrome for Testing)
  • Chrome Headless Shell

📁 配置文件

文件 说明
playwright.config.js Playwright 主配置文件
e2e/example.spec.js 示例 E2E 测试
test-results/ 测试结果输出目录(.gitignore)
playwright-report/ HTML 报告输出目录(.gitignore)

🚀 快速开始

1. 运行所有测试

# 运行所有 E2E 测试
pnpm test:e2e

# 输出示例:
# Running 9 tests using 1 worker
#
# ✓ [chromium-mobile]: › example.spec.js:3:1 › 基础功能测试 › 首页加载成功 (2.5s)
# ✓ [chromium-mobile]: › example.spec.js:10:1 › 基础功能测试 › 导航功能正常 (1.8s)
# ...
#
# 9 passed (12.3s)

2. 查看测试报告

# 生成并打开 HTML 报告
pnpm test:e2e:report

# 报告会在浏览器中自动打开

3. 调试模式

# 调试模式(逐步执行)
pnpm test:e2e:debug

# 有头模式(可以看到浏览器操作)
pnpm test:e2e:headed

# UI 模式(可视化界面)
pnpm test:e2e:ui

📝 编写测试

基础测试结构

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

test.describe('测试套件', () => {
  test.beforeEach(async ({ page }) => {
    // 每个测试前执行
    await page.goto('/')
  })

  test('测试名称', async ({ page }) => {
    // 测试步骤
    await page.click('button')
    await expect(page).toHaveURL('/dashboard')
  })
})

常用操作

页面导航

// 访问 URL
await page.goto('/login')

// 等待导航完成
await page.waitForLoadState('networkidle')

// 等待特定 URL
await expect(page).toHaveURL(/\/dashboard/)

元素定位

// CSS 选择器
const button = page.locator('button.submit')
const input = page.locator('#username')
const element = page.locator('.class-name')

// 文本选择器
const link = page.locator('text=点击这里')
const title = page.locator('h1:has-text("欢迎")')

// XPath
const element = page.locator('//button[@type="submit"]')

交互操作

// 点击
await page.click('button')

// 输入
await page.fill('input[name="email"]', 'test@example.com')

// 选择下拉
await page.selectOption('select#country', 'China')

// 上传文件
await page.setInputFiles('input[type="file"]', 'file.pdf')

// 悬停
await page.hover('.menu-item')

# 滚动
await page.evaluate(() => window.scrollTo(0, 1000))

断言

// 元素可见性
await expect(page.locator('.header')).toBeVisible()

// 元素存在
await expect(page.locator('.error')).not.toBeVisible()

// 文本内容
await expect(page.locator('h1')).toHaveText('欢迎')

// 属性值
await expect(page.locator('input')).toHaveValue('test')

# URL
await expect(page).toHaveURL('/dashboard')

# 标题
await expect(page).toHaveTitle('首页')

高级用法

等待策略

// 等待元素出现
await page.waitForSelector('.loading', { state: 'hidden' })

// 等待特定时间
await page.waitForTimeout(1000)

// 等待网络响应
await page.waitForResponse('**/api/user')

// 等待函数条件
await page.waitForFunction(() => {
  return document.querySelector('.button').disabled === false
})

处理弹窗

// 处理 alert
page.on('dialog', dialog => {
  console.log(dialog.message())
  dialog.accept()
})
await page.click('button#alert')

// 处理 confirm
page.on('dialog', dialog => {
  dialog.dismiss() // 取消
})
await page.click('button#confirm')

截图和录制

// 截图
await page.screenshot({ path: 'screenshot.png' })

// 元素截图
await page.locator('.header').screenshot({ path: 'header.png' })

// 全页截图
await page.screenshot({ path: 'full.png', fullPage: true })

API 请求

// 拦截请求
page.route('**/api/user', route => {
  route.fulfill({
    status: 200,
    body: JSON.stringify({ name: 'Test' })
  })
})

// 监听响应
page.on('response', response => {
  if (response.url().includes('api/user')) {
    console.log(await response.json())
  }
})

📋 测试最佳实践

1. 测试命名

// ✓ GOOD - 清晰的描述
test('用户登录后应跳转到首页', async ({ page }) => {})

// ✗ BAD - 不明确的名称
test('test1', async ({ page }) => {})

2. 选择器优先级

// 优先级:data-testid > aria-label > 文本 > CSS 选择器

// ✓ BEST - 使用 data-testid
await page.click('[data-testid="submit-button"]')

// ✓ GOOD - 使用 aria-label
await page.click('button[aria-label="提交"]')

// ✓ OK - 使用文本
await page.click('text=提交')

// ✗ BAD - 脆弱的 CSS 选择器
await page.click('div.container > div:nth-child(3) > button')

3. Page Object Model

// pages/LoginPage.js
class LoginPage {
  constructor(page) {
    this.page = page
    this.usernameInput = page.locator('input[name="username"]')
    this.passwordInput = page.locator('input[name="password"]')
    this.submitButton = page.locator('button[type="submit"]')
  }

  async login(username, password) {
    await this.usernameInput.fill(username)
    await this.passwordInput.fill(password)
    await this.submitButton.click()
  }
}

// 测试中使用
test('用户登录', async ({ page }) => {
  const loginPage = new LoginPage(page)
  await loginPage.login('test@example.com', 'password')
  await expect(page).toHaveURL('/dashboard')
})

4. 测试数据管理

// 使用 fixtures 管理测试数据
const testUsers = {
  validUser: {
    email: 'test@example.com',
    password: 'password123',
  },
  invalidUser: {
    email: 'invalid@example.com',
    password: 'wrongpassword',
  },
}

test('登录成功', async ({ page }) => {
  await login(page, testUsers.validUser)
  await expect(page).toHaveURL('/dashboard')
})

🎯 测试场景示例

1. 完整的用户流程

test('新用户注册流程', async ({ page }) => {
  // 1. 访问注册页面
  await page.goto('/register')

  // 2. 填写表单
  await page.fill('input[name="email"]', 'test@example.com')
  await page.fill('input[name="password"]', 'password123')
  await page.fill('input[name="confirm"]', 'password123')

  // 3. 提交表单
  await page.click('button[type="submit"]')

  // 4. 验证跳转
  await expect(page).toHaveURL('/welcome')

  // 5. 验证提示信息
  const toast = page.locator('.van-toast--success')
  await expect(toast).toBeVisible()
})

2. 表单验证

test('登录表单验证', async ({ page }) => {
  await page.goto('/login')

  // 不输入任何信息,直接提交
  await page.click('button[type="submit"]')

  // 验证错误提示
  const errorToast = page.locator('.van-toast--fail')
  await expect(errorToast).toBeVisible()

  // 输入无效邮箱
  await page.fill('input[name="email"]', 'invalid-email')
  await page.click('button[type="submit"]')

  // 验证错误提示
  await expect(errorToast).toContainText('邮箱格式不正确')
})

3. 响应式测试

test.describe('响应式布局', () => {
  test('移动端显示底部导航', async ({ page }) => {
    await page.setViewportSize({ width: 375, height: 667 })
    await page.goto('/')

    const bottomNav = page.locator('.van-tabbar')
    await expect(bottomNav).toBeVisible()
  })

  test('桌面端显示侧边栏', async ({ page }) => {
    await page.setViewportSize({ width: 1280, height: 720 })
    await page.goto('/')

    const sidebar = page.locator('.sidebar')
    await expect(sidebar).toBeVisible()
  })
})

🔧 调试技巧

1. 使用 Playwright Inspector

# 启动 Inspector 模式
pnpm test:e2e:debug

# 在代码中添加断点
await page.pause()  // 暂停执行,打开 Inspector

2. 查看执行过程

# 有头模式(可以看到浏览器操作)
pnpm test:e2e --headed

# 慢动作模式
pnpm test:e2e --slow-mo=1000  # 每个操作延迟 1 秒

3. 详细日志

// 在测试中启用调试
test('调试示例', async ({ page }) => {
  // 启用详细日志
  page.on('console', msg => console.log(msg.text()))
  page.on('pageerror', err => console.log(err))

  await page.goto('/')
})

📊 测试报告

HTML 报告

# 生成 HTML 报告
pnpm test:e2e

# 查看报告
pnpm test:e2e:report

报告功能:

  • 查看所有测试结果
  • 查看截图和视频
  • 查看时间线
  • 追踪错误

JSON 报告

# JSON 报告会自动生成
cat test-results/test-results.json

JUnit 报告

# JUnit 报告用于 CI 集成
cat test-results/test-results.xml

🎭 配置选项

测试项目(projects)

playwright.config.js 中配置了 3 个测试项目:

projects: [
  {
    name: 'chromium-mobile',
    use: { ...devices['iPhone 12'] },
  },
  {
    name: 'chromium-desktop',
    use: { ...devices['Desktop Chrome'] },
  },
  {
    name: 'webkit-mobile',
    use: { ...devices['iPhone 12'], browserName: 'webkit' },
  },
]

运行特定项目

# 只运行移动端测试
pnpm test:e2e --project=chromium-mobile

# 只运行桌面端测试
pnpm test:e2e --project=chromium-desktop

失败重试

// CI 环境自动重试 2 次
retries: process.env.CI ? 2 : 0

超时配置

// 测试超时 30 秒
timeout: 30 * 1000

// 断言超时 5 秒
expect: {
  timeout: 5 * 1000
}

// 操作超时 10 秒
use: {
  actionTimeout: 10 * 1000
}

🚀 CI/CD 集成

GitHub Actions

name: E2E Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: 18

      - name: Install pnpm
        uses: pnpm/action-setup@v2

      - name: Install dependencies
        run: pnpm install

      - name: Install Playwright browsers
        run: npx playwright install --with-deps

      - name: Run E2E tests
        run: pnpm test:e2e

      - name: Upload test report
        if: always()
        uses: actions/upload-artifact@v3
        with:
          name: playwright-report
          path: playwright-report/

📚 参考资源

🎉 总结

Playwright 优势:

  • ✅ 跨浏览器支持(Chromium、Firefox、WebKit)
  • ✅ 跨平台支持(Windows、macOS、Linux)
  • ✅ 快速可靠(并行执行、自动等待)
  • ✅ 强大的调试工具(Inspector、Trace、Video)
  • ✅ 完整的测试报告
  • ✅ 移动端支持

下一步:

  • 编写核心功能的 E2E 测试
  • 集成到 CI/CD 流程
  • 定期更新测试用例

享受高质量的 E2E 测试!🚀