Puppeteer 与 Playwright 深度实战指南:从零到精通,全面提升开发效率

一、它们是什么?

1.1 Puppeteer

Puppeteer 是由 Google Chrome 团队开发和维护的 Node.js 库,它通过 Chrome DevTools Protocol (CDP) 提供了一套高级 API,用于控制 Chromium/Chrome 浏览器。你可以把它理解为"用代码操控浏览器的手"。

核心特点:

  • 由 Google 官方维护,与 Chrome 深度集成
  • 默认运行 Headless(无头)模式,无需打开浏览器窗口
  • 原生支持 Chrome/Chromium,v23+ 实验性支持 Firefox
  • 轻量、启动快、API 简洁

1.2 Playwright

Playwright 是由 Microsoft 开发的跨浏览器自动化框架,最初由 Puppeteer 的原班人马跳槽到微软后打造。它通过各浏览器原生的调试协议,统一控制 Chromium、Firefox、WebKit(Safari 内核) 三大引擎。

核心特点:

  • 真正的跨浏览器支持(Chrome、Firefox、Safari 一套代码)
  • 内置 自动等待(Auto-Waiting) 机制,大幅减少 sleep / 显式等待
  • 原生支持多页面、多标签页、多浏览器上下文
  • 提供 Playwright Test 完整测试框架
  • 内置 Codegen(代码生成器)Trace ViewerInspector 等调试利器
  • 支持 JavaScript/TypeScript、Python、Java、.NET 多语言

1.3 核心对比一览

维度 Puppeteer Playwright
维护方 Google Microsoft
浏览器支持 Chrome/Chromium(Firefox 实验性) Chromium + Firefox + WebKit
自动等待 ❌ 需手动处理 ✅ 内置智能等待
多语言 仅 JS/TS JS/TS、Python、Java、.NET
移动端模拟 基础支持 完善的设备模拟
网络拦截 ✅(更强大的路由能力)
测试框架 无内置,需搭配 Jest/Mocha 内置 @playwright/test
调试工具 DevTools Trace Viewer + Inspector + Codegen
并发模型 单浏览器多页面 多浏览器上下文,天然隔离
社区生态 成熟稳定 增长迅猛,功能迭代快

选择建议: 如果你只需要操控 Chrome 做轻量任务(截图、爬虫、PDF),Puppeteer 足够且更轻;如果你需要跨浏览器测试、企业级 E2E 测试、或希望更少的样板代码,Playwright 是更优选择


二、Puppeteer 完整安装指南

2.1 环境前置要求

项目 最低要求 推荐版本
Node.js >= 18 20 LTS 或 22 LTS
npm >= 9 10+
操作系统 Windows 10+ / macOS 11+ / Ubuntu 20.04+ 最新稳定版
磁盘空间 >= 500MB(含 Chromium) >= 1GB
内存 >= 2GB >= 4GB

检查命令:

bash 复制代码
# 检查 Node.js 版本
node -v
# 期望输出: v20.x.x 或 v22.x.x

# 检查 npm 版本
npm -v
# 期望输出: 10.x.x

# 检查 npx 是否可用
npx --version

# 如果版本过低,使用 nvm 升级
# macOS / Linux:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
source ~/.bashrc
nvm install 22
nvm use 22

# Windows: 下载 nvm-windows
# https://github.com/coreybutler/nvm-windows/releases
nvm install 22
nvm use 22

2.2 安装方式一:标准安装(自动下载 Chromium)

适用场景:本地开发、学习、个人项目。安装后开箱即用。

bash 复制代码
# Step 1: 创建项目目录
mkdir puppeteer-project
cd puppeteer-project

# Step 2: 初始化 npm 项目
npm init -y

# Step 3: 安装 Puppeteer(自动下载匹配的 Chromium,约 170MB)
npm install puppeteer

# Step 4: 验证安装
node -e "console.log(require('puppeteer/package.json').version)"

安装过程说明:

  • npm install puppeteer 会触发 postinstall 脚本
  • 该脚本自动从 Google CDN 下载与当前版本匹配的 Chromium
  • 下载位置:node_modules/puppeteer/.local-chromium/(旧版)或 ~/.cache/puppeteer/(新版)

2.3 安装方式二:puppeteer-core(不下载浏览器)

适用场景:CI/CD 环境、Docker 容器、已有 Chrome 的服务器、体积敏感场景。

bash 复制代码
# Step 1: 创建项目
mkdir puppeteer-core-project
cd puppeteer-core-project
npm init -y

# Step 2: 安装(不下载任何浏览器,仅 ~5MB)
npm install puppeteer-core

# Step 3: 验证
node -e "console.log(require('puppeteer-core/package.json').version)"

⚠️ 重要区别:

  • puppeteer:包含浏览器下载逻辑,require('puppeteer') 后直接 launch() 即可
  • puppeteer-core:不包含浏览器,必须在 launch() 时指定 executablePath

2.4 安装方式三:跳过浏览器下载,使用系统 Chrome

ini 复制代码
# 设置环境变量跳过下载
export PUPPETEER_SKIP_DOWNLOAD=true
npm install puppeteer

# 或者写入 .npmrc 文件(项目级永久生效)
echo "PUPPETEER_SKIP_DOWNLOAD=true" >> .npmrc
npm install puppeteer

代码中指定系统 Chrome 路径:

php 复制代码
const puppeteer = require('puppeteer');

const browser = await puppeteer.launch({
  executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', // macOS
  // executablePath: 'C:\Program Files\Google\Chrome\Application\chrome.exe', // Windows
  // executablePath: '/usr/bin/google-chrome-stable', // Linux
});

各系统查找 Chrome 路径:

bash 复制代码
# macOS
ls "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"

# Linux
which google-chrome-stable || which chromium-browser || which chromium

# Windows PowerShell
Get-Command chrome | Select-Object -ExpandProperty Source
# 或
(Get-Item "C:\Program Files\Google\Chrome\Application\chrome.exe").FullName

2.5 国内镜像加速配置

bash 复制代码
# ===== 方法一:临时环境变量(当前终端有效) =====
export PUPPETEER_DOWNLOAD_BASE_URL=https://npmmirror.com/mirrors/chrome-for-testing
npm install puppeteer

# ===== 方法二:项目级 .npmrc(推荐) =====
echo "PUPPETEER_DOWNLOAD_BASE_URL=https://npmmirror.com/mirrors/chrome-for-testing" >> .npmrc
npm install puppeteer

# ===== 方法三:全局 .npmrc =====
npm config set PUPPETEER_DOWNLOAD_BASE_URL https://npmmirror.com/mirrors/chrome-for-testing

# ===== 方法四:npm 命令参数 =====
PUPPETEER_DOWNLOAD_BASE_URL=https://npmmirror.com/mirrors/chrome-for-testing npm install puppeteer

2.6 Linux 服务器额外依赖安装

Puppeteer 的 Chromium 在 Linux 上需要以下系统库:

csharp 复制代码
# Ubuntu / Debian
sudo apt-get update
sudo apt-get install -y \
  ca-certificates \
  fonts-liberation \
  libasound2 \
  libatk-bridge2.0-0 \
  libatk1.0-0 \
  libcups2 \
  libdbus-1-3 \
  libdrm2 \
  libgbm1 \
  libgtk-3-0 \
  libnspr4 \
  libnss3 \
  libx11-xcb1 \
  libxcomposite1 \
  libxdamage1 \
  libxfixes3 \
  libxrandr2 \
  libxshmfence1 \
  xdg-utils \
  wget

