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

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)
```