Initial commit
This commit is contained in:
632
references/common-errors.md
Normal file
632
references/common-errors.md
Normal file
@@ -0,0 +1,632 @@
|
||||
# Common Errors and Solutions
|
||||
|
||||
Complete reference for all known Browser Rendering errors with sources, root causes, and solutions.
|
||||
|
||||
---
|
||||
|
||||
## Error 1: "Cannot read properties of undefined (reading 'fetch')"
|
||||
|
||||
**Full Error:**
|
||||
```
|
||||
TypeError: Cannot read properties of undefined (reading 'fetch')
|
||||
```
|
||||
|
||||
**Source**: https://developers.cloudflare.com/browser-rendering/faq/#cannot-read-properties-of-undefined-reading-fetch
|
||||
|
||||
**Root Cause**: Browser binding not passed to `puppeteer.launch()`
|
||||
|
||||
**Why It Happens:**
|
||||
```typescript
|
||||
// ❌ Missing browser binding
|
||||
const browser = await puppeteer.launch();
|
||||
// ^ undefined - no binding passed!
|
||||
```
|
||||
|
||||
**Solution:**
|
||||
```typescript
|
||||
// ✅ Pass browser binding
|
||||
const browser = await puppeteer.launch(env.MYBROWSER);
|
||||
// ^^^^^^^^^^^^^^^^ binding from env
|
||||
```
|
||||
|
||||
**Prevention**: Always pass `env.MYBROWSER` (or your configured binding name) to `puppeteer.launch()`.
|
||||
|
||||
---
|
||||
|
||||
## Error 2: XPath Selector Not Supported
|
||||
|
||||
**Full Error:**
|
||||
```
|
||||
Error: XPath selectors are not supported in Browser Rendering
|
||||
```
|
||||
|
||||
**Source**: https://developers.cloudflare.com/browser-rendering/faq/#why-cant-i-use-an-xpath-selector-when-using-browser-rendering-with-puppeteer
|
||||
|
||||
**Root Cause**: XPath poses security risk to Workers
|
||||
|
||||
**Why It Happens:**
|
||||
```typescript
|
||||
// ❌ XPath selectors not directly supported
|
||||
const elements = await page.$x('/html/body/div/h1');
|
||||
```
|
||||
|
||||
**Solution 1: Use CSS Selectors**
|
||||
```typescript
|
||||
// ✅ Use CSS selector instead
|
||||
const element = await page.$("div > h1");
|
||||
const elements = await page.$$("div > h1");
|
||||
```
|
||||
|
||||
**Solution 2: Use XPath in page.evaluate()**
|
||||
```typescript
|
||||
// ✅ Use XPath inside page.evaluate()
|
||||
const innerHtml = await page.evaluate(() => {
|
||||
return (
|
||||
// @ts-ignore - runs in browser context
|
||||
new XPathEvaluator()
|
||||
.createExpression("/html/body/div/h1")
|
||||
// @ts-ignore
|
||||
.evaluate(document, XPathResult.FIRST_ORDERED_NODE_TYPE)
|
||||
.singleNodeValue.innerHTML
|
||||
);
|
||||
});
|
||||
```
|
||||
|
||||
**Prevention**: Use CSS selectors by default. Only use XPath via `page.evaluate()` if absolutely necessary.
|
||||
|
||||
---
|
||||
|
||||
## Error 3: Browser Timeout
|
||||
|
||||
**Full Error:**
|
||||
```
|
||||
Error: Browser session closed due to inactivity
|
||||
```
|
||||
|
||||
**Source**: https://developers.cloudflare.com/browser-rendering/platform/limits/#note-on-browser-timeout
|
||||
|
||||
**Root Cause**: Default 60 second idle timeout
|
||||
|
||||
**Why It Happens:**
|
||||
- No devtools commands sent for 60 seconds
|
||||
- Browser automatically closes to free resources
|
||||
|
||||
**Solution: Extend Timeout**
|
||||
```typescript
|
||||
// ✅ Extend timeout to 5 minutes
|
||||
const browser = await puppeteer.launch(env.MYBROWSER, {
|
||||
keep_alive: 300000 // 5 minutes = 300,000 ms
|
||||
});
|
||||
```
|
||||
|
||||
**Maximum**: 600,000ms (10 minutes)
|
||||
|
||||
**Use Cases for Extended Timeout:**
|
||||
- Multi-step workflows
|
||||
- Long-running scraping
|
||||
- Session reuse across requests
|
||||
|
||||
**Prevention**: Only extend if actually needed. Longer timeout = more billable hours.
|
||||
|
||||
---
|
||||
|
||||
## Error 4: Rate Limit Exceeded
|
||||
|
||||
**Full Error:**
|
||||
```
|
||||
Error: Rate limit exceeded. Too many concurrent browsers.
|
||||
```
|
||||
|
||||
**Source**: https://developers.cloudflare.com/browser-rendering/platform/limits/
|
||||
|
||||
**Root Cause**: Exceeded concurrent browser limit
|
||||
|
||||
**Limits:**
|
||||
- Free tier: 3 concurrent browsers
|
||||
- Paid tier: 10-30 concurrent browsers
|
||||
|
||||
**Solution 1: Check Limits Before Launching**
|
||||
```typescript
|
||||
const limits = await puppeteer.limits(env.MYBROWSER);
|
||||
|
||||
if (limits.allowedBrowserAcquisitions === 0) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: "Rate limit reached",
|
||||
retryAfter: limits.timeUntilNextAllowedBrowserAcquisition
|
||||
}),
|
||||
{ status: 429 }
|
||||
);
|
||||
}
|
||||
|
||||
const browser = await puppeteer.launch(env.MYBROWSER);
|
||||
```
|
||||
|
||||
**Solution 2: Reuse Sessions**
|
||||
```typescript
|
||||
// Try to connect to existing session first
|
||||
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);
|
||||
```
|
||||
|
||||
**Solution 3: Use Multiple Tabs**
|
||||
```typescript
|
||||
// ❌ Bad: 10 browsers
|
||||
for (const url of urls) {
|
||||
const browser = await puppeteer.launch(env.MYBROWSER);
|
||||
// ...
|
||||
}
|
||||
|
||||
// ✅ Good: 1 browser, 10 tabs
|
||||
const browser = await puppeteer.launch(env.MYBROWSER);
|
||||
await Promise.all(urls.map(async url => {
|
||||
const page = await browser.newPage();
|
||||
// ...
|
||||
await page.close();
|
||||
}));
|
||||
await browser.close();
|
||||
```
|
||||
|
||||
**Prevention**: Monitor concurrency usage, implement session reuse, use tabs instead of multiple browsers.
|
||||
|
||||
---
|
||||
|
||||
## Error 5: Local Development Request Size Limit
|
||||
|
||||
**Full Error:**
|
||||
```
|
||||
Error: Request payload too large (>1MB)
|
||||
```
|
||||
|
||||
**Source**: https://developers.cloudflare.com/browser-rendering/faq/#does-local-development-support-all-browser-rendering-features
|
||||
|
||||
**Root Cause**: Local development limitation (requests >1MB fail)
|
||||
|
||||
**Solution: Use Remote Binding**
|
||||
```jsonc
|
||||
// wrangler.jsonc
|
||||
{
|
||||
"browser": {
|
||||
"binding": "MYBROWSER",
|
||||
"remote": true // ← Use real headless browser during dev
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**With Remote Binding:**
|
||||
- Connects to actual Cloudflare browser (not local simulation)
|
||||
- No 1MB request limit
|
||||
- Counts toward your quota
|
||||
|
||||
**Prevention**: Enable `remote: true` for local development if working with large payloads.
|
||||
|
||||
---
|
||||
|
||||
## Error 6: Bot Protection Triggered
|
||||
|
||||
**Full Error:**
|
||||
```
|
||||
Blocked by bot protection / CAPTCHA challenge
|
||||
```
|
||||
|
||||
**Source**: https://developers.cloudflare.com/browser-rendering/faq/#will-browser-rendering-bypass-cloudflares-bot-protection
|
||||
|
||||
**Root Cause**: Browser Rendering requests always identified as bots
|
||||
|
||||
**Why It Happens:**
|
||||
- Cloudflare automatically identifies Browser Rendering traffic
|
||||
- Cannot bypass bot protection
|
||||
- Automatic headers added: `cf-biso-request-id`, `cf-biso-devtools`
|
||||
|
||||
**Solution (If Scraping Your Own Zone):**
|
||||
Create WAF skip rule:
|
||||
|
||||
1. Go to Security > WAF > Custom rules
|
||||
2. Create skip rule with custom header:
|
||||
- Header: `X-Custom-Auth`
|
||||
- Value: `your-secret-token`
|
||||
3. Add header in your Worker:
|
||||
|
||||
```typescript
|
||||
const browser = await puppeteer.launch(env.MYBROWSER);
|
||||
const page = await browser.newPage();
|
||||
|
||||
// Set custom header
|
||||
await page.setExtraHTTPHeaders({
|
||||
"X-Custom-Auth": "your-secret-token"
|
||||
});
|
||||
|
||||
await page.goto(url);
|
||||
```
|
||||
|
||||
**Solution (If Scraping External Sites):**
|
||||
- Cannot bypass bot protection
|
||||
- Some sites will block Browser Rendering traffic
|
||||
- Consider using site's official API instead
|
||||
|
||||
**Prevention**: Use official APIs when available. Only scrape your own zones if possible.
|
||||
|
||||
---
|
||||
|
||||
## Error 7: Navigation Timeout
|
||||
|
||||
**Full Error:**
|
||||
```
|
||||
TimeoutError: Navigation timeout of 30000 ms exceeded
|
||||
```
|
||||
|
||||
**Root Cause**: Page failed to load within timeout
|
||||
|
||||
**Why It Happens:**
|
||||
- Slow website
|
||||
- Large page assets
|
||||
- Network issues
|
||||
- Page never reaches desired load state
|
||||
|
||||
**Solution 1: Increase Timeout**
|
||||
```typescript
|
||||
await page.goto(url, {
|
||||
timeout: 60000 // 60 seconds
|
||||
});
|
||||
```
|
||||
|
||||
**Solution 2: Change Wait Condition**
|
||||
```typescript
|
||||
// ❌ Strict (waits for all network requests)
|
||||
await page.goto(url, { waitUntil: "networkidle0" });
|
||||
|
||||
// ✅ More lenient (waits for DOMContentLoaded)
|
||||
await page.goto(url, { waitUntil: "domcontentloaded" });
|
||||
|
||||
// ✅ Most lenient (waits for load event only)
|
||||
await page.goto(url, { waitUntil: "load" });
|
||||
```
|
||||
|
||||
**Solution 3: Handle Timeout Gracefully**
|
||||
```typescript
|
||||
try {
|
||||
await page.goto(url, { timeout: 30000 });
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.name === "TimeoutError") {
|
||||
console.log("Navigation timeout, taking screenshot anyway");
|
||||
const screenshot = await page.screenshot();
|
||||
return screenshot;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
```
|
||||
|
||||
**Prevention**: Set appropriate timeouts for your use case. Use lenient wait conditions for slow sites.
|
||||
|
||||
---
|
||||
|
||||
## Error 8: Memory Limit Exceeded
|
||||
|
||||
**Full Error:**
|
||||
```
|
||||
Error: Browser exceeded its memory limit
|
||||
```
|
||||
|
||||
**Root Cause**: Page too large or too many tabs open
|
||||
|
||||
**Why It Happens:**
|
||||
- Opening many tabs simultaneously
|
||||
- Large pages with many assets
|
||||
- Memory leaks from not closing pages
|
||||
|
||||
**Solution 1: Close Pages**
|
||||
```typescript
|
||||
const page = await browser.newPage();
|
||||
// ... use page ...
|
||||
await page.close(); // ← Don't forget!
|
||||
```
|
||||
|
||||
**Solution 2: Limit Concurrent Tabs**
|
||||
```typescript
|
||||
import PQueue from "p-queue";
|
||||
|
||||
const browser = await puppeteer.launch(env.MYBROWSER);
|
||||
const queue = new PQueue({ concurrency: 5 }); // Max 5 tabs
|
||||
|
||||
await Promise.all(urls.map(url =>
|
||||
queue.add(async () => {
|
||||
const page = await browser.newPage();
|
||||
await page.goto(url);
|
||||
// ...
|
||||
await page.close();
|
||||
})
|
||||
));
|
||||
```
|
||||
|
||||
**Solution 3: Use Smaller Viewports**
|
||||
```typescript
|
||||
await page.setViewport({
|
||||
width: 1280,
|
||||
height: 720 // Smaller than default
|
||||
});
|
||||
```
|
||||
|
||||
**Prevention**: Always close pages when done. Limit concurrent tabs. Process URLs in batches.
|
||||
|
||||
---
|
||||
|
||||
## Error 9: Failed to Connect to Session
|
||||
|
||||
**Full Error:**
|
||||
```
|
||||
Error: Failed to connect to browser session
|
||||
```
|
||||
|
||||
**Root Cause**: Session closed between `.sessions()` and `.connect()` calls
|
||||
|
||||
**Why It Happens:**
|
||||
- Session timed out (60s idle)
|
||||
- Session closed by another Worker
|
||||
- Session terminated unexpectedly
|
||||
|
||||
**Solution: Handle Connection Failures**
|
||||
```typescript
|
||||
const sessions = await puppeteer.sessions(env.MYBROWSER);
|
||||
const freeSession = sessions.find(s => !s.connectionId);
|
||||
|
||||
if (freeSession) {
|
||||
try {
|
||||
const browser = await puppeteer.connect(env.MYBROWSER, freeSession.sessionId);
|
||||
return browser;
|
||||
} catch (error) {
|
||||
console.log("Failed to connect to session, launching new browser");
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to launching new browser
|
||||
return await puppeteer.launch(env.MYBROWSER);
|
||||
```
|
||||
|
||||
**Prevention**: Always wrap `puppeteer.connect()` in try-catch. Have fallback to `puppeteer.launch()`.
|
||||
|
||||
---
|
||||
|
||||
## Error 10: Too Many Requests Per Minute
|
||||
|
||||
**Full Error:**
|
||||
```
|
||||
Error: Too many browser launches per minute
|
||||
```
|
||||
|
||||
**Root Cause**: Exceeded "new browsers per minute" limit
|
||||
|
||||
**Limits:**
|
||||
- Free tier: 3 per minute (1 every 20 seconds)
|
||||
- Paid tier: 30 per minute (1 every 2 seconds)
|
||||
|
||||
**Solution: Implement Rate Limiting**
|
||||
```typescript
|
||||
async function launchWithRateLimit(env: Env): Promise<Browser> {
|
||||
const limits = await puppeteer.limits(env.MYBROWSER);
|
||||
|
||||
if (limits.allowedBrowserAcquisitions === 0) {
|
||||
const delay = limits.timeUntilNextAllowedBrowserAcquisition || 2000;
|
||||
console.log(`Rate limited, waiting ${delay}ms`);
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
}
|
||||
|
||||
return await puppeteer.launch(env.MYBROWSER);
|
||||
}
|
||||
```
|
||||
|
||||
**Prevention**: Check limits before launching. Implement exponential backoff. Reuse sessions instead of launching new browsers.
|
||||
|
||||
---
|
||||
|
||||
## Error 11: Binding Not Configured
|
||||
|
||||
**Full Error:**
|
||||
```
|
||||
Error: Browser binding not found
|
||||
```
|
||||
|
||||
**Root Cause**: Browser binding not configured in wrangler.jsonc
|
||||
|
||||
**Solution: Add Browser Binding**
|
||||
```jsonc
|
||||
// wrangler.jsonc
|
||||
{
|
||||
"browser": {
|
||||
"binding": "MYBROWSER"
|
||||
},
|
||||
"compatibility_flags": ["nodejs_compat"]
|
||||
}
|
||||
```
|
||||
|
||||
**Also Add to TypeScript Types:**
|
||||
```typescript
|
||||
interface Env {
|
||||
MYBROWSER: Fetcher;
|
||||
}
|
||||
```
|
||||
|
||||
**Prevention**: Always configure browser binding and nodejs_compat flag.
|
||||
|
||||
---
|
||||
|
||||
## Error 12: nodejs_compat Flag Missing
|
||||
|
||||
**Full Error:**
|
||||
```
|
||||
Error: Node.js APIs not available
|
||||
```
|
||||
|
||||
**Root Cause**: `nodejs_compat` compatibility flag not enabled
|
||||
|
||||
**Solution: Add Compatibility Flag**
|
||||
```jsonc
|
||||
// wrangler.jsonc
|
||||
{
|
||||
"compatibility_flags": ["nodejs_compat"]
|
||||
}
|
||||
```
|
||||
|
||||
**Why It's Required:**
|
||||
Browser Rendering needs Node.js APIs and polyfills to work.
|
||||
|
||||
**Prevention**: Always include `nodejs_compat` when using Browser Rendering.
|
||||
|
||||
---
|
||||
|
||||
## Error Handling Template
|
||||
|
||||
Complete error handling for production use:
|
||||
|
||||
```typescript
|
||||
import puppeteer, { Browser } from "@cloudflare/puppeteer";
|
||||
|
||||
interface Env {
|
||||
MYBROWSER: Fetcher;
|
||||
}
|
||||
|
||||
async function withBrowser<T>(
|
||||
env: Env,
|
||||
fn: (browser: Browser) => Promise<T>
|
||||
): Promise<T> {
|
||||
let browser: Browser | null = null;
|
||||
|
||||
try {
|
||||
// Check limits
|
||||
const limits = await puppeteer.limits(env.MYBROWSER);
|
||||
if (limits.allowedBrowserAcquisitions === 0) {
|
||||
throw new Error(
|
||||
`Rate limit reached. Retry after ${limits.timeUntilNextAllowedBrowserAcquisition}ms`
|
||||
);
|
||||
}
|
||||
|
||||
// Try to reuse session
|
||||
const sessions = await puppeteer.sessions(env.MYBROWSER);
|
||||
const freeSession = sessions.find(s => !s.connectionId);
|
||||
|
||||
if (freeSession) {
|
||||
try {
|
||||
browser = await puppeteer.connect(env.MYBROWSER, freeSession.sessionId);
|
||||
} catch (error) {
|
||||
console.log("Failed to connect, launching new browser");
|
||||
browser = await puppeteer.launch(env.MYBROWSER);
|
||||
}
|
||||
} else {
|
||||
browser = await puppeteer.launch(env.MYBROWSER);
|
||||
}
|
||||
|
||||
// Execute user function
|
||||
const result = await fn(browser);
|
||||
|
||||
// Disconnect (keep session alive)
|
||||
await browser.disconnect();
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
// Close on error
|
||||
if (browser) {
|
||||
await browser.close();
|
||||
}
|
||||
|
||||
// Re-throw with context
|
||||
if (error instanceof Error) {
|
||||
error.message = `Browser operation failed: ${error.message}`;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
async fetch(request: Request, env: Env): Promise<Response> {
|
||||
try {
|
||||
const screenshot = await withBrowser(env, async (browser) => {
|
||||
const page = await browser.newPage();
|
||||
|
||||
try {
|
||||
await page.goto("https://example.com", {
|
||||
waitUntil: "networkidle0",
|
||||
timeout: 30000
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.name === "TimeoutError") {
|
||||
console.log("Navigation timeout, taking screenshot anyway");
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return await page.screenshot();
|
||||
});
|
||||
|
||||
return new Response(screenshot, {
|
||||
headers: { "content-type": "image/png" }
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Request failed:", error);
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: error instanceof Error ? error.message : "Unknown error"
|
||||
}),
|
||||
{
|
||||
status: 500,
|
||||
headers: { "content-type": "application/json" }
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Debugging Checklist
|
||||
|
||||
When encountering browser errors:
|
||||
|
||||
1. **Check browser binding**
|
||||
- [ ] Binding configured in wrangler.jsonc?
|
||||
- [ ] nodejs_compat flag enabled?
|
||||
- [ ] Binding passed to puppeteer.launch()?
|
||||
|
||||
2. **Check limits**
|
||||
- [ ] Within concurrent browser limit?
|
||||
- [ ] Within new browsers/minute limit?
|
||||
- [ ] Call puppeteer.limits() to verify?
|
||||
|
||||
3. **Check timeouts**
|
||||
- [ ] Navigation timeout appropriate?
|
||||
- [ ] Browser keep_alive set if needed?
|
||||
- [ ] Timeout errors handled gracefully?
|
||||
|
||||
4. **Check session management**
|
||||
- [ ] browser.close() called on errors?
|
||||
- [ ] Pages closed when done?
|
||||
- [ ] Session reuse implemented correctly?
|
||||
|
||||
5. **Check network**
|
||||
- [ ] Target URL accessible?
|
||||
- [ ] No CORS/bot protection issues?
|
||||
- [ ] Appropriate wait conditions used?
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- **FAQ**: https://developers.cloudflare.com/browser-rendering/faq/
|
||||
- **Limits**: https://developers.cloudflare.com/browser-rendering/platform/limits/
|
||||
- **GitHub Issues**: https://github.com/cloudflare/puppeteer/issues
|
||||
- **Discord**: https://discord.cloudflare.com/
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: 2025-10-22
|
||||
Reference in New Issue
Block a user