一、Playwright 是什么?为什么选择它?
Playwright 是由 Microsoft 开发的现代化端到端(End-to-End, E2E)Web 自动化测试框架,于 2020 年正式开源。它区别于传统 Selenium 的核心优势在于:
-
自动等待(Auto-Wait)机制 :在执行点击、输入、断言前自动等待元素可见/可交互/稳定,彻底告别
time.sleep()导致的脆弱测试。 -
全浏览器引擎支持:统一 API 驱动 Chromium(Chrome/Edge)、Firefox、WebKit(Safari),跨平台 Windows/macOS/Linux。
-
多语言绑定:原生支持 TypeScript/JavaScript(首选)、Python、Java、.NET(C#)。
-
内置强大工具链:测试代码录制(Codegen)、追踪查看器(Trace Viewer)、UI Mode 调试、内置 HTML/JSON 报告器。
-
无头/有头模式:默认无头(Headless)运行于 CI,开发时可开启有头模式观察执行过程。
-
网络拦截与 Mock:可拦截 HTTP 请求、Mock API 响应,方便前后端分离测试。
💡 与 Selenium 对比:Playwright 安装更简单(一条命令下载浏览器)、执行速度更快(基于 DevTools Protocol/CDP 而非 WebDriver 协议)、自动等待减少 Flaky 测试,是现代 Web 应用 E2E 测试的首选。
二、环境搭建
以下分别给出 TypeScript/JavaScript(官方推荐) 和 **Python(pytest + playwright)** 两种主流方案的完整搭建步骤。
2.1 Node.js + Playwright(TypeScript)方案
前置条件
-
Node.js ≥ 18(建议 LTS 版本)
-
npm / yarn / pnpm
初始化项目
# 创建项目目录
mkdir playwright-demo && cd playwright-demo
# 初始化 Playwright(交互式引导)
npm init playwright@latest
运行 npm init playwright@latest后会询问:
-
测试目录名(默认
tests/)→ 回车 -
是否添加 GitHub Actions 工作流 → 可选 Y
-
是否安装浏览器(Chromium/Firefox/WebKit)→ Y
安装完成后目录结构:
playwright-demo/
├── playwright.config.ts # 核心配置文件
├── package.json
├── tests/
│ └── example.spec.ts # 示例测试用例
└── tests-examples/ # 更多示例
安装浏览器(如未自动安装)
npx playwright install
# 只安装 Chromium
npx playwright install chromium
2.2 Python + pytest-playwright 方案
前置条件
-
Python ≥ 3.8
安装核心包
pip install pytest playwright pytest-playwright
安装浏览器
playwright install
(可选)安装报告插件
pip install allure-pytest pytest-html
Python 方案使用 pytest作为测试运行器,@playwright/test的同步/异步 API 通过 playwright.sync_api或 playwright.async_api调用。
三、第一个自动化测试用例
3.1 TypeScript 版本------百度搜索测试
tests/baidu_search.spec.ts:
import { test, expect } from '@playwright/test';
test.describe('百度搜索功能', () => {
test('搜索 Playwright 并验证标题', async ({ page }) => {
// 1. 打开网页
await page.goto('https://www.baidu.com');
// 2. 填写搜索框(Playwright 推荐使用 getByRole / getByPlaceholder)
await page.getByPlaceholder('请输入搜索关键词').fill('Playwright');
// 3. 点击搜索按钮
await page.getByRole('button', { name: '百度一下' }).click();
// 4. 等待导航完成并断言
await expect(page).toHaveTitle(/Playwright/);
await expect(page.locator('#content_left')).toBeVisible();
});
});
运行:
# 无头模式运行
npx playwright test
# 有头模式 + 慢动作观察
npx playwright test tests/baidu_search.spec.ts --headed --slowmo=1000
# UI 模式(推荐调试用)
npx playwright test --ui
3.2 Python 版本------百度搜索测试
tests/test_baidu.py:
import pytest
from playwright.sync_api import Page, expect
def test_baidu_search(page: Page):
page.goto("https://www.baidu.com")
page.get_by_placeholder("请输入搜索关键词").fill("Playwright")
page.get_by_role("button", name="百度一下").click()
expect(page).to_have_title(/Playwright/)
运行:
pytest tests/test_baidu.py --headed
四、Playwright 核心概念详解
4.1 Locators(定位器)------测试稳定性的关键
Playwright 推荐按用户可感知的特征定位,优先级如下:
| 推荐度 | 方式 | 示例 |
|---|---|---|
| ⭐⭐⭐⭐⭐ 最推荐 | getByRole |
page.getByRole('button', { name: '提交' }) |
| ⭐⭐⭐⭐⭐ | getByLabel |
page.getByLabel('用户名').fill('admin') |
| ⭐⭐⭐⭐⭐ | getByPlaceholder |
page.getByPlaceholder('请输入密码') |
| ⭐⭐⭐⭐ | getByText |
page.getByText('登录成功').click() |
| ⭐⭐⭐ | getByTestId(需加 data-testid) |
page.getByTestId('submit-btn') |
| ⚠️ 不推荐 | CSS Selector / XPath | page.locator('#kw')------ 易随 UI 变动失效 |
✅ 最佳实践:与前端约定给关键元素加
data-testid="xxx",用getByTestId定位,兼顾语义稳定和抗重构。
4.2 Actions(操作)
// 点击
await page.getByRole('button', { name: '登录' }).click();
// 输入(逐字符输入,可带延迟)
await page.getByLabel('密码').fill('123456');
// 双击 / 右键
await page.getByText('Item').dblclick();
await page.getByText('Item').click({ button: 'right' });
// 下拉框
await page.getByRole('combobox').selectOption('value2');
// 文件上传
await page.getByLabel('上传头像').setInputFiles('avatar.png');
// 键盘 / 鼠标
await page.keyboard.press('Control+A');
await page.mouse.wheel(0, 500);
4.3 Assertions(断言)
Playwright 使用 expect配合自动重试断言(Soft Assert 也支持):
// 元素可见
await expect(page.getByText('欢迎')).toBeVisible();
// URL 匹配
await expect(page).toHaveURL(/dashboard/);
// 输入框值
await expect(page.getByLabel('邮箱')).toHaveValue('a@b.com');
// 元素包含文本
await expect(page.locator('.msg')).toContainText('保存成功');
// 元素不存在
await expect(page.getByText('错误提示')).not.toBeVisible();
// 软断言(一个失败不阻断后续检查)
await expect.soft(page.getByText('A')).toBeVisible();
await expect.soft(page.getByText('B')).toBeVisible();
4.4 Fixtures 与 Hooks
test.beforeAll(async ({ browser }) => {
// 整个 describe 块前执行一次,适合全局登录获取 token
});
test.beforeEach(async ({ page }) => {
// 每个测试前执行,常用跳转登录页
await page.goto('/login');
await page.fill('#user', 'admin');
await page.fill('#pass', '123456');
await page.click('button[type=submit]');
});
test.afterEach(async ({ page }) => {
// 每个测试后清理
});
test.afterAll(async () => {
// 全部结束后执行
});
{ page }、{ browser }、{ context }、{ request }(API 测试)均为 Playwright 内置 Fixture,自动注入。
五、Page Object Model(POM)------可维护框架基石
随着用例增多,直接把定位器和操作写在 spec 文件中会导致大量重复代码。Page Object Model 将每个页面封装为一个类:
目录结构示例
tests/
├── login.spec.ts
├── dashboard.spec.ts
└── pages/
├── LoginPage.ts
└── DashboardPage.ts
pages/LoginPage.ts
import { Page, Locator, expect } from '@playwright/test';
export class LoginPage {
readonly page: Page;
readonly usernameInput: Locator;
readonly passwordInput: Locator;
readonly loginBtn: Locator;
readonly errorMsg: Locator;
constructor(page: Page) {
this.page = page;
this.usernameInput = page.getByLabel('用户名');
this.passwordInput = page.getByLabel('密码');
this.loginBtn = page.getByRole('button', { name: '登录' });
this.errorMsg = page.locator('.error-message');
}
async goto() {
await this.page.goto('/login');
}
async login(user: string, pwd: string) {
await this.usernameInput.fill(user);
await this.passwordInput.fill(pwd);
await this.loginBtn.click();
}
async assertLoginSuccess() {
await expect(this.page).toHaveURL(/dashboard/);
}
async assertLoginError(msg: string) {
await expect(this.errorMsg).toHaveText(msg);
}
}
使用 POM 编写测试
import { test } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';
test('正常登录', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('admin', '123456');
await loginPage.assertLoginSuccess();
});
test('密码错误提示', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('admin', 'wrong');
await loginPage.assertLoginError('用户名或密码错误');
});
POM 使定位器变更只需改一处,测试代码只描述业务流,清晰且可复用。
六、测试报告生成------重点!
Playwright 内置多种 Reporter,也支持第三方 Allure 报告。
6.1 内置 HTML 报告(最简单,开箱即用)
在 playwright.config.ts中配置:
import { defineConfig } from '@playwright/test';
export default defineConfig({
reporter: [['html', { open: 'never', outputFolder: 'playwright-report' }]],
// 失败时自动截图 + 追踪
use: {
screenshot: 'only-on-failure',
trace: 'retain-on-failure',
video: 'retain-on-failure',
},
});
运行后查看:
npx playwright test
npx playwright show-report
内置 HTML 报告支持:按浏览器/状态筛选、查看失败截图、下载 Trace ZIP 进行时间旅行调试。
6.2 Allure 报告(企业级,图形化趋势+分类)
TypeScript 项目集成 Allure
npm install -D allure-playwright allure-commandline
playwright.config.ts:
reporter: [
['list'],
['allure-playwright', { resultsDir: 'allure-results' }],
],
运行与生成报告:
# 执行测试
npx playwright test
# 生成并打开报告
allure generate allure-results --clean -o allure-report
allure open allure-report
# 或直接 serve
allure serve allure-results
在用例中添加 Allure 元数据:
import * as allure from 'allure-js-commons';
test('登录功能 - 正常', async ({ page }) => {
await allure.epic('用户认证');
await allure.feature('登录');
await allure.story('有效账号登录');
await allure.severity('critical');
await allure.owner('QA Team');
await page.goto('/login');
// ... 测试步骤
const shot = await page.screenshot();
await allure.attachment('登录页截图', shot, 'image/png');
});
Python + pytest 集成 Allure
pip install allure-pytest
pytest --alluredir=allure-results
allure serve allure-results
6.3 pytest-html(Python 轻量报告)
pip install pytest-html
pytest --html=report.html --self-contained-html
七、playwright.config.ts 核心配置详解
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
// 全局超时(单用例默认 30s)
timeout: 30000,
// 并行 worker 数(CI 可调大)
workers: process.env.CI ? 4 : undefined,
// 测试文件匹配规则
testMatch: '**/*.spec.ts',
// 多浏览器项目
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
],
// 基础 URL(page.goto('/login') 会拼此地址)
use: {
baseURL: 'http://localhost:3000',
headless: !process.env.HEADED,
viewport: { width: 1280, height: 720 },
// 自动截图/trace/video
screenshot: 'only-on-failure',
trace: 'retain-on-failure',
video: 'retain-on-failure',
// 全局 HTTP 凭证(内网系统)
// httpCredentials: { username: 'user', password: 'pass' },
},
// 报告器
reporter: [
['list'],
['html', { open: 'never' }],
],
// 全局 setup(如共享登录获取 storageState)
// globalSetup: require.resolve('./global-setup'),
// storageState: 'storageState.json',
});
八、高级特性
8.1 Codegen------录制生成测试代码
无需手写初始代码,让 Playwright 录制你的操作:
npx playwright codegen https://www.baidu.com
浏览器中你做的每一步点击/输入,Inspector 都会生成对应代码,可复制进 .spec.ts。注意录制不会自动加断言,需手动补充 expect。
8.2 Trace Viewer------时间旅行调试
// 在 beforeEach 中开启
await context.tracing.start({ screenshots: true, snapshots: true, sources: true });
// 在 afterEach(失败时)停止
await context.tracing.stop({ path: 'trace.zip' });
# 查看
npx playwright show-trace trace.zip
可拖拽时间轴查看每步 DOM 快照、网络请求、控制台日志,是排查 Flaky 测试的神器。
8.3 API 测试(混合测试)
Playwright 内置 requestFixture 可做接口测试或 Mock:
test('API 获取用户列表', async ({ request }) => {
const res = await request.get('/api/users');
expect(res.ok()).toBeTruthy();
const body = await res.json();
expect(body.data).toHaveLength(3);
});
8.4 失败重试与超时调优
# 失败自动重试 2 次(适合偶尔网络抖动)
npx playwright test --retries=2
# 只运行标记 smoke 的用例
npx playwright test --grep @smoke
在 config 中配置 retries: 2,projects可单独设不同重试次数。
九、CI/CD 集成(GitHub Actions 示例)
.github/workflows/e2e.yml:
name: Playwright E2E Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20' }
- run: npm ci
- run: npx playwright install --with-deps
- run: npx playwright test
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
每次 Push 自动跑测试并上传 HTML 报告制品。
十、最佳实践总结
| 维度 | 建议 |
|---|---|
| 定位策略 | 优先 getByRole/getByLabel/getByTestId,避免脆弱 CSS/XPath |
| 等待 | 用 expect(...).toBeVisible()自动等待,禁用 sleep |
| 测试隔离 | 每个 test 独立,用 beforeEach初始化,不依赖前序用例执行结果 |
| 登录态复用 | 用 globalSetup+ storageState.json保存 Cookie/Token,加速用例 |
| 断言丰富度 | 断言 URL、文本、属性、可见性,不止截图对比 |
| 报告 | 开发阶段用内置 HTML;团队交付用 Allure(含趋势、分类、附件) |
| 失败分析 | 开启 trace: 'retain-on-failure'+ screenshot: 'only-on-failure' |
| 稳定性 | 配置 --retries=2应对偶发网络抖动,配合 Allure @flaky标记 |
十一、快速上手命令速查
# 初始化
npm init playwright@latest
# 运行全部
npx playwright test
# 有头模式运行单个文件
npx playwright test tests/login.spec.ts --headed
# UI 模式(强烈推荐)
npx playwright test --ui
# 录制
npx playwright codegen https://example.com
# 查看报告
npx playwright show-report
# Allure(TS)
allure serve allure-results