强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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', '[email protected]');  // 自动等待元素可编辑

// 断言也有自动等待
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

对比维度PlaywrightSeleniumPuppeteer
浏览器支持Chromium + Firefox + WebKit所有主流Chromium only
自动等待✅ 内置❌ 需手动❌ 需手动
代码生成✅ Codegen
Trace Viewer✅ 内置
并行测试✅ 内置需 TestNG/JUnit需额外框架
网络拦截✅ Route API⚠️ 有限✅ RequestInterception
多语言JS/TS/Python/Java/C#Java/Python/C#/JS/RubyJS/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 GitHubhttps://github.com/microsoft/playwright
Playwright Test 文档https://playwright.dev/docs/test-intro
Playwright Codegenhttps://playwright.dev/docs/codegen
Playwright Trace Viewerhttps://trace.playwright.dev/
最佳实践https://playwright.dev/docs/best-practices