Playwright 入门指南:从零跑通第一个 E2E 测试
Playwright 是微软开源的端到端(E2E)测试框架,支持 Chromium / Firefox / WebKit 三大浏览器引擎,跨 Windows、Linux、macOS,本地和 CI 都能跑。它最大的卖点就一句话:自动等待 + Web-first 断言,不需要手写 sleep,测试也不容易 flaky。这篇教程带你在 5 分钟内置好环境、写出第一个测试、看懂输出报告,Node.js 和 Python 两条线都覆盖。
目录
一、Playwright 是什么,为什么选它
传统 Selenium 测试最大的痛点是显式等待——你得自己写 WebDriverWait 或者 time.sleep(),写少了就 flaky,写多了就慢。Playwright 换了个思路:
- 自动等待(Actionability):每次
click()/fill()之前,框架自动检查元素是否可见、稳定、可交互,通过后才执行,不需要手动 sleep。 - Web-first 断言:
expect(locator).toBeVisible()这类断言是异步重试的,会一直等到条件满足或超时,而不是”检查一次就 fail”。 - 测试隔离:每个测试拿到一个全新的
BrowserContext,相当于独立浏览器 profile,测试之间 cookie / storage 互不干扰。 - 多引擎支持:同一套测试跑 Chromium、Firefox、WebKit,跨浏览器覆盖在每次提交都有。
二、Node.js 版:安装与初始化
前置要求
- Node.js:最新 22.x / 24.x / 26.x
- 系统:Windows 11+ / macOS 14+ / Debian 12-13 / Ubuntu 22.04+
初始化项目
# 在新项目目录下初始化(会生成 playwright.config.ts + tests/) npm init playwright@latest
交互式提示会让你选:
- TypeScript 还是 JavaScript(默认 TS)
- 测试文件夹名(默认
tests,若已存在则e2e) - 是否加 GitHub Actions 工作流(CI 推荐选是)
- 是否安装浏览器二进制(默认是)
初始化完成后,项目结构大致如下:
playwright.config.ts # 测试配置(浏览器、超时、重试、reporter) package.json tests/ example.spec.ts # 示例测试
npm init playwright@latest 下载浏览器二进制可能需要几分钟(约 200 MB+),首次安装请耐心等待,不要中途 Ctrl+C。三、Node.js 版:写第一个测试
打开 tests/example.spec.ts,Playwright 默认生成一个最小示例。下面是一个稍完整的版本,演示导航 + 点击 + 断言的完整流程:
import { test, expect } from '@playwright/test';
test('页面标题包含 Playwright', async ({ page }) => {
await page.goto('https://playwright.dev/');
// 异步断言:自动等待标题包含 "Playwright",最多等默认 5s
await expect(page).toHaveTitle(/Playwright/);
});
test('点击 Get started 链接后看到 Installation 标题', async ({ page }) => {
await page.goto('https://playwright.dev/');
// 用 getByRole 定位,比 CSS 选择器稳定得多
await page.getByRole('link', { name: 'Get started' }).click();
await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible();
});
常用 Locators 速查
Playwright 推荐用角色(role)/ 标签 / 占位符定位,而不是脆弱的 CSS 路径。最常用几个:
// 按 ARIA 角色
page.getByRole('button', { name: 'Submit' });
page.getByRole('link', { name: 'Get started' });
page.getByRole('heading', { name: 'Installation' });
// 按表单标签
page.getByLabel('Username');
// 按 placeholder
page.getByPlaceholder('Search');
// 按 data-testid(最稳定,推荐生产环境用)
page.getByTestId('submit-btn');
常用 Action 速查
await loc.click(); // 点击
await loc.fill('hello'); // 输入文本(会先清空)
await loc.press('Enter'); // 按键
await loc.hover(); // 悬停
await loc.check(); // 勾选 checkbox
await loc.selectOption('zh'); // 下拉框选值
await loc.setInputFiles('a.png'); // 上传文件
四、Python 版:pytest-playwright
Python 用户用 pytest-playwright 插件,API 几乎和 Node 版一一对应:
pip install pytest-playwright playwright install
创建 test_example.py:
import re
from playwright.sync_api import Page, expect
def test_has_title(page: Page):
page.goto("https://playwright.dev/")
# 同步版 assert,自动重试直到标题匹配
expect(page).to_have_title(re.compile("Playwright"))
def test_get_started_link(page: Page):
page.goto("https://playwright.dev/")
page.get_by_role("link", name="Get started").click()
expect(page.get_by_role("heading", name="Installation")).to_be_visible()
运行:
# 默认只跑 chromium pytest # 指定浏览器 pytest --browser firefox # 有头模式(可以看到浏览器窗口) pytest --browser chromium --headed
page 是 pytest-playwright 自动注入的 fixture,每个测试拿到隔离的 BrowserContext,不需要手动 playwright.chromium.launch()。五、运行、看报告、常用命令速查
Node.js 版常用命令
# 跑全部测试(默认并行,三引擎) npx playwright test # 只跑 chromium npx playwright test --project=chromium # 有头模式 npx playwright test --headed # 跑单个文件 npx playwright test tests/example.spec.ts # 按测试名过滤 npx playwright test -g "页面标题" # UI 模式(watch + 时间旅行调试) npx playwright test --ui # 看 HTML 报告 npx playwright show-report
Python 版常用命令
# 默认 chromium,headless pytest # 指定 firefox pytest --browser firefox # 有头模式 pytest --browser chromium --headed # 按文件跑 pytest test_example.py # 按测试名 pytest -k "test_has_title"
六、核心概念:Locators、自动等待、隔离
自动等待 vs 显式等待
Playwright 的所有 action(click、fill 等)和 web-first 断言(expect(...).toBeVisible())都内置自动重试,默认超时 5 秒(可在 playwright.config.ts 里改)。你几乎永远不需要写 await page.waitForTimeout()。
- 等待网络请求完成:
await page.waitForResponse(url => ...) - 等待某个 URL 跳转:
await page.waitForURL(/dashboard/) - 等待 Web API 状态(如 WebRTC 连接建立):这类 Playwright 自动等待覆盖不到,需要手动
waitForFunction
测试隔离(BrowserContext)
每个测试拿到一个独立的 BrowserContext,相当于开一个全新浏览器 profile:cookie、localStorage、sessionStorage 全部隔离。这意味着:
- 测试 A 里登录了,测试 B 里是未登录状态——符合预期,互不污染
- 想共享登录态:用
storageState把登录后的 cookie 存下来,后续测试注入
// 保存登录态
test('login', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Username').fill('admin');
await page.getByLabel('Password').fill('pass');
await page.getByRole('button', { name: 'Sign in' }).click();
await expect(page.getByText('Dashboard')).toBeVisible();
await page.context().storageState({ path: 'auth.json' });
});
// 后续测试复用登录态(在 playwright.config.ts 里配置 storageState: 'auth.json')
test hooks
test.describe('navigation', () => {
// 每个测试前执行
test.beforeEach(async ({ page }) => {
await page.goto('https://playwright.dev/');
});
// 所有测试前执行一次(worker 级别)
test.beforeAll(async () => {
// 初始化数据库、启动 mock server 等
});
test('main navigation', async ({ page }) => {
await expect(page).toHaveURL('https://playwright.dev/');
});
});
七、小结与下一步
- 装 →
npm init playwright@latest(或pip install pytest-playwright) - 写 → 用
getByRole / getByLabel / getByTestId定位,用expect(...).toBe...断言,不写 sleep - 跑 →
npx playwright test(Node)/pytest(Python),默认 headless + 并行 - 看 → 失败时跑
npx playwright show-report看 HTML 报告 + trace - 接 CI → 加 GitHub Actions 工作流,每次提交自动跑,跨浏览器覆盖
下一步可以深入的内容:
- Trace Viewer:失败后回放每一步的 DOM 快照、网络请求、控制台日志
- Codegen:录制操作自动生成测试代码
- Test Fixtures:自定义 fixture 封装共享逻辑
- CI 集成:GitHub Actions / GitLab CI 配置
有问题去 Playwright Discord 或 Stack Overflow 搜,社区响应很快。