nixos/shared/linked-dotfiles/opencode/skills/browser-automation/references/examples.md
2025-10-29 18:46:16 -06:00

19 KiB

Browser Automation Examples

Complete workflows demonstrating the use_browser tool capabilities.

Table of Contents

  1. E-Commerce Workflows
  2. Form Automation
  3. Data Extraction
  4. Multi-Tab Operations
  5. Dynamic Content Handling
  6. Authentication Workflows

E-Commerce Workflows

Complete Booking Flow

Navigate multi-step booking process with validation:

// Step 1: Search
{action: "navigate", payload: "https://booking.example.com"}
{action: "await_element", selector: "input[name=destination]"}
{action: "type", selector: "input[name=destination]", payload: "San Francisco"}
{action: "type", selector: "input[name=checkin]", payload: "2025-12-01"}
{action: "click", selector: "button.search"}

// Step 2: Select hotel
{action: "await_element", selector: ".hotel-results"}
{action: "click", selector: ".hotel-card:first-child .select"}

// Step 3: Choose room
{action: "await_element", selector: ".room-options"}
{action: "click", selector: ".room[data-type=deluxe] .book"}

// Step 4: Guest info
{action: "await_element", selector: "form.guest-info"}
{action: "type", selector: "input[name=firstName]", payload: "Jane"}
{action: "type", selector: "input[name=lastName]", payload: "Smith"}
{action: "type", selector: "input[name=email]", payload: "jane@example.com"}

// Step 5: Review
{action: "click", selector: "button.review"}
{action: "await_element", selector: ".summary"}

// Validate
{action: "extract", payload: "text", selector: ".hotel-name"}
{action: "extract", payload: "text", selector: ".total-price"}

Price Comparison Across Sites

Open multiple stores in tabs and compare:

// Store 1
{action: "navigate", payload: "https://store1.com/product/12345"}
{action: "await_element", selector: ".price"}

// Open Store 2
{action: "new_tab"}
{action: "navigate", tab_index: 1, payload: "https://store2.com/product/12345"}
{action: "await_element", tab_index: 1, selector: ".price"}

// Open Store 3
{action: "new_tab"}
{action: "navigate", tab_index: 2, payload: "https://store3.com/product/12345"}
{action: "await_element", tab_index: 2, selector: ".price"}

// Extract all prices
{action: "extract", tab_index: 0, payload: "text", selector: ".price"}
{action: "extract", tab_index: 1, payload: "text", selector: ".price"}
{action: "extract", tab_index: 2, payload: "text", selector: ".price"}

// Get product info
{action: "extract", tab_index: 0, payload: "text", selector: ".product-name"}
{action: "extract", tab_index: 0, payload: "text", selector: ".stock-status"}

Product Data Extraction

Scrape structured product information:

{action: "navigate", payload: "https://shop.example.com/product/123"}
{action: "await_element", selector: ".product-details"}

// Extract all product data with one eval
{action: "eval", payload: `
  ({
    name: document.querySelector('h1.product-name').textContent.trim(),
    price: document.querySelector('.price').textContent.trim(),
    image: document.querySelector('.product-image img').src,
    description: document.querySelector('.description').textContent.trim(),
    stock: document.querySelector('.stock-status').textContent.trim(),
    rating: document.querySelector('.rating').textContent.trim(),
    reviews: Array.from(document.querySelectorAll('.review')).map(r => ({
      author: r.querySelector('.author').textContent,
      rating: r.querySelector('.stars').textContent,
      text: r.querySelector('.review-text').textContent
    }))
  })
`}

Batch Product Extraction

Get multiple products from category page:

{action: "navigate", payload: "https://shop.example.com/category/electronics"}
{action: "await_element", selector: ".product-grid"}

// Extract all products as array
{action: "eval", payload: `
  Array.from(document.querySelectorAll('.product-card')).map(card => ({
    name: card.querySelector('.product-name').textContent.trim(),
    price: card.querySelector('.price').textContent.trim(),
    image: card.querySelector('img').src,
    url: card.querySelector('a').href,
    inStock: !card.querySelector('.out-of-stock')
  }))
`}

Form Automation

Multi-Step Registration Form

Handle progressive form with validation at each step:

// Step 1: Personal info
{action: "navigate", payload: "https://example.com/register"}
{action: "await_element", selector: "input[name=firstName]"}

{action: "type", selector: "input[name=firstName]", payload: "John"}
{action: "type", selector: "input[name=lastName]", payload: "Doe"}
{action: "type", selector: "input[name=email]", payload: "john@example.com"}
{action: "click", selector: "button.next"}

// Wait for step 2
{action: "await_element", selector: "input[name=address]"}

