Back to Skills
    šŸ¦ž

    browse

    Complete guide for creating and deploying browser automation functions

    By @pkiv
    View on GitHub
    SKILL.md
    ---
    name: browse
    description: Complete guide for creating and deploying browser automation functions using the stagehand CLI
    homepage: https://browserbase.com
    metadata: {"moltbot":{"emoji":"🌐","requires":{"bins":["stagehand"],"env":["BROWSERBASE_API_KEY","BROWSERBASE_PROJECT_ID"]},"primaryEnv":"BROWSERBASE_API_KEY"}}
    ---
    
    # Browser 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
    
    ```bash
    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:
    
    ```bash
    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:
    ```bash
    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
    
    ```bash
    stagehand fn init my-automation
    cd my-automation
    ```
    
    Creates:
    - `package.json` - Dependencies
    - `.env` - Credentials (from `~/.stagehand/config.json`)
    - `index.ts` - Function template
    - `tsconfig.json` - TypeScript config
    
    ### 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:
    
    ```json
    {
      "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:
    ```bash
    pnpm install
    ```
    
    ### Step 4: Write Automation Code
    
    Edit `index.ts`:
    
    ```typescript
    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:
    ```bash
    pnpm bb dev index.ts
    ```
    
    Server runs at `http://127.0.0.1:14113`
    
    Invoke with curl:
    ```bash
    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
    
    ```bash
    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
    
    ```bash
    stagehand fn invoke <function-id> -p '{"param": "value"}'
    ```
    
    Or via API:
    ```bash
    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
    
    ```typescript
    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
    ```typescript
    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
    ```typescript
    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
    ```typescript
    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:**
    1. Update `package.json` (see Step 3 above)
    2. Add all required fields: `description`, `main`, `packageManager`
    3. Change `"latest"` to pinned versions like `"^0.0.5"`
    4. Add `devDependencies` section with TypeScript and types
    5. Run `pnpm install`
    6. Try deploying again
    
    **Quick check:** Compare your `package.json` to `bitcoin-functions/package.json` in the codebase.
    
    ### Local dev server won't start
    
    ```bash
    # 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:**
    1. Missing `devDependencies` (TypeScript won't compile)
    2. Using `"latest"` instead of pinned versions
    3. Missing required fields in `package.json`
    
    **Solution:** Fix package.json as described in Step 3.
    
    ### Cannot extract data from page
    
    1. Take screenshot: `stagehand screenshot -o debug.png`
    2. Get snapshot: `stagehand snapshot`
    3. Use `page.evaluate()` to log what's in the DOM
    4. 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
    
    1. āœ… **Fix package.json immediately** after `stagehand fn init`
    2. āœ… **Explore interactively first** - Use local browser session to understand site
    3. āœ… **Test manually** - Verify each step works before writing code
    4. āœ… **Test locally** - Use dev server before deploying
    5. āœ… **Return meaningful data** - Include timestamps, counts, URLs
    6. āœ… **Handle errors gracefully** - Try/catch around risky operations
    7. āœ… **Use specific selectors** - Prefer data a
    
    ... (truncated)