Skip to main content
ClaudeWave
EN, English
Skill11.1k repo starsupdated 8d ago

browser-edge-cases

browser-edge-cases is a standard operating procedure for debugging browser automation failures on complex websites like LinkedIn, Twitter/X, and single-page applications. Use this skill when browser tools succeed without triggering expected actions, such as scroll commands that don't move the page, clicks with no effect, text that disappears after typing, or snapshots that hang or return outdated content. The skill provides a systematic three-phase debugging approach: reproducing the issue on simple sites, analyzing root causes through console logs and DOM inspection, and implementing fallback solutions for nested scroll containers, Shadow DOM, and other site-specific edge cases.

Install in Claude Code
Copy
git clone --depth 1 https://github.com/aden-hive/hive /tmp/browser-edge-cases && cp -r /tmp/browser-edge-cases/.claude/skills/browser-edge-cases ~/.claude/skills/browser-edge-cases
Then start a new Claude Code session; the skill loads automatically.

SKILL.md

# Browser Tool Edge Cases

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

## When to Use This Skill

- `browser_interact(action="scroll")` succeeds but page doesn't move
- `browser_interact(action="left_click")` succeeds but no action triggered
- `browser_interact(action="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:**
```python
# 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 case
```

### Phase 2: Analyze Root Cause

**Step 2a: Check console for errors**
```python
console = await browser_console(tab_id)
# Look for: CSP violations, React errors, JavaScript exceptions
```

**Step 2b: Inspect DOM structure**
```python
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 a `type` action with no selector |
| 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**

```python
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**

```python
# 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:**
```javascript
// 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:**
```javascript
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:
```javascript
element.click();
```

### P4: Huge DOM / Accessibility Tree

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

**Detection:**
```javascript
document.querySelectorAll('*').length > 5000
```

**Fix:**
1. Add timeout to snapshot operation
2. Truncate tree at 2000 nodes
3. Fall back to DOM-based snapshot if accessibility tree too large

### P5: SPA Hydration Delay

**Sites:** React, Vue, Angular SPAs after navigation

**Detection:**
```javascript
// Check if React app has hydrated
document.querySelector('[data-reactroot]') ||
document.querySelector('[data-reactid]')
```

**Fix:** Wait for specific selector after navigation:
```python
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)
```

### P6: Shadow DOM

**Sites:** Components using Shadow DOM, Lit elements

**Detection:**
```javascript
document.querySelectorAll('*').some(el => el.shadowRoot)
```

**Fix:** Pierce shadow root:
```javascript
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());
        }
    }
    re
hive.browser-automationSkill

Required before any hive-browser CLI command. The browser is driven from the terminal by running `hive-browser <command> ... --json` via terminal_exec — not via MCP tools. Teaches the browser lifecycle rules (the bridge attaches to the USER'S running Chrome — never kill or launch browser processes; timeouts are transport issues, not crashes), the screenshot + coordinate workflow (hive-browser interact with a fractional coordinate) that reaches shadow-DOM inputs selectors can't see, the viewport-fraction coordinate rule (not pixels), rich-text editor quirks ("send button stays disabled" failures), and CSP gotchas. Covers Chrome via CDP through the GCU Beeline extension.

hive.chart-creation-foundationsSkill

Required reading whenever any chart_* tool is available. Teaches the one-tool embedding contract (call chart_render → live chart appears in chat AND a downloadable PNG lands in the queen session dir), the ECharts (data viz) vs Mermaid (structural diagrams) decision, the BI/financial-grade aesthetic baseline (no chartjunk, restrained palette, proper typography, single message per chart), and the canonical spec patterns for the 12 most-common chart types. Skipping this leads to 1990s-Excel charts, missing downloads, and the agent writing markdown image links by hand instead of letting chart_render drive the UI.

hive.colony-progress-trackerSkill

Claim tasks, record step progress, and verify SOP gates in the colony SQLite queue. Applies when your spawn message includes a db_path field.

hive.context-preservationSkill

Proactively extract critical values from tool results into working notes before automatic context pruning destroys them.

hive.error-recoverySkill

Follow a structured recovery decision tree when tool calls fail instead of blindly retrying or giving up.

hive.image-generationSkill

Required before calling image_generate. Create and edit images from a prompt — generate an image, make a picture / logo / illustration / icon / banner / poster / thumbnail / hero image / mockup / product shot / social graphic, or edit / restyle / combine existing images from reference images. Uses OpenAI gpt-image-2 through the Hive image service, billed to the user's Hive credits like an LLM call (no API key needed). Teaches the exact call shape, the quality/cost tradeoff (quality="low" is the default and cheapest), reference-image editing, how to show the result to the user with attach_file, and the failure modes (out of credits, model unavailable, moderation).

hive.linkedin-automationSkill

Read before automating LinkedIn with browser_* tools. LinkedIn combines shadow DOM (#interop-outlet), strict Trusted Types CSP that silently drops innerHTML, Lexical composer, native beforeunload dialogs that hang the bridge, and aggressive spam filters — each has bitten us at least once. Verified flows for profile messaging, connection-request acceptance, feed composition, and search. Requires hive.browser-automation. Verified against logged-in production 2026-04-11.

hive.note-takingSkill

Maintain a free-form scratchpad of decisions, extracted values, and open questions so context pruning doesn't lose anything you still need.