# CentOS / RHEL / Fedora
sudo yum install -y \
  alsa-lib \
  atk \
  at-spi2-atk \
  cups-libs \
  gtk3 \
  libXcomposite \
  libXdamage \
  libXrandr \
  mesa-libgbm \
  nss \
  pango \
  xorg-x11-fonts-100dpi \
  xorg-x11-fonts-75dpi \
  xorg-x11-fonts-cyrillic \
  xorg-x11-fonts-misc \
  xorg-x11-fonts-Type1 \
  xorg-x11-utils

# Alpine Linux (Docker)
apk add --no-cache \
  chromium \
  nss \
  freetype \
  harfbuzz \
  ca-certificates \
  ttf-freefont

2.7 安装验证(完整测试脚本)

javascript 复制代码
# 创建验证脚本
cat > verify-puppeteer.js << 'EOF'
const puppeteer = require('puppeteer');

(async () => {
  try {
    console.log('🚀 正在启动浏览器...');
    const browser = await puppeteer.launch({
      headless: 'new',
      args: ['--no-sandbox', '--disable-setuid-sandbox'],
    });

    console.log('📄 正在打开页面...');
    const page = await browser.newPage();
    await page.goto('https://example.com', { timeout: 30000 });

    const title = await page.title();
    console.log('✅ 页面标题:', title);

    await page.screenshot({ path: 'verify-screenshot.png' });
    console.log('✅ 截图已保存: verify-screenshot.png');

    await browser.close();
    console.log('🎉 Puppeteer 安装验证全部通过!');
  } catch (error) {
    console.error('❌ 验证失败:', error.message);
    process.exit(1);
  }
})();
EOF

# 运行验证
node verify-puppeteer.js

2.8 Puppeteer 安装注意事项

序号 注意事项 说明
1 不要使用 sudo npm install 会导致权限混乱,应修复 npm 全局目录权限或使用 nvm
2 Chromium 下载失败时先检查网络 公司内网需配置代理:export HTTPS_PROXY=http://proxy:8080
3 puppeteer 与 puppeteer-core 不要混装 二者功能重叠,选一个即可
4 版本锁定 生产环境建议 npm install puppeteer@23.x.x --save-exact
5 Docker 中必须加 --no-sandbox 否则 Chromium 无法启动
6 中文字体缺失 Linux 服务器需安装 fonts-noto-cjk
7 磁盘空间 每次升级 Puppeteer 会下载新版 Chromium,注意清理旧版本
8 Node.js 版本兼容 Puppeteer v23+ 要求 Node >= 18,旧版可能要求不同

2.9 Puppeteer 卸载与清理

bash 复制代码
# 卸载包
npm uninstall puppeteer

# 清理下载的 Chromium 缓存
rm -rf ~/.cache/puppeteer
# 或(旧版路径)
rm -rf node_modules/puppeteer/.local-chromium

# 完全清理项目
rm -rf node_modules package-lock.json

三、Playwright 完整安装指南

3.1 环境前置要求

项目 最低要求 推荐版本
Node.js >= 18 20 LTS 或 22 LTS
npm >= 9 10+
Python(可选) >= 3.8 3.11+
操作系统 Windows 10+ / macOS 11+ / Ubuntu 20.04+ 最新稳定版
磁盘空间 >= 1.5GB(三大浏览器) >= 3GB
内存 >= 4GB >= 8GB

3.2 安装方式一:官方初始化向导(最推荐)

适用场景:新建 E2E 测试项目。一条命令搞定所有配置。

ini 复制代码
# Step 1: 在目标项目根目录运行(或新建目录)
npm init playwright@latest

# ===== 交互式问答(推荐选项) =====
# ? Do you want to use TypeScript or JavaScript?
#   → TypeScript(推荐,类型安全)
#
# ? Where to put your end-to-end tests?
#   → tests(默认即可)
#
# ? Add a GitHub Actions workflow?
#   → true(如果项目用 GitHub)
#
# ? Install Playwright browsers?
#   → true(自动下载浏览器)

向导完成后自动生成的目录结构:

bash 复制代码
your-project/
├── tests/
│   └── example.spec.ts          # 示例测试文件
├── tests-examples/
│   └── demo-todo-app.spec.ts    # Todo 应用示例
├── playwright.config.ts         # 核心配置文件
├── package.json                 # 依赖已自动添加
├── .github/
│   └── workflows/
│       └── playwright.yml       # CI 配置(如选择了)
└── node_modules/

3.3 安装方式二:手动安装(仅作为库使用)

适用场景:写爬虫脚本、自动化工具、不需要测试框架。

bash 复制代码
# Step 1: 创建项目
mkdir playwright-script
cd playwright-script
npm init -y

# Step 2: 安装 Playwright 库
npm install playwright

# Step 3: 下载浏览器引擎(三选一或全选)
npx playwright install              # 下载全部三个浏览器(~500MB)
npx playwright install chromium     # 仅 Chromium(~150MB)
npx playwright install firefox      # 仅 Firefox(~80MB)
npx playwright install webkit       # 仅 WebKit(~50MB)

# Step 4: 验证
node -e "const { chromium } = require('playwright'); console.log('✅ Playwright 已就绪')"

3.4 安装方式三:仅安装测试框架(@playwright/test)

适用场景:已有项目,只需添加 E2E 测试能力。

php 复制代码
# Step 1: 在已有项目中安装
npm install -D @playwright/test

# Step 2: 下载浏览器
npx playwright install

# Step 3: 手动创建配置文件
cat > playwright.config.ts << 'EOF'
import { defineConfig } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  use: {
    baseURL: 'http://localhost:3000',
  },
});
EOF

# Step 4: 创建测试目录
mkdir -p tests

3.5 安装方式四:Python 版 Playwright

适用场景:Python 技术栈团队、数据分析、非前端开发者。

bash 复制代码
# Step 1: 确认 Python 版本
python3 --version  # 需要 >= 3.8

# Step 2: 安装(推荐使用虚拟环境)
python3 -m venv playwright-env
source playwright-env/bin/activate   # macOS/Linux
# playwright-env\Scripts\activate    # Windows

# Step 3: pip 安装
pip install playwright

# Step 4: 下载浏览器引擎
playwright install

# Step 5: 安装系统依赖(Linux)
playwright install-deps

# Step 6: 验证
python3 -c "from playwright.sync_api import sync_playwright; print('✅ Python Playwright 就绪')"

3.6 安装方式五:Java / .NET 版

python 复制代码
# ===== Java (Maven) =====
# pom.xml 中添加:
# <dependency>
#   <groupId>com.microsoft.playwright</groupId>
#   <artifactId>playwright</artifactId>
#   <version>1.48.0</version>
# </dependency>

# 下载浏览器
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install"

# ===== .NET (C#) =====
dotnet add package Microsoft.Playwright
pwsh bin/Debug/net8.0/playwright.ps1 install

3.7 国内镜像加速配置

bash 复制代码
# ===== 方法一:临时环境变量 =====
export PLAYWRIGHT_DOWNLOAD_HOST=https://npmmirror.com/mirrors/playwright
npx playwright install

# ===== 方法二:项目级 .npmrc =====
echo "PLAYWRIGHT_DOWNLOAD_HOST=https://npmmirror.com/mirrors/playwright" >> .npmrc
npx playwright install

