633 lines
15 KiB
Markdown
633 lines
15 KiB
Markdown
# 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
|