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

14 KiB

Browser Automation Troubleshooting Guide

Quick reference for common issues and solutions.

Common Errors

Element Not Found

Error: Element not found: button.submit

Causes:

  1. Page still loading
  2. Wrong selector
  3. Element in iframe
  4. Element hidden/not rendered

Solutions:

// 1. Add wait before interaction
{action: "await_element", selector: "button.submit", timeout: 10000}
{action: "click", selector: "button.submit"}

// 2. Verify selector exists
{action: "extract", payload: "html"}
{action: "eval", payload: "document.querySelector('button.submit')"}

// 3. Check if in iframe
{action: "eval", payload: "document.querySelectorAll('iframe').length"}

// 4. Check visibility
{action: "eval", payload: "window.getComputedStyle(document.querySelector('button.submit')).display"}

Timeout Errors

Error: Timeout waiting for element after 5000ms

Solutions:

// Increase timeout for slow pages
{action: "await_element", selector: ".content", timeout: 30000}

// Wait for loading to complete first
{action: "await_element", selector: ".spinner"}
{action: "eval", payload: `
  new Promise(r => {
    const check = () => {
      if (!document.querySelector('.spinner')) r(true);
      else setTimeout(check, 100);
    };
    check();
  })
`}

// Use JavaScript to wait for specific condition
{action: "eval", payload: `
  new Promise(resolve => {
    const observer = new MutationObserver(() => {
      if (document.querySelector('.loaded')) {
        observer.disconnect();
        resolve(true);
      }
    });
    observer.observe(document.body, { childList: true, subtree: true });
  })
`}

Click Not Working

Error: Click executes but nothing happens

Causes:

  1. JavaScript event handler not attached yet
  2. Element covered by another element
  3. Need to scroll element into view

Solutions:

// 1. Wait longer before click
{action: "await_element", selector: "button"}
{action: "eval", payload: "new Promise(r => setTimeout(r, 1000))"}
{action: "click", selector: "button"}

// 2. Check z-index and overlays
{action: "eval", payload: `
  (() => {
    const elem = document.querySelector('button');
    const rect = elem.getBoundingClientRect();
    const topElem = document.elementFromPoint(rect.left + rect.width/2, rect.top + rect.height/2);
    return topElem === elem || elem.contains(topElem);
  })()
`}

// 3. Scroll into view first
{action: "eval", payload: "document.querySelector('button').scrollIntoView()"}
{action: "click", selector: "button"}

// 4. Force click via JavaScript
{action: "eval", payload: "document.querySelector('button').click()"}

Form Submission Issues

Error: Form doesn't submit with \n

Solutions:

// Try explicit click
{action: "type", selector: "input[name=password]", payload: "pass123"}
{action: "click", selector: "button[type=submit]"}

// Or trigger form submit
{action: "eval", payload: "document.querySelector('form').submit()"}

// Or press Enter key specifically
{action: "eval", payload: `
  const input = document.querySelector('input[name=password]');
  input.dispatchEvent(new KeyboardEvent('keypress', { key: 'Enter', keyCode: 13 }));
`}

Tab Index Errors

Error: Tab index 2 out of range

Cause: Tab closed or indices shifted

Solution:

// Always list tabs before operating on them
{action: "list_tabs"}

// After closing tabs, re-list
{action: "close_tab", tab_index: 1}
{action: "list_tabs"}
{action: "click", tab_index: 1, selector: "a"}  // Now correct index

Extract Returns Empty

Error: Extract returns empty string

Causes:

  1. Element not loaded yet
  2. Content in shadow DOM
  3. Text in ::before/::after pseudo-elements

Solutions:

// 1. Wait for content
{action: "await_element", selector: ".content"}
{action: "await_text", payload: "Expected text"}
{action: "extract", payload: "text", selector: ".content"}

// 2. Check shadow DOM
{action: "eval", payload: "document.querySelector('my-component').shadowRoot.querySelector('.content').textContent"}

// 3. Get computed styles for pseudo-elements
{action: "eval", payload: "window.getComputedStyle(document.querySelector('.content'), '::before').content"}

Best Practices

Selector Specificity

Use ID when available:

{action: "click", selector: "#submit-button"}  // ✅ Best
{action: "click", selector: "button.submit"}    // ✅ Good
{action: "click", selector: "button"}           // ❌ Too generic