# ===== 方法三:全局配置 =====
npm config set PLAYWRIGHT_DOWNLOAD_HOST https://npmmirror.com/mirrors/playwright

# ===== Python 版镜像 =====
export PLAYWRIGHT_DOWNLOAD_HOST=https://npmmirror.com/mirrors/playwright
playwright install

3.8 Linux 系统依赖安装

csharp 复制代码
# ===== 推荐:一键安装(自动检测缺失依赖) =====
npx playwright install-deps

# 仅安装某个浏览器的依赖
npx playwright install-deps chromium
npx playwright install-deps firefox
npx playwright install-deps webkit

# ===== 手动安装(当 install-deps 不可用时) =====
# Ubuntu / Debian
sudo apt-get update && sudo apt-get install -y \
  libnss3 \
  libnspr4 \
  libatk1.0-0 \
  libatk-bridge2.0-0 \
  libcups2 \
  libdrm2 \
  libdbus-1-3 \
  libxkbcommon0 \
  libatspi2.0-0 \
  libxcomposite1 \
  libxdamage1 \
  libxfixes3 \
  libxrandr2 \
  libgbm1 \
  libpango-1.0-0 \
  libcairo2 \
  libasound2 \
  libwayland-client0

# CentOS / RHEL
sudo yum install -y \
  nss atk at-spi2-atk cups-libs libdrm libxkbcommon \
  libXcomposite libXdamage libXrandr mesa-libgbm \
  pango alsa-lib wayland-client

3.9 Docker 环境安装

bash 复制代码
# ===== 推荐:使用官方预构建镜像 =====
FROM mcr.microsoft.com/playwright:v1.48.0-noble

WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .

# 运行测试
CMD ["npx", "playwright", "test"]
bash 复制代码
# 构建并运行
docker build -t pw-tests .
docker run --rm -v $(pwd)/test-results:/app/test-results pw-tests

# ===== 如果必须用自定义基础镜像 =====
FROM node:22-slim