// Step 2: Address
{action: "type", selector: "input[name=address]", payload: "123 Main St"}
{action: "type", selector: "input[name=city]", payload: "Springfield"}
{action: "select", selector: "select[name=state]", payload: "IL"}
{action: "type", selector: "input[name=zip]", payload: "62701"}
{action: "click", selector: "button.next"}

// Wait for step 3
{action: "await_element", selector: "input[name=cardNumber]"}

// Step 3: Payment
{action: "type", selector: "input[name=cardNumber]", payload: "4111111111111111"}
{action: "select", selector: "select[name=expMonth]", payload: "12"}
{action: "select", selector: "select[name=expYear]", payload: "2028"}
{action: "type", selector: "input[name=cvv]", payload: "123"}

// Review before submit
{action: "click", selector: "button.review"}
{action: "await_element", selector: ".summary"}

// Extract confirmation
{action: "extract", payload: "markdown", selector: ".summary"}

Search with Multiple Filters

Use dropdowns, checkboxes, and text inputs:

{action: "navigate", payload: "https://library.example.com/search"}
{action: "await_element", selector: "form.search"}

// Category dropdown
{action: "select", selector: "select[name=category]", payload: "books"}

// Price range
{action: "type", selector: "input[name=priceMin]", payload: "10"}
{action: "type", selector: "input[name=priceMax]", payload: "50"}

// Checkboxes via JavaScript
{action: "eval", payload: "document.querySelector('input[name=inStock]').checked = true"}
{action: "eval", payload: "document.querySelector('input[name=freeShipping]').checked = true"}

// Search term and submit
{action: "type", selector: "input[name=query]", payload: "chrome devtools\n"}

// Wait for results
{action: "await_element", selector: ".results"}

// Count and extract
{action: "eval", payload: "document.querySelectorAll('.result').length"}
{action: "extract", payload: "text", selector: ".result-count"}

File Upload

Handle file input using JavaScript:

{action: "navigate", payload: "https://example.com/upload"}
{action: "await_element", selector: "input[type=file]"}

// Read file and set via JavaScript (for testing)
{action: "eval", payload: `
  const fileInput = document.querySelector('input[type=file]');
  const dataTransfer = new DataTransfer();
  const file = new File(['test content'], 'test.txt', { type: 'text/plain' });
  dataTransfer.items.add(file);
  fileInput.files = dataTransfer.files;
`}

// Submit
{action: "click", selector: "button.upload"}
{action: "await_text", payload: "Upload complete"}

Data Extraction

Article Scraping

Extract blog post with metadata:

{action: "navigate", payload: "https://blog.example.com/article"}
{action: "await_element", selector: "article"}

// Extract complete article structure
{action: "eval", payload: `
  ({
    title: document.querySelector('article h1').textContent.trim(),
    author: document.querySelector('.author-name').textContent.trim(),
    date: document.querySelector('time').getAttribute('datetime'),
    tags: Array.from(document.querySelectorAll('.tag')).map(t => t.textContent.trim()),
    content: document.querySelector('article .content').textContent.trim(),
    images: Array.from(document.querySelectorAll('article img')).map(img => ({
      src: img.src,
      alt: img.alt
    })),
    links: Array.from(document.querySelectorAll('article a')).map(a => ({
      text: a.textContent.trim(),
      href: a.href
    }))
  })
`}

Table Data Extraction

Convert HTML table to structured JSON:

{action: "navigate", payload: "https://example.com/data/table"}
{action: "await_element", selector: "table"}

// Extract table with headers
{action: "eval", payload: `
  (() => {
    const headers = Array.from(document.querySelectorAll('table thead th'))
      .map(th => th.textContent.trim());
    const rows = Array.from(document.querySelectorAll('table tbody tr'))
      .map(tr => {
        const cells = Array.from(tr.cells).map(td => td.textContent.trim());
        return Object.fromEntries(headers.map((h, i) => [h, cells[i]]));
      });
    return rows;
  })()
`}

Paginated Results

Extract data across multiple pages:

{action: "navigate", payload: "https://example.com/results?page=1"}
{action: "await_element", selector: ".results"}

// Page 1
{action: "eval", payload: "Array.from(document.querySelectorAll('.result')).map(r => r.textContent.trim())"}

// Navigate to page 2
{action: "click", selector: "a.next-page"}
{action: "await_element", selector: ".results"}
{action: "await_text", payload: "Page 2"}

// Page 2
{action: "eval", payload: "Array.from(document.querySelectorAll('.result')).map(r => r.textContent.trim())"}

// Continue pattern for additional pages...

Multi-Tab Operations

Email Receipt Extraction

Find specific email and extract data:

