SOP for debugging browser automation failures on complex websites. Use when browser tools fail on specific sites like LinkedIn, Twitter/X, SPAs, or sites with Shadow DOM.
63
75%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Low
Low-risk findings worth noting
Fix and improve this skill with Tessl
tessl review fix ./.claude/skills/browser-edge-cases/SKILL.mdStandard Operating Procedure for debugging and fixing browser automation failures on complex websites.
browser_interact(action="scroll") succeeds but page doesn't movebrowser_interact(action="left_click") succeeds but no action triggeredbrowser_interact(action="type") text disappears or doesn't workbrowser_snapshot hangs or returns stale contentbrowser_navigate loads wrong content1. Create minimal test case demonstrating failure
2. Test against simple site (example.com) to verify tool works
3. Test against problematic site to confirm issueQuick isolation test:
# Test 1: Does the tool work at all?
await browser_navigate(tab_id, "https://example.com")
result = await browser_interact(action="scroll", tab_id=tab_id, scroll_direction="down", scroll_amount=100)
# Should work on simple sites
# Test 2: Does it fail on the problematic site?
await browser_navigate(tab_id, "https://linkedin.com/feed")
result = await browser_interact(action="scroll", tab_id=tab_id, scroll_direction="down", scroll_amount=100)
# If this fails but example.com works → site-specific edge caseStep 2a: Check console for errors
console = await browser_console(tab_id)
# Look for: CSP violations, React errors, JavaScript exceptionsStep 2b: Inspect DOM structure
html = await browser_html(tab_id)
snapshot = await browser_snapshot(tab_id)
# Look for:
# - Nested scrollable divs (overflow: scroll/auto)
# - Shadow DOM roots
# - iframes
# - Custom widgetsStep 2c: Identify the pattern
| Symptom | Likely Cause | Check |
|---|---|---|
| Scroll doesn't move | Nested scroll container | Look for overflow: scroll divs |
| Click no effect | Element covered | Check getBoundingClientRect vs viewport |
| Type clears | Autocomplete/React | Check for event listeners on input; try a type action with no selector |
| Snapshot hangs | Huge DOM | Check node count in snapshot |
| Snapshot stale | SPA hydration | Wait after navigation |
Pattern: Always have fallbacks
async def robust_operation(tab_id):
# Method 1: Primary approach
try:
result = await primary_method(tab_id)
if verify_success(result):
return result
except Exception:
pass
# Method 2: CDP fallback
try:
result = await cdp_fallback(tab_id)
if verify_success(result):
return result
except Exception:
pass
# Method 3: JavaScript fallback
return await javascript_fallback(tab_id)Pattern: Always add timeouts
# Bad - can hang forever
result = await browser_snapshot(tab_id)
# Good - fails fast with useful error
try:
result = await browser_snapshot(tab_id, timeout_s=10.0)
except asyncio.TimeoutError:
# Handle timeout gracefully
result = await fallback_snapshot(tab_id)1. Run against problematic site → should work
2. Run against simple site → should still work (regression check)
3. Document in registry.mdSites: LinkedIn, Twitter/X, any SPA with scrollable feeds
Detection:
// Find largest scrollable container
const candidates = [];
document.querySelectorAll('*').forEach(el => {
const style = getComputedStyle(el);
if (style.overflow.includes('scroll') || style.overflow.includes('auto')) {
const rect = el.getBoundingClientRect();
if (rect.width > 100 && rect.height > 100) {
candidates.push({el, area: rect.width * rect.height});
}
}
});
candidates.sort((a, b) => b.area - a.area);
return candidates[0]?.el;Fix: Dispatch scroll events at container's center, not viewport center.
Sites: Modals, tooltips, SPAs with loading overlays
Detection:
const rect = element.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
const topElement = document.elementFromPoint(centerX, centerY);
return topElement === element || element.contains(topElement);Fix: Wait for overlay to disappear, or use JavaScript click.
Sites: React SPAs, modern web apps
Detection: If CDP click doesn't trigger handler but manual click works.
Fix: Use JavaScript click as primary:
element.click();Sites: LinkedIn, Facebook, Twitter (feeds with 1000s of nodes)
Detection:
document.querySelectorAll('*').length > 5000Fix:
Sites: React, Vue, Angular SPAs after navigation
Detection:
// Check if React app has hydrated
document.querySelector('[data-reactroot]') ||
document.querySelector('[data-reactid]')Fix: Wait for specific selector after navigation:
await browser_navigate(tab_id, url, wait_until="load")
await browser_interact(action="wait", tab_id=tab_id, wait_for_selector='[data-testid="content"]', timeout_ms=5000)Sites: Components using Shadow DOM, Lit elements
Detection:
document.querySelectorAll('*').some(el => el.shadowRoot)Fix: Pierce shadow root:
function queryShadow(selector) {
const parts = selector.split('>>>');
let node = document;
for (const part of parts) {
if (node.shadowRoot) {
node = node.shadowRoot.querySelector(part.trim());
} else {
node = node.querySelector(part.trim());
}
}
return node;
}| Issue | Primary Fix | Fallback |
|---|---|---|
| Scroll not working | Find scrollable container | Mouse wheel at container center |
| Click no effect | JavaScript click() | CDP mouse events |
| Type clears | use_insert_text=False (per-keystroke) | Use a type action (Input.insertText) |
| Snapshot hangs | Add timeout_s | DOM snapshot fallback |
| Stale content | Wait for selector | Increase wait_until timeout |
| Shadow DOM | Pierce selector | JavaScript traversal |
0c38749
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.