RUN apt-get update && apt-get install -y \
  libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 \
  libcups2 libdrm2 libdbus-1-3 libxkbcommon0 \
  libatspi2.0-0 libxcomposite1 libxdamage1 libxfixes3 \
  libxrandr2 libgbm1 libpango-1.0-0 libcairo2 libasound2 \
  && rm -rf /var/lib/apt/lists/*

WORKDIR /app
COPY package*.json ./
RUN npm ci
RUN npx playwright install --with-deps
COPY . .

3.10 安装验证(完整测试脚本)

javascript 复制代码
# 创建验证脚本
cat > verify-playwright.js << 'EOF'
const { chromium, firefox, webkit } = require('playwright');

(async () => {
  const browsers = [
    { name: 'Chromium', engine: chromium },
    { name: 'Firefox', engine: firefox },
    { name: 'WebKit', engine: webkit },
  ];

  for (const { name, engine } of browsers) {
    try {
      console.log(`🚀 正在测试 ${name}...`);
      const browser = await engine.launch();
      const page = await browser.newPage();
      await page.goto('https://example.com');
      const title = await page.title();
      console.log(`  ✅ ${name} 正常 | 页面标题: ${title}`);
      await browser.close();
    } catch (e) {
      console.log(`  ⚠️ ${name} 未安装或启动失败: ${e.message}`);
    }
  }

  console.log('\n🎉 Playwright 安装验证完成!');
})();
EOF

node verify-playwright.js

3.11 Playwright 安装注意事项

序号 注意事项 说明
1 首次安装耗时长 三大浏览器总计约 500MB,耐心等待或使用镜像
2 npx playwright install 必须执行 npm install 不会下载浏览器
3 Linux 必须安装系统依赖 否则浏览器启动报 error while loading shared libraries
4 Docker 推荐官方镜像 mcr.microsoft.com/playwright 已预装所有依赖
5 不要混用全局和局部安装 统一使用项目局部 npx playwright
6 版本升级后需重新下载浏览器 npm update 后执行 npx playwright install
7 WebKit 在 Linux 上需要额外依赖 install-deps 会自动处理
8 CI 环境加 --with-deps npx playwright install --with-deps 一步到位
9 防火墙环境 配置 HTTPS_PROXY 或使用离线包
10 .gitignore 排除 添加 test-results/playwright-report/playwright/.cache/

3.12 Playwright 卸载与清理

bash 复制代码
# 卸载包
npm uninstall playwright @playwright/test

# 清理浏览器缓存
rm -rf ~/.cache/ms-playwright
# Windows: %USERPROFILE%\AppData\Local\ms-playwright

# 清理测试产物
rm -rf test-results/ playwright-report/ playwright/.cache/

# 完全清理
rm -rf node_modules package-lock.json

四、Puppeteer 使用命令与实战详解

4.1 使用场景总览

场景 复杂度 典型应用
网页截图 / 缩略图 社交媒体预览图、报告配图
PDF 报告生成 ⭐⭐ 发票、合同、数据报表
SPA 爬虫 / 数据采集 ⭐⭐⭐ 需要 JS 渲染的动态页面
自动化表单填写 ⭐⭐⭐ 内部系统批量操作
性能监控 / CWV 采集 ⭐⭐⭐ LCP、CLS、FCP 指标
邮件/通知截图 动态内容转静态图片
自动化回归测试 ⭐⭐⭐ 搭配 Jest/Mocha

4.2 启动浏览器(所有操作的起点)

javascript 复制代码
const puppeteer = require('puppeteer');

// ===== 基础启动 =====
const browser = await puppeteer.launch();

// ===== 有头模式(调试用,能看到浏览器窗口) =====
const browser = await puppeteer.launch({ headless: false });

// ===== 新版无头模式(Chrome 112+,推荐) =====
const browser = await puppeteer.launch({ headless: 'new' });

// ===== 带常用参数启动 =====
const browser = await puppeteer.launch({
  headless: 'new',
  args: [
    '--no-sandbox',              // Docker/Linux 必须
    '--disable-setuid-sandbox',  // Docker/Linux 必须
    '--disable-dev-shm-usage',   // 防止 /dev/shm 空间不足
    '--disable-gpu',             // 无 GPU 环境
    '--window-size=1920,1080',   // 窗口大小
    '--lang=zh-CN',              // 语言
  ],
  defaultViewport: { width: 1920, height: 1080 },
});

// ===== 指定浏览器路径 =====
const browser = await puppeteer.launch({
  executablePath: '/usr/bin/google-chrome-stable',
});

// ===== 连接已运行的浏览器 =====
const browser = await puppeteer.connect({
  browserURL: 'http://localhost:9222',
});
// 或
const browser = await puppeteer.connect({
  browserWSEndpoint: 'ws://localhost:9222/devtools/browser/xxx',
});

// ===== 启动 Firefox(v23+ 实验性) =====
const browser = await puppeteer.launch({ product: 'firefox' });

4.3 页面导航与等待

dart 复制代码
const page = await browser.newPage();

// ===== 导航 =====
await page.goto('https://example.com');
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.goto('https://example.com', { timeout: 60000 });

// waitUntil 选项说明:
// 'load'           → window.onload 触发
// 'domcontentloaded' → DOMContentLoaded 触发
// 'networkidle0'   → 500ms 内无网络请求
// 'networkidle2'   → 500ms 内最多 2 个网络请求

// ===== 前进/后退/刷新 =====
await page.goBack();
await page.goForward();
await page.reload({ waitUntil: 'networkidle2' });

// ===== 等待 =====
await page.waitForSelector('.result-item');
await page.waitForSelector('#btn', { visible: true, timeout: 10000 });
await page.waitForNavigation({ waitUntil: 'networkidle2' });
await page.waitForResponse(res => res.url().includes('/api/data'));
await page.waitForRequest(req => req.url().includes('/api/submit'));
await page.waitForFunction(() => document.querySelectorAll('.item').length > 10);
await page.waitForTimeout(2000); // 硬等待(尽量避免)

4.4 页面交互操作

csharp 复制代码
// ===== 点击 =====
await page.click('#submit-btn');
await page.click('a.next-page');
await page.click('.item', { button: 'right' });  // 右键
await page.click('.item', { clickCount: 2 });     // 双击

// ===== 输入 =====
await page.type('#username', 'admin');                    // 逐字输入
await page.type('#search', '关键词', { delay: 100 });     // 模拟打字速度
await page.click('#input'); await page.keyboard.type('text'); // 另一种方式

// ===== 键盘操作 =====
await page.keyboard.press('Enter');
await page.keyboard.press('Tab');
await page.keyboard.press('Escape');
await page.keyboard.down('Shift');
await page.keyboard.press('ArrowDown');
await page.keyboard.up('Shift');
await page.keyboard.sendCharacter('你好');  // 直接输入中文

// ===== 鼠标操作 =====
await page.mouse.click(100, 200);           // 坐标点击
await page.mouse.move(100, 200);            // 移动
await page.mouse.down();                     // 按下
await page.mouse.up();                       // 释放
await page.mouse.wheel({ deltaY: 500 });    // 滚动

// ===== 下拉选择 =====
await page.select('#country', 'CN');
await page.select('#city', 'beijing', 'shanghai'); // 多选

// ===== 复选框/单选框 =====
await page.click('#agree-checkbox');

// ===== 文件上传 =====
const inputElement = await page.$('input[type="file"]');
await inputElement.uploadFile('/path/to/file.pdf');

// ===== 清空输入框 =====
await page.click('#input', { clickCount: 3 }); // 全选
await page.keyboard.press('Backspace');         // 删除

4.5 数据提取

dart 复制代码
// ===== 获取单个元素文本 =====
const title = await page.$eval('h1', el => el.textContent);
const href = await page.$eval('a.main-link', el => el.href);

// ===== 获取多个元素 =====
const items = await page.$$eval('.product-card', els =>
  els.map(el => ({
    name: el.querySelector('.name')?.textContent?.trim(),
    price: el.querySelector('.price')?.textContent?.trim(),
    image: el.querySelector('img')?.src,
  }))
);

// ===== 在页面上下文中执行任意 JS =====
const data = await page.evaluate(() => {
  return {
    url: window.location.href,
    title: document.title,
    allLinks: Array.from(document.querySelectorAll('a')).map(a => a.href),
    localStorage: { ...localStorage },
  };
});

// ===== 带参数传递 =====
const result = await page.evaluate((selector, count) => {
  return document.querySelectorAll(selector).length >= count;
}, '.item', 10);

// ===== 获取元素属性 =====
const element = await page.$('.target');
const className = await page.evaluate(el => el.className, element);
const isVisible = await page.evaluate(el => el.offsetParent !== null, element);

4.6 截图与 PDF

php 复制代码
// ===== 截图 =====
await page.screenshot({ path: 'full.png', fullPage: true });       // 全页
await page.screenshot({ path: 'viewport.png' });                    // 视口
await page.screenshot({ path: 'element.png', clip: { x: 10, y: 10, width: 200, height: 100 } }); // 区域
await page.screenshot({ path: 'quality.jpg', type: 'jpeg', quality: 80 }); // JPEG

// 元素截图
const element = await page.$('.chart');
await element.screenshot({ path: 'chart.png' });

// ===== PDF(仅 Chromium) =====
await page.pdf({
  path: 'report.pdf',
  format: 'A4',              // A4, Letter, Legal 等
  landscape: false,          // 横向
  printBackground: true,     // 打印背景色
  scale: 0.8,               // 缩放
  margin: {
    top: '20mm',
    bottom: '20mm',
    left: '15mm',
    right: '15mm',
  },
  headerTemplate: '<div style="font-size:10px; text-align:center; width:100%;">报告标题</div>',
  footerTemplate: '<div style="font-size:10px; text-align:center; width:100%;">第 <span class="pageNumber"></span> 页</div>',
  displayHeaderFooter: true,
});

4.7 网络拦截与请求控制

javascript 复制代码
// ===== 开启拦截 =====
await page.setRequestInterception(true);

// ===== 屏蔽资源类型(加速加载) =====
page.on('request', (req) => {
  const blocked = ['image', 'font', 'media', 'stylesheet'];
  if (blocked.includes(req.resourceType())) {
    req.abort();
  } else {
    req.continue();
  }
});

// ===== 修改请求头 =====
page.on('request', (req) => {
  req.continue({
    headers: {
      ...req.headers(),
      'Authorization': 'Bearer your-token',
      'X-Custom-Header': 'value',
    },
  });
});

// ===== Mock API 响应 =====
page.on('request', (req) => {
  if (req.url().includes('/api/users')) {
    req.respond({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify([{ id: 1, name: 'Mock 用户' }]),
    });
  } else {
    req.continue();
  }
});

// ===== 监听响应 =====
page.on('response', async (res) => {
  if (res.url().includes('/api/')) {
    console.log(`[${res.status()}] ${res.url()}`);
    // const body = await res.json();
  }
});
javascript 复制代码
// ===== Cookie 操作 =====
await page.setCookie({ name: 'token', value: 'abc123', domain: '.example.com' });
const cookies = await page.cookies();
const specificCookies = await page.cookies('https://example.com');
await page.deleteCookie({ name: 'token', domain: '.example.com' });

// ===== LocalStorage 操作 =====
await page.evaluate(() => {
  localStorage.setItem('key', 'value');
});
const value = await page.evaluate(() => localStorage.getItem('key'));
await page.evaluate(() => localStorage.clear());

// ===== 持久化登录态 =====
const fs = require('fs');

// 保存
const cookies = await page.cookies();
fs.writeFileSync('cookies.json', JSON.stringify(cookies));

// 加载
const savedCookies = JSON.parse(fs.readFileSync('cookies.json', 'utf-8'));
await page.setCookie(...savedCookies);

4.9 多页面与 iframe

dart 复制代码
// ===== 多标签页 =====
const page1 = await browser.newPage();
const page2 = await browser.newPage();
await page1.goto('https://site-a.com');
await page2.goto('https://site-b.com');

// 监听新打开的标签页
browser.on('targetcreated', async (target) => {
  if (target.type() === 'page') {
    const newPage = await target.page();
    console.log('新标签页:', await newPage.title());
  }
});

// ===== iframe 操作 =====
// 获取 frame
const frame = page.frames().find(f => f.name() === 'my-iframe');
const frame = page.frames().find(f => f.url().includes('payment'));

// 在 frame 中操作
await frame.click('#submit');
await frame.type('#input', 'text');

// 嵌套 iframe
const childFrame = frame.childFrames()[0];
await childFrame.click('#inner-btn');

4.10 性能与调试

javascript 复制代码
// ===== 开启 DevTools(有头模式) =====
const browser = await puppeteer.launch({
  headless: false,
  devtools: true,  // 自动打开 DevTools
});

// ===== CDP 直接访问 =====
const client = await page.target().createCDPSession();
await client.send('Performance.enable');
const { metrics } = await client.send('Performance.getMetrics');
console.table(metrics);

// ===== 网络性能 =====
await page.setCacheEnabled(false); // 禁用缓存测试
const startTime = Date.now();
await page.goto(url, { waitUntil: 'load' });
console.log(`加载耗时: ${Date.now() - startTime}ms`);

// ===== 控制台日志监听 =====
page.on('console', msg => console.log('PAGE LOG:', msg.text()));
page.on('pageerror', err => console.error('PAGE ERROR:', err.message));
page.on('requestfailed', req => console.log('FAILED:', req.url(), req.failure()?.errorText));

// ===== 开启 tracing =====
await page.tracing.start({ path: 'trace.json', screenshots: true });
// ... 执行操作 ...
await page.tracing.stop();

4.11 Puppeteer 常用命令速查表

操作 命令/代码
启动浏览器 puppeteer.launch()
新建页面 browser.newPage()
导航 page.goto(url, options)
截图 page.screenshot({ path, fullPage })
生成 PDF page.pdf({ path, format })
点击 page.click(selector)
输入 page.type(selector, text)
按键 page.keyboard.press(key)
等待元素 page.waitForSelector(sel, opts)
等待导航 page.waitForNavigation()
等待响应 page.waitForResponse(pred)
执行 JS page.evaluate(fn, ...args)
获取文本 page.$eval(sel, el => el.textContent)
获取多元素 page.$$eval(sel, els => els.map(...))
设置 Cookie page.setCookie(cookie)
获取 Cookie page.cookies()
设置视口 page.setViewport({ width, height })
设置 UA page.setUserAgent(ua)
开启拦截 page.setRequestInterception(true)
关闭浏览器 browser.close()
关闭页面 page.close()
获取所有页面 browser.pages()
获取浏览器版本 browser.version()

五、Playwright 使用命令与实战详解

5.1 使用场景总览

场景 复杂度 典型应用
跨浏览器 E2E 测试 ⭐⭐⭐ 一套代码验证 Chrome/Firefox/Safari
视觉回归测试 ⭐⭐⭐ 截图对比,检测 UI 变更
API + UI 混合测试 ⭐⭐⭐ 先调 API 准备数据,再验证 UI
移动端适配验证 ⭐⭐ 内置 100+ 设备描述符
多租户/多角色并行测试 ⭐⭐⭐⭐ BrowserContext 天然隔离
爬虫 / 数据采集 ⭐⭐ 自动等待让爬虫更稳定
无障碍(A11y)测试 ⭐⭐ 结合 axe-core
CI/CD 流水线集成 ⭐⭐⭐ GitHub Actions / GitLab CI

5.2 CLI 命令大全(npx playwright)

ini 复制代码
# ===== 浏览器管理 =====
npx playwright install                    # 安装所有浏览器
npx playwright install chromium           # 仅安装 Chromium
npx playwright install firefox            # 仅安装 Firefox
npx playwright install webkit             # 仅安装 WebKit
npx playwright install --with-deps        # 安装浏览器 + 系统依赖
npx playwright install-deps               # 仅安装系统依赖
npx playwright install --dry-run          # 预览将安装什么(不实际执行)
npx playwright install --list             # 列出已安装的浏览器

# ===== 运行测试 =====
npx playwright test                       # 运行所有测试
npx playwright test tests/login.spec.ts   # 运行指定文件
npx playwright test -g "登录成功"          # 按测试名称过滤
npx playwright test --project=chromium    # 仅 Chromium
npx playwright test --project=firefox     # 仅 Firefox
npx playwright test --project=webkit      # 仅 WebKit
npx playwright test --headed              # 有头模式(看到浏览器)
npx playwright test --debug               # 调试模式(Inspector)
npx playwright test --ui                  # UI 模式(图形界面)
npx playwright test --workers=1           # 单线程(调试用)
npx playwright test --workers=8           # 8 个并行
npx playwright test --repeat-each=5       # 每个测试重复 5 次
npx playwright test --retries=3           # 失败重试 3 次
npx playwright test --timeout=60000       # 超时 60 秒
npx playwright test --grep "smoke"        # 按 tag 过滤
npx playwright test --grep-invert "slow"  # 排除某些测试
npx playwright test --list                # 仅列出测试(不运行)
npx playwright test --last-failed         # 仅重跑上次失败的
npx playwright test --shard=1/3           # 分片(CI 并行)
npx playwright test --reporter=html       # HTML 报告
npx playwright test --reporter=json       # JSON 报告
npx playwright test --reporter=junit      # JUnit 报告
npx playwright test --update-snapshots    # 更新截图基准
npx playwright test --trace on            # 开启 trace
npx playwright test --video on            # 录制视频
npx playwright test --screenshot on       # 每步截图

# ===== 代码生成(Codegen) =====
npx playwright codegen https://example.com
npx playwright codegen --target=python https://example.com
npx playwright codegen --target=javascript https://example.com
npx playwright codegen --target=csharp https://example.com
npx playwright codegen --target=java https://example.com
npx playwright codegen --device="iPhone 14 Pro" https://example.com
npx playwright codegen --viewport-size=1920,1080 https://example.com
npx playwright codegen --save-storage=auth.json https://example.com
npx playwright codegen --load-storage=auth.json https://example.com
npx playwright codegen --lang=zh-CN https://example.com
npx playwright codegen -o tests/generated.spec.ts https://example.com
npx playwright codegen --browser=firefox https://example.com
npx playwright codegen --color-scheme=dark https://example.com

# ===== 报告与调试 =====
npx playwright show-report                # 打开 HTML 报告
npx playwright show-report ./report-dir   # 指定报告目录
npx playwright show-trace trace.zip       # 打开 Trace Viewer
npx playwright show-trace --port=8080     # 指定端口

# ===== 截图 =====
npx playwright screenshot https://example.com out.png
npx playwright screenshot --full-page https://example.com full.png
npx playwright screenshot --viewport-size=375,812 https://example.com mobile.png
npx playwright screenshot --browser=firefox https://example.com ff.png
npx playwright screenshot --wait-for-timeout=3000 https://example.com delayed.png

# ===== PDF =====
npx playwright pdf https://example.com page.pdf  # 仅 Chromium

# ===== 其他 =====
npx playwright --version                  # 查看版本
npx playwright --help                     # 帮助
npx playwright test --help                # 测试命令帮助
npx playwright codegen --help             # Codegen 帮助

5.3 启动浏览器

php 复制代码
const { chromium, firefox, webkit } = require('playwright');

// ===== 基础启动 =====
const browser = await chromium.launch();
const browser = await firefox.launch();
const browser = await webkit.launch();

// ===== 有头模式 =====
const browser = await chromium.launch({ headless: false });

// ===== 慢动作(每步操作间隔 500ms,调试用) =====
const browser = await chromium.launch({ slowMo: 500 });

// ===== 带参数启动 =====
const browser = await chromium.launch({
  headless: false,
  args: ['--start-maximized', '--lang=zh-CN'],
});

// ===== 使用系统 Chrome / Edge =====
const browser = await chromium.launch({ channel: 'chrome' });
const browser = await chromium.launch({ channel: 'msedge' });

// ===== 连接已运行的浏览器 =====
const browser = await chromium.connectOverCDP('http://localhost:9222');
const browser = await chromium.connect('ws://localhost:3000');

// ===== 创建浏览器上下文(核心概念) =====
const context = await browser.newContext();           // 默认上下文
const context = await browser.newContext({            // 自定义上下文
  viewport: { width: 1280, height: 720 },
  userAgent: 'Custom UA',
  locale: 'zh-CN',
  timezoneId: 'Asia/Shanghai',
  geolocation: { latitude: 39.9, longitude: 116.4 },
  permissions: ['geolocation'],
  colorScheme: 'dark',
  extraHTTPHeaders: { 'X-Custom': 'value' },
});

// ===== 使用设备描述符 =====
const { devices } = require('playwright');
const iPhone = devices['iPhone 14 Pro'];
const pixel = devices['Pixel 7'];
const iPad = devices['iPad Pro 11'];

const context = await browser.newContext({ ...iPhone });

// ===== 列出所有可用设备 =====
console.log(Object.keys(devices));

// ===== 创建页面 =====
const page = await context.newPage();

5.4 页面导航与等待

javascript 复制代码
// ===== 导航 =====
await page.goto('https://example.com');
await page.goto('https://example.com', { waitUntil: 'load' });
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.goto('https://example.com', { waitUntil: 'networkidle' });
await page.goto('https://example.com', { timeout: 60000 });

// ===== 前进/后退/刷新 =====
await page.goBack();
await page.goForward();
await page.reload();
await page.reload({ waitUntil: 'networkidle' });

// ===== 等待(Playwright 大部分操作自动等待,以下是显式等待) =====
await page.waitForURL('**/dashboard');
await page.waitForURL(url => url.includes('success'));
await page.waitForLoadState('networkidle');
await page.waitForLoadState('domcontentloaded');
await page.waitForTimeout(2000); // 硬等待(尽量避免)

