# Browser Hybrid

**Chrome DevTools with accessibility-tree semantics for browser automation.**

Use your authenticated Chrome sessions with agent-browser-like element targeting.

[![PyPI version](https://badge.fury.io/py/browser-hybrid.svg)](https://badge.fury.io/py/browser-hybrid)
[![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

## Why?

| Tool | Your Sessions | Accessibility Tree | Performance |
|------|---------------|---------------------|-------------|
| agent-browser | ❌ Fresh browser | ✅ Built-in refs | Fast |
| Playwright | ❌ Fresh browser | ❌ CSS selectors | Medium |
| Chrome DevTools | ✅ Your Chrome | ❌ Raw DOM | Fast |
| **Browser Hybrid** | ✅ Your Chrome | ✅ Accessibility refs | Fast |

## Installation

```bash
pip install browser-hybrid
```

Or from source:

```bash
git clone https://github.com/your-repo/browser-hybrid
cd browser-hybrid
pip install -e .
```

## Automatic Reconnection

Browser Hybrid automatically reconnects when the Chrome WebSocket
connection drops (network issues, Chrome restart, etc.).

### Configuration

```python
# Default: auto-reconnect enabled
browser = Browser()

# Disable auto-reconnect
browser = Browser(reconnect=False)

# Custom retry settings
browser = Browser(
    reconnect_max_retries=5,
    reconnect_backoff=1.0  # seconds
)

# Monitor reconnection events
def on_reconnect(status):
    print(f"Reconnect: {status}")

browser = Browser(reconnect_callback=on_reconnect)
```

### Handling Errors

If reconnection fails after max retries, `ConnectionError` is raised.
Catch it to handle gracefully:

```python
from browser_hybrid import Browser, ConnectionError

try:
    browser.click("axnode@123")
except ConnectionError:
    print("Connection lost. Restart Chrome?")
```

## Prerequisites

Chrome must be running with remote debugging:

```bash
# macOS
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
  --remote-debugging-port=9222 \
  --user-data-dir="$HOME/Library/Application Support/Google/Chrome-Debug"

# Or use the provided script
~/scripts/chrome-debug.sh
```

## Async Considerations

While core interactions (`click`, `type_text`, `evaluate`) use asynchronous WebSockets for maximum performance, **Tab Management** (`new_tab`, `list_tabs`, `close_tab`) uses synchronous HTTP calls to the Chrome DevTools API.

In highly asynchronous environments (e.g., event-driven spiders), these HTTP calls will **briefly block the event loop**. For most use cases, this is negligible (~10-50ms), but for extreme performance, consider running `Browser` in a dedicated thread or process.

## Quick Start

### Python API

```python
from browser_hybrid import Browser

# Connect to your Chrome
browser = Browser()

# Tab management
tab = browser.new_tab("https://gmail.com")  # Your logged-in session!

# Navigation
browser.navigate("https://github.com")

# Accessibility tree (like agent-browser)
tree = browser.accessibility_tree()
print(tree.to_tree_str())
# - heading "Welcome" [ref=1]
# - link "Sign in" [ref=2]
# - textbox "Email" [ref=3]

# Click by accessibility ref
browser.click("2")

# Or click by text
browser.click_by_text("Sign in")

# Fill form
browser.fill_form({
    "Email": "user@example.com",
    "Password": "secret"
})

# Screenshot
browser.screenshot("/tmp/page.png")

# Execute JavaScript
title = browser.evaluate("document.title")
```

### CLI

```bash
# List tabs
browser-hybrid list

# Open new page
browser-hybrid new https://example.com

# Get accessibility snapshot (like agent-browser)
browser-hybrid snapshot
# - heading "Example Domain" [ref=1]
# - link "Learn more" [ref=2]

# Click by text
browser-hybrid click "Learn more"

# Take screenshot
browser-hybrid screenshot /tmp/page.png

# Execute JavaScript
browser-hybrid eval "document.title"

# Close tabs
browser-hybrid close
```

## Features

| Feature | Description |
|---------|-------------|
| **Your sessions** | Use Gmail, banking, SSO without re-auth |
| **Accessibility refs** | Target elements like `agent-browser` |
| **Click by text** | Find elements by visible text |
| **Form filling** | Fill multiple fields at once |
| **Visual Regression** | Screenshot comparison (pixel-by-pixel), tolerance, regions |
| **History recording** | Record and playback sessions to JSON format |
| **Tab isolation** | `temp_tab()` context manager for isolated workflows |
| **PDF Generation** | Generate PDF with headers, footers, & paper size control |
| **Zero dependencies** | Only Python stdlib + websockets |

## API Reference

### Tab Management

```python
tabs = browser.list_tabs()           # List all open tabs
tab = browser.new_tab(url)           # Open new tab
browser.activate_tab(tab.id)         # Focus tab
browser.close_tab(tab.id)            # Close tab
```

### Navigation

```python
browser.navigate(url)                # Navigate tab
browser.wait_for(text="Sign in")     # Wait for text
browser.wait_for(selector="button")  # Wait for element
```

### Accessibility Tree

```python
tree = browser.accessibility_tree()           # Get full tree
links = tree.find_by_role("link")             # Find by role
headings = tree.find_by_role("heading")        # Find headings
matching = tree.find_by_name("Submit")         # Find by name
node = tree.find("ref_id")                     # Find by ref

print(tree.to_tree_str())              # agent-browser format
```

### Interactions

```python
browser.click(ref)                    # Click by accessibility ref
browser.click_by_text("Sign in")       # Click by visible text
browser.type_text(ref, "hello")        # Type into input
browser.type_slowly(ref, "hello", delay=0.05)  # Character-by-character typing
browser.fill_form({"name": "John"})    # Fill form fields
browser.hover(ref)                     # Hover over element

# Click with modifiers
browser.click(ref, double=True)         # Double-click
browser.click(ref, button="right")      # Right-click
browser.press_key("Enter")              # Press key
browser.press_key("c", modifiers=["Control"])  # Ctrl+C
```

### Wait Conditions

```python
# Wait for element to appear
browser.wait_for(selector="button.submit")
browser.wait_for(text="Welcome")

# Wait for navigation
browser.wait_for(url="https://example.com/success")
browser.wait_for_load_state("complete")

# Custom condition
browser.wait_until(lambda: browser.evaluate("document.readyState") == "complete")
```

### Cookies

```python
# Get all cookies
cookies = browser.get_cookies()

# Set cookies
browser.set_cookies([{"name": "session", "value": "abc123", "domain": ".example.com"}])

# Delete cookies
browser.delete_cookies("session")
```

### Network Interception

```python
# Intercept requests
browser.on_request(lambda req: print(f"Request: {req['url']}"))
browser.on_response(lambda res: print(f"Response: {res['status']}"))

### Content

```python
html = browser.get_html()              # Get page HTML
title = browser.evaluate("document.title")  # Execute JS
browser.screenshot("/tmp/page.png")    # Take screenshot

### Visual Regression

```python
# Compare screenshots with tolerance
result = browser.compare_screenshots("baseline.png", "current.png", threshold=0.1)

# Or assert in tests
browser.assert_visual_match("baseline.png", threshold=0.01)
```
```

## Comparison

| Feature | Selenium | Playwright | agent-browser | Browser Hybrid |
|---------|----------|------------|---------------|----------------|
| Uses installed Chrome | ❌ | ❌ | ❌ | ✅ |
| Authenticated sessions | ❌ | ❌ | ❌ | ✅ |
| Accessibility tree | ❌ | ✅ | ✅ | ✅ |
| Ref-based targeting | ❌ | ❌ | ✅ | ✅ |
| Python API | ✅ | ✅ | ❌ | ✅ |
| CLI | ❌ | ✅ | ✅ | ✅ |
| Dependencies | Heavy | Heavy | Rust | Minimal |

## Architecture

```
┌──────────────────────────────────────────┐
│            Browser Hybrid                 │
├──────────────────────────────────────────┤
│   Python API    │    CLI                  │
│   browser.py    │    cli.py               │
├──────────────────────────────────────────┤
│              CDP Client                   │
│   ┌─────────────┐     ┌───────────────┐   │
│   │ HTTP API    │     │ WebSocket API │   │
│   │ /json/*     │     │ CDP Commands  │   │
│   └─────────────┘     └───────────────┘   │
└──────────────────────────────────────────┘
                    │
                    ▼
        ┌───────────────────┐
        │  Chrome DevTools  │
        │  localhost:9222    │
        │  (Your Chrome)     │
        └───────────────────┘
```

## Development

```bash
# Clone and install dev dependencies
git clone https://github.com/your-repo/browser-hybrid
cd browser-hybrid
pip install -e ".[dev]"

# Run tests
pytest

# Type check
mypy src/browser_hybrid

# Format
ruff format src/
```

## License

MIT