// List available tabs
{action: "list_tabs"}

// Switch to email tab (assume index 2 from list)
{action: "click", tab_index: 2, selector: "a[title*='Receipt']"}
{action: "await_element", tab_index: 2, selector: ".email-body"}

// Extract receipt details
{action: "extract", tab_index: 2, payload: "text", selector: ".order-number"}
{action: "extract", tab_index: 2, payload: "text", selector: ".total-amount"}
{action: "extract", tab_index: 2, payload: "markdown", selector: ".items-list"}

Cross-Site Data Correlation

Extract from one site, verify on another:

// Get company phone from website
{action: "navigate", payload: "https://company.com/contact"}
{action: "await_element", selector: ".contact-info"}
{action: "extract", payload: "text", selector: ".phone-number"}

// Store result: "+1-555-0123"

// Open verification site in new tab
{action: "new_tab"}
{action: "navigate", tab_index: 1, payload: "https://phonevalidator.com"}
{action: "await_element", tab_index: 1, selector: "input[name=phone]"}

// Fill and search
{action: "type", tab_index: 1, selector: "input[name=phone]", payload: "+1-555-0123\n"}
{action: "await_element", tab_index: 1, selector: ".results"}

// Extract validation result
{action: "extract", tab_index: 1, payload: "text", selector: ".verification-status"}

Parallel Data Collection

Collect data from multiple sources simultaneously:

// Tab 0: Weather
{action: "navigate", tab_index: 0, payload: "https://weather.com/city"}
{action: "await_element", tab_index: 0, selector: ".temperature"}

// Tab 1: News
{action: "new_tab"}
{action: "navigate", tab_index: 1, payload: "https://news.com"}
{action: "await_element", tab_index: 1, selector: ".headlines"}

// Tab 2: Stock prices
{action: "new_tab"}
{action: "navigate", tab_index: 2, payload: "https://stocks.com"}
{action: "await_element", tab_index: 2, selector: ".market-summary"}

// Extract all data
{action: "extract", tab_index: 0, payload: "text", selector: ".temperature"}
{action: "extract", tab_index: 1, payload: "text", selector: ".headline:first-child"}
{action: "extract", tab_index: 2, payload: "text", selector: ".market-summary"}

Dynamic Content Handling

Infinite Scroll Loading

Load all content with scroll-triggered pagination:

{action: "navigate", payload: "https://example.com/feed"}
{action: "await_element", selector: ".feed-item"}

// Count initial items
{action: "eval", payload: "document.querySelectorAll('.feed-item').length"}

// Scroll and wait multiple times
{action: "eval", payload: "window.scrollTo(0, document.body.scrollHeight)"}
{action: "eval", payload: "new Promise(r => setTimeout(r, 2000))"}

{action: "eval", payload: "window.scrollTo(0, document.body.scrollHeight)"}
{action: "eval", payload: "new Promise(r => setTimeout(r, 2000))"}

{action: "eval", payload: "window.scrollTo(0, document.body.scrollHeight)"}
{action: "eval", payload: "new Promise(r => setTimeout(r, 2000))"}

// Extract all loaded items
{action: "eval", payload: `
  Array.from(document.querySelectorAll('.feed-item')).map(item => ({
    title: item.querySelector('.title').textContent.trim(),
    date: item.querySelector('.date').textContent.trim(),
    url: item.querySelector('a').href
  }))
`}

Wait for AJAX Response

Wait for loading indicator to disappear:

{action: "navigate", payload: "https://app.com/dashboard"}
{action: "await_element", selector: ".content"}

// Trigger AJAX request
{action: "click", selector: "button.load-data"}

// Wait for spinner to appear then disappear
{action: "eval", payload: `
  new Promise(resolve => {
    const checkGone = () => {
      const spinner = document.querySelector('.spinner');
      if (!spinner || spinner.style.display === 'none') {
        resolve(true);
      } else {
        setTimeout(checkGone, 100);
      }
    };
    checkGone();
  })
`}

// Now safe to extract
{action: "extract", payload: "text", selector: ".data-table"}

Modal Dialog Handling

Open modal, interact, wait for close:

{action: "click", selector: "button.open-settings"}
{action: "await_element", selector: ".modal.visible"}

// Interact with modal
{action: "type", selector: ".modal input[name=username]", payload: "newuser"}
{action: "select", selector: ".modal select[name=theme]", payload: "dark"}
{action: "click", selector: ".modal button.save"}

// Wait for modal to close
{action: "eval", payload: `
  new Promise(resolve => {
    const check = () => {
      const modal = document.querySelector('.modal.visible');
      if (!modal) {
        resolve(true);
      } else {
        setTimeout(check, 100);
      }
    };
    check();
  })
`}