Combine selectors for uniqueness:

{action: "click", selector: "form.login button[type=submit]"}  // ✅ Specific
{action: "click", selector: ".modal.active button.primary"}    // ✅ Specific

Use data attributes:

{action: "click", selector: "[data-testid='submit-btn']"}      // ✅ Reliable
{action: "click", selector: "[data-action='save']"}            // ✅ Semantic

Waiting Strategy

Always wait before interaction:

// ❌ BAD - No waiting
{action: "navigate", payload: "https://example.com"}
{action: "click", selector: "button"}

// ✅ GOOD - Wait for element
{action: "navigate", payload: "https://example.com"}
{action: "await_element", selector: "button"}
{action: "click", selector: "button"}

// ✅ BETTER - Wait for specific state
{action: "navigate", payload: "https://example.com"}
{action: "await_text", payload: "Page loaded"}
{action: "click", selector: "button"}

Wait for dynamic content:

// After triggering AJAX
{action: "click", selector: "button.load-more"}
{action: "await_element", selector: ".new-content"}

// After form submit
{action: "click", selector: "button[type=submit]"}
{action: "await_text", payload: "Success"}

Error Detection

Check for error messages:

{action: "click", selector: "button.submit"}
{action: "eval", payload: "!!document.querySelector('.error-message')"}
{action: "extract", payload: "text", selector: ".error-message"}

Validate expected state:

{action: "click", selector: "button.add-to-cart"}
{action: "await_element", selector: ".cart-count"}
{action: "extract", payload: "text", selector: ".cart-count"}
// Verify count increased

Data Extraction Efficiency

Use single eval for multiple fields:

// ❌ Inefficient - Multiple calls
{action: "extract", payload: "text", selector: "h1"}
{action: "extract", payload: "text", selector: ".author"}
{action: "extract", payload: "text", selector: ".date"}

// ✅ Efficient - One call
{action: "eval", payload: `
  ({
    title: document.querySelector('h1').textContent.trim(),
    author: document.querySelector('.author').textContent.trim(),
    date: document.querySelector('.date').textContent.trim()
  })
`}

Extract arrays efficiently:

{action: "eval", payload: `
  Array.from(document.querySelectorAll('.item')).map(item => ({
    name: item.querySelector('.name').textContent.trim(),
    price: item.querySelector('.price').textContent.trim(),
    url: item.querySelector('a').href
  }))
`}

Performance Optimization

Minimize navigation:

// ❌ Slow - Navigate for each item
{action: "navigate", payload: "https://example.com/item/1"}
{action: "extract", payload: "text", selector: ".price"}
{action: "navigate", payload: "https://example.com/item/2"}
{action: "extract", payload: "text", selector: ".price"}

// ✅ Fast - Use API or extract list page
{action: "navigate", payload: "https://example.com/items"}
{action: "eval", payload: "Array.from(document.querySelectorAll('.item')).map(i => i.querySelector('.price').textContent)"}

Reuse tabs:

// ✅ Keep tabs open for repeated access
{action: "new_tab"}
{action: "navigate", tab_index: 1, payload: "https://tool.com"}

// Later, reuse same tab
{action: "click", tab_index: 1, selector: "button.refresh"}

Debugging Workflows

Step 1: Check page HTML:

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

Step 2: Test selectors:

{action: "eval", payload: "document.querySelector('button.submit')"}
{action: "eval", payload: "document.querySelectorAll('button').length"}

Step 3: Check element state:

{action: "eval", payload: `
  (() => {
    const elem = document.querySelector('button.submit');
    return {
      exists: !!elem,
      visible: elem ? window.getComputedStyle(elem).display !== 'none' : false,
      enabled: elem ? !elem.disabled : false,
      text: elem ? elem.textContent : null
    };
  })()
`}

Step 4: Check console errors:

{action: "eval", payload: "console.error.toString()"}

Patterns Library

Retry Logic

// Attempt operation with retry
{action: "click", selector: "button.submit"}

// Check if succeeded
{action: "eval", payload: "document.querySelector('.success-message')"}

// If null, retry
{action: "click", selector: "button.submit"}
{action: "await_text", payload: "Success", timeout: 10000}

Conditional Branching

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

// Branch based on result (in your logic)
// If "available":
{action: "click", selector: "button.buy"}

