Claude-skill-registry browse
Complete guide for creating and deploying browser automation functions using the stagehand CLI
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/browse" ~/.claude/skills/majiayu000-claude-skill-registry-browse-9891af && rm -rf "$T"
skills/data/browse/SKILL.mdBrowser Automation & Functions Skill
Complete guide for creating and deploying browser automation functions using the
stagehand CLI.
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
stagehand fn auth status # Check if configured stagehand fn auth login # If needed - get credentials from https://browserbase.com/settings
Complete Workflow
Step 1: Explore the Site Interactively
Start a local browser session to understand the site structure:
stagehand session create --local stagehand goto https://example.com stagehand snapshot # Get DOM structure with refs stagehand screenshot -o page.png # Visual inspection
Test interactions manually:
stagehand click @0-5 stagehand fill @0-6 "value" stagehand eval "document.querySelector('.price').textContent" stagehand session end # When done exploring
Step 2: Initialize Function Project
stagehand fn init my-automation cd my-automation
Creates:
- Dependenciespackage.json
- Credentials (from.env
)~/.stagehand/config.json
- Function templateindex.ts
- TypeScript configtsconfig.json
Step 3: ⚠️ FIX package.json IMMEDIATELY
CRITICAL BUG:
stagehand fn init generates 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
anddescription
fieldsmain - ✅ Add
fieldpackageManager - ✅ Change
to pinned versions like"latest""^0.0.5" - ✅ Add
with TypeScript and typesdevDependencies
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:
- Browser session info (id, connectUrl)context.session
- Input parameters from invocationcontext.params- 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 # or: stagehand fn 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
stagehand fn invoke <function-id> -p '{"param": "value"}'
Or via API:
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 from stagehand fn init is incomplete.
Fix:
- Update
(see Step 3 above)package.json - Add all required fields:
,description
,mainpackageManager - Change
to pinned versions like"latest""^0.0.5" - Add
section with TypeScript and typesdevDependencies - 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 stagehand fn auth status # Re-login if needed stagehand fn auth login # Install SDK globally pnpm add -g @browserbasehq/sdk-functions
Function works locally but fails on deploy
Common causes:
- Missing
(TypeScript won't compile)devDependencies - Using
instead of pinned versions"latest" - Missing required fields in
package.json
Solution: Fix package.json as described in Step 3.
Cannot extract data from page
- Take screenshot:
stagehand screenshot -o debug.png - Get snapshot:
stagehand snapshot - Use
to log what's in the DOMpage.evaluate() - 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
stagehand fn init - ✅ Explore interactively first - Use local 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 -
helps debug deployed functionsconsole.log() - ✅ Validate parameters - Check
before usingparams - ✅ Set reasonable timeouts - Don't wait forever
Quick Checklist
- Explore site with
stagehand session create --local - Test interactions manually
- Create project:
stagehand fn 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:
stagehand fn invoke <function-id> - 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.