跳转至

Playwright 自动化端到端测试

一句话概述:Playwright 是微软出品的 E2E 测试框架,一套 API 驱动 Chromium/Firefox/WebKit 三大浏览器,自动等待元素就绪、内置追踪和截图,2026 年还集成了 AI 辅助测试生成(MCP)。

核心知识点

概念白话解释
E2E 测试端到端测试,模拟真实用户操作(点击、输入、跳转)来验证整个应用
自动等待点击按钮前会自动等它出现并可点击,不用手动写 sleep
Locator定位器,用来找页面上的元素,推荐用 getByRole 等语义化方式
Browser Context浏览器上下文,每个测试相当于一个全新的隐身窗口
Trace追踪,录制测试执行的每一步(截图、网络请求、DOM 快照)
Codegen代码生成器,录制你的操作自动生成测试代码
Playwright MCP2026 新功能,让 AI Agent 用自然语言控制浏览器

安装配置

# 方式一:新项目初始化(推荐)
npm init playwright@latest  # 交互式初始化,自动创建配置和示例测试
# 会问你:用 TypeScript 还是 JavaScript?测试放哪个目录?要不要装浏览器?

# 方式二:已有项目添加
npm install -D @playwright/test  # 安装 Playwright 测试框架
npx playwright install  # 下载浏览器(Chromium、Firefox、WebKit)

配置文件

// playwright.config.ts - 测试配置
import { defineConfig, devices } from '@playwright/test'  // 导入配置函数

export default defineConfig({
  testDir: './tests',  // 测试文件目录
  fullyParallel: true,  // 所有测试并行执行
  forbidOnly: !!process.env.CI,  // CI 环境禁止 .only(防止忘记去掉)
  retries: process.env.CI ? 2 : 0,  // CI 环境失败重试 2 次
  workers: process.env.CI ? 1 : undefined,  // CI 单线程,本地自动
  reporter: 'html',  // 生成 HTML 报告

  use: {
    baseURL: 'http://localhost:3000',  // 测试的基础 URL
    trace: 'on-first-retry',  // 第一次重试时录制 trace(调试用)
    screenshot: 'only-on-failure',  // 失败时自动截图
  },

  projects: [  // 多浏览器配置
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },  // Chrome 桌面端
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },  // Firefox
    },
    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'] },  // Safari
    },
    {
      name: 'Mobile Chrome',
      use: { ...devices['Pixel 5'] },  // 手机端 Chrome
    },
  ],

  webServer: {  // 测试前自动启动开发服务器
    command: 'npm run dev',  // 启动命令
    url: 'http://localhost:3000',  // 等待这个 URL 可访问
    reuseExistingServer: !process.env.CI,  // 本地复用已启动的服务器
  },
})

基本使用

编写第一个测试

// tests/example.spec.ts
import { test, expect } from '@playwright/test'  // 导入测试和断言

test('首页标题正确', async ({ page }) => {  // page 是浏览器页面对象
  await page.goto('/')  // 访问首页
  await expect(page).toHaveTitle(/我的网站/)  // 断言标题包含"我的网站"
})

test('登录流程', async ({ page }) => {
  await page.goto('/login')  // 访问登录页

  // 用语义化 Locator 定位元素(推荐!)
  await page.getByLabel('邮箱').fill('test@example.com')  // 找到"邮箱"标签的输入框,填入值
  await page.getByLabel('密码').fill('password123')  // 找到"密码"标签的输入框

  await page.getByRole('button', { name: '登录' }).click()  // 找到名为"登录"的按钮并点击

  // 断言:登录成功后应该跳转到仪表板
  await expect(page).toHaveURL('/dashboard')  // 验证 URL
  await expect(page.getByText('欢迎回来')).toBeVisible()  // 验证文字可见
})

运行测试

npx playwright test  # 运行所有测试
npx playwright test tests/login.spec.ts  # 运行指定文件
npx playwright test --headed  # 打开浏览器窗口看测试过程
npx playwright test --ui  # 打开交互式 UI 模式
npx playwright test --debug  # 调试模式,一步一步执行
npx playwright test --project=chromium  # 只用 Chrome 测试
npx playwright show-report  # 查看 HTML 测试报告

代码生成器(录制测试)

npx playwright codegen http://localhost:3000  # 打开浏览器,操作会自动生成代码
# 你在浏览器里点击、输入,右侧面板实时生成 Playwright 代码

Locator 定位元素

// 推荐的语义化 Locator(更稳定,不怕 CSS 改动)
page.getByRole('button', { name: '提交' })  // 通过角色和名称找按钮
page.getByLabel('用户名')  // 通过 label 找输入框
page.getByPlaceholder('请输入搜索内容')  // 通过 placeholder 找
page.getByText('欢迎回来')  // 通过文字内容找
page.getByTestId('submit-btn')  // 通过 data-testid 找(测试专用)

// CSS/XPath 选择器(不推荐,但有时需要)
page.locator('.my-class')  // CSS 类名
page.locator('#my-id')  // CSS ID
page.locator('xpath=//div[@class="content"]')  // XPath

