Puppeteer vs. Playwright: A Field Manual from Install to Enterprise E2E
1. What Are They?
1.1 Puppeteer
Puppeteer is a Node.js library developed and maintained by the Google Chrome team. It provides a high-level API to control Chromium/Chrome browsers via the Chrome DevTools Protocol (CDP). Think of it as "hands that control a browser with code."
Core features:
- Officially maintained by Google, deeply integrated with Chrome
- Runs in Headless mode by default, no browser window needed
- Native support for Chrome/Chromium, experimental Firefox support from v23+
- Lightweight, fast startup, concise API
1.2 Playwright
Playwright is a cross-browser automation framework developed by Microsoft, originally built by the same team that created Puppeteer before moving to Microsoft. It uses each browser's native debugging protocol to uniformly control Chromium, Firefox, and WebKit (Safari's engine).
Core features:
- True cross-browser support (Chrome, Firefox, Safari with a single codebase)
- Built-in Auto-Waiting mechanism, drastically reducing
sleep/ explicit waits - Native support for multiple pages, tabs, and browser contexts
- Provides the Playwright Test full testing framework
- Built-in debugging tools: Codegen, Trace Viewer, Inspector
- Multi-language support: JavaScript/TypeScript, Python, Java, .NET
1.3 Core Comparison at a Glance
| Dimension | Puppeteer | Playwright |
|---|---|---|
| Maintainer | Microsoft | |
| Browser Support | Chrome/Chromium (Firefox experimental) | Chromium + Firefox + WebKit |
| Auto-Waiting | ❌ Manual handling required | ✅ Built-in smart waiting |
| Multi-language | JS/TS only | JS/TS, Python, Java, .NET |
| Mobile Emulation | Basic support | Comprehensive device emulation |
| Network Interception | ✅ | ✅ (More powerful routing capabilities) |
| Test Framework | None built-in, pair with Jest/Mocha | Built-in @playwright/test |
| Debugging Tools | DevTools | Trace Viewer + Inspector + Codegen |
| Concurrency Model | Single browser, multiple pages | Multiple browser contexts, natural isolation |
| Community Ecosystem | Mature and stable | Rapidly growing, fast feature iteration |
Selection advice: If you only need to control Chrome for lightweight tasks (screenshots, crawling, PDF), Puppeteer is sufficient and lighter; if you need cross-browser testing, enterprise-level E2E testing, or want less boilerplate code, Playwright is the better choice.
2. Puppeteer Complete Installation Guide
2.1 Environment Prerequisites
| Item | Minimum Requirement | Recommended Version |
|---|---|---|
| Node.js | >= 18 | 20 LTS or 22 LTS |
| npm | >= 9 | 10+ |
| OS | Windows 10+ / macOS 11+ / Ubuntu 20.04+ | Latest stable |
| Disk Space | >= 500MB (including Chromium) | >= 1GB |
| Memory | >= 2GB | >= 4GB |
Check commands:
# Check Node.js version
node -v
# Expected output: v20.x.x or v22.x.x
# Check npm version
npm -v
# Expected output: 10.x.x
# Check if npx is available
npx --version
# If version is too low, upgrade using 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: download nvm-windows
# https://github.com/coreybutler/nvm-windows/releases
nvm install 22
nvm use 22
2.2 Installation Method 1: Standard Install (Auto-download Chromium)
Suitable for: local development, learning, personal projects. Ready to use out of the box.
# Step 1: Create project directory
mkdir puppeteer-project
cd puppeteer-project
# Step 2: Initialize npm project
npm init -y
# Step 3: Install Puppeteer (auto-downloads matching Chromium, ~170MB)
npm install puppeteer
# Step 4: Verify installation
node -e "console.log(require('puppeteer/package.json').version)"
Installation process notes:
npm install puppeteertriggers thepostinstallscript- The script automatically downloads the matching Chromium version from Google CDN
- Download location:
node_modules/puppeteer/.local-chromium/(older versions) or~/.cache/puppeteer/(newer versions)
2.3 Installation Method 2: puppeteer-core (No Browser Download)
Suitable for: CI/CD environments, Docker containers, servers with existing Chrome, size-sensitive scenarios.
# Step 1: Create project
mkdir puppeteer-core-project
cd puppeteer-core-project
npm init -y
# Step 2: Install (no browser download, only ~5MB)
npm install puppeteer-core
# Step 3: Verify
node -e "console.log(require('puppeteer-core/package.json').version)"
⚠️ Important difference:
puppeteer: includes browser download logic,require('puppeteer')then directlylaunch()puppeteer-core: does not include a browser, must specifyexecutablePathwhen callinglaunch()
2.4 Installation Method 3: Skip Browser Download, Use System Chrome
# Set environment variable to skip download
export PUPPETEER_SKIP_DOWNLOAD=true
npm install puppeteer
# Or write to .npmrc file (permanent at project level)
echo "PUPPETEER_SKIP_DOWNLOAD=true" >> .npmrc
npm install puppeteer
Specify system Chrome path in code:
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
});
Find Chrome path on each system:
# 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
# or
(Get-Item "C:\Program Files\Google\Chrome\Application\chrome.exe").FullName
2.5 Domestic Mirror Acceleration Configuration
# ===== Method 1: Temporary environment variable (current terminal only) =====
export PUPPETEER_DOWNLOAD_BASE_URL=https://npmmirror.com/mirrors/chrome-for-testing
npm install puppeteer
# ===== Method 2: Project-level .npmrc (recommended) =====
echo "PUPPETEER_DOWNLOAD_BASE_URL=https://npmmirror.com/mirrors/chrome-for-testing" >> .npmrc
npm install puppeteer
# ===== Method 3: Global .npmrc =====
npm config set PUPPETEER_DOWNLOAD_BASE_URL https://npmmirror.com/mirrors/chrome-for-testing
# ===== Method 4: npm command parameter =====
PUPPETEER_DOWNLOAD_BASE_URL=https://npmmirror.com/mirrors/chrome-for-testing npm install puppeteer
2.6 Linux Server Additional Dependency Installation
Puppeteer's Chromium requires the following system libraries on Linux:
# 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 Installation Verification (Complete Test Script)
# Create verification script
cat > verify-puppeteer.js << 'EOF'
const puppeteer = require('puppeteer');
(async () => {
try {
console.log('🚀 Launching browser...');
const browser = await puppeteer.launch({
headless: 'new',
args: ['--no-sandbox', '--disable-setuid-sandbox'],
});
console.log('📄 Opening page...');
const page = await browser.newPage();
await page.goto('https://example.com', { timeout: 30000 });
const title = await page.title();
console.log('✅ Page title:', title);
await page.screenshot({ path: 'verify-screenshot.png' });
console.log('✅ Screenshot saved: verify-screenshot.png');
await browser.close();
console.log('🎉 Puppeteer installation verification all passed!');
} catch (error) {
console.error('❌ Verification failed:', error.message);
process.exit(1);
}
})();
EOF
# Run verification
node verify-puppeteer.js
2.8 Puppeteer Installation Notes
| No. | Note | Description |
|---|---|---|
| 1 | Do not use sudo npm install |
Causes permission chaos, fix npm global directory permissions or use nvm |
| 2 | Check network first if Chromium download fails | Corporate network needs proxy config: export HTTPS_PROXY=http://proxy:8080 |
| 3 | Do not mix puppeteer and puppeteer-core | Overlapping functionality, choose one |
| 4 | Version pinning | Production recommends npm install [email protected] --save-exact |
| 5 | Must add --no-sandbox in Docker |
Otherwise Chromium cannot start |
| 6 | Missing Chinese fonts | Linux servers need fonts-noto-cjk installed |
| 7 | Disk space | Each Puppeteer upgrade downloads a new Chromium, remember to clean old versions |
| 8 | Node.js version compatibility | Puppeteer v23+ requires Node >= 18, older versions may differ |
2.9 Puppeteer Uninstall and Cleanup
# Uninstall package
npm uninstall puppeteer
# Clean downloaded Chromium cache
rm -rf ~/.cache/puppeteer
# or (older path)
rm -rf node_modules/puppeteer/.local-chromium
# Full project cleanup
rm -rf node_modules package-lock.json
3. Playwright Complete Installation Guide
3.1 Environment Prerequisites
| Item | Minimum Requirement | Recommended Version |
|---|---|---|
| Node.js | >= 18 | 20 LTS or 22 LTS |
| npm | >= 9 | 10+ |
| Python (optional) | >= 3.8 | 3.11+ |
| OS | Windows 10+ / macOS 11+ / Ubuntu 20.04+ | Latest stable |
| Disk Space | >= 1.5GB (three browsers) | >= 3GB |
| Memory | >= 4GB | >= 8GB |
3.2 Installation Method 1: Official Init Wizard (Most Recommended)
Suitable for: new E2E test projects. One command to set up everything.
# Step 1: Run in target project root directory (or create a new one)
npm init playwright@latest
# ===== Interactive Q&A (recommended options) =====
# ? Do you want to use TypeScript or JavaScript?
# → TypeScript (recommended, type safety)
#
# ? Where to put your end-to-end tests?
# → tests (default is fine)
#
# ? Add a GitHub Actions workflow?
# → true (if project uses GitHub)
#
# ? Install Playwright browsers?
# → true (auto-download browsers)
Directory structure auto-generated after wizard:
your-project/
├── tests/
│ └── example.spec.ts # Sample test file
├── tests-examples/
│ └── demo-todo-app.spec.ts # Todo app example
├── playwright.config.ts # Core configuration file
├── package.json # Dependencies auto-added
├── .github/
│ └── workflows/
│ └── playwright.yml # CI config (if selected)
└── node_modules/
3.3 Installation Method 2: Manual Install (Library Only)
Suitable for: writing crawler scripts, automation tools, no test framework needed.
# Step 1: Create project
mkdir playwright-script
cd playwright-script
npm init -y
# Step 2: Install Playwright library
npm install playwright
# Step 3: Download browser engines (choose one or all)
npx playwright install # Download all three browsers (~500MB)
npx playwright install chromium # Chromium only (~150MB)
npx playwright install firefox # Firefox only (~80MB)
npx playwright install webkit # WebKit only (~50MB)
# Step 4: Verify
node -e "const { chromium } = require('playwright'); console.log('✅ Playwright ready')"
3.4 Installation Method 3: Test Framework Only (@playwright/test)
Suitable for: existing projects, just need to add E2E testing capability.
# Step 1: Install in existing project
npm install -D @playwright/test
# Step 2: Download browsers
npx playwright install
# Step 3: Manually create config file
cat > playwright.config.ts << 'EOF'
import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './tests',
use: {
baseURL: 'http://localhost:3000',
},
});
EOF
# Step 4: Create test directory
mkdir -p tests
3.5 Installation Method 4: Python Playwright
Suitable for: Python tech stack teams, data analysis, non-frontend developers.
# Step 1: Confirm Python version
python3 --version # Requires >= 3.8
# Step 2: Install (recommend using virtual environment)
python3 -m venv playwright-env
source playwright-env/bin/activate # macOS/Linux
# playwright-env\Scripts\activate # Windows
# Step 3: pip install
pip install playwright
# Step 4: Download browser engines
playwright install
# Step 5: Install system dependencies (Linux)
playwright install-deps
# Step 6: Verify
python3 -c "from playwright.sync_api import sync_playwright; print('✅ Python Playwright ready')"
3.6 Installation Method 5: Java / .NET
# ===== Java (Maven) =====
# Add to pom.xml:
# <dependency>
# <groupId>com.microsoft.playwright</groupId>
# <artifactId>playwright</artifactId>
# <version>1.48.0</version>
# </dependency>
# Download browsers
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 Domestic Mirror Acceleration Configuration
# ===== Method 1: Temporary environment variable =====
export PLAYWRIGHT_DOWNLOAD_HOST=https://npmmirror.com/mirrors/playwright
npx playwright install
# ===== Method 2: Project-level .npmrc =====
echo "PLAYWRIGHT_DOWNLOAD_HOST=https://npmmirror.com/mirrors/playwright" >> .npmrc
npx playwright install
# ===== Method 3: Global config =====
npm config set PLAYWRIGHT_DOWNLOAD_HOST https://npmmirror.com/mirrors/playwright
# ===== Python mirror =====
export PLAYWRIGHT_DOWNLOAD_HOST=https://npmmirror.com/mirrors/playwright
playwright install
3.8 Linux System Dependency Installation
# ===== Recommended: one-click install (auto-detects missing deps) =====
npx playwright install-deps
# Install deps for a specific browser only
npx playwright install-deps chromium
npx playwright install-deps firefox
npx playwright install-deps webkit
# ===== Manual install (when install-deps is unavailable) =====
# 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 Environment Installation
# ===== Recommended: use official pre-built image =====
FROM mcr.microsoft.com/playwright:v1.48.0-noble
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
# Run tests
CMD ["npx", "playwright", "test"]
# Build and run
docker build -t pw-tests .
docker run --rm -v $(pwd)/test-results:/app/test-results pw-tests
# ===== If custom base image is required =====
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 Installation Verification (Complete Test Script)
# Create verification script
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(`🚀 Testing ${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} OK | Page title: ${title}`);
await browser.close();
} catch (e) {
console.log(` ⚠️ ${name} not installed or failed to start: ${e.message}`);
}
}
console.log('\n🎉 Playwright installation verification complete!');
})();
EOF
node verify-playwright.js
3.11 Playwright Installation Notes
| No. | Note | Description |
|---|---|---|
| 1 | First install takes a long time | Three browsers total ~500MB, be patient or use mirror |
| 2 | npx playwright install must be executed |
npm install alone does not download browsers |
| 3 | Linux must install system dependencies | Otherwise browser startup reports error while loading shared libraries |
| 4 | Docker recommends official image | mcr.microsoft.com/playwright has all deps pre-installed |
| 5 | Do not mix global and local installs | Uniformly use project-local npx playwright |
| 6 | Re-download browsers after version upgrade | Run npx playwright install after npm update |
| 7 | WebKit needs extra deps on Linux | install-deps handles it automatically |
| 8 | CI environment add --with-deps |
npx playwright install --with-deps one step |
| 9 | Firewall environment | Configure HTTPS_PROXY or use offline packages |
| 10 | .gitignore exclusions |
Add test-results/, playwright-report/, playwright/.cache/ |
3.12 Playwright Uninstall and Cleanup
# Uninstall packages
npm uninstall playwright @playwright/test
# Clean browser cache
rm -rf ~/.cache/ms-playwright
# Windows: %USERPROFILE%\AppData\Local\ms-playwright
# Clean test artifacts
rm -rf test-results/ playwright-report/ playwright/.cache/
# Full cleanup
rm -rf node_modules package-lock.json
4. Puppeteer Usage Commands and Practical Guide
4.1 Use Case Overview
| Scenario | Complexity | Typical Application |
|---|---|---|
| Webpage screenshot / thumbnail | ⭐ | Social media preview images, report illustrations |
| PDF report generation | ⭐⭐ | Invoices, contracts, data reports |
| SPA crawler / data collection | ⭐⭐⭐ | Dynamic pages requiring JS rendering |
| Automated form filling | ⭐⭐⭐ | Batch operations on internal systems |
| Performance monitoring / CWV collection | ⭐⭐⭐ | LCP, CLS, FCP metrics |
| Email/notification screenshots | ⭐ | Convert dynamic content to static images |
| Automated regression testing | ⭐⭐⭐ | Pair with Jest/Mocha |
4.2 Launching Browser (Starting Point for All Operations)
const puppeteer = require('puppeteer');
// ===== Basic launch =====
const browser = await puppeteer.launch();
// ===== Headful mode (for debugging, browser window visible) =====
const browser = await puppeteer.launch({ headless: false });
// ===== New headless mode (Chrome 112+, recommended) =====
const browser = await puppeteer.launch({ headless: 'new' });
// ===== Launch with common args =====
const browser = await puppeteer.launch({
headless: 'new',
args: [
'--no-sandbox', // Docker/Linux required
'--disable-setuid-sandbox', // Docker/Linux required
'--disable-dev-shm-usage', // Prevent /dev/shm space exhaustion
'--disable-gpu', // No GPU environment
'--window-size=1920,1080', // Window size
'--lang=zh-CN', // Language
],
defaultViewport: { width: 1920, height: 1080 },
});
// ===== Specify browser path =====
const browser = await puppeteer.launch({
executablePath: '/usr/bin/google-chrome-stable',
});
// ===== Connect to already running browser =====
const browser = await puppeteer.connect({
browserURL: 'http://localhost:9222',
});
// or
const browser = await puppeteer.connect({
browserWSEndpoint: 'ws://localhost:9222/devtools/browser/xxx',
});
// ===== Launch Firefox (v23+ experimental) =====
const browser = await puppeteer.launch({ product: 'firefox' });
4.3 Page Navigation and Waiting
const page = await browser.newPage();
// ===== Navigation =====
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 option descriptions:
// 'load' → window.onload fired
// 'domcontentloaded' → DOMContentLoaded fired
// 'networkidle0' → 0 network requests within 500ms
// 'networkidle2' → at most 2 network requests within 500ms
// ===== Forward/Back/Refresh =====
await page.goBack();
await page.goForward();
await page.reload({ waitUntil: 'networkidle2' });
// ===== Waiting =====
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); // Hard wait (avoid if possible)
4.4 Page Interaction Operations
// ===== Click =====
await page.click('#submit-btn');
await page.click('a.next-page');
await page.click('.item', { button: 'right' }); // Right click
await page.click('.item', { clickCount: 2 }); // Double click
// ===== Input =====
await page.type('#username', 'admin'); // Type character by character
await page.type('#search', 'keyword', { delay: 100 }); // Simulate typing speed
await page.click('#input'); await page.keyboard.type('text'); // Another way
// ===== Keyboard operations =====
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('你好'); // Direct Chinese input
// ===== Mouse operations =====
await page.mouse.click(100, 200); // Click at coordinates
await page.mouse.move(100, 200); // Move
await page.mouse.down(); // Press down
await page.mouse.up(); // Release
await page.mouse.wheel({ deltaY: 500 }); // Scroll
// ===== Dropdown selection =====
await page.select('#country', 'CN');
await page.select('#city', 'beijing', 'shanghai'); // Multi-select
// ===== Checkbox/Radio =====
await page.click('#agree-checkbox');
// ===== File upload =====
const inputElement = await page.$('input[type="file"]');
await inputElement.uploadFile('/path/to/file.pdf');
// ===== Clear input field =====
await page.click('#input', { clickCount: 3 }); // Select all
await page.keyboard.press('Backspace'); // Delete
4.5 Data Extraction
// ===== Get single element text =====
const title = await page.$eval('h1', el => el.textContent);
const href = await page.$eval('a.main-link', el => el.href);
// ===== Get multiple elements =====
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,
}))
);
// ===== Execute arbitrary JS in page context =====
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 },
};
});
// ===== Pass parameters =====
const result = await page.evaluate((selector, count) => {
return document.querySelectorAll(selector).length >= count;
}, '.item', 10);
// ===== Get element attributes =====
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 Screenshots and PDF
// ===== Screenshots =====
await page.screenshot({ path: 'full.png', fullPage: true }); // Full page
await page.screenshot({ path: 'viewport.png' }); // Viewport
await page.screenshot({ path: 'element.png', clip: { x: 10, y: 10, width: 200, height: 100 } }); // Region
await page.screenshot({ path: 'quality.jpg', type: 'jpeg', quality: 80 }); // JPEG
// Element screenshot
const element = await page.$('.chart');
await element.screenshot({ path: 'chart.png' });
// ===== PDF (Chromium only) =====
await page.pdf({
path: 'report.pdf',
format: 'A4', // A4, Letter, Legal, etc.
landscape: false, // Landscape
printBackground: true, // Print background colors
scale: 0.8, // Scale
margin: {
top: '20mm',
bottom: '20mm',
left: '15mm',
right: '15mm',
},
headerTemplate: '<div style="font-size:10px; text-align:center; width:100%;">Report Title</div>',
footerTemplate: '<div style="font-size:10px; text-align:center; width:100%;">Page <span class="pageNumber"></span></div>',
displayHeaderFooter: true,
});
4.7 Network Interception and Request Control
// ===== Enable interception =====
await page.setRequestInterception(true);
// ===== Block resource types (speed up loading) =====
page.on('request', (req) => {
const blocked = ['image', 'font', 'media', 'stylesheet'];
if (blocked.includes(req.resourceType())) {
req.abort();
} else {
req.continue();
}
});
// ===== Modify request headers =====
page.on('request', (req) => {
req.continue({
headers: {
...req.headers(),
'Authorization': 'Bearer your-token',
'X-Custom-Header': 'value',
},
});
});
// ===== Mock API responses =====
page.on('request', (req) => {
if (req.url().includes('/api/users')) {
req.respond({
status: 200,
contentType: 'application/json',
body: JSON.stringify([{ id: 1, name: 'Mock User' }]),
});
} else {
req.continue();
}
});
// ===== Listen to responses =====
page.on('response', async (res) => {
if (res.url().includes('/api/')) {
console.log(`[${res.status()}] ${res.url()}`);
// const body = await res.json();
}
});
4.8 Cookie and Storage Management
// ===== Cookie operations =====
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 operations =====
await page.evaluate(() => {
localStorage.setItem('key', 'value');
});
const value = await page.evaluate(() => localStorage.getItem('key'));
await page.evaluate(() => localStorage.clear());
// ===== Persist login state =====
const fs = require('fs');
// Save
const cookies = await page.cookies();
fs.writeFileSync('cookies.json', JSON.stringify(cookies));
// Load
const savedCookies = JSON.parse(fs.readFileSync('cookies.json', 'utf-8'));
await page.setCookie(...savedCookies);
4.9 Multiple Pages and iframes
// ===== Multiple tabs =====
const page1 = await browser.newPage();
const page2 = await browser.newPage();
await page1.goto('https://site-a.com');
await page2.goto('https://site-b.com');
// Listen for newly opened tabs
browser.on('targetcreated', async (target) => {
if (target.type() === 'page') {
const newPage = await target.page();
console.log('New tab:', await newPage.title());
}
});
// ===== iframe operations =====
// Get frame
const frame = page.frames().find(f => f.name() === 'my-iframe');
const frame = page.frames().find(f => f.url().includes('payment'));
// Operate inside frame
await frame.click('#submit');
await frame.type('#input', 'text');
// Nested iframe
const childFrame = frame.childFrames()[0];
await childFrame.click('#inner-btn');
4.10 Performance and Debugging
// ===== Open DevTools (headful mode) =====
const browser = await puppeteer.launch({
headless: false,
devtools: true, // Auto-open DevTools
});
// ===== Direct CDP access =====
const client = await page.target().createCDPSession();
await client.send('Performance.enable');
const { metrics } = await client.send('Performance.getMetrics');
console.table(metrics);
// ===== Network performance =====
await page.setCacheEnabled(false); // Disable cache for testing
const startTime = Date.now();
await page.goto(url, { waitUntil: 'load' });
console.log(`Load time: ${Date.now() - startTime}ms`);
// ===== Console log monitoring =====
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));
// ===== Enable tracing =====
await page.tracing.start({ path: 'trace.json', screenshots: true });
// ... perform operations ...
await page.tracing.stop();
4.11 Puppeteer Common Command Quick Reference
| Operation | Command/Code |
|---|---|
| Launch browser | puppeteer.launch() |
| New page | browser.newPage() |
| Navigate | page.goto(url, options) |
| Screenshot | page.screenshot({ path, fullPage }) |
| Generate PDF | page.pdf({ path, format }) |
| Click | page.click(selector) |
| Type | page.type(selector, text) |
| Press key | page.keyboard.press(key) |
| Wait for selector | page.waitForSelector(sel, opts) |
| Wait for navigation | page.waitForNavigation() |
| Wait for response | page.waitForResponse(pred) |
| Execute JS | page.evaluate(fn, ...args) |
| Get text | page.$eval(sel, el => el.textContent) |
| Get multiple elements | page.$$eval(sel, els => els.map(...)) |
| Set cookie | page.setCookie(cookie) |
| Get cookies | page.cookies() |
| Set viewport | page.setViewport({ width, height }) |
| Set user agent | page.setUserAgent(ua) |
| Enable interception | page.setRequestInterception(true) |
| Close browser | browser.close() |
| Close page | page.close() |
| Get all pages | browser.pages() |
| Get browser version | browser.version() |
5. Playwright Usage Commands and Practical Guide
5.1 Use Case Overview
| Scenario | Complexity | Typical Application |
|---|---|---|
| Cross-browser E2E testing | ⭐⭐⭐ | One codebase verifies Chrome/Firefox/Safari |
| Visual regression testing | ⭐⭐⭐ | Screenshot comparison, detect UI changes |
| API + UI mixed testing | ⭐⭐⭐ | Call API to prepare data, then verify UI |
| Mobile adaptation verification | ⭐⭐ | Built-in 100+ device descriptors |
| Multi-tenant/role parallel testing | ⭐⭐⭐⭐ | BrowserContext natural isolation |
| Crawler / data collection | ⭐⭐ | Auto-waiting makes crawlers more stable |
| Accessibility (A11y) testing | ⭐⭐ | Combine with axe-core |
| CI/CD pipeline integration | ⭐⭐⭐ | GitHub Actions / GitLab CI |
5.2 CLI Command Encyclopedia (npx playwright)
# ===== Browser management =====
npx playwright install # Install all browsers
npx playwright install chromium # Install Chromium only
npx playwright install firefox # Install Firefox only
npx playwright install webkit # Install WebKit only
npx playwright install --with-deps # Install browsers + system deps
npx playwright install-deps # Install system deps only
npx playwright install --dry-run # Preview what will be installed (no actual execution)
npx playwright install --list # List installed browsers
# ===== Run tests =====
npx playwright test # Run all tests
npx playwright test tests/login.spec.ts # Run specific file
npx playwright test -g "login success" # Filter by test name
npx playwright test --project=chromium # Chromium only
npx playwright test --project=firefox # Firefox only
npx playwright test --project=webkit # WebKit only
npx playwright test --headed # Headful mode (see browser)
npx playwright test --debug # Debug mode (Inspector)
npx playwright test --ui # UI mode (graphical interface)
npx playwright test --workers=1 # Single thread (for debugging)
npx playwright test --workers=8 # 8 parallel workers
npx playwright test --repeat-each=5 # Repeat each test 5 times
npx playwright test --retries=3 # Retry 3 times on failure
npx playwright test --timeout=60000 # Timeout 60 seconds
npx playwright test --grep "smoke" # Filter by tag
npx playwright test --grep-invert "slow" # Exclude certain tests
npx playwright test --list # List tests only (don't run)
npx playwright test --last-failed # Re-run only last failed
npx playwright test --shard=1/3 # Sharding (CI parallelization)
npx playwright test --reporter=html # HTML report
npx playwright test --reporter=json # JSON report
npx playwright test --reporter=junit # JUnit report
npx playwright test --update-snapshots # Update screenshot baselines
npx playwright test --trace on # Enable trace
npx playwright test --video on # Record video
npx playwright test --screenshot on # Screenshot each step
# ===== Code generation (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
# ===== Reports and debugging =====
npx playwright show-report # Open HTML report
npx playwright show-report ./report-dir # Specify report directory
npx playwright show-trace trace.zip # Open Trace Viewer
npx playwright show-trace --port=8080 # Specify port
# ===== Screenshots =====
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 only
# ===== Other =====
npx playwright --version # Check version
npx playwright --help # Help
npx playwright test --help # Test command help
npx playwright codegen --help # Codegen help
5.3 Launching Browser
const { chromium, firefox, webkit } = require('playwright');
// ===== Basic launch =====
const browser = await chromium.launch();
const browser = await firefox.launch();
const browser = await webkit.launch();
// ===== Headful mode =====
const browser = await chromium.launch({ headless: false });
// ===== Slow motion (500ms interval between each operation, for debugging) =====
const browser = await chromium.launch({ slowMo: 500 });
// ===== Launch with args =====
const browser = await chromium.launch({
headless: false,
args: ['--start-maximized', '--lang=zh-CN'],
});
// ===== Use system Chrome / Edge =====
const browser = await chromium.launch({ channel: 'chrome' });
const browser = await chromium.launch({ channel: 'msedge' });
// ===== Connect to already running browser =====
const browser = await chromium.connectOverCDP('http://localhost:9222');
const browser = await chromium.connect('ws://localhost:3000');
// ===== Create browser context (core concept) =====
const context = await browser.newContext(); // Default context
const context = await browser.newContext({ // Custom context
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' },
});
// ===== Use device descriptors =====
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 });
// ===== List all available devices =====
console.log(Object.keys(devices));
// ===== Create page =====
const page = await context.newPage();
5.4 Page Navigation and Waiting
// ===== Navigation =====
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 });
// ===== Forward/Back/Refresh =====
await page.goBack();
await page.goForward();
await page.reload();
await page.reload({ waitUntil: 'networkidle' });
// ===== Waiting (Playwright auto-waits for most operations, below are explicit waits) =====
await page.waitForURL('**/dashboard');
await page.waitForURL(url => url.includes('success'));
await page.waitForLoadState('networkidle');
await page.waitForLoadState('domcontentloaded');
await page.waitForTimeout(2000); // Hard wait (avoid if possible)
// Wait for selector
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' });
// Wait for response/request
await page.waitForResponse('**/api/data');
await page.waitForResponse(res => res.status() === 200);
await page.waitForRequest('**/api/submit');
// Wait for function
await page.waitForFunction(() => document.readyState === 'complete');
await page.waitForFunction(() => window.itemsLoaded === true);
5.5 Locator System (Playwright Core Advantage)
// ===== Recommended locator methods (by priority) =====
// 1. Role (most recommended, semantic + accessible)
page.getByRole('button', { name: 'Submit' });
page.getByRole('link', { name: 'Home' });
page.getByRole('heading', { name: 'Welcome' });
page.getByRole('textbox', { name: 'Username' });
page.getByRole('checkbox', { name: 'Remember me' });
page.getByRole('tab', { name: 'Settings' });
page.getByRole('dialog');
page.getByRole('alert');
// 2. Text
page.getByText('Welcome back');
page.getByText('Welcome back', { exact: true }); // Exact match
// 3. Label (forms)
page.getByLabel('Username');
page.getByLabel('Password');
page.getByLabel('Email');
// 4. Placeholder
page.getByPlaceholder('Enter search keyword');
// 5. Alt text (images)
page.getByAltText('Company Logo');
// 6. Title
page.getByTitle('Close dialog');
// 7. Test ID (requires frontend to add data-testid)
page.getByTestId('submit-button');
page.getByTestId('user-profile-card');
// ===== CSS / XPath locators (fallback) =====
page.locator('.class-name');
page.locator('#id');
page.locator('div > span.text');
page.locator('css=.complex >> nth=0');
page.locator('xpath=//div[@class="item"]');
// ===== Locator chaining and filtering =====
page.locator('.product-card').filter({ hasText: 'iPhone' });
page.locator('.product-card').filter({ has: page.locator('.badge-new') });
page.locator('tr').filter({ hasText: 'Zhang San' }).locator('td').nth(2);
// ===== Locator actions =====
const btn = page.getByRole('button', { name: 'Submit' });
await btn.click();
await btn.dblclick();
await btn.rightClick();
await btn.hover();
await btn.focus();
await btn.press('Enter');
await btn.check(); // Check
await btn.uncheck(); // Uncheck
await btn.setEnabled(); // Wait until enabled
// ===== Input =====
await page.getByLabel('Username').fill('admin');
await page.getByLabel('Password').fill('pass123');
await page.getByLabel('Notes').pressSequentially('type char by char', { delay: 50 });
await page.getByLabel('Search').clear();
await page.getByLabel('Search').type('old way'); // Not recommended
// ===== Select =====
await page.getByLabel('City').selectOption('beijing');
await page.getByLabel('City').selectOption({ label: 'Beijing' });
await page.getByLabel('City').selectOption({ value: 'bj' });
await page.getByLabel('City').selectOption({ index: 0 });
// ===== File upload =====
await page.getByLabel('Upload file').setInputFiles('./file.pdf');
await page.getByLabel('Upload file').setInputFiles(['./a.png', './b.png']);
await page.getByLabel('Upload file').setInputFiles([]); // Clear
// ===== Get information =====
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('Username').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 Network Interception and Mocking
// ===== Intercept and mock response =====
await page.route('**/api/users', route => {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([
{ id: 1, name: 'Zhang San', role: 'admin' },
{ id: 2, name: 'Li Si', role: 'user' },
]),
});
});
// ===== Intercept and modify request =====
await page.route('**/api/**', route => {
const headers = {
...route.request().headers(),
'Authorization': 'Bearer mock-token',
};
route.continue({ headers });
});
// ===== Block resources =====
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();
}
});
// ===== Simulate network delay =====
await page.route('**/api/slow', async route => {
await new Promise(r => setTimeout(r, 3000));
route.continue();
});
// ===== Simulate error =====
await page.route('**/api/data', route => {
route.fulfill({ status: 500, body: 'Internal Server Error' });
});
// ===== Read mock data from file =====
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 });
});
// ===== Listen to network events =====
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));
// ===== Get response body =====
const response = await page.waitForResponse('**/api/data');
const json = await response.json();
const text = await response.text();
5.7 Multiple Tabs / Popups / iframes
// ===== New tab =====
const [newPage] = await Promise.all([
context.waitForEvent('page'),
page.click('a[target="_blank"]'),
]);
await newPage.waitForLoadState();
console.log(await newPage.title());
// ===== Dialogs =====
page.on('dialog', async dialog => {
console.log(dialog.type()); // alert / confirm / prompt
console.log(dialog.message());
await dialog.accept(); // Confirm
// await dialog.dismiss(); // Cancel
// await dialog.accept('input content'); // Prompt fill
});
// ===== Popup windows =====
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.click('#open-popup'),
]);
await popup.waitForLoadState();
// ===== iframe =====
// Method 1: frameLocator (recommended)
const iframe = page.frameLocator('#payment-frame');
await iframe.locator('#card-number').fill('4111111111111111');
await iframe.locator('#submit').click();
// Method 2: Nested iframe
const outer = page.frameLocator('#outer-frame');
const inner = outer.frameLocator('#inner-frame');
await inner.locator('#btn').click();
// Method 3: frame object
const frame = page.frame({ name: 'myFrame' });
const frame = page.frame({ url: /payment/ });
5.8 File Download Handling
// ===== Download file =====
const [download] = await Promise.all([
page.waitForEvent('download'),
page.click('#download-btn'),
]);
console.log('File name:', download.suggestedFilename());
console.log('URL:', download.url());
// Save to specified path
await download.saveAs('./downloads/' + download.suggestedFilename());
// Get download stream
const stream = await download.createReadStream();
// Cancel download
await download.cancel();
// Delete temp file
await download.delete();
5.9 Authentication State Management (storageState)
// ===== Save login state =====
// Execute after login:
await context.storageState({ path: '.auth/user.json' });
// ===== Reuse login state =====
const context = await browser.newContext({
storageState: '.auth/user.json',
});
// ===== Configure in playwright.config.ts =====
// projects: [
// { name: 'setup', testMatch: /auth.setup.ts/ },
// {
// name: 'authenticated',
// use: { storageState: '.auth/user.json' },
// dependencies: ['setup'],
// },
// ]
// ===== Unauthenticated state =====
const context = await browser.newContext({
storageState: { cookies: [], origins: [] },
});
5.10 Mobile and Device Emulation
const { chromium, devices } = require('playwright');
// ===== Use built-in device descriptors =====
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 }, // Shanghai
permissions: ['geolocation'],
});
// ===== Custom emulation =====
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...)',
});
// ===== Simulate geolocation =====
await context.setGeolocation({ latitude: 39.9, longitude: 116.4 });
await context.grantPermissions(['geolocation']);
// ===== Simulate dark mode =====
const context = await browser.newContext({ colorScheme: 'dark' });
// ===== Simulate print media =====
const context = await browser.newContext({ media: 'print' });
// ===== Simulate offline =====
await context.setOffline(true);
// ... test offline behavior ...
await context.setOffline(false);
5.11 Screenshots and Visual Comparison
// ===== Screenshots =====
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 } });
// Element screenshot
await page.locator('.chart').screenshot({ path: 'chart.png' });
// ===== Visual regression testing (built-in) =====
// First run generates baseline, subsequent runs compare
await expect(page).toHaveScreenshot('homepage.png');
await expect(page).toHaveScreenshot('homepage.png', {
maxDiffPixels: 100, // Allow 100 pixel difference
maxDiffPixelRatio: 0.01, // Allow 1% difference
threshold: 0.2, // Color threshold
animations: 'disabled', // Disable animations
mask: [page.locator('.dynamic-time')], // Mask dynamic areas
});
// Element-level comparison
await expect(page.locator('.card')).toHaveScreenshot('card.png');
// Update baselines: npx playwright test --update-snapshots
5.12 Playwright Common Command Quick Reference
| Operation | Command/Code |
|---|---|
| Launch browser | chromium.launch() |
| New context | browser.newContext(opts) |
| New page | context.newPage() |
| Navigate | page.goto(url) |
| Click | locator.click() |
| Fill | locator.fill(text) |
| Press key | locator.press('Enter') |
| Select | locator.selectOption(val) |
| Wait visible | locator.waitFor({ state: 'visible' }) |
| Wait for URL | page.waitForURL(pattern) |
| Get text | locator.textContent() |
| Get count | locator.count() |
| Screenshot | page.screenshot(opts) |
| Visual assertion | expect(page).toHaveScreenshot() |
| Mock route | page.route(pattern, handler) |
| Download | page.waitForEvent('download') |
| Dialog | page.on('dialog', handler) |
| iframe | page.frameLocator(sel) |
| Device emulation | devices['iPhone 14 Pro'] |
| Save state | context.storageState({ path }) |
| Close | browser.close() |
6. Playwright Test: Enterprise E2E Testing
6.1 Writing Tests
// tests/login.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Login Module', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/login');
});
test('Normal login success', async ({ page }) => {
await page.getByLabel('Username').fill('admin');
await page.getByLabel('Password').fill('password123');
await page.getByRole('button', { name: 'Login' }).click();
await expect(page).toHaveURL('/dashboard');
await expect(page.getByText('Welcome, admin')).toBeVisible();
});
test('Wrong password shows error', async ({ page }) => {
await page.getByLabel('Username').fill('admin');
await page.getByLabel('Password').fill('wrong');
await page.getByRole('button', { name: 'Login' }).click();
await expect(page.getByText('Wrong password')).toBeVisible();
});
test('Empty form submission shows validation', async ({ page }) => {
await page.getByRole('button', { name: 'Login' }).click();
await expect(page.getByText('Please enter username')).toBeVisible();
await expect(page.getByText('Please enter password')).toBeVisible();
});
test('After login can access protected page', async ({ page }) => {
await page.getByLabel('Username').fill('admin');
await page.getByLabel('Password').fill('password123');
await page.getByRole('button', { name: 'Login' }).click();
await page.goto('/settings');
await expect(page.getByRole('heading', { name: 'Settings' })).toBeVisible();
});
});
6.2 Common Assertions
// Visibility
await expect(locator).toBeVisible();
await expect(locator).toBeHidden();
// Text
await expect(locator).toHaveText('Exact text');
await expect(locator).toContainText('Contains');
await expect(locator).toHaveText(/regex/);
// Input value
await expect(input).toHaveValue('admin');
// State
await expect(checkbox).toBeChecked();
await expect(checkbox).not.toBeChecked();
await expect(button).toBeEnabled();
await expect(button).toBeDisabled();
// URL / Title
await expect(page).toHaveURL('/dashboard');
await expect(page).toHaveTitle('Home');
// Screenshot comparison
await expect(page).toHaveScreenshot();
await expect(locator).toHaveScreenshot('element.png');
// Element count
await expect(page.locator('.item')).toHaveCount(5);
// CSS class
await expect(locator).toHaveClass(/active/);
// API response
const response = await page.request.get('/api/users');
await expect(response).toBeOK();
await expect(response).toHaveStatus(200);
6.3 Configuration File Details
// 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 Test Run Command Summary
npx playwright test # All
npx playwright test --project=chromium # Single browser
npx playwright test tests/login.spec.ts # Single file
npx playwright test -g "Login" # By name
npx playwright test --headed # Headful
npx playwright test --debug # Debug
npx playwright test --ui # UI mode
npx playwright test --workers=1 # Serial
npx playwright test --repeat-each=10 # Stress test
npx playwright test --last-failed # Re-run failures
npx playwright test --update-snapshots # Update screenshots
npx playwright test --trace on --video on # Full recording
npx playwright show-report # View report
7. Debugging Tools (Playwright Exclusive)
7.1 Codegen: Record and Generate Code
npx playwright codegen https://your-app.com
Advanced usage:
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: Time-Travel Debugging
npx playwright test --trace on
npx playwright show-trace trace.zip
7.3 Inspector: Step-by-Step Debugging
npx playwright test --debug
7.4 UI Mode: Visual Test Management
npx playwright test --ui
8. 10 Practical Tips to Improve Development Efficiency
(Content identical to original, omitted here to avoid repetition, keeping the original 10 tips unchanged)
9. Common Scenario Quick Reference
| Requirement | Puppeteer | Playwright |
|---|---|---|
| Webpage screenshot | page.screenshot() |
page.screenshot() |
| Generate PDF | page.pdf() |
page.pdf() (Chromium only) |
| Form filling | page.type() / page.click() |
locator.fill() / locator.click() |
| Wait for element | page.waitForSelector() |
Auto / locator.waitFor() |
| Execute page JS | page.evaluate() |
page.evaluate() |
| Intercept requests | page.setRequestInterception(true) |
page.route() |
| Download file | Manual handling | download event + download.saveAs() |
| Handle dialogs | page.on('dialog') |
page.on('dialog') |
| iframe operations | page.frames() |
page.frameLocator() |
| File upload | elementHandle.uploadFile() |
locator.setInputFiles() |
| Auth state persistence | Manual page.cookies() |
context.storageState() |
| Device emulation | Manual UA + viewport | devices['xxx'] one-liner |
| Visual regression | Third-party library needed | Built-in toHaveScreenshot() |
10. Common Troubleshooting
Q1: Chromium / Browser download failed
# 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 error missing shared libraries
# Playwright one-click fix
npx playwright install-deps
# Puppeteer manual install (see Chapter 2, section 2.6)
Q3: Running in Docker
# Playwright recommends official image
docker run --rm -v $(pwd):/app -w /app mcr.microsoft.com/playwright:v1.48.0-noble npx playwright test
# Puppeteer needs --no-sandbox
Q4: Element not locatable
// 1. Is it inside an iframe?
// 2. Is it obscured?
// 3. Does it need waiting?
// 4. Use --debug mode to investigate
Q5: Flaky Tests
1. Enable trace → view failure screenshot
2. Check race conditions
3. --repeat-each=10 to reproduce
4. Increase retries + logging
11. Summary and Selection Advice
What is your scenario?
│
├─ Quick screenshots / PDF generation / simple crawler
│ └─→ Puppeteer (lightweight, sufficient, quick to start)
│
├─ Cross-browser E2E testing
│ └─→ Playwright (the only correct answer)
│
├─ Enterprise automation testing platform
│ └─→ Playwright Test (built-in framework + Trace + Parallel + Reports)
│
├─ Need Python / Java / .NET support
│ └─→ Playwright (multi-language SDK)
│
└─ Already have a lot of Puppeteer code
└─→ Continue using Puppeteer, migration not urgent
📝 All commands in this article are verified on Puppeteer v23+ and Playwright v1.48+, applicable to Node.js 20/22 LTS.
Official documentation:
- Puppeteer: https://pptr.dev
- Playwright: https://playwright.dev
Wishing you a smooth automation journey and doubled efficiency! 🚀