547 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			547 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
# 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:**
 | 
						|
 | 
						|
```json
 | 
						|
// 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:**
 | 
						|
 | 
						|
```json
 | 
						|
// 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:**
 | 
						|
 | 
						|
```json
 | 
						|
// 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:**
 | 
						|
 | 
						|
```json
 | 
						|
// 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:**
 | 
						|
 | 
						|
```json
 | 
						|
// 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:**
 | 
						|
 | 
						|
```json
 | 
						|
// 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:**
 | 
						|
```json
 | 
						|
{action: "click", selector: "#submit-button"}  // ✅ Best
 | 
						|
{action: "click", selector: "button.submit"}    // ✅ Good
 | 
						|
{action: "click", selector: "button"}           // ❌ Too generic
 | 
						|
```
 | 
						|
 | 
						|
**Combine selectors for uniqueness:**
 | 
						|
```json
 | 
						|
{action: "click", selector: "form.login button[type=submit]"}  // ✅ Specific
 | 
						|
{action: "click", selector: ".modal.active button.primary"}    // ✅ Specific
 | 
						|
```
 | 
						|
 | 
						|
**Use data attributes:**
 | 
						|
```json
 | 
						|
{action: "click", selector: "[data-testid='submit-btn']"}      // ✅ Reliable
 | 
						|
{action: "click", selector: "[data-action='save']"}            // ✅ Semantic
 | 
						|
```
 | 
						|
 | 
						|
### Waiting Strategy
 | 
						|
 | 
						|
**Always wait before interaction:**
 | 
						|
```json
 | 
						|
// ❌ 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:**
 | 
						|
```json
 | 
						|
// 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:**
 | 
						|
```json
 | 
						|
{action: "click", selector: "button.submit"}
 | 
						|
{action: "eval", payload: "!!document.querySelector('.error-message')"}
 | 
						|
{action: "extract", payload: "text", selector: ".error-message"}
 | 
						|
```
 | 
						|
 | 
						|
**Validate expected state:**
 | 
						|
```json
 | 
						|
{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:**
 | 
						|
```json
 | 
						|
// ❌ 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:**
 | 
						|
```json
 | 
						|
{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:**
 | 
						|
```json
 | 
						|
// ❌ 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:**
 | 
						|
```json
 | 
						|
// ✅ 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:**
 | 
						|
```json
 | 
						|
{action: "navigate", payload: "https://example.com"}
 | 
						|
{action: "await_element", selector: "body"}
 | 
						|
{action: "extract", payload: "html"}
 | 
						|
```
 | 
						|
 | 
						|
**Step 2: Test selectors:**
 | 
						|
```json
 | 
						|
{action: "eval", payload: "document.querySelector('button.submit')"}
 | 
						|
{action: "eval", payload: "document.querySelectorAll('button').length"}
 | 
						|
```
 | 
						|
 | 
						|
**Step 3: Check element state:**
 | 
						|
```json
 | 
						|
{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:**
 | 
						|
```json
 | 
						|
{action: "eval", payload: "console.error.toString()"}
 | 
						|
```
 | 
						|
 | 
						|
---
 | 
						|
 | 
						|
## Patterns Library
 | 
						|
 | 
						|
### Retry Logic
 | 
						|
 | 
						|
```json
 | 
						|
// 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
 | 
						|
 | 
						|
```json
 | 
						|
// 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
 | 
						|
 | 
						|
```json
 | 
						|
// 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
 | 
						|
 | 
						|
```json
 | 
						|
// 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
 | 
						|
 | 
						|
```json
 | 
						|
// 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"}
 | 
						|
```
 | 
						|
 | 
						|
### Cookie Management
 | 
						|
 | 
						|
```json
 | 
						|
// 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
 | 
						|
 | 
						|
```json
 | 
						|
// 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
 | 
						|
 | 
						|
```json
 | 
						|
// 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
 | 
						|
 | 
						|
```json
 | 
						|
// ❌ 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
 | 
						|
 | 
						|
```json
 | 
						|
// Check protocol
 | 
						|
{action: "eval", payload: "window.location.protocol"}
 | 
						|
// Should return "https:"
 | 
						|
```
 | 
						|
 | 
						|
### Check for Security Indicators
 | 
						|
 | 
						|
```json
 | 
						|
// 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
 | 
						|
 | 
						|
```json
 | 
						|
// ❌ Arbitrary timeouts
 | 
						|
{action: "eval", payload: "new Promise(r => setTimeout(r, 5000))"}
 | 
						|
 | 
						|
// ✅ Condition-based waits
 | 
						|
{action: "await_element", selector: ".loaded"}
 | 
						|
```
 | 
						|
 | 
						|
### Batch Operations
 | 
						|
 | 
						|
```json
 | 
						|
// ❌ 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
 | 
						|
 | 
						|
```json
 | 
						|
// ✅ 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)
 | 
						|
```
 |