// 链式过滤
page.getByRole('listitem')  // 找到所有列表项
  .filter({ hasText: '重要' })  // 过滤包含"重要"的
  .first()  // 取第一个

高级用法

页面对象模式(POM)

// pages/login-page.ts - 封装登录页操作
import { Page, Locator } from '@playwright/test'

export class LoginPage {
  readonly page: Page
  readonly emailInput: Locator  // 邮箱输入框
  readonly passwordInput: Locator  // 密码输入框
  readonly submitButton: Locator  // 提交按钮

  constructor(page: Page) {
    this.page = page
    this.emailInput = page.getByLabel('邮箱')  // 定义 Locator
    this.passwordInput = page.getByLabel('密码')
    this.submitButton = page.getByRole('button', { name: '登录' })
  }

  async goto() {
    await this.page.goto('/login')  // 导航到登录页
  }

  async login(email: string, password: string) {
    await this.emailInput.fill(email)  // 填邮箱
    await this.passwordInput.fill(password)  // 填密码
    await this.submitButton.click()  // 点击登录
  }
}

// tests/login.spec.ts - 使用页面对象
import { LoginPage } from '../pages/login-page'

test('登录成功', async ({ page }) => {
  const loginPage = new LoginPage(page)  // 创建页面对象
  await loginPage.goto()
  await loginPage.login('test@example.com', '123456')  // 调用封装的方法
  await expect(page).toHaveURL('/dashboard')
})

API 测试

test('API 接口测试', async ({ request }) => {  // request 用于直接调 API
  // POST 请求
  const response = await request.post('/api/users', {  // 发送 POST
    data: { name: '张三', email: 'zhang@test.com' },  // 请求体
  })
  expect(response.ok()).toBeTruthy()  // 断言响应成功(2xx)

  const user = await response.json()  // 解析 JSON
  expect(user.name).toBe('张三')  // 验证返回数据
})

截图与视觉回归

test('页面截图对比', async ({ page }) => {
  await page.goto('/dashboard')
  // 第一次运行会保存基准截图,之后每次运行对比差异
  await expect(page).toHaveScreenshot('dashboard.png', {
    maxDiffPixels: 100,  // 允许最多 100 个像素不同
  })
})

模拟网络请求

test('模拟 API 响应', async ({ page }) => {
  // 拦截 API 请求,返回模拟数据
  await page.route('/api/users', async (route) => {
    await route.fulfill({  // 用模拟数据响应
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify([{ id: 1, name: '模拟用户' }]),
    })
  })

  await page.goto('/users')  // 页面会收到模拟数据
  await expect(page.getByText('模拟用户')).toBeVisible()
})

认证状态复用

// auth.setup.ts - 登录一次,保存状态给其他测试用
import { test as setup } from '@playwright/test'

setup('登录', async ({ page }) => {
  await page.goto('/login')
  await page.getByLabel('邮箱').fill('admin@test.com')
  await page.getByLabel('密码').fill('admin123')
  await page.getByRole('button', { name: '登录' }).click()
  await page.waitForURL('/dashboard')

  await page.context().storageState({ path: '.auth/user.json' })  // 保存登录状态
})

常见报错

报错信息原因解决方案
Timeout 30000ms exceeded元素没出现或操作超时检查 Locator 是否正确,或增加超时时间
locator.click: Target closed页面在操作过程中关闭了检查是否有意外的页面跳转或弹窗
Browser was not installed浏览器没下载运行 npx playwright install
net::ERR_CONNECTION_REFUSED测试的服务没启动配置 webServer 自动启动
strict mode violationLocator 匹配到多个元素.first() / .nth(0) 或更精确的定位
截图对比失败不同环境渲染有差异maxDiffPixels 或在 Docker 里跑

速查表

# CLI 命令
npx playwright test                    # 运行所有测试
npx playwright test --headed           # 有头模式(看浏览器)
npx playwright test --ui               # 交互式 UI
npx playwright test --debug            # 调试模式
npx playwright test -g "登录"          # 按名称过滤测试
npx playwright codegen <url>           # 代码生成器
npx playwright show-report             # 查看报告
npx playwright install                 # 安装浏览器
npx playwright test --trace on         # 开启 trace

# 常用断言
expect(page).toHaveTitle(/xxx/)        # 页面标题
expect(page).toHaveURL('/path')        # 页面 URL
expect(locator).toBeVisible()          # 元素可见
expect(locator).toBeHidden()           # 元素隐藏
expect(locator).toHaveText('xxx')      # 元素文字
expect(locator).toHaveValue('xxx')     # 输入框值
expect(locator).toBeEnabled()          # 可操作
expect(locator).toBeDisabled()         # 已禁用
expect(locator).toHaveCount(n)         # 匹配数量
expect(locator).toHaveAttribute(k, v)  # HTML 属性
expect(page).toHaveScreenshot()        # 截图对比

# 常用操作
page.goto(url)                 # 导航
locator.click()                # 点击
locator.fill(text)             # 填入文字
locator.press('Enter')         # 按键
locator.selectOption(value)    # 选择下拉项
page.waitForURL(url)           # 等待 URL 变化
page.waitForResponse(url)      # 等待网络响应

参考:Playwright 官网 | GitHub | 入门指南