# Rod - Go Browser Automation Library Rod is a high-level Go driver for browser automation directly based on the Chrome DevTools Protocol. It provides intuitive APIs for web scraping, automated testing, and browser control with features like chained context design for timeouts, auto-wait for elements, thread-safe operations, and automatic browser download. Rod handles complex scenarios including nested iframes, shadow DOMs, and request hijacking while ensuring no zombie browser processes after crashes. The library is designed for both high-level and low-level use cases. Senior developers can customize or build their own version using the low-level packages, while the high-level functions serve as default implementations. Key features include debugging helpers (tracing, slow motion, remote monitoring), two-step WaitEvent design to never miss events, and CI-enforced 100% test coverage. ## Browser - Create and Connect to Browser The Browser type represents the browser instance and provides methods to create pages, manage cookies, handle downloads, and control the browser lifecycle. It automatically launches or downloads a browser binary if needed. ```go package main import ( "fmt" "github.com/go-rod/rod" "github.com/go-rod/rod/lib/launcher" "time" ) func main() { // Basic browser creation - auto downloads browser if needed browser := rod.New().MustConnect() defer browser.MustClose() // Custom browser launch with options url := launcher.New(). Headless(false). // Show browser window Devtools(true). // Auto-open DevTools Proxy("127.0.0.1:8080"). // Set proxy MustLaunch() browser = rod.New(). ControlURL(url). Trace(true). // Enable visual tracing SlowMotion(2 * time.Second). // Slow down for debugging MustConnect() defer browser.MustClose() // Create incognito browser context incognito, _ := browser.Incognito() defer incognito.MustClose() // Get browser version version, _ := browser.Version() fmt.Printf("Browser: %s %s\n", version.Product, version.Revision) } ``` ## Page - Navigate and Control Pages The Page type represents a browser tab and provides methods for navigation, screenshots, PDF generation, and DOM interaction. Pages support context-based timeouts and cancellation. ```go package main import ( "fmt" "github.com/go-rod/rod" "github.com/go-rod/rod/lib/proto" "github.com/go-rod/rod/lib/utils" "github.com/ysmood/gson" "time" ) func main() { browser := rod.New().MustConnect() defer browser.MustClose() // Create page and navigate page := browser.MustPage("https://github.com") // Wait for page to be stable (load + idle requests + DOM stable) page.MustWaitStable() // Get page info info, _ := page.Info() fmt.Printf("URL: %s, Title: %s\n", info.URL, info.Title) // Get page HTML html, _ := page.HTML() fmt.Println("HTML length:", len(html)) // Navigate with timeout page.Timeout(5 * time.Second).MustNavigate("https://example.com").MustWaitLoad() // Take screenshot page.MustScreenshot("page.png") // Full page screenshot with scroll img, _ := page.ScrollScreenshot(&rod.ScrollScreenshotOptions{ Format: proto.PageCaptureScreenshotFormatJpeg, Quality: gson.Int(90), }) _ = utils.OutputFile("fullpage.jpg", img) // Generate PDF page.MustPDF("page.pdf") // Set cookies page.MustSetCookies(&proto.NetworkCookieParam{ Name: "session", Value: "abc123", Domain: "example.com", }) // Get cookies cookies, _ := page.Cookies(nil) for _, c := range cookies { fmt.Printf("Cookie: %s=%s\n", c.Name, c.Value) } } ``` ## Element - Query and Interact with DOM Elements The Element type provides methods to find, interact with, and extract data from DOM elements. It supports CSS selectors, XPath, and regex matching with automatic retry and wait mechanisms. ```go package main import ( "fmt" "github.com/go-rod/rod" "github.com/go-rod/rod/lib/input" ) func main() { browser := rod.New().MustConnect() defer browser.MustClose() page := browser.MustPage("https://github.com").MustWaitStable() // Find element by CSS selector searchBtn := page.MustElement("button[data-target='qbsearch-input.inputButton']") searchBtn.MustClick() // Find element by CSS selector and text regex link := page.MustElementR("a", "Explore") fmt.Println("Link text:", link.MustText()) // Find element by XPath heading := page.MustElementX("//h1") fmt.Println("Heading:", heading.MustText()) // Get multiple elements links := page.MustElements("a") fmt.Println("Found", len(links), "links") // Input text input := page.MustElement("input[type='text']") input.MustInput("search query") input.MustType(input.Enter) // Get element attributes and properties href, _ := link.Attribute("href") fmt.Println("href:", *href) className, _ := link.Property("className") fmt.Println("className:", className.String()) // Wait for element visibility page.MustElement(".results").MustWaitVisible() // Wait for element to be stable (no position/size changes) page.MustElement(".modal").MustWaitStable() // Check if element exists has, el, _ := page.Has(".optional-element") if has { fmt.Println("Found:", el.MustText()) } // Element screenshot screenshot, _ := page.MustElement("header").Screenshot(proto.PageCaptureScreenshotFormatPng, 0) _ = utils.OutputFile("header.png", screenshot) } ``` ## Input - Keyboard and Mouse Interaction Rod provides detailed control over keyboard and mouse inputs for simulating user interactions including clicks, typing, drag operations, and touch events. ```go package main import ( "github.com/go-rod/rod" "github.com/go-rod/rod/lib/input" "github.com/go-rod/rod/lib/proto" ) func main() { browser := rod.New().MustConnect() defer browser.MustClose() page := browser.MustPage("https://example.com").MustWaitLoad() // Keyboard typing page.MustElement("input").MustClick() page.Keyboard.MustType(input.Shift, 'H') // Shift+H = uppercase H page.Keyboard.MustType('e', 'l', 'l', 'o') page.Keyboard.MustType(input.Enter) // Key actions with modifiers page.KeyActions(). Press(input.ControlLeft). Type('a'). // Ctrl+A (select all) Release(input.ControlLeft). MustDo() // Mouse operations page.Mouse.MustMoveTo(100, 200) page.Mouse.MustClick(proto.InputMouseButtonLeft) page.Mouse.MustClick(proto.InputMouseButtonRight) // Right click // Double click el := page.MustElement("button") el.MustClick() // Single click el.Click(proto.InputMouseButtonLeft, 2) // Double click // Drag and drop page.Mouse.MustMoveTo(100, 100) page.Mouse.MustDown(proto.InputMouseButtonLeft) page.Mouse.MustMoveTo(200, 200) page.Mouse.MustUp(proto.InputMouseButtonLeft) // Scroll page.Mouse.MustScroll(0, 500) // Scroll down 500px // Touch events (for mobile emulation) page.Touch.MustTap(100, 200) } ``` ## Hijack Requests - Intercept and Modify Network Traffic The HijackRequests feature allows intercepting HTTP requests and responses to modify headers, body content, or block requests entirely. ```go package main import ( "fmt" "net/http" "github.com/go-rod/rod" "github.com/go-rod/rod/lib/proto" ) func main() { browser := rod.New().MustConnect() defer browser.MustClose() // Create hijack router router := browser.HijackRequests() defer router.MustStop() // Intercept all JavaScript files router.MustAdd("*.js", func(ctx *rod.Hijack) { // Modify request headers ctx.Request.Req().Header.Set("X-Custom-Header", "value") // Load original response _ = ctx.LoadResponse(http.DefaultClient, true) // Modify response body ctx.Response.SetBody(ctx.Response.Body() + "\n// Injected code") }) // Block image requests router.MustAdd("*.png", func(ctx *rod.Hijack) { ctx.Response.Fail(proto.NetworkErrorReasonBlockedByClient) }) // Mock API response router.MustAdd("*/api/users*", func(ctx *rod.Hijack) { ctx.Response.SetBody(`{"users": [{"id": 1, "name": "Mock User"}]}`) ctx.Response.SetHeader("Content-Type", "application/json") }) // Log all requests router.MustAdd("*", func(ctx *rod.Hijack) { fmt.Printf("Request: %s %s\n", ctx.Request.Method(), ctx.Request.URL()) ctx.ContinueRequest(&proto.FetchContinueRequest{}) }) go router.Run() page := browser.MustPage("https://example.com") page.MustWaitLoad() } ``` ## Events - Listen to Browser and Page Events Rod provides a powerful event system to listen for browser events like console messages, network activity, page lifecycle, and dialogs. ```go package main import ( "context" "fmt" "github.com/go-rod/rod" "github.com/go-rod/rod/lib/proto" "time" ) func main() { browser := rod.New().MustConnect() defer browser.MustClose() page := browser.MustPage() // Listen for console messages go page.EachEvent(func(e *proto.RuntimeConsoleAPICalled) { if e.Type == proto.RuntimeConsoleAPICalledTypeLog { fmt.Println("Console:", page.MustObjectsToJSON(e.Args)) } })() // Wait for specific event wait := page.WaitEvent(&proto.PageLoadEventFired{}) page.MustNavigate("https://example.com") wait() fmt.Println("Page loaded") // Listen for navigation events wait = page.WaitNavigation(proto.PageLifecycleEventNameNetworkAlmostIdle) page.MustElement("a").MustClick() wait() fmt.Println("Navigation complete") // Handle dialogs (alert, confirm, prompt) wait, handle := page.MustHandleDialog() go page.MustEval(`() => alert("Hello!")`) dialog := wait() fmt.Printf("Dialog type: %s, message: %s\n", dialog.Type, dialog.Message) handle(true, "") // Accept the dialog // Wait for request idle waitIdle := page.MustWaitRequestIdle() page.MustElement("button").MustClick() waitIdle() fmt.Println("No pending requests") // Context with cancellation page, cancel := page.WithCancel() go func() { time.Sleep(5 * time.Second) cancel() }() page.EachEvent(func(_ *proto.PageLifecycleEvent) {})() // Will stop after 5 seconds } ``` ## Wait Functions - Synchronization and Timing Rod provides various wait functions to synchronize with page state, including waiting for elements, network idle, DOM stability, and custom JavaScript conditions. ```go package main import ( "github.com/go-rod/rod" "time" ) func main() { browser := rod.New().MustConnect() defer browser.MustClose() page := browser.MustPage("https://example.com") // Wait for page load event page.MustWaitLoad() // Wait for DOM to be stable (no changes for duration) page.MustWaitDOMStable(300 * time.Millisecond, 0) // Wait for network requests to be idle page.MustWaitRequestIdle() // Combined wait: load + request idle + DOM stable page.MustWaitStable() // Wait for specific element el := page.MustElement(".dynamic-content") // Wait for element to be visible el.MustWaitVisible() // Wait for element to be invisible page.MustElement(".loading").MustWaitInvisible() // Wait for element to be interactable (visible, not covered) el.MustWaitInteractable() // Wait for element position to be stable el.MustWaitStable() // Wait for element to be enabled page.MustElement("button").MustWaitEnabled() // Wait with custom JavaScript condition page.MustWait(`() => document.querySelector('.loaded') !== null`) // Wait for more than N elements page.MustWaitElementsMoreThan(".item", 5) // Wait for next repaint page.MustWaitRepaint() // Wait for window idle callback page.MustWaitIdle(time.Second) } ``` ## Launcher - Browser Launch Configuration The Launcher package provides fine-grained control over browser launch options including headless mode, proxy settings, user data directories, and Chrome extensions. ```go package main import ( "fmt" "os" "path/filepath" "github.com/go-rod/rod" "github.com/go-rod/rod/lib/launcher" ) func main() { // Basic launcher with default settings (headless) url := launcher.New().MustLaunch() browser := rod.New().ControlURL(url).MustConnect() browser.MustClose() // Launch with custom options url = launcher.New(). Headless(false). // Show browser Devtools(true). // Auto-open DevTools UserDataDir("/tmp/my-browser-data"). // Persist data Proxy("socks5://127.0.0.1:1080"). // Use proxy NoSandbox(true). // For Docker/root WindowSize(1920, 1080). // Set window size Env(append(os.Environ(), "TZ=UTC")...). // Set timezone MustLaunch() browser = rod.New().ControlURL(url).MustConnect() defer browser.MustClose() // User mode - reuse existing Chrome profile url = launcher.NewUserMode(). RemoteDebuggingPort(9222). MustLaunch() // App mode - run as native app url = launcher.NewAppMode("https://example.com"). Headless(false). MustLaunch() // Load Chrome extension extPath, _ := filepath.Abs("./my-extension") url = launcher.New(). Set("load-extension", extPath). Headless(false). MustLaunch() // Get browser binary path binPath, _ := launcher.LookPath() fmt.Println("Browser path:", binPath) } ``` ## Page Pool and Browser Pool - Concurrency Management Rod provides pool utilities for managing multiple pages and browsers concurrently, enabling efficient parallel scraping and testing. ```go package main import ( "fmt" "sync" "github.com/go-rod/rod" ) func main() { browser := rod.New().MustConnect() defer browser.MustClose() // Create page pool with max 3 concurrent pages pagePool := rod.NewPagePool(3) // Factory function to create new pages createPage := func() *rod.Page { return browser.MustIncognito().MustPage() } // URLs to scrape urls := []string{ "https://example.com/page1", "https://example.com/page2", "https://example.com/page3", "https://example.com/page4", "https://example.com/page5", } var wg sync.WaitGroup results := make(chan string, len(urls)) for _, url := range urls { wg.Add(1) go func(u string) { defer wg.Done() // Get page from pool (blocks if pool is full) page := pagePool.MustGet(createPage) defer pagePool.Put(page) page.MustNavigate(u).MustWaitLoad() title := page.MustElement("title").MustText() results <- fmt.Sprintf("%s: %s", u, title) }(url) } wg.Wait() close(results) for result := range results { fmt.Println(result) } // Cleanup pool pagePool.Cleanup(func(p *rod.Page) { p.MustClose() }) // Browser pool for heavy parallel workloads browserPool := rod.NewBrowserPool(3) createBrowser := func() *rod.Browser { return rod.New().MustConnect() } for i := 0; i < 5; i++ { wg.Add(1) go func(id int) { defer wg.Done() b := browserPool.MustGet(createBrowser) defer browserPool.Put(b) p := b.MustPage("https://example.com") fmt.Printf("Worker %d: %s\n", id, p.MustInfo().Title) }(i) } wg.Wait() browserPool.Cleanup(func(b *rod.Browser) { b.MustClose() }) } ``` ## Race Selectors - Handle Multiple Possible Outcomes The Race feature allows waiting for multiple possible elements or conditions, useful for handling dynamic UIs where different outcomes are possible (e.g., login success vs. error message). ```go package main import ( "fmt" "github.com/go-rod/rod" ) func main() { browser := rod.New().MustConnect() defer browser.MustClose() page := browser.MustPage("https://example.com/login") // Submit login form page.MustElement("#username").MustInput("user") page.MustElement("#password").MustInput("pass") page.MustElement("button[type=submit]").MustClick() // Race: wait for either success or error el := page.Race(). Element(".welcome-message").MustHandle(func(e *rod.Element) { fmt.Println("Login successful:", e.MustText()) }). Element(".error-message").MustHandle(func(e *rod.Element) { fmt.Println("Login failed:", e.MustText()) }). Element(".captcha-required").MustHandle(func(e *rod.Element) { fmt.Println("Captcha required") }). MustDo() // Check which element was found if matched, _ := el.Matches(".welcome-message"); matched { // Continue with authenticated actions page.MustNavigate("/dashboard") } // Race with XPath and regex selectors page.Race(). ElementX("//div[@class='success']"). ElementR("span", "Error:.*"). MustDo() } ``` ## Search - Find Elements in Nested Iframes and Shadow DOM The Search feature provides deep element searching across nested iframes and shadow DOMs, similar to Chrome DevTools' search functionality. ```go package main import ( "fmt" "github.com/go-rod/rod" ) func main() { browser := rod.New().MustConnect() defer browser.MustClose() page := browser.MustPage("https://example.com") // Search finds elements even in iframes and shadow DOM result := page.MustSearch(".deep-nested-element") fmt.Println("Found:", result.First.MustText()) // Get all matching elements allMatches, _ := result.All() fmt.Println("Total matches:", len(allMatches)) // Get elements by range subset, _ := result.Get(0, 5) // First 5 elements for _, el := range subset { fmt.Println(el.MustText()) } // Release search results when done result.Release() // Search can use plain text, CSS selectors, or XPath page.MustSearch("Submit Button") // Plain text page.MustSearch("button.primary") // CSS page.MustSearch("//button[@type='submit']") // XPath } ``` ## Error Handling - Graceful Error Management Rod provides both panic-style (Must*) and error-returning methods. The Try function catches panics from Must* methods for cleaner error handling. ```go package main import ( "context" "errors" "fmt" "time" "github.com/go-rod/rod" ) func main() { browser := rod.New().MustConnect() defer browser.MustClose() page := browser.MustPage("https://example.com") // Method 1: Traditional error handling el, err := page.Element(".may-not-exist") if err != nil { var notFound *rod.ElementNotFoundError if errors.As(err, ¬Found) { fmt.Println("Element not found") } else { fmt.Println("Other error:", err) } } // Method 2: Using Try to catch Must* panics err = rod.Try(func() { page.MustElement(".may-not-exist").MustClick() }) if err != nil { fmt.Println("Caught error:", err) } // Timeout errors ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() _, err = page.Context(ctx).Element(".slow-loading") if errors.Is(err, context.DeadlineExceeded) { fmt.Println("Timed out waiting for element") } // Eval errors _, err = page.Eval(`() => throw new Error("JS Error")`) var evalErr *rod.EvalError if errors.As(err, &evalErr) { fmt.Printf("JS Error at line %d: %s\n", evalErr.LineNumber, evalErr.Exception.Text) } // Using timeout method err = rod.Try(func() { page.Timeout(time.Second).MustElement(".never-exists") }) if errors.Is(err, context.DeadlineExceeded) { fmt.Println("Element search timed out") } } ``` ## Evaluate JavaScript - Execute JS in Page Context Rod allows executing arbitrary JavaScript code in the page context, passing parameters, and retrieving results. ```go package main import ( "fmt" "github.com/go-rod/rod" ) func main() { browser := rod.New().MustConnect() defer browser.MustClose() page := browser.MustPage("https://example.com") // Simple evaluation result := page.MustEval(`() => document.title`) fmt.Println("Title:", result.String()) // With parameters sum := page.MustEval(`(a, b) => a + b`, 10, 20) fmt.Println("Sum:", sum.Int()) // 30 // Return complex objects data := page.MustEval(`() => ({ url: location.href, cookies: document.cookie, dimensions: { width: window.innerWidth, height: window.innerHeight } })`) fmt.Println("URL:", data.Get("url").String()) fmt.Println("Width:", data.Get("dimensions.width").Int()) // Evaluate on element el := page.MustElement("h1") text := el.MustEval(`() => this.innerText`) fmt.Println("H1 text:", text.String()) // Async evaluation (with promise) asyncResult := page.MustEval(`() => new Promise(resolve => { setTimeout(() => resolve("delayed result"), 1000) })`) fmt.Println("Async:", asyncResult.String()) // Inject and call functions page.MustEval(`() => { window.myHelper = (x) => x * 2 }`) doubled := page.MustEval(`(n) => window.myHelper(n)`, 21) fmt.Println("Doubled:", doubled.Int()) // 42 // Add script to evaluate on new documents remove, _ := page.EvalOnNewDocument(` Object.defineProperty(navigator, 'webdriver', { get: () => false }) `) defer remove() } ``` ## Summary Rod is a comprehensive browser automation library for Go that excels in web scraping, automated testing, and browser control scenarios. Its primary use cases include extracting data from dynamic websites, automating form submissions and user flows, generating screenshots and PDFs, testing web applications, and intercepting/modifying network requests. The library handles complex scenarios like lazy-loaded content, infinite scrolling, single-page applications, and sites with heavy JavaScript rendering. Integration patterns typically involve creating a Browser instance, navigating to pages, using CSS/XPath selectors to find elements, and performing actions like clicking, typing, or extracting text. For production use, the error-returning methods (without Must* prefix) are recommended along with proper timeout handling using context. The pool utilities enable efficient concurrent scraping, while the hijack feature allows building proxies and mocking API responses. Rod integrates well with testing frameworks and can run in Docker containers with headless mode and appropriate sandbox settings.