// Verify settings saved
{action: "await_text", payload: "Settings saved"}

Wait for Button Enabled

Wait for form validation before submission:

{action: "type", selector: "input[name=email]", payload: "user@example.com"}
{action: "type", selector: "input[name=password]", payload: "securepass123"}

// Wait for submit button to become enabled
{action: "eval", payload: `
  new Promise(resolve => {
    const check = () => {
      const btn = document.querySelector('button[type=submit]');
      if (btn && !btn.disabled && !btn.classList.contains('disabled')) {
        resolve(true);
      } else {
        setTimeout(check, 100);
      }
    };
    check();
  })
`}

// Now safe to click
{action: "click", selector: "button[type=submit]"}

Authentication Workflows

Standard Login

{action: "navigate", payload: "https://app.example.com/login"}
{action: "await_element", selector: "form.login"}

// Fill credentials
{action: "type", selector: "input[name=email]", payload: "user@example.com"}
{action: "type", selector: "input[name=password]", payload: "password123\n"}

// Wait for redirect
{action: "await_text", payload: "Dashboard"}

// Verify logged in
{action: "extract", payload: "text", selector: ".user-name"}

OAuth Flow

{action: "navigate", payload: "https://app.example.com/connect"}
{action: "await_element", selector: "button.oauth-login"}

// Trigger OAuth
{action: "click", selector: "button.oauth-login"}

// Wait for OAuth provider page
{action: "await_text", payload: "Authorize"}

// Fill OAuth credentials
{action: "await_element", selector: "input[name=username]"}
{action: "type", selector: "input[name=username]", payload: "oauthuser"}
{action: "type", selector: "input[name=password]", payload: "oauthpass\n"}

// Wait for redirect back
{action: "await_text", payload: "Connected successfully"}

Session Persistence Check

// Load page
{action: "navigate", payload: "https://app.example.com/dashboard"}
{action: "await_element", selector: "body"}

// Check if logged in via cookie/localStorage
{action: "eval", payload: "document.cookie.includes('session_id')"}
{action: "eval", payload: "localStorage.getItem('auth_token') !== null"}

// Verify user data loaded
{action: "extract", payload: "text", selector: ".user-profile"}

Advanced Patterns

Conditional Workflow

Branch based on page content:

{action: "navigate", payload: "https://example.com/status"}
{action: "await_element", selector: "body"}

// Check status
{action: "extract", payload: "text", selector: ".status-message"}

// If result contains "Available":
{action: "click", selector: "button.purchase"}
{action: "await_text", payload: "Added to cart"}

// If result contains "Out of stock":
{action: "click", selector: "button.notify-me"}
{action: "type", selector: "input[name=email]", payload: "notify@example.com\n"}

Error Recovery

Handle and retry failed operations:

{action: "navigate", payload: "https://app.example.com/data"}
{action: "await_element", selector: ".content"}

// Attempt operation
{action: "click", selector: "button.load"}

// Check for error
{action: "eval", payload: "!!document.querySelector('.error-message')"}

// If error present, retry
{action: "click", selector: "button.retry"}
{action: "await_element", selector: ".data-loaded"}

Screenshot Comparison

Capture before and after states:

// Initial state
{action: "navigate", payload: "https://example.com"}
{action: "await_element", selector: ".content"}
{action: "screenshot", payload: "/tmp/before.png"}

// Make changes
{action: "click", selector: "button.dark-mode"}
{action: "await_element", selector: "body.dark"}

// Capture new state
{action: "screenshot", payload: "/tmp/after.png"}

// Or screenshot specific element
{action: "screenshot", payload: "/tmp/header.png", selector: "header"}

Tips for Complex Workflows

Build Incrementally

Start simple, add complexity:

  1. Navigate and verify page loads
  2. Extract one element
  3. Add interaction
  4. Add waiting logic
  5. Add error handling
  6. Add validation

Use JavaScript for Complex Logic

When multiple operations needed, use eval:

{action: "eval", payload: `
  (async () => {
    // Complex multi-step logic
    const results = [];
    const items = document.querySelectorAll('.item');
    
    for (const item of items) {
      if (item.classList.contains('active')) {
        results.push({
          id: item.dataset.id,
          text: item.textContent.trim()
        });
      }
    }
    
    return results;
  })()
`}

Validate Selectors

Always test selectors return expected elements:

// Check element exists
{action: "eval", payload: "!!document.querySelector('button.submit')"}

// Check element visible
{action: "eval", payload: "window.getComputedStyle(document.querySelector('button.submit')).display !== 'none'"}

// Check element count
{action: "eval", payload: "document.querySelectorAll('.item').length"}