跪拜 Guibai
← Back to the summary

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:

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:

1.3 Core Comparison at a Glance

Dimension Puppeteer Playwright
Maintainer Google 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:

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 directly launch()
  • puppeteer-core: does not include a browser, must specify executablePath when calling launch()

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:

Wishing you a smooth automation journey and doubled efficiency! 🚀