// If "out of stock":
{action: "type", selector: "input.email", payload: "notify@example.com\n"}

Pagination Handling

// Page 1
{action: "navigate", payload: "https://example.com/results"}
{action: "await_element", selector: ".results"}
{action: "eval", payload: "Array.from(document.querySelectorAll('.result')).map(r => r.textContent)"}

// Check if next page exists
{action: "eval", payload: "!!document.querySelector('a.next-page')"}

// If yes, navigate
{action: "click", selector: "a.next-page"}
{action: "await_element", selector: ".results"}
// Repeat extraction

Form Validation Waiting

// Fill form field
{action: "type", selector: "input[name=email]", payload: "user@example.com"}

// Wait for validation icon
{action: "await_element", selector: "input[name=email] + .valid-icon"}

// Proceed to next field
{action: "type", selector: "input[name=password]", payload: "password123"}

Autocomplete Selection

// Type in autocomplete field
{action: "type", selector: "input.autocomplete", payload: "San Fr"}

// Wait for suggestions
{action: "await_element", selector: ".autocomplete-suggestions"}

// Click suggestion
{action: "click", selector: ".autocomplete-suggestions li:first-child"}

// Verify selection
{action: "extract", payload: "text", selector: "input.autocomplete"}
// Check if cookie exists
{action: "eval", payload: "document.cookie.includes('session_id')"}

// Set cookie
{action: "eval", payload: "document.cookie = 'preferences=dark; path=/; max-age=31536000'"}

// Clear specific cookie
{action: "eval", payload: "document.cookie = 'session_id=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;'"}

// Get all cookies as object
{action: "eval", payload: `
  Object.fromEntries(
    document.cookie.split('; ').map(c => c.split('='))
  )
`}

XPath Examples

XPath is auto-detected (starts with / or //).

Basic XPath Selectors

// Find by text content
{action: "click", selector: "//button[text()='Submit']"}
{action: "click", selector: "//a[contains(text(), 'Learn more')]"}

// Find by attribute
{action: "click", selector: "//button[@type='submit']"}
{action: "extract", payload: "text", selector: "//div[@class='content']"}

// Hierarchical
{action: "click", selector: "//form[@id='login']//button[@type='submit']"}
{action: "extract", payload: "text", selector: "//article/div[@class='content']/p[1]"}

Advanced XPath

// Multiple conditions
{action: "click", selector: "//button[@type='submit' and contains(@class, 'primary')]"}

// Following sibling
{action: "extract", payload: "text", selector: "//label[text()='Username']/following-sibling::input/@value"}

// Parent selection
{action: "click", selector: "//td[text()='Active']/..//button[@class='edit']"}

// Multiple elements
{action: "extract", payload: "text", selector: "//h2 | //h3"}

Security Considerations

Avoid Hardcoded Credentials

// ❌ BAD - Credentials in workflow
{action: "type", selector: "input[name=password]", payload: "mypassword123"}

// ✅ GOOD - Use environment variables or secure storage
// Load credentials from secure source before workflow

Validate HTTPS

// Check protocol
{action: "eval", payload: "window.location.protocol"}
// Should return "https:"

Check for Security Indicators

// Verify login page is secure
{action: "eval", payload: `
  ({
    protocol: window.location.protocol,
    hasLock: document.querySelector('link[rel=icon]')?.href.includes('secure'),
    url: window.location.href
  })
`}

Performance Tips

Minimize Waits

// ❌ Arbitrary timeouts
{action: "eval", payload: "new Promise(r => setTimeout(r, 5000))"}

// ✅ Condition-based waits
{action: "await_element", selector: ".loaded"}

Batch Operations

// ❌ Individual extracts
{action: "extract", payload: "text", selector: ".title"}
{action: "extract", payload: "text", selector: ".author"}
{action: "extract", payload: "text", selector: ".date"}

// ✅ Single eval
{action: "eval", payload: `
  ({
    title: document.querySelector('.title').textContent,
    author: document.querySelector('.author').textContent,
    date: document.querySelector('.date').textContent
  })
`}

Reuse Browser State

// ✅ Stay logged in across operations
{action: "navigate", payload: "https://app.com/login"}
// ... login ...

{action: "navigate", payload: "https://app.com/page1"}
// ... work ...

{action: "navigate", payload: "https://app.com/page2"}
// ... work ... (still logged in)