// 等待选择器
await page.waitForSelector('.loaded');
await page.waitForSelector('.modal', { state: 'visible' });
await page.waitForSelector('.spinner', { state: 'hidden' });
await page.waitForSelector('.item', { state: 'attached' });
await page.waitForSelector('.item', { state: 'detached' });

// 等待响应/请求
await page.waitForResponse('**/api/data');
await page.waitForResponse(res => res.status() === 200);
await page.waitForRequest('**/api/submit');

// 等待函数
await page.waitForFunction(() => document.readyState === 'complete');
await page.waitForFunction(() => window.itemsLoaded === true);

5.5 定位器系统(Playwright 核心优势)

php 复制代码
// ===== 推荐定位方式(按优先级排序) =====

// 1. Role(最推荐,语义化 + 无障碍)
page.getByRole('button', { name: '提交' });
page.getByRole('link', { name: '首页' });
page.getByRole('heading', { name: '欢迎' });
page.getByRole('textbox', { name: '用户名' });
page.getByRole('checkbox', { name: '记住我' });
page.getByRole('tab', { name: '设置' });
page.getByRole('dialog');
page.getByRole('alert');

// 2. 文本
page.getByText('欢迎回来');
page.getByText('欢迎回来', { exact: true });  // 精确匹配

// 3. Label(表单)
page.getByLabel('用户名');
page.getByLabel('密码');
page.getByLabel('电子邮箱');

// 4. Placeholder
page.getByPlaceholder('请输入搜索关键词');

// 5. Alt text(图片)
page.getByAltText('公司 Logo');

// 6. Title
page.getByTitle('关闭对话框');

// 7. Test ID(需前端配合添加 data-testid)
page.getByTestId('submit-button');
page.getByTestId('user-profile-card');

// ===== CSS / XPath 定位(兜底) =====
page.locator('.class-name');
page.locator('#id');
page.locator('div > span.text');
page.locator('css=.complex >> nth=0');
page.locator('xpath=//div[@class="item"]');

// ===== 定位器链式过滤 =====
page.locator('.product-card').filter({ hasText: 'iPhone' });
page.locator('.product-card').filter({ has: page.locator('.badge-new') });
page.locator('tr').filter({ hasText: '张三' }).locator('td').nth(2);

// ===== 定位器操作 =====
const btn = page.getByRole('button', { name: '提交' });
await btn.click();
await btn.dblclick();
await btn.rightClick();
await btn.hover();
await btn.focus();
await btn.press('Enter');
await btn.check();       // 勾选
await btn.uncheck();     // 取消勾选
await btn.setEnabled();  // 等待可用

// ===== 输入 =====
await page.getByLabel('用户名').fill('admin');
await page.getByLabel('密码').fill('pass123');
await page.getByLabel('备注').pressSequentially('逐字输入', { delay: 50 });
await page.getByLabel('搜索').clear();
await page.getByLabel('搜索').type('old way'); // 不推荐

// ===== 选择 =====
await page.getByLabel('城市').selectOption('beijing');
await page.getByLabel('城市').selectOption({ label: '北京' });
await page.getByLabel('城市').selectOption({ value: 'bj' });
await page.getByLabel('城市').selectOption({ index: 0 });

// ===== 文件上传 =====
await page.getByLabel('上传文件').setInputFiles('./file.pdf');
await page.getByLabel('上传文件').setInputFiles(['./a.png', './b.png']);
await page.getByLabel('上传文件').setInputFiles([]);  // 清空

// ===== 获取信息 =====
const text = await page.getByRole('heading').textContent();
const innerText = await page.locator('.content').innerText();
const innerHTML = await page.locator('.content').innerHTML();
const value = await page.getByLabel('用户名').inputValue();
const isVisible = await page.locator('.modal').isVisible();
const isEnabled = await page.getByRole('button').isEnabled();
const isChecked = await page.getByRole('checkbox').isChecked();
const count = await page.locator('.item').count();
const allTexts = await page.locator('.item').allTextContents();
const attr = await page.locator('a').getAttribute('href');

5.6 网络拦截与 Mock

javascript 复制代码
// ===== 拦截并 Mock 响应 =====
await page.route('**/api/users', route => {
  route.fulfill({
    status: 200,
    contentType: 'application/json',
    body: JSON.stringify([
      { id: 1, name: '张三', role: 'admin' },
      { id: 2, name: '李四', role: 'user' },
    ]),
  });
});

// ===== 拦截并修改请求 =====
await page.route('**/api/**', route => {
  const headers = {
    ...route.request().headers(),
    'Authorization': 'Bearer mock-token',
  };
  route.continue({ headers });
});

// ===== 屏蔽资源 =====
await page.route('**/*.{png,jpg,jpeg,gif,svg,webp}', route => route.abort());
await page.route('**/*', route => {
  if (['image', 'font', 'media'].includes(route.request().resourceType())) {
    route.abort();
  } else {
    route.continue();
  }
});

// ===== 模拟网络延迟 =====
await page.route('**/api/slow', async route => {
  await new Promise(r => setTimeout(r, 3000));
  route.continue();
});

// ===== 模拟错误 =====
await page.route('**/api/data', route => {
  route.fulfill({ status: 500, body: 'Internal Server Error' });
});

// ===== 从文件读取 Mock 数据 =====
const fs = require('fs');
await page.route('**/api/config', route => {
  const data = fs.readFileSync('./mocks/config.json', 'utf-8');
  route.fulfill({ contentType: 'application/json', body: data });
});

// ===== 监听网络事件 =====
page.on('request', req => console.log('→', req.method(), req.url()));
page.on('response', res => console.log('←', res.status(), res.url()));
page.on('requestfailed', req => console.log('✗', req.url(), req.failure()?.errorText));

// ===== 获取响应体 =====
const response = await page.waitForResponse('**/api/data');
const json = await response.json();
const text = await response.text();

5.7 多标签页 / 弹窗 / iframe

javascript 复制代码
// ===== 新标签页 =====
const [newPage] = await Promise.all([
  context.waitForEvent('page'),
  page.click('a[target="_blank"]'),
]);
await newPage.waitForLoadState();
console.log(await newPage.title());

// ===== 弹窗(Dialog) =====
page.on('dialog', async dialog => {
  console.log(dialog.type());    // alert / confirm / prompt
  console.log(dialog.message());
  await dialog.accept();         // 确认
  // await dialog.dismiss();     // 取消
  // await dialog.accept('输入内容'); // prompt 填写
});

// ===== Popup 窗口 =====
const [popup] = await Promise.all([
  page.waitForEvent('popup'),
  page.click('#open-popup'),
]);
await popup.waitForLoadState();

// ===== iframe =====
// 方式一:frameLocator(推荐)
const iframe = page.frameLocator('#payment-frame');
await iframe.locator('#card-number').fill('4111111111111111');
await iframe.locator('#submit').click();

// 方式二:嵌套 iframe
const outer = page.frameLocator('#outer-frame');
const inner = outer.frameLocator('#inner-frame');
await inner.locator('#btn').click();

// 方式三:frame 对象
const frame = page.frame({ name: 'myFrame' });
const frame = page.frame({ url: /payment/ });

5.8 文件下载处理

javascript 复制代码
// ===== 下载文件 =====
const [download] = await Promise.all([
  page.waitForEvent('download'),
  page.click('#download-btn'),
]);

console.log('文件名:', download.suggestedFilename());
console.log('URL:', download.url());

// 保存到指定路径
await download.saveAs('./downloads/' + download.suggestedFilename());

// 获取下载流
const stream = await download.createReadStream();

// 取消下载
await download.cancel();

// 删除临时文件
await download.delete();

5.9 认证状态管理(storageState)

php 复制代码
// ===== 保存登录态 =====
// 登录后执行:
await context.storageState({ path: '.auth/user.json' });

// ===== 复用登录态 =====
const context = await browser.newContext({
  storageState: '.auth/user.json',
});

// ===== 在 playwright.config.ts 中配置 =====
// projects: [
//   { name: 'setup', testMatch: /auth.setup.ts/ },
//   {
//     name: 'authenticated',
//     use: { storageState: '.auth/user.json' },
//     dependencies: ['setup'],
//   },
// ]

// ===== 未登录状态 =====
const context = await browser.newContext({
  storageState: { cookies: [], origins: [] },
});

5.10 移动端与设备模拟

php 复制代码
const { chromium, devices } = require('playwright');

// ===== 使用内置设备描述符 =====
const iPhone = devices['iPhone 14 Pro'];
const pixel = devices['Pixel 7'];
const iPad = devices['iPad Pro 11'];
const galaxy = devices['Galaxy S9+'];

const context = await browser.newContext({
  ...iPhone,
  locale: 'zh-CN',
  timezoneId: 'Asia/Shanghai',
  geolocation: { latitude: 31.23, longitude: 121.47 }, // 上海
  permissions: ['geolocation'],
});

// ===== 自定义模拟 =====
const context = await browser.newContext({
  viewport: { width: 375, height: 812 },
  deviceScaleFactor: 3,
  isMobile: true,
  hasTouch: true,
  userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0...)',
});

// ===== 模拟地理位置 =====
await context.setGeolocation({ latitude: 39.9, longitude: 116.4 });
await context.grantPermissions(['geolocation']);

// ===== 模拟暗色模式 =====
const context = await browser.newContext({ colorScheme: 'dark' });

// ===== 模拟打印媒体 =====
const context = await browser.newContext({ media: 'print' });

// ===== 模拟离线 =====
await context.setOffline(true);
// ... 测试离线行为 ...
await context.setOffline(false);

5.11 截图与视觉对比

php 复制代码
// ===== 截图 =====
await page.screenshot({ path: 'full.png', fullPage: true });
await page.screenshot({ path: 'viewport.png' });
await page.screenshot({ path: 'area.png', clip: { x: 0, y: 0, width: 500, height: 300 } });

// 元素截图
await page.locator('.chart').screenshot({ path: 'chart.png' });

// ===== 视觉回归测试(内置) =====
// 首次运行生成基准图,后续对比
await expect(page).toHaveScreenshot('homepage.png');
await expect(page).toHaveScreenshot('homepage.png', {
  maxDiffPixels: 100,          // 允许 100 像素差异
  maxDiffPixelRatio: 0.01,     // 允许 1% 差异
  threshold: 0.2,              // 颜色阈值
  animations: 'disabled',      // 禁用动画
  mask: [page.locator('.dynamic-time')],  // 遮罩动态区域
});

// 元素级对比
await expect(page.locator('.card')).toHaveScreenshot('card.png');

// 更新基准:npx playwright test --update-snapshots

5.12 Playwright 常用命令速查表

操作 命令/代码
启动浏览器 chromium.launch()
新建上下文 browser.newContext(opts)
新建页面 context.newPage()
导航 page.goto(url)
点击 locator.click()
填写 locator.fill(text)
按键 locator.press('Enter')
选择 locator.selectOption(val)
等待可见 locator.waitFor({ state: 'visible' })
等待 URL page.waitForURL(pattern)
获取文本 locator.textContent()
获取数量 locator.count()
截图 page.screenshot(opts)
视觉断言 expect(page).toHaveScreenshot()
Mock 路由 page.route(pattern, handler)
下载 page.waitForEvent('download')
弹窗 page.on('dialog', handler)
iframe page.frameLocator(sel)
设备模拟 devices['iPhone 14 Pro']
保存状态 context.storageState({ path })
关闭 browser.close()

六、Playwright Test:企业级 E2E 测试

6.1 编写测试

dart 复制代码
// 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('欢迎, admin')).toBeVisible();
  });

  test('密码错误显示提示', async ({ page }) => {
    await page.getByLabel('用户名').fill('admin');
    await page.getByLabel('密码').fill('wrong');
    await page.getByRole('button', { name: '登录' }).click();

    await expect(page.getByText('密码错误')).toBeVisible();
  });

  test('空表单提交显示校验', async ({ page }) => {
    await page.getByRole('button', { name: '登录' }).click();

    await expect(page.getByText('请输入用户名')).toBeVisible();
    await expect(page.getByText('请输入密码')).toBeVisible();
  });

  test('登录后可以访问受保护页面', async ({ page }) => {
    await page.getByLabel('用户名').fill('admin');
    await page.getByLabel('密码').fill('password123');
    await page.getByRole('button', { name: '登录' }).click();

    await page.goto('/settings');
    await expect(page.getByRole('heading', { name: '设置' })).toBeVisible();
  });
});

6.2 常用断言

scss 复制代码
// 可见性
await expect(locator).toBeVisible();
await expect(locator).toBeHidden();

// 文本
await expect(locator).toHaveText('精确文本');
await expect(locator).toContainText('包含');
await expect(locator).toHaveText(/正则/);

// 输入值
await expect(input).toHaveValue('admin');

// 状态
await expect(checkbox).toBeChecked();
await expect(checkbox).not.toBeChecked();
await expect(button).toBeEnabled();
await expect(button).toBeDisabled();

// URL / 标题
await expect(page).toHaveURL('/dashboard');
await expect(page).toHaveTitle('首页');

// 截图对比
await expect(page).toHaveScreenshot();
await expect(locator).toHaveScreenshot('element.png');

// 元素数量
await expect(page.locator('.item')).toHaveCount(5);

// CSS 类
await expect(locator).toHaveClass(/active/);

// API 响应
const response = await page.request.get('/api/users');
await expect(response).toBeOK();
await expect(response).toHaveStatus(200);

6.3 配置文件详解

php 复制代码
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  timeout: 30_000,
  expect: { timeout: 5_000 },
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 4 : undefined,
  reporter: [['html'], ['list']],

  use: {
    baseURL: 'http://localhost:3000',
    screenshot: 'only-on-failure',
    video: 'on-first-retry',
    trace: 'on-first-retry',
    actionTimeout: 10_000,
    navigationTimeout: 30_000,
  },

  projects: [
    { name: 'setup', testMatch: /auth.setup.ts/ },
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },
    },
    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'] },
    },
    {
      name: 'mobile-chrome',
      use: { ...devices['Pixel 7'] },
    },
    {
      name: 'mobile-safari',
      use: { ...devices['iPhone 14 Pro'] },
    },
  ],

  webServer: {
    command: 'npm run dev',
    port: 3000,
    reuseExistingServer: !process.env.CI,
    timeout: 120_000,
  },
});

6.4 运行测试命令汇总

bash 复制代码
npx playwright test                              # 全部
npx playwright test --project=chromium           # 单浏览器
npx playwright test tests/login.spec.ts          # 单文件
npx playwright test -g "登录"                    # 按名称
npx playwright test --headed                     # 有头
npx playwright test --debug                      # 调试
npx playwright test --ui                         # UI 模式
npx playwright test --workers=1                  # 串行
npx playwright test --repeat-each=10             # 压力测试
npx playwright test --last-failed                # 重跑失败
npx playwright test --update-snapshots           # 更新截图
npx playwright test --trace on --video on        # 全量记录
npx playwright show-report                       # 查看报告

七、调试利器(Playwright 专属)

7.1 Codegen:录制生成代码

arduino 复制代码
npx playwright codegen https://your-app.com

高级用法:

ini 复制代码
npx playwright codegen --target=python https://your-app.com
npx playwright codegen --device="iPhone 14 Pro" https://your-app.com
npx playwright codegen --save-storage=auth.json https://your-app.com
npx playwright codegen --load-storage=auth.json https://your-app.com
npx playwright codegen -o tests/generated.spec.ts https://your-app.com
npx playwright codegen --browser=firefox https://your-app.com
npx playwright codegen --color-scheme=dark https://your-app.com

7.2 Trace Viewer:时间旅行调试

sql 复制代码
npx playwright test --trace on
npx playwright show-trace trace.zip

7.3 Inspector:逐步调试

bash 复制代码
npx playwright test --debug

7.4 UI Mode:可视化测试管理

bash 复制代码
npx playwright test --ui

八、提高开发效率的 10 个实战技巧

(内容与原文一致,此处省略重复,保持原有 10 个技巧不变)


九、常见场景速查表

需求 Puppeteer Playwright
网页截图 page.screenshot() page.screenshot()
生成 PDF page.pdf() page.pdf()(仅 Chromium)
表单填写 page.type() / page.click() locator.fill() / locator.click()
等待元素 page.waitForSelector() 自动 / locator.waitFor()
执行页面 JS page.evaluate() page.evaluate()
拦截请求 page.setRequestInterception(true) page.route()
下载文件 手动处理 download 事件 + download.saveAs()
处理弹窗 page.on('dialog') page.on('dialog')
iframe 操作 page.frames() page.frameLocator()
文件上传 elementHandle.uploadFile() locator.setInputFiles()
认证状态持久化 手动 page.cookies() context.storageState()
设备模拟 手动设置 UA + viewport devices['xxx'] 一行搞定
视觉回归 需第三方库 内置 toHaveScreenshot()

十、常见问题排查

Q1:Chromium / 浏览器下载失败

bash 复制代码
# Puppeteer
export PUPPETEER_DOWNLOAD_BASE_URL=https://npmmirror.com/mirrors/chrome-for-testing
npm install puppeteer

# Playwright
export PLAYWRIGHT_DOWNLOAD_HOST=https://npmmirror.com/mirrors/playwright
npx playwright install

Q2:Linux 报错缺少共享库

bash 复制代码
# Playwright 一键修复
npx playwright install-deps

# Puppeteer 手动安装(见第二章 2.6 节)

Q3:Docker 中运行

bash 复制代码
# Playwright 推荐官方镜像
docker run --rm -v $(pwd):/app -w /app mcr.microsoft.com/playwright:v1.48.0-noble npx playwright test

# Puppeteer 需要 --no-sandbox

Q4:元素定位不到

arduino 复制代码
// 1. 是否在 iframe 中
// 2. 是否被遮挡
// 3. 是否需要等待
// 4. 使用 --debug 模式排查

Q5:测试不稳定(Flaky Test)

markdown 复制代码
1. 开启 trace → 查看失败截图
2. 检查竞态条件
3. --repeat-each=10 复现
4. 增加重试 + 日志

十一、总结与选型建议

复制代码
你的场景是什么?
│
├─ 快速截图 / 生成 PDF / 简单爬虫
│  └─→ Puppeteer(轻量、够用、上手快)
│
├─ 跨浏览器 E2E 测试
│  └─→ Playwright(唯一正解)
│
├─ 企业级自动化测试平台
│  └─→ Playwright Test(内置框架 + Trace + 并行 + 报告)
│
├─ 需要支持 Python / Java / .NET
│  └─→ Playwright(多语言 SDK)
│
└─ 已有大量 Puppeteer 代码
   └─→ 继续使用 Puppeteer,迁移不紧急

📝 本文所有命令均基于 Puppeteer v23+ 和 Playwright v1.48+ 验证,适用于 Node.js 20/22 LTS。

官方文档:

祝你自动化之路顺利,效率翻倍!🚀

相关推荐
sunphp开发者22 分钟前
阿里云CDN加速配置问题
前端·阿里云·云计算
小小数媒成员29 分钟前
存储器层级结构
java·服务器·前端
单线程121382 小时前
从案例分析 Vue3 Tokenizer 源码
前端·javascript·vue.js
明月_清风2 小时前
🖥️ Electron 三进程 Host 实战:从架构设计到生产落地的完整指南
前端·electron·客户端
GIS数据转换器2 小时前
智慧林草“一张图“平台
java·大数据·服务器·前端·javascript·数据库·人工智能
2401_885885042 小时前
国际语音php接口代码示例:PHP使用cURL快速调用语音发送API
android·开发语言·前端·人工智能·python·php·语音识别
leoZ2312 小时前
AI+前端提效-08 AI自动化文档:前端组件、接口、项目文档自动生成
前端·人工智能·深度学习·神经网络·目标检测·自然语言处理·自动化
lilian2332 小时前
HarmonyOS 7 新特性(五)|ContainerReader 容器断点与自适应布局
前端·华为·harmonyos
计算机魔术师2 小时前
Google DeepMind 将 AI 科学家 Co-Scientist 扩展为实验室集成研究伙伴
前端