#!/usr/bin/env python """ Browser Edge Case Test Template This script provides a template for testing and debugging browser tool failures on specific websites. Use this to reproduce, isolate, and verify fixes. Usage: 1. Copy this file: cp test_case.py test_#[number]_[site].py 2. Fill in the CONFIG section with your test details 3. Run: uv run python test_#[number]_[site].py Example: uv run python test_01_linkedin_scroll.py """ import asyncio import sys import time from pathlib import Path # Add tools to path sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent / "tools" / "src")) from gcu.browser.bridge import BeelineBridge # ═══════════════════════════════════════════════════════════════════════════════ # CONFIG: Fill in these values for your test case # ═══════════════════════════════════════════════════════════════════════════════ TEST_CASE = { "number": 1, "name": "LinkedIn Nested Scroll Container", "site": "https://www.linkedin.com/feed", "simple_site": "https://example.com", "category": "scroll", # scroll, click, input, snapshot, navigation "symptom": "scroll() returns success but page doesn't move", } BRIDGE_PORT = 9229 CONTEXT_NAME = "edge-case-test" # ═══════════════════════════════════════════════════════════════════════════════ # TEST FUNCTIONS # ═══════════════════════════════════════════════════════════════════════════════ async def test_simple_site(bridge: BeelineBridge, tab_id: int) -> dict: """Test that the tool works on a simple site (baseline).""" print("\n--- Baseline Test (Simple Site) ---") await bridge.navigate(tab_id, TEST_CASE["simple_site"], wait_until="load") await asyncio.sleep(1) # Adjust this based on category if TEST_CASE["category"] == "scroll": result = await bridge.scroll(tab_id, "down", 100) print(f" Scroll result: {result}") return result elif TEST_CASE["category"] == "click": # Add click test pass elif TEST_CASE["category"] == "snapshot": result = await bridge.snapshot(tab_id, timeout_s=5.0) print(f" Snapshot length: {len(result.get('tree', ''))}") return result return {"ok": True} async def test_problematic_site(bridge: BeelineBridge, tab_id: int) -> dict: """Test the tool on the problematic site.""" print("\n--- Problem Site Test ---") await bridge.navigate(tab_id, TEST_CASE["site"], wait_until="load", timeout_ms=30000) await asyncio.sleep(2) # Adjust this based on category if TEST_CASE["category"] == "scroll": # Get scroll positions before before = await bridge.evaluate( tab_id, """ (function() { const results = { window: { y: window.scrollY } }; document.querySelectorAll('*').forEach((el, i) => { const style = getComputedStyle(el); if ((style.overflowY === 'scroll' || style.overflowY === 'auto') && el.scrollHeight > el.clientHeight) { results['el_' + i] = { tag: el.tagName, scrollTop: el.scrollTop, class: el.className.substring(0, 30) }; } }); return results; })(); """, ) print(f" Before scroll: {before.get('result', {})}") # Try to scroll result = await bridge.scroll(tab_id, "down", 500) print(f" Scroll result: {result}") await asyncio.sleep(1) # Get scroll positions after after = await bridge.evaluate( tab_id, """ (function() { const results = { window: { y: window.scrollY } }; document.querySelectorAll('*').forEach((el, i) => { const style = getComputedStyle(el); if ((style.overflowY === 'scroll' || style.overflowY === 'auto') && el.scrollHeight > el.clientHeight) { results['el_' + i] = { tag: el.tagName, scrollTop: el.scrollTop, class: el.className.substring(0, 30) }; } }); return results; })(); """, ) print(f" After scroll: {after.get('result', {})}") # Check if anything changed before_data = before.get("result", {}) or {} after_data = after.get("result", {}) or {} changed = False for key in after_data: if key in before_data: b_val = before_data[key].get("scrollTop", 0) if isinstance(before_data[key], dict) else 0 a_val = after_data[key].get("scrollTop", 0) if isinstance(after_data[key], dict) else 0 if a_val != b_val: print(f" ✓ CHANGE DETECTED: {key} scrolled from {b_val} to {a_val}") changed = True if not changed: print(" ✗ NO CHANGE: Scroll did not affect any container") return {"ok": changed, "scroll_result": result} elif TEST_CASE["category"] == "snapshot": start = time.perf_counter() try: result = await bridge.snapshot(tab_id, timeout_s=15.0) elapsed = time.perf_counter() - start tree_len = len(result.get("tree", "")) print(f" Snapshot completed in {elapsed:.2f}s, {tree_len} chars") return {"ok": True, "elapsed": elapsed, "tree_length": tree_len} except asyncio.TimeoutError: print(" ✗ SNAPSHOT TIMED OUT") return {"ok": False, "error": "timeout"} return {"ok": True} async def detect_root_cause(bridge: BeelineBridge, tab_id: int) -> dict: """Run detection scripts to identify the root cause.""" print("\n--- Root Cause Detection ---") detections = {} # Detection 1: Nested scrollable containers scroll_check = await bridge.evaluate( tab_id, """ (function() { 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({ tag: el.tagName, area: rect.width * rect.height, class: el.className.substring(0, 30) }); } } }); candidates.sort((a, b) => b.area - a.area); return { count: candidates.length, largest: candidates[0] }; })(); """, ) detections["nested_scroll"] = scroll_check.get("result", {}) print(f" Nested scroll containers: {detections['nested_scroll']}") # Detection 2: Shadow DOM shadow_check = await bridge.evaluate( tab_id, """ (function() { const withShadow = []; document.querySelectorAll('*').forEach(el => { if (el.shadowRoot) { withShadow.push(el.tagName); } }); return { count: withShadow.length, elements: withShadow.slice(0, 5) }; })(); """, ) detections["shadow_dom"] = shadow_check.get("result", {}) print(f" Shadow DOM: {detections['shadow_dom']}") # Detection 3: iframes iframe_check = await bridge.evaluate( tab_id, """ (function() { const iframes = document.querySelectorAll('iframe'); return { count: iframes.length }; })(); """, ) detections["iframes"] = iframe_check.get("result", {}) print(f" iframes: {detections['iframes']}") # Detection 4: DOM size dom_check = await bridge.evaluate( tab_id, """ (function() { return { elements: document.querySelectorAll('*').length, body_children: document.body.children.length }; })(); """, ) detections["dom_size"] = dom_check.get("result", {}) print(f" DOM size: {detections['dom_size']}") # Detection 5: Framework detection framework_check = await bridge.evaluate( tab_id, """ (function() { return { react: !!document.querySelector('[data-reactroot], [data-reactid]'), vue: !!document.querySelector('[data-v-]'), angular: !!document.querySelector('[ng-app], [ng-version]') }; })(); """, ) detections["frameworks"] = framework_check.get("result", {}) print(f" Frameworks: {detections['frameworks']}") return detections # ═══════════════════════════════════════════════════════════════════════════════ # MAIN # ═══════════════════════════════════════════════════════════════════════════════ async def main(): print("=" * 70) print(f"EDGE CASE TEST #{TEST_CASE['number']}: {TEST_CASE['name']}") print("=" * 70) print(f"Site: {TEST_CASE['site']}") print(f"Category: {TEST_CASE['category']}") print(f"Symptom: {TEST_CASE['symptom']}") bridge = BeelineBridge() try: print("\n--- Starting Bridge ---") await bridge.start() # Wait for extension connection for i in range(10): await asyncio.sleep(1) if bridge.is_connected: print("✓ Extension connected!") break print(f"Waiting for extension... ({i + 1}/10)") else: print("✗ Extension not connected. Ensure Chrome with Beeline extension is running.") return # Create browser context context = await bridge.create_context(CONTEXT_NAME) tab_id = context.get("tabId") group_id = context.get("groupId") print(f"✓ Created tab: {tab_id}") # Run tests baseline_result = await test_simple_site(bridge, tab_id) problem_result = await test_problematic_site(bridge, tab_id) detections = await detect_root_cause(bridge, tab_id) # Summary print("\n" + "=" * 70) print("SUMMARY") print("=" * 70) print(f"Baseline test: {'✓ PASS' if baseline_result.get('ok') else '✗ FAIL'}") print(f"Problem test: {'✓ PASS' if problem_result.get('ok') else '✗ FAIL'}") print(f"Root cause indicators: {list(k for k, v in detections.items() if v)}") # Cleanup print("\n--- Cleanup ---") await bridge.destroy_context(group_id) print("✓ Context destroyed") finally: await bridge.stop() print("✓ Bridge stopped") if __name__ == "__main__": asyncio.run(main())