mcpbeat

Browser Edge Cases

aden-hive/browser-edge-cases

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.

20k tokens
context cost
the whole folder, loaded on every use
12
files
ships runnable scripts
0
copies elsewhere
how many repositories repackaged it
10839
stars on the repo
on the repository, not the skill itself

Install

one command, takes just this skill from the repository
npx skills add https://github.com/aden-hive/hive --skill browser-edge-cases

What comes with it

74 480 bytes besides the instruction
registry.md
scripts/test_02_twitter_scroll.py
scripts/test_03_modal_scroll.py
scripts/test_04_element_covered.py
scripts/test_06_shadow_dom.py
scripts/test_07_contenteditable.py
scripts/test_08_autocomplete.py
scripts/test_10_huge_dom.py
scripts/test_13_spa_navigation.py
scripts/test_15_screenshot.py
scripts/test_case.py

The instruction itself

16 sections, as written by the author

Browser Tool Edge Cases

Standard Operating Procedure for debugging and fixing browser automation failures on complex websites.

When to Use This Skill

  • browser_scroll succeeds but page doesn't move
  • browser_click succeeds but no action triggered
  • browser_type text disappears or doesn't work
  • browser_snapshot hangs or returns stale content
  • browser_navigate loads wrong content

SOP: Debugging Browser Tool Failures

Phase 1: Reproduce & Isolate

1. Create minimal test case demonstrating failure
2. Test against simple site (example.com) to verify tool works
3. Test against problematic site to confirm issue

Quick isolation test:

# Test 1: Does the tool work at all?
await browser_navigate(tab_id, "https://example.com")
result = await browser_scroll(tab_id, "down", 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_scroll(tab_id, "down", 100)
# If this fails but example.com works → site-specific edge case

Phase 2: Analyze Root Cause

Step 2a: Check console for errors

console = await browser_console(tab_id)
# Look for: CSP violations, React errors, JavaScript exceptions

Step 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 widgets

Step 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 browser_type_focused |

| Snapshot hangs | Huge DOM | Check node count in snapshot |

| Snapshot stale | SPA hydration | Wait after navigation |

Phase 3: Implement Multi-Layer Fix

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)

Phase 4: Verify Fix

1. Run against problematic site → should work
2. Run against simple site → should still work (regression check)
3. Document in registry.md

Pattern Library

P1: Nested Scrollable Containers

Sites: 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.

P2: Element Covered by Overlay

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.

P3: React Synthetic Events

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();

P4: Huge DOM / Accessibility Tree

Sites: LinkedIn, Facebook, Twitter (feeds with 1000s of nodes)

Detection:

document.querySelectorAll('*').length > 5000

Fix:

  • Add timeout to snapshot operation
  • Truncate tree at 2000 nodes
  • Fall back to DOM-based snapshot if accessibility tree too large

P5: SPA Hydration Delay

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_wait(tab_id, selector='[data-testid="content"]', timeout_ms=5000)

P6: Shadow DOM

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;
}

Quick Reference

| 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 | Add delay_ms | Use browser_type_focused (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 |

References

  • registry.md - Full list of known edge cases
  • scripts/test_case.py - Template for testing new cases
  • BROWSER_USE_PATTERNS.md - Implementation patterns from browser-use

How to use it

Copy the folder

Take aden-hive/browser-edge-cases from the repository into ~/.claude/skills for personal use, or into .claude/skills inside a project.

Check the name does not clash

The agent identifies a skill by the name field in its header. Two skills with the same name cannot sit side by side — one of them will be ignored.