Chromium / ChromeDriver 完全指南 / 07 - Playwright 集成
07 - Playwright 集成
使用 Playwright 进行跨浏览器自动化,掌握自动等待、代码生成、多浏览器测试等现代特性。
7.1 Playwright 简介
Playwright 是由 Microsoft 维护的跨浏览器自动化框架,支持 Chromium (Chrome/Edge)、Firefox 和 WebKit (Safari)。
| 特征 | 说明 |
|---|---|
| 维护方 | Microsoft (前 Puppeteer 团队成员创建) |
| 协议 | CDP (Chromium) + 自有协议 (Firefox/WebKit) |
| 浏览器支持 | Chromium, Firefox, WebKit |
| 语言 | JavaScript/TypeScript, Python, Java, C# |
| 自动等待 | ✅ 内置,所有操作自动等待元素就绪 |
| 测试框架 | 内置 @playwright/test,支持并行测试 |
| 代码生成 | codegen 工具录制操作生成代码 |
| Trace Viewer | 内置操作录制与可视化回放 |
| 安装 | 自动下载浏览器二进制 |
架构
┌─────────────────────────────────────────────────────────┐
│ Playwright 应用 │
│ ┌───────────────────────────────────────────────────┐ │
│ │ Playwright API (多语言) │ │
│ │ JS/TS │ Python │ Java │ C# │ │
│ └──────────┬────────────────────────────────────────┘ │
│ │ WebSocket / Pipe │
│ ┌──────────▼────────────────────────────────────────┐ │
│ │ Browser Server │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ Chromium │ │ Firefox │ │ WebKit │ │ │
│ │ │ (Chrome) │ │ │ │ (Safari) │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ │ │
│ └───────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
7.2 安装
Node.js
# 创建项目
mkdir pw-demo && cd pw-demo
npm init -y
# 安装 Playwright Test (推荐,包含测试框架)
npm init playwright@latest
# 或仅安装 Playwright 库
npm install playwright
# 下载浏览器 (通常自动执行)
npx playwright install
# 仅下载 Chromium
npx playwright install chromium
Python
pip install playwright
playwright install # 下载所有浏览器
playwright install chromium # 仅下载 Chromium
Java
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>playwright</artifactId>
<version>1.40.0</version>
</dependency>
mvn exec:java -e -Dexec.mainClass=com.microsoft.playwright.CLI -Dexec.args="install"
C# (.NET)
dotnet add package Microsoft.Playwright
dotnet build
pwsh bin/Debug/net8.0/playwright.ps1 install
7.3 基础用法 (Node.js)
第一个脚本
const { chromium } = require('playwright');
(async () => {
// 启动浏览器
const browser = await chromium.launch({
headless: true,
args: ['--no-sandbox']
});
// 创建页面 (等同于创建 BrowserContext + Page)
const context = await browser.newContext({
viewport: { width: 1920, height: 1080 },
locale: 'zh-CN',
});
const page = await context.newPage();
// 导航
await page.goto('https://example.com');
// 获取标题
console.log('标题:', await page.title());
// 截图
await page.screenshot({ path: '/tmp/pw-example.png', fullPage: true });
// 关闭
await browser.close();
})();
使用 Playwright Test
// tests/example.spec.js
const { test, expect } = require('@playwright/test');
test('首页加载正确', async ({ page }) => {
await page.goto('https://example.com');
await expect(page).toHaveTitle(/Example/);
});
test('搜索功能正常', async ({ page }) => {
await page.goto('https://example.com');
await page.fill('#search', 'Playwright');
await page.press('#search', 'Enter');
await expect(page.locator('.results')).toBeVisible();
});
# 运行测试
npx playwright test
# 运行指定文件
npx playwright test tests/example.spec.js
# 有头模式 (调试)
npx playwright test --headed
# UI 模式 (可视化调试)
npx playwright test --ui
# 查看报告
npx playwright show-report
7.4 元素定位
Playwright 使用 Locator API,与 Selenium 的 find_element 有本质区别——Locator 是惰性的,每次操作时重新查找元素。
定位器 (Locator)
// CSS 选择器
const button = page.locator('#submit-btn');
const items = page.locator('.list-item');
// XPath
const heading = page.locator('xpath=//h1');
// 文本内容
const link = page.locator('text=登录'); // 精确匹配
const link2 = page.locator('text=/log\\w+/i'); // 正则匹配
const partial = page.locator(':text("欢迎")'); // 包含文本
// 角色 (ARIA Role, 推荐)
const submitBtn = page.getByRole('button', { name: '提交' });
const heading = page.getByRole('heading', { level: 1 });
const textbox = page.getByRole('textbox', { name: '用户名' });
const link = page.getByRole('link', { name: '首页' });
// 按标签文本 (表单)
const email = page.getByLabel('邮箱地址');
// 按 placeholder
const search = page.getByPlaceholder('输入搜索关键词');
// 按测试 ID
const element = page.getByTestId('submit-button');
// 按 Alt 文本 (图片)
const logo = page.getByAltText('公司 Logo');
// 按 Title 属性
const tooltip = page.getByTitle('提示信息');
// 组合定位
const row = page.locator('tr').filter({ hasText: '张三' });
const item = page.locator('.card', { has: page.locator('.active') });
推荐定位策略优先级
1. page.getByRole() ← 最语义化,推荐首选
2. page.getByLabel() ← 表单元素
3. page.getByPlaceholder() ← 输入框
4. page.getByTestId() ← 测试专用,不受 UI 变化影响
5. page.getByText() ← 文本匹配
6. page.locator(CSS) ← 灵活但可能因 CSS 变化而失效
7. page.locator(XPath) ← 最后选择
7.5 自动等待
Playwright 的核心优势之一——每个操作都会自动等待元素满足条件。
// 无需手动 waitForSelector!
await page.click('#submit-btn'); // 自动等待:
// 1. 元素存在于 DOM
// 2. 元素可见
// 3. 元素稳定 (无动画)
// 4. 元素可接收事件 (未被遮挡)
// 5. 元素已启用 (未 disabled)
await page.fill('#email', 'test@example.com'); // 自动等待元素可编辑
// 断言也有自动等待
await expect(page.locator('.result')).toBeVisible();
await expect(page.locator('.item')).toHaveCount(5);
await expect(page.locator('.status')).toHaveText('完成');
await expect(page.locator('#price')).toHaveText(/^\$\d+/);
手动等待 (少数场景需要)
// 等待元素出现
await page.locator('#dynamic-content').waitFor({ state: 'attached' });
// 等待元素消失
await page.locator('.loading').waitFor({ state: 'detached' });
// 等待网络空闲
await page.waitForLoadState('networkidle');
// 等待特定 URL
await page.waitForURL('**/dashboard');
// 等待响应
const response = await page.waitForResponse('**/api/data');
const data = await response.json();
// 等待超时设置
await page.click('#btn', { timeout: 10000 });
7.6 页面交互
表单操作
// 输入文本 (自动清空)
await page.fill('#username', 'admin');
await page.fill('#password', 'secret123');
// 逐字符输入 (模拟真实用户)
await page.locator('#search').pressSequentially('Hello', { delay: 100 });
// 清空
await page.fill('#field', '');
// 选择下拉框
await page.selectOption('#country', 'CN'); // 按值
await page.selectOption('#country', { label: '中国' }); // 按标签
await page.selectOption('#country', { index: 0 }); // 按索引
// 复选框 / 单选框
await page.check('#agree');
await page.uncheck('#agree');
await page.setChecked('#agree', true);
await page.setInputFiles('#upload', '/path/to/file.pdf'); // 单文件
await page.setInputFiles('#upload', ['/file1.pdf', '/file2.pdf']); // 多文件
// 提交表单
await page.locator('#search').press('Enter');
鼠标操作
// 点击
await page.click('#button');
await page.dblclick('#item'); // 双击
await page.click('#button', { button: 'right' }); // 右键
await page.click('#button', { modifiers: ['Shift'] }); // Shift+点击
// 悬停
await page.hover('.dropdown-toggle');
// 拖拽
await page.locator('#source').dragTo(page.locator('#target'));
// 精确鼠标操作
await page.mouse.click(100, 200);
await page.mouse.dblclick(100, 200);
await page.mouse.move(100, 200);
await page.mouse.down();
await page.mouse.move(300, 400);
await page.mouse.up();
键盘操作
await page.keyboard.press('Enter');
await page.keyboard.press('Tab');
await page.keyboard.press('Control+a');
await page.keyboard.press('Control+c');
await page.keyboard.press('Control+v');
await page.keyboard.press('Escape');
// 组合键
await page.keyboard.press('Shift+KeyA');
await page.keyboard.press('Control+Shift+KeyI'); // 打开 DevTools
iframe 处理
// 方法 1: 通过 frameLocator
const frame = page.frameLocator('#iframe-id');
await frame.locator('#inner-element').click();
// 方法 2: 获取 Frame 对象
const frameHandle = page.frame({ url: /iframe-url/ });
await frameHandle.locator('#element').click();
// 嵌套 iframe
const nested = page.frameLocator('#outer').frameLocator('#inner');
await nested.locator('#deep-element').click();
弹窗处理
// 监听 dialog 事件
page.on('dialog', async (dialog) => {
console.log(`弹窗类型: ${dialog.type()}`);
console.log(`弹窗消息: ${dialog.message()}`);
await dialog.accept(); // 确定
// await dialog.dismiss(); // 取消
// await dialog.accept('输入内容'); // prompt 输入
});
// 触发弹窗
await page.click('#trigger-alert');
7.7 多浏览器测试
Node.js — Playwright Test
// playwright.config.js
const { defineConfig, devices } = require('@playwright/test');
module.exports = defineConfig({
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
{
name: 'mobile-chrome',
use: { ...devices['Pixel 5'] },
},
{
name: 'mobile-safari',
use: { ...devices['iPhone 14'] },
},
],
fullyParallel: true,
retries: 2,
workers: 4,
reporter: [['html'], ['list']],
});
# 运行所有浏览器
npx playwright test
# 仅运行 Chromium
npx playwright test --project=chromium
# 仅运行移动设备
npx playwright test --project=mobile-chrome --project=mobile-safari
Python
import pytest
from playwright.sync_api import Page, expect
# conftest.py 或直接使用 pytest-playwright
@pytest.fixture(params=["chromium", "firefox", "webkit"])
def browser_name(request):
return request.param
def test_homepage(page: Page):
page.goto("https://example.com")
expect(page).to_have_title(/Example/)
7.8 代码生成器 (Codegen)
Playwright 内置录制工具,自动生成自动化代码。
# 启动录制
npx playwright codegen https://example.com
# 指定语言
npx playwright codegen --target=python https://example.com
npx playwright codegen --target=java https://example.com
# 模拟移动设备
npx playwright codegen --device="iPhone 14" https://example.com
# 保存到文件
npx playwright codegen --output=tests/generated.spec.ts https://example.com
生成的代码示例:
// 由 Playwright Codegen 自动生成
import { test, expect } from '@playwright/test';
test('test', async ({ page }) => {
await page.goto('https://example.com/');
await page.getByPlaceholder('搜索').click();
await page.getByPlaceholder('搜索').fill('playwright');
await page.getByRole('button', { name: '搜索' }).click();
await expect(page.getByText('搜索结果')).toBeVisible();
});
7.9 Trace Viewer
Trace Viewer 记录测试执行的完整过程,包括截图、网络请求、DOM 快照等。
// playwright.config.js
module.exports = defineConfig({
use: {
trace: 'on-first-retry', // 仅在重试时记录
// trace: 'on', // 始终记录
// trace: 'retain-on-failure', // 失败时保留
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
});
# 查看 Trace
npx playwright show-trace trace.zip
# 在线查看
# https://trace.playwright.dev
7.10 Playwright vs Selenium vs Puppeteer
| 对比维度 | Playwright | Selenium | Puppeteer |
|---|---|---|---|
| 浏览器支持 | Chromium + Firefox + WebKit | 所有主流 | Chromium only |
| 自动等待 | ✅ 内置 | ❌ 需手动 | ❌ 需手动 |
| 代码生成 | ✅ Codegen | ❌ | ❌ |
| Trace Viewer | ✅ 内置 | ❌ | ❌ |
| 并行测试 | ✅ 内置 | 需 TestNG/JUnit | 需额外框架 |
| 网络拦截 | ✅ Route API | ⚠️ 有限 | ✅ RequestInterception |
| 多语言 | JS/TS/Python/Java/C# | Java/Python/C#/JS/Ruby | JS/TS only |
| 测试框架 | @playwright/test | 需第三方 | 需第三方 |
| 安装 | 自动下载浏览器 | 需手动管理驱动 | 自动下载 Chrome |
| 社区规模 | 大,快速增长 | 最大 | 大 |
| 企业采用 | 快速增长 | 最广泛 | Chrome 专项 |
选择建议
选择 Playwright:
✅ 新项目首选(现代化 API、自动等待、内置测试框架)
✅ 需要跨浏览器测试
✅ 需要录制/回放调试能力
✅ 需要并行测试
选择 Selenium:
✅ 已有 Selenium 项目(迁移成本高)
✅ 需要 Selenium Grid 分布式测试
✅ 需要最广泛的浏览器支持(旧版浏览器)
选择 Puppeteer:
✅ 只需 Chrome 专项操作
✅ 需要 CDP 底层能力(性能 Trace、协议级操作)
✅ Node.js 项目,不需要跨浏览器
7.11 完整示例 — 跨浏览器 E2E 测试
// tests/login.spec.ts
import { test, expect } from '@playwright/test';
test.describe('用户登录', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/login');
});
test('使用正确凭据登录成功', async ({ page }) => {
await page.getByLabel('用户名').fill('admin');
await page.getByLabel('密码').fill('password123');
await page.getByRole('button', { name: '登录' }).click();
// 验证跳转到首页
await expect(page).toHaveURL(/.*dashboard/);
await expect(page.getByText('欢迎回来')).toBeVisible();
});
test('错误密码显示提示', async ({ page }) => {
await page.getByLabel('用户名').fill('admin');
await page.getByLabel('密码').fill('wrongpassword');
await page.getByRole('button', { name: '登录' }).click();
// 验证错误提示
await expect(page.locator('.error-message')).toContainText('用户名或密码错误');
});
test('空用户名阻止提交', async ({ page }) => {
await page.getByLabel('密码').fill('password123');
await page.getByRole('button', { name: '登录' }).click();
// 验证仍在登录页
await expect(page).toHaveURL(/.*login/);
await expect(page.getByLabel('用户名')).toBeFocused();
});
});
7.12 要点回顾
| 要点 | 说明 |
|---|---|
| 自动等待是核心优势 | 每个操作自动等待元素就绪,消除 flaky tests |
| Locator 是惰性的 | 每次操作重新查找,不怕 DOM 变化 |
| 优先使用语义化定位 | getByRole() > getByLabel() > CSS |
| Codegen 录制代码 | 可视化录制自动生成测试代码 |
| Trace Viewer 调试 | 记录完整执行过程,方便问题定位 |
| 多浏览器 + 多语言 | 一套代码支持 Chromium/Firefox/WebKit |
7.13 注意事项
⚠️ 浏览器版本: Playwright 下载的浏览器独立于系统安装的版本,更新 Playwright 可能改变浏览器版本。
⚠️ CI 环境: 在 CI 中使用
npx playwright install --with-deps安装浏览器及系统依赖。⚠️
waitForLoadState不等于 Selenium 的implicitly_wait: Playwright 自动等待已内置,一般不需要额外等待。⚠️ Locator 不等于 ElementHandle: Playwright 推荐使用 Locator 而非 ElementHandle,后者在复杂场景下容易 stale。
7.14 扩展阅读
| 资源 | 链接 |
|---|---|
| Playwright 官方文档 | https://playwright.dev/ |
| Playwright GitHub | https://github.com/microsoft/playwright |
| Playwright Test 文档 | https://playwright.dev/docs/test-intro |
| Playwright Codegen | https://playwright.dev/docs/codegen |
| Playwright Trace Viewer | https://trace.playwright.dev/ |
| 最佳实践 | https://playwright.dev/docs/best-practices |