Playwright 自动化端到端测试¶
一句话概述:Playwright 是微软出品的 E2E 测试框架,一套 API 驱动 Chromium/Firefox/WebKit 三大浏览器,自动等待元素就绪、内置追踪和截图,2026 年还集成了 AI 辅助测试生成(MCP)。
核心知识点¶
| 概念 | 白话解释 |
|---|---|
| E2E 测试 | 端到端测试,模拟真实用户操作(点击、输入、跳转)来验证整个应用 |
| 自动等待 | 点击按钮前会自动等它出现并可点击,不用手动写 sleep |
| Locator | 定位器,用来找页面上的元素,推荐用 getByRole 等语义化方式 |
| Browser Context | 浏览器上下文,每个测试相当于一个全新的隐身窗口 |
| Trace | 追踪,录制测试执行的每一步(截图、网络请求、DOM 快照) |
| Codegen | 代码生成器,录制你的操作自动生成测试代码 |
| Playwright MCP | 2026 新功能,让 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 测试报告
代码生成器(录制测试)¶
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 violation | Locator 匹配到多个元素 | 用 .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 | 入门指南