Browser Automation & Functions Skill
Complete guide for creating and deploying browser automation functions using the browse CLI.
Installation
npm install -g @browserbasehq/browse-cli
Requires Chrome/Chromium installed on the system.
When to Use
-
User wants to automate a website task
-
User needs to scrape data from a site
-
User wants to create a Browserbase Function
-
User wants to deploy automation to run on a schedule or via webhook
Prerequisites
Set Up Credentials
Get API key and Project ID from: https://browserbase.com/settings
export BROWSERBASE_API_KEY="your_api_key" export BROWSERBASE_PROJECT_ID="your_project_id"
Complete Workflow
Step 1: Explore the Site Interactively
Start a browser session to understand the site structure:
browse open https://example.com browse snapshot # Get DOM structure with refs browse screenshot page.png # Visual inspection
Test interactions manually:
browse click @0-5 browse fill @0-6 "value" browse stop # When done exploring
Step 2: Initialize Function Project
pnpm dlx @browserbasehq/sdk-functions init my-automation cd my-automation
Creates:
-
package.json
-
Dependencies
-
.env
-
Add credentials here
-
index.ts
-
Function template
-
tsconfig.json
-
TypeScript config
Step 3: ⚠️ FIX package.json IMMEDIATELY
CRITICAL BUG: The init command may generate incomplete package.json that causes deployment to fail with "No functions were built."
REQUIRED FIX - Update package.json before doing anything else:
{ "name": "my-automation", "version": "1.0.0", "description": "My automation description", "main": "index.js", "type": "module", "packageManager": "pnpm@10.14.0", "scripts": { "dev": "pnpm bb dev index.ts", "publish": "pnpm bb publish index.ts" }, "dependencies": { "@browserbasehq/sdk-functions": "^0.0.5", "playwright-core": "^1.58.0" }, "devDependencies": { "@types/node": "^25.0.10", "typescript": "^5.9.3" } }
Key changes from generated file:
-
✅ Add description and main fields
-
✅ Add packageManager field
-
✅ Change "latest" to pinned versions like "^0.0.5"
-
✅ Add devDependencies with TypeScript and types
Then install:
pnpm install
Step 4: Write Automation Code
Edit index.ts :
import { defineFn } from "@browserbasehq/sdk-functions"; import { chromium } from "playwright-core";
defineFn("my-automation", async (context) => { const { session, params } = context; console.log("Connecting to browser session:", session.id);
const browser = await chromium.connectOverCDP(session.connectUrl); const page = browser.contexts()[0]!.pages()[0]!;
// Your automation here await page.goto("https://example.com"); await page.waitForLoadState("domcontentloaded");
// Extract data const data = await page.evaluate(() => { // Complex extraction logic return Array.from(document.querySelectorAll('.item')).map(el => ({ title: el.querySelector('.title')?.textContent, value: el.querySelector('.value')?.textContent, })); });
// Return results (must be JSON-serializable) return { success: true, count: data.length, data, timestamp: new Date().toISOString(), }; });
Key Concepts:
-
context.session
-
Browser session info (id, connectUrl)
-
context.params
-
Input parameters from invocation
-
Return JSON-serializable data
-
15 minute max execution time
Step 5: Test Locally
Start dev server:
pnpm bb dev index.ts
Server runs at http://127.0.0.1:14113
Invoke with curl:
curl -X POST http://127.0.0.1:14113/v1/functions/my-automation/invoke
-H "Content-Type: application/json"
-d '{"params": {"url": "https://example.com"}}'
Dev server auto-reloads on file changes. Check terminal for logs.
Step 6: Deploy to Browserbase
pnpm bb publish index.ts
Expected output:
✓ Build completed successfully Build ID: xxx-xxx-xxx Function ID: yyy-yyy-yyy ← Save this!
If you see "No functions were built" → Your package.json is incomplete (see Step 3).
Step 7: Test Production
curl -X POST https://api.browserbase.com/v1/functions/<function-id>/invoke
-H "Content-Type: application/json"
-H "x-bb-api-key: $BROWSERBASE_API_KEY"
-d '{"params": {}}'
Complete Working Example: Hacker News Scraper
import { defineFn } from "@browserbasehq/sdk-functions"; import { chromium } from "playwright-core";
defineFn("hn-scraper", async (context) => { const { session } = context; console.log("Connecting to browser session:", session.id);
const browser = await chromium.connectOverCDP(session.connectUrl); const page = browser.contexts()[0]!.pages()[0]!;
await page.goto("https://news.ycombinator.com"); await page.waitForLoadState("domcontentloaded");
// Extract top 10 stories const stories = await page.evaluate(() => { const storyRows = Array.from(document.querySelectorAll('.athing')).slice(0, 10);
return storyRows.map((row) => {
const titleLine = row.querySelector('.titleline a');
const subtext = row.nextElementSibling?.querySelector('.subtext');
const commentsLink = Array.from(subtext?.querySelectorAll('a') || []).pop();
return {
rank: row.querySelector('.rank')?.textContent?.replace('.', '') || '',
title: titleLine?.textContent || '',
url: titleLine?.getAttribute('href') || '',
points: subtext?.querySelector('.score')?.textContent?.replace(' points', '') || '0',
author: subtext?.querySelector('.hnuser')?.textContent || '',
time: subtext?.querySelector('.age')?.textContent || '',
comments: commentsLink?.textContent?.replace(/\u00a0comments?/, '').trim() || '0',
id: row.id,
};
});
});
return { success: true, count: stories.length, stories, timestamp: new Date().toISOString(), }; });
Common Patterns
Parameterized Scraping
defineFn("scrape", async (context) => { const { session, params } = context; const { url, selector } = params; // Accept params from invocation
const browser = await chromium.connectOverCDP(session.connectUrl); const page = browser.contexts()[0]!.pages()[0]!;
await page.goto(url); const data = await page.$$eval(selector, els => els.map(el => el.textContent) );
return { url, data }; });
Authentication
defineFn("auth-action", async (context) => { const { session, params } = context; const { username, password } = params;
const browser = await chromium.connectOverCDP(session.connectUrl); const page = browser.contexts()[0]!.pages()[0]!;
await page.goto("https://example.com/login"); await page.fill('input[name="email"]', username); await page.fill('input[name="password"]', password); await page.click('button[type="submit"]'); await page.waitForURL("**/dashboard");
const data = await page.textContent('.user-data'); return { success: true, data }; });
Multi-Page Workflow
defineFn("multi-page", async (context) => { const { session, params } = context; const browser = await chromium.connectOverCDP(session.connectUrl); const page = browser.contexts()[0]!.pages()[0]!;
const results = []; for (const url of params.urls) { await page.goto(url); await page.waitForLoadState("domcontentloaded");
const title = await page.title();
results.push({ url, title });
}
return { results }; });
Troubleshooting
🔴 "No functions were built. Please check your entrypoint and function exports."
This is the #1 error!
Cause: Generated package.json is incomplete.
Fix:
-
Update package.json (see Step 3 above)
-
Add all required fields: description , main , packageManager
-
Change "latest" to pinned versions like "^0.0.5"
-
Add devDependencies section with TypeScript and types
-
Run pnpm install
-
Try deploying again
Quick check: Compare your package.json to bitcoin-functions/package.json in the codebase.
Local dev server won't start
Check credentials in .env
cat .env
Install SDK globally
pnpm add -g @browserbasehq/sdk-functions
Function works locally but fails on deploy
Common causes:
-
Missing devDependencies (TypeScript won't compile)
-
Using "latest" instead of pinned versions
-
Missing required fields in package.json
Solution: Fix package.json as described in Step 3.
Cannot extract data from page
-
Take screenshot: browse screenshot debug.png
-
Get snapshot: browse snapshot
-
Use page.evaluate() to log what's in the DOM
-
Check if selectors match actual HTML structure
"Invocation timed out"
-
Functions have 15 minute max
-
Use specific waits instead of long sleeps
-
Check if page is actually loading
Best Practices
-
✅ Fix package.json immediately after init
-
✅ Explore interactively first - Use browser session to understand site
-
✅ Test manually - Verify each step works before writing code
-
✅ Test locally - Use dev server before deploying
-
✅ Return meaningful data - Include timestamps, counts, URLs
-
✅ Handle errors gracefully - Try/catch around risky operations
-
✅ Use specific selectors - Prefer data attributes over CSS classes
-
✅ Add logging - console.log() helps debug deployed functions
-
✅ Validate parameters - Check params before using
-
✅ Set reasonable timeouts - Don't wait forever
Quick Checklist
-
Explore site with browse open <url>
-
Test interactions manually
-
Create project: pnpm dlx @browserbasehq/sdk-functions init <name>
-
Fix package.json immediately (Step 3)
-
Run pnpm install
-
Write automation in index.ts
-
Test locally: pnpm bb dev index.ts
-
Verify with curl
-
Deploy: pnpm bb publish index.ts
-
Test production via API
-
Save function ID
Code Fix Needed (For Maintainers)
File: /src/commands/functions.ts
Lines: 146-158 Function: initFunction()
Replace the current packageJson object with:
const packageJson = {
name,
version: '1.0.0',
description: ${name} function,
main: 'index.js',
type: 'module',
packageManager: 'pnpm@10.14.0',
scripts: {
dev: 'pnpm bb dev index.ts',
publish: 'pnpm bb publish index.ts',
},
dependencies: {
'@browserbasehq/sdk-functions': '^0.0.5',
'playwright-core': '^1.58.0',
},
devDependencies: {
'@types/node': '^25.0.10',
'typescript': '^5.9.3',
},
};
This will eliminate the "No functions were built" error for all new projects.