Back to Articles

Browser Automation in Go: Learning chromedp Through 30+ Real-World Examples

[ View on GitHub ]

Browser Automation in Go: Learning chromedp Through 30+ Real-World Examples

Hook

While most browser automation examples showcase trivial Hello World scenarios, chromedp/examples tackles real-world problems like OAuth flows, file downloads, and remote debugging—and shows you exactly why the Chrome DevTools Protocol is faster than Selenium could ever be.

Context

Browser automation in Go has historically meant wrestling with Selenium WebDriver, accepting the overhead of a separate driver binary, dealing with cross-language serialization, and suffering through slower execution times. When Chrome shipped the DevTools Protocol, it opened a new path: direct communication with the browser through a native JSON-based protocol over WebSockets. The chromedp library emerged to wrap this protocol in an idiomatic Go API, but like many low-level tools, the learning curve was steep.

The chromedp/examples repository exists as the de facto field guide for this ecosystem. Rather than burying examples in documentation or scattering them across blog posts, the maintainers created a standalone collection of working code. Each example is a complete, runnable program demonstrating a specific automation pattern—from basic screenshots to complex scenarios like proxy configuration, device emulation, and listening to network events. For developers migrating from Selenium or building browser automation for the first time in Go, this repository serves as both tutorial and reference implementation.

Technical Insight

The architecture of chromedp/examples is deliberately simple: each example lives in its own directory with a self-contained main.go file. This design choice means you can copy a single file to your project and understand exactly what's happening without navigating complex abstractions. Let's examine how the repository teaches chromedp's core concepts.

The most fundamental pattern appears in the screenshot example, which reveals chromedp's context-based API design:

package main

import (
    "context"
    "log"
    "os"
    "github.com/chromedp/chromedp"
)

func main() {
    ctx, cancel := chromedp.NewContext(context.Background())
    defer cancel()

    var buf []byte
    if err := chromedp.Run(ctx,
        chromedp.Navigate("https://github.com/chromedp"),
        chromedp.FullScreenshot(&buf, 90),
    ); err != nil {
        log.Fatal(err)
    }

    if err := os.WriteFile("screenshot.png", buf, 0644); err != nil {
        log.Fatal(err)
    }
}

This pattern—creating a context, passing a sequence of chromedp actions to Run(), and handling the result—becomes the template for every automation task. The context carries the browser state, and chromedp.Run() executes actions serially. The brilliance here is that actions compose: you can mix navigation, DOM queries, JavaScript evaluation, and data extraction in a single Run() call, and chromedp handles the synchronization.

The repository shines when demonstrating more complex patterns. The "submit" example shows how to interact with forms using CSS selectors and wait for navigation:

chromedp.Run(ctx,
    chromedp.Navigate("https://github.com/login"),
    chromedp.WaitVisible(`#login_field`, chromedp.ByID),
    chromedp.SendKeys(`#login_field`, "username", chromedp.ByID),
    chromedp.SendKeys(`#password`, "password", chromedp.ByID),
    chromedp.Click(`input[type="submit"]`, chromedp.ByQuery),
    chromedp.WaitVisible(`body`, chromedp.ByQuery),
)

The WaitVisible() action demonstrates chromedp's approach to timing issues—instead of arbitrary sleep() calls, you wait for specific DOM conditions. This is crucial for reliable automation, as it handles varying page load times gracefully.

One of the most instructive examples covers remote browser connections, showing how to connect to an existing Chrome instance rather than spawning a new one. This pattern is essential for debugging and for environments where you need persistent browser state:

devtoolsURL := "ws://127.0.0.1:9222/devtools/browser/..."
allocCtx, cancel := chromedp.NewRemoteAllocator(context.Background(), devtoolsURL)
defer cancel()

ctx, cancel := chromedp.NewContext(allocCtx)
defer cancel()

chromedp.Run(ctx,
    chromedp.Navigate("https://example.com"),
    // your automation tasks
)

This reveals chromedp's allocator abstraction: NewContext() accepts an allocator that determines how Chrome instances are managed. The default allocator spawns headless Chrome, but NewRemoteAllocator() connects to an existing instance. This separation of concerns makes chromedp flexible for different deployment scenarios—local development, containerized environments, or remote browser farms.

The download example exposes a particularly tricky aspect of browser automation: programmatic downloads require listening to browser events, not just DOM manipulation. You must enable the download behavior and listen for the completion event through the DevTools Protocol directly:

chromedp.Run(ctx,
    page.SetDownloadBehavior(page.SetDownloadBehaviorBehaviorAllow).WithDownloadPath(downloadPath),
    chromedp.Navigate(downloadURL),
)

This example demonstrates that while chromedp provides a high-level API, it doesn't hide the underlying protocol. When you need protocol-level control, you can import the generated Chrome DevTools Protocol packages (like github.com/chromedp/cdproto/page) and use them alongside high-level actions. This escape hatch is what makes chromedp powerful for complex automation scenarios that would be impossible with Selenium's abstraction layer.

The repository also includes examples of device emulation, which showcases chromedp's ability to control browser features that don't exist in traditional automation frameworks. You can emulate mobile devices, set custom user agents, override geolocation, and control permissions—all through the same action-based API. This level of control stems directly from Chrome's DevTools Protocol, which exposes every browser capability to programmatic control.

Gotcha

The most significant limitation isn't technical—it's maintenance. Because examples demonstrate automation against real websites (GitHub, Google, etc.), they break when those sites change their HTML structure. A CSS selector that worked yesterday might fail today after a website redesign. The repository explicitly acknowledges this in its README: examples are maintained on a best-effort basis and may not always work. For learning purposes this is acceptable, but it means you can't copy-paste an example and assume it'll work without verification.

The dependency on external websites also creates a testing problem. There's no automated validation to detect when examples stop working, so breakage often goes unnoticed until someone files an issue. This is particularly frustrating for examples demonstrating authentication flows or form submissions, where even minor HTML changes break the automation. If you're using these examples as a learning resource, you'll occasionally need to inspect the target website yourself and adjust selectors.

Another limitation is the lack of error handling patterns. Most examples use simple log.Fatal() on errors, which is fine for demonstration but inadequate for production code. The repository doesn't show patterns for retrying flaky operations, handling timeouts gracefully, or distinguishing between different error types (network failures vs. element not found vs. JavaScript errors). You'll need to build this error handling infrastructure yourself, and the gap between example code and production-ready code can be substantial. The examples also don't demonstrate testing strategies—how to mock browser interactions, how to structure code for testability, or how to run automation in CI/CD pipelines reliably.

Verdict

Use if: You're learning chromedp and need practical examples beyond basic documentation, you're implementing a specific feature like PDF generation or screenshot capture and want a working reference, you're evaluating whether chromedp can handle your automation use case before committing to it, or you need to understand how Chrome DevTools Protocol features map to Go code. This repository is invaluable for the first few weeks of working with chromedp.

Skip if: You need production-ready, maintained automation code—these are educational examples, not a library to import. Skip if you expect examples to work perfectly without modification, as website changes will break selectors. Also skip if you need comprehensive error handling and retry logic; you'll need to build that yourself. Finally, if you need extensive documentation explaining why certain approaches are chosen over alternatives, these examples are minimally commented and assume you'll read the chromedp documentation alongside them. Think of this repository as a cookbook, not a complete guide to browser automation architecture.

// ADD TO YOUR README
[![Featured on Starlog](https://starlog.is/api/badge/developer-tools/chromedp-examples.svg)](https://starlog.is/api/badge-click/developer-tools/chromedp-examples)