目录
-
- 背景与目标
- 快速上手
- 核心机制:为什么它不怎么"飘"
-
- [1. 自动等待(Auto-waiting)](#1. 自动等待(Auto-waiting))
- [2. Web 优先断言(Web-first assertions)](#2. Web 优先断言(Web-first assertions))
- [3. 弹性定位器(Resilient locators)](#3. 弹性定位器(Resilient locators))
- 测试隔离:每个用例都是"干净的新浏览器"
- 进阶用法:真实流程里的三件事
-
- [拦截网络请求(Mock / 造数据)](#拦截网络请求(Mock / 造数据))
- 响应式与多浏览器
- 追踪与截图:失败现场可回放
- 踩过的坑
-
- [坑 1:`waitForTimeout` 用了就回不去了](#坑 1:
waitForTimeout用了就回不去了) - [坑 2:XPath 选择器一重构就挂](#坑 2:XPath 选择器一重构就挂)
- [坑 3:CI 里偶发失败,本地却过](#坑 3:CI 里偶发失败,本地却过)
- [坑 4:多个 iframe / 新标签页找不到元素](#坑 4:多个 iframe / 新标签页找不到元素)
- [坑 1:`waitForTimeout` 用了就回不去了](#坑 1:
- [与 Selenium / Cypress 的取舍](#与 Selenium / Cypress 的取舍)
- 总结与后续
- 参考链接
背景与目标
Web 应用越来越复杂,回归测试靠人工点一遍既慢又容易漏。Playwright 是微软开源(Apache-2.0 协议)的端到端测试框架,一套 API 覆盖 Chromium、Firefox、WebKit 三种内核,JavaScript/TypeScript、Python、Java、.NET 四种语言都有官方支持。本文以 TypeScript + @playwright/test 为主线,从安装写起,讲清楚它的核心机制(自动等待、定位器、断言),再用一个真实流程串起来。
快速上手
环境准备
需要 Node.js 18+。在项目里初始化:
bash
npm init playwright@latest
按提示选择 TypeScript、是否生成 GitHub Actions 工作流等。或者手动装:
bash
npm init -y
npm i -D @playwright/test
npx playwright install
npx playwright install 会下载 Chromium、Firefox、WebKit 三个浏览器内核(也可以只装需要的,如 npx playwright install chromium)。
第一个用例
新建 tests/example.spec.ts:
typescript
import { test, expect } from '@playwright/test';
test('页面标题正确', async ({ page }) => {
await page.goto('https://www.codefather.cn/post/1797520541019598849');
await expect(page).toHaveTitle(/2026 年最新编程学习路线零基础到精通一条龙(万人收藏⭐️) - 编程导航 | 一线开发者编程经验和技术实战分享/);
});
test('点击链接后跳转', async ({ page }) => {
await page.goto('https://www.codefather.cn/post/1797520541019598849');
await page.getByRole('link', { name: 'Java 学习路线 | 26 年最新零基础到精通一条龙(万人收藏⭐️)' }).click();
await expect(page).toHaveURL(/1789190431398928386/);
});
运行:
bash
npx playwright test
默认无头模式(headless)跑,浏览器不弹出来;想看过程加 --headed。跑完 npx playwright show-report 打开 HTML 报告。
常用命令
bash
npx playwright test # 跑全部用例
npx playwright test tests/login # 跑指定目录
npx playwright test -g "登录" # 按标题过滤
npx playwright test --headed # 有头模式
npx playwright codegen example.com # 录制用例(自动生成选择器)
npx playwright show-report # 打开 HTML 报告
核心机制:为什么它不怎么"飘"
端到端测试最常见的痛点是不稳定(flaky)------明明功能正常,用例却偶发失败,多半是竞态:元素还没渲染出来就去点了。Playwright 用三层设计解决这个问题。
1. 自动等待(Auto-waiting)
每个操作(click、fill 等)执行前,Playwright 会自动等待元素达到可操作状态(actionability) :可见、稳定(不抖动)、接收事件、未被遮挡。你不需要写 sleep(1000) 或 waitForElementVisible 这类手动等待。
2. Web 优先断言(Web-first assertions)
断言是异步的、会自动重试 直到满足条件或超时(默认 5 秒)。比如 await expect(page.getByText('保存成功')).toBeVisible() 会一直等到这个文本出现,而不是只查一次。这条和自动等待配合,把"等一个稳定状态"这件事彻底交给框架。
3. 弹性定位器(Resilient locators)
官方推荐用用户可见的方式定位,而不是脆弱的 CSS/XPath:
typescript
// 推荐:按角色+名称定位,贴近用户怎么找元素
await page.getByRole('button', { name: '提交' }).click();
await page.getByLabel('用户名').fill('tester');
await page.getByText('保存成功').toBeVisible();
// 不推荐:容易因样式/结构变化而失效
await page.locator('#root > div > div > button.submit-btn').click();
getByRole、getByLabel、getByPlaceholder、getByTestId 这些定位器是实时解析的------页面变了,下次执行时重新查找,天然抗重构。
测试隔离:每个用例都是"干净的新浏览器"
Playwright Test 里每个用例都拿到一个全新的 BrowserContext(相当于一个全新的浏览器配置文件,独立 cookie、localStorage、缓存)。这样用例之间互不污染:
typescript
test('用例A:未登录状态', async ({ page }) => {
// page 属于隔离的 context,cookie 是空的
await page.goto('/');
await expect(page.getByRole('button', { name: '登录' })).toBeVisible();
});
test('用例B:已登录状态', async ({ page }) => {
// 这里不会继承用例A的任何状态
});
登录态可以在 playwright.config.ts 里用 globalSetup 提前登录一次、保存 storageState,后续用例复用,避免每个用例都走一遍登录流程。
进阶用法:真实流程里的三件事
拦截网络请求(Mock / 造数据)
前后端联调时不想依赖真实后端,用 page.route 拦截:
typescript
await page.route('**/api/user', route => {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ name: '测试用户', level: 'vip' }),
});
});
await page.goto('/profile');
await expect(page.getByText('测试用户')).toBeVisible();
响应式与多浏览器
配置里声明 projects,一份代码跑多套浏览器和多套视口:
typescript
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'mobile', use: { ...devices['iPhone 13'] } },
],
});
追踪与截图:失败现场可回放
配置开启 trace,用例失败时自动保留完整轨迹(DOM 快照、网络、控制台、每一步截图),在 Trace Viewer 里回放排查:
typescript
export default defineConfig({
use: {
trace: 'on-first-retry', // 首次重试时记录
screenshot: 'only-on-failure',
},
});
踩过的坑
坑 1:waitForTimeout 用了就回不去了
早期习惯写 await page.waitForTimeout(3000) 等加载完成。它治标不治本,还拖慢套件。正确做法是等一个业务信号(文本出现、按钮可点):
typescript
// 不要:await page.waitForTimeout(3000);
// 要:等待真实状态
await expect(page.getByText('加载完成')).toBeVisible();
坑 2:XPath 选择器一重构就挂
从别的框架迁移来的代码常带一长串 #app > div:nth-child(2)。UI 微调就全红。换 getByRole / getByTestId 后,选择器跟"用户找元素的方式"绑定,重构影响小得多。给关键测试元素加 data-testid 是团队里性价比最高的约定。
坑 3:CI 里偶发失败,本地却过
多半是环境差异:CI 机器慢、字体没装导致截图对比失败、时区/语言不同。排查顺序:先看 trace 里失败那一步的截图 → 确认是不是断言超时 → 把默认 timeout 从 5s 调到 10s,或针对慢操作单独加 { timeout: 15000 }。截图对比(toHaveScreenshot)在 CI 上先 --update-snapshots 重新生成基线。
坑 4:多个 iframe / 新标签页找不到元素
Playwright 对 iframe 和弹窗支持很直接,但要用对 API:
typescript
// iframe:用 frameLocator,而不是在 page 里硬找
const frame = page.frameLocator('#payment-frame');
await frame.getByRole('button', { name: '确认支付' }).click();
// 新标签页:监听 context 的 'page' 事件
const [newPage] = await Promise.all([
page.context().waitForEvent('page'),
page.getByRole('link', { name: '打开新窗口' }).click(),
]);
await newPage.waitForLoadState();
与 Selenium / Cypress 的取舍
| 维度 | Playwright | Selenium | Cypress |
|---|---|---|---|
| 浏览器支持 | Chromium、Firefox、WebKit 原生支持 | 全浏览器(需驱动适配) | 仅 Chromium 系 |
| 语言 | JS/TS、Python、Java、.NET | Java/Python/JS/C#/Ruby 等 7+ | 仅 JS/TS |
| 自动等待 | 内置(自动等待 + 断言重试) | 需手动处理显式等待 | 内置自动重试 |
| 并行执行 | 内置 worker 并行 | 依赖 Grid,配置复杂 | 免费版有限,云服务付费 |
| 网络拦截 | 内置 page.route |
依赖第三方库 | 内置有限 |
| 多标签页/iframe | 原生体验好 | 需 context 切换,繁琐 | 支持一般 |
| 移动端 | 内置设备模拟 | 需集成 Appium | 不支持真机 |
什么时候选 Playwright :需要多浏览器覆盖、多语言团队、要内置并行和网络拦截的新项目------目前是新项目的常见默认选择。什么时候考虑其他:团队已深度绑定 Java/Selenium 生态且浏览器覆盖面要求极广;或只做简单单页应用、且必须用 JS/TS 且预算有限选 Cypress 也有道理。客观说,Playwright 的生态和资料比 Selenium 少,复杂需求有时要自己查源码。
总结与后续
Playwright 把端到端测试最头疼的"不稳定"问题,通过自动等待 + Web 优先断言 + 弹性定位器三层机制基本解决;测试隔离和内置并行又降低了维护成本。建议新项目直接用它,把"给关键元素加 data-testid、断言只等业务信号、失败必开 trace"当成团队约定,稳定性和排查效率都能明显提升。
下一步可以看:Codegen 录制、UI Mode 交互调试、GitHub Actions CI 集成、视觉回归(toHaveScreenshot)。进阶可以用 Playwright 的 MCP 能力结合 AI 生成用例(v1.60+ 起官方捆绑 Playwright MCP 服务)。