Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:24:08 +08:00
commit 7c90a3ac2b
18 changed files with 4579 additions and 0 deletions

View File

@@ -0,0 +1,627 @@
# Puppeteer vs Playwright Comparison
Complete comparison guide for choosing between @cloudflare/puppeteer and @cloudflare/playwright.
---
## Quick Recommendation
**Use Puppeteer if:**
- ✅ Starting a new project
- ✅ Need session management features
- ✅ Want to optimize performance/costs
- ✅ Building screenshot/PDF services
- ✅ Web scraping workflows
**Use Playwright if:**
- ✅ Already have Playwright tests to migrate
- ✅ Prefer auto-waiting behavior
- ✅ Don't need advanced session features
- ✅ Want cross-browser APIs (even if only Chromium supported now)
**Bottom Line**: **Puppeteer is recommended** for most Browser Rendering use cases.
---
## Package Installation
### Puppeteer
```bash
npm install @cloudflare/puppeteer
```
**Version**: 1.0.4 (based on Puppeteer v23.x)
### Playwright
```bash
npm install @cloudflare/playwright
```
**Version**: 1.0.0 (based on Playwright v1.55.0)
---
## API Comparison
### Launching a Browser
**Puppeteer:**
```typescript
import puppeteer from "@cloudflare/puppeteer";
const browser = await puppeteer.launch(env.MYBROWSER);
```
**Playwright:**
```typescript
import { chromium } from "@cloudflare/playwright";
const browser = await chromium.launch(env.BROWSER);
```
**Key Difference**: Playwright uses `chromium.launch()` (browser-specific), Puppeteer uses `puppeteer.launch()` (generic).
---
### Basic Screenshot Example
**Puppeteer:**
```typescript
import puppeteer from "@cloudflare/puppeteer";
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const browser = await puppeteer.launch(env.MYBROWSER);
const page = await browser.newPage();
await page.goto("https://example.com");
const screenshot = await page.screenshot();
await browser.close();
return new Response(screenshot, {
headers: { "content-type": "image/png" }
});
}
};
```
**Playwright:**
```typescript
import { chromium } from "@cloudflare/playwright";
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const browser = await chromium.launch(env.BROWSER);
const page = await browser.newPage();
await page.goto("https://example.com");
const screenshot = await page.screenshot();
await browser.close();
return new Response(screenshot, {
headers: { "content-type": "image/png" }
});
}
};
```
**Key Difference**: Nearly identical! Main difference is import and launch method.
---
## Feature Comparison
| Feature | Puppeteer | Playwright | Notes |
|---------|-----------|------------|-------|
| **Basic Screenshots** | ✅ Yes | ✅ Yes | Both support PNG/JPEG |
| **PDF Generation** | ✅ Yes | ✅ Yes | Identical API |
| **Page Navigation** | ✅ Yes | ✅ Yes | Similar API |
| **Element Selectors** | CSS only | CSS, text | Playwright has more selector types |
| **Auto-waiting** | ❌ Manual | ✅ Built-in | Playwright waits for elements automatically |
| **Session Management** | ✅ Advanced | ⚠️ Basic | Puppeteer has .sessions(), .history(), .limits() |
| **Session Reuse** | ✅ Yes | ⚠️ Limited | Puppeteer has .connect() with sessionId |
| **Browser Contexts** | ✅ Yes | ✅ Yes | Both support incognito contexts |
| **Multiple Tabs** | ✅ Yes | ✅ Yes | Both support multiple pages |
| **Network Interception** | ✅ Yes | ✅ Yes | Similar APIs |
| **Geolocation** | ✅ Yes | ✅ Yes | Similar APIs |
| **Emulation** | ✅ Yes | ✅ Yes | Device emulation, viewport |
| **Browser Support** | Chromium only | Chromium only | Firefox/Safari not yet supported |
| **TypeScript Types** | ✅ Yes | ✅ Yes | Both fully typed |
---
## Session Management
### Puppeteer (Advanced)
```typescript
// List active sessions
const sessions = await puppeteer.sessions(env.MYBROWSER);
// Find free session
const freeSession = sessions.find(s => !s.connectionId);
// Connect to existing session
if (freeSession) {
const browser = await puppeteer.connect(env.MYBROWSER, freeSession.sessionId);
}
// Check limits
const limits = await puppeteer.limits(env.MYBROWSER);
console.log("Can launch:", limits.allowedBrowserAcquisitions > 0);
// View history
const history = await puppeteer.history(env.MYBROWSER);
```
**Puppeteer APIs:**
-`puppeteer.sessions()` - List active sessions
-`puppeteer.connect()` - Connect to session by ID
-`puppeteer.history()` - View recent sessions
-`puppeteer.limits()` - Check account limits
-`browser.sessionId()` - Get current session ID
-`browser.disconnect()` - Disconnect without closing
---
### Playwright (Basic)
```typescript
// Launch browser
const browser = await chromium.launch(env.BROWSER);
// Get session info (basic)
// Note: No .sessions(), .history(), or .limits() APIs
```
**Playwright APIs:**
- ❌ No `chromium.sessions()` equivalent
- ❌ No session reuse APIs
- ❌ No limits checking
- ❌ No session history
**Workaround**: Use Puppeteer-style session management via REST API (more complex).
---
## Auto-Waiting Behavior
### Puppeteer (Manual)
```typescript
// Must explicitly wait for elements
await page.goto("https://example.com");
await page.waitForSelector("button#submit");
await page.click("button#submit");
```
**Pros**: Fine-grained control
**Cons**: More verbose, easy to forget waits
---
### Playwright (Auto-waiting)
```typescript
// Automatically waits for elements
await page.goto("https://example.com");
await page.click("button#submit"); // Waits automatically!
```
**Pros**: Less boilerplate, fewer timing issues
**Cons**: Less control over wait behavior
---
## Selector Support
### Puppeteer
**Supported:**
- CSS selectors: `"button#submit"`, `"div > p"`
- `:visible`, `:hidden` pseudo-classes
- `page.$()`, `page.$$()` for querying
**Not Supported:**
- XPath selectors (use `page.evaluate()` workaround)
- Text selectors
- Layout selectors
**Example:**
```typescript
// CSS selector
const button = await page.$("button#submit");
// XPath workaround
const heading = await page.evaluate(() => {
return new XPathEvaluator()
.createExpression("//h1[@class='title']")
.evaluate(document, XPathResult.FIRST_ORDERED_NODE_TYPE)
.singleNodeValue.textContent;
});
```
---
### Playwright
**Supported:**
- CSS selectors: `"button#submit"`
- Text selectors: `"text=Submit"`
- XPath selectors: `"xpath=//button"`
- Layout selectors: `"button :right-of(:text('Cancel'))"`
**Example:**
```typescript
// CSS selector
await page.click("button#submit");
// Text selector
await page.click("text=Submit");
// Combined selector
await page.click("button >> text=Submit");
```
**Advantage**: More flexible selector options
---
## Performance & Cost
### Puppeteer (Optimized)
**Session Reuse:**
```typescript
// Reuse sessions to reduce costs
const sessions = await puppeteer.sessions(env.MYBROWSER);
const browser = await puppeteer.connect(env.MYBROWSER, sessionId);
await browser.disconnect(); // Keep alive
```
**Cost Impact:**
- ✅ Reduce cold starts by 50-70%
- ✅ Lower concurrency charges
- ✅ Better throughput
---
### Playwright (Limited Optimization)
**No Session Reuse:**
```typescript
// Must launch new browser each time
const browser = await chromium.launch(env.BROWSER);
await browser.close(); // Cannot keep alive for reuse
```
**Cost Impact:**
- ❌ Higher browser hours (cold starts every request)
- ❌ Higher concurrency usage
- ❌ Lower throughput
**Difference**: ~30-50% higher costs with Playwright vs optimized Puppeteer.
---
## API Differences
| Operation | Puppeteer | Playwright |
|-----------|-----------|------------|
| **Import** | `import puppeteer from "@cloudflare/puppeteer"` | `import { chromium } from "@cloudflare/playwright"` |
| **Launch** | `puppeteer.launch(env.MYBROWSER)` | `chromium.launch(env.BROWSER)` |
| **Connect** | `puppeteer.connect(env.MYBROWSER, sessionId)` | ❌ Not available |
| **Sessions** | `puppeteer.sessions(env.MYBROWSER)` | ❌ Not available |
| **Limits** | `puppeteer.limits(env.MYBROWSER)` | ❌ Not available |
| **Goto** | `page.goto(url, { waitUntil: "networkidle0" })` | `page.goto(url, { waitUntil: "networkidle" })` |
| **Screenshot** | `page.screenshot({ fullPage: true })` | `page.screenshot({ fullPage: true })` |
| **PDF** | `page.pdf({ format: "A4" })` | `page.pdf({ format: "A4" })` |
| **Wait** | `page.waitForSelector("button")` | `page.locator("button").waitFor()` |
| **Click** | `page.click("button")` | `page.click("button")` (auto-waits) |
---
## Migration Guide
### Puppeteer → Playwright
```typescript
// Before (Puppeteer)
import puppeteer from "@cloudflare/puppeteer";
const browser = await puppeteer.launch(env.MYBROWSER);
const page = await browser.newPage();
await page.goto(url, { waitUntil: "networkidle0" });
await page.waitForSelector("button#submit");
await page.click("button#submit");
const screenshot = await page.screenshot();
await browser.close();
```
```typescript
// After (Playwright)
import { chromium } from "@cloudflare/playwright";
const browser = await chromium.launch(env.BROWSER);
const page = await browser.newPage();
await page.goto(url, { waitUntil: "networkidle" });
// No waitForSelector needed - auto-waits
await page.click("button#submit");
const screenshot = await page.screenshot();
await browser.close();
```
**Changes:**
1. Import: `puppeteer``{ chromium }`
2. Launch: `puppeteer.launch()``chromium.launch()`
3. Wait: `networkidle0``networkidle`
4. Remove explicit `waitForSelector()` (auto-waits)
---
### Playwright → Puppeteer
```typescript
// Before (Playwright)
import { chromium } from "@cloudflare/playwright";
const browser = await chromium.launch(env.BROWSER);
const page = await browser.newPage();
await page.goto(url);
await page.click("button#submit"); // Auto-waits
```
```typescript
// After (Puppeteer)
import puppeteer from "@cloudflare/puppeteer";
const browser = await puppeteer.launch(env.MYBROWSER);
const page = await browser.newPage();
await page.goto(url, { waitUntil: "networkidle0" });
await page.waitForSelector("button#submit"); // Explicit wait
await page.click("button#submit");
```
**Changes:**
1. Import: `{ chromium }``puppeteer`
2. Launch: `chromium.launch()``puppeteer.launch()`
3. Add explicit waits: `page.waitForSelector()`
4. Specify wait conditions: `waitUntil: "networkidle0"`
---
## Use Case Recommendations
### Screenshot Service
**Winner**: **Puppeteer**
**Reason**: Session reuse reduces costs by 30-50%
```typescript
// Puppeteer: Reuse sessions
const sessions = await puppeteer.sessions(env.MYBROWSER);
const browser = await puppeteer.connect(env.MYBROWSER, sessionId);
await browser.disconnect(); // Keep alive
```
---
### PDF Generation
**Winner**: **Tie**
**Reason**: Identical API, no session reuse benefit
```typescript
// Both have same API
const pdf = await page.pdf({ format: "A4" });
```
---
### Web Scraping
**Winner**: **Puppeteer**
**Reason**: Session management + limit checking
```typescript
// Puppeteer: Check limits before scraping
const limits = await puppeteer.limits(env.MYBROWSER);
if (limits.allowedBrowserAcquisitions === 0) {
await delay(limits.timeUntilNextAllowedBrowserAcquisition);
}
```
---
### Test Migration
**Winner**: **Playwright**
**Reason**: Easier to migrate existing Playwright tests
```typescript
// Minimal changes needed
// Just update imports and launch
```
---
### Interactive Automation
**Winner**: **Tie**
**Reason**: Both support form filling, clicking, etc.
---
## Configuration
### wrangler.jsonc (Puppeteer)
```jsonc
{
"browser": {
"binding": "MYBROWSER"
},
"compatibility_flags": ["nodejs_compat"]
}
```
```typescript
interface Env {
MYBROWSER: Fetcher;
}
```
---
### wrangler.jsonc (Playwright)
```jsonc
{
"browser": {
"binding": "BROWSER"
},
"compatibility_flags": ["nodejs_compat"]
}
```
```typescript
interface Env {
BROWSER: Fetcher;
}
```
**Note**: Binding name is arbitrary, but convention is `MYBROWSER` for Puppeteer and `BROWSER` for Playwright.
---
## Production Considerations
### Puppeteer Advantages
- ✅ Session reuse (30-50% cost savings)
- ✅ Limit checking (`puppeteer.limits()`)
- ✅ Session monitoring (`puppeteer.sessions()`, `.history()`)
- ✅ Better performance optimization options
- ✅ More mature Cloudflare fork
### Playwright Advantages
- ✅ Auto-waiting (less code)
- ✅ More selector types
- ✅ Better cross-browser APIs (future-proof)
- ✅ Easier migration from existing tests
---
## Recommendation Summary
| Scenario | Recommended | Reason |
|----------|-------------|--------|
| New project | **Puppeteer** | Session management + cost optimization |
| Screenshot service | **Puppeteer** | Session reuse saves 30-50% |
| PDF generation | **Tie** | Identical API |
| Web scraping | **Puppeteer** | Limit checking + session management |
| Migrating Playwright tests | **Playwright** | Minimal changes needed |
| High traffic production | **Puppeteer** | Better performance optimization |
| Quick prototype | **Tie** | Both easy to start with |
---
## Code Examples
### Puppeteer (Production-Optimized)
```typescript
import puppeteer, { Browser } from "@cloudflare/puppeteer";
async function getBrowser(env: Env): Promise<Browser> {
// Check limits
const limits = await puppeteer.limits(env.MYBROWSER);
if (limits.allowedBrowserAcquisitions === 0) {
throw new Error("Rate limit reached");
}
// Try to reuse session
const sessions = await puppeteer.sessions(env.MYBROWSER);
const freeSession = sessions.find(s => !s.connectionId);
if (freeSession) {
try {
return await puppeteer.connect(env.MYBROWSER, freeSession.sessionId);
} catch {
// Session closed, launch new
}
}
return await puppeteer.launch(env.MYBROWSER);
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const browser = await getBrowser(env);
try {
const page = await browser.newPage();
await page.goto("https://example.com", {
waitUntil: "networkidle0",
timeout: 30000
});
const screenshot = await page.screenshot();
// Disconnect (keep alive)
await browser.disconnect();
return new Response(screenshot, {
headers: { "content-type": "image/png" }
});
} catch (error) {
await browser.close();
throw error;
}
}
};
```
---
### Playwright (Simple)
```typescript
import { chromium } from "@cloudflare/playwright";
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const browser = await chromium.launch(env.BROWSER);
try {
const page = await browser.newPage();
await page.goto("https://example.com", {
waitUntil: "networkidle",
timeout: 30000
});
const screenshot = await page.screenshot();
await browser.close();
return new Response(screenshot, {
headers: { "content-type": "image/png" }
});
} catch (error) {
await browser.close();
throw error;
}
}
};
```
---
## References
- **Puppeteer Docs**: https://pptr.dev/
- **Playwright Docs**: https://playwright.dev/
- **Cloudflare Puppeteer Fork**: https://github.com/cloudflare/puppeteer
- **Cloudflare Playwright Fork**: https://github.com/cloudflare/playwright
- **Browser Rendering Docs**: https://developers.cloudflare.com/browser-rendering/
---
**Last Updated**: 2025-10-22