Browser Automation in Go: A Deep Dive into chromedp/examples
Hook
While most developers reach for Puppeteer or Selenium, Go’s chromedp library can automate Chrome in a single statically-compiled binary with no Node.js runtime or Java dependencies—and this examples repository is your blueprint.
Context
Browser automation has traditionally been the domain of JavaScript (via Puppeteer) or multi-language frameworks like Selenium. But if you’re building backend services in Go, spinning up a Node.js runtime just to take a screenshot or generate a PDF feels like architectural baggage. You want the simplicity of a single binary deployment, the performance characteristics of Go’s concurrency model, and the type safety that prevents runtime surprises in production.
The chromedp library emerged to fill this gap, providing a native Go interface to the Chrome DevTools Protocol. But like many powerful libraries, the learning curve is steep—the action-based API is elegant but unfamiliar, and the official documentation, while comprehensive, doesn’t always show you how to solve real-world problems. That’s where chromedp/examples comes in. It’s not a framework or a library itself, but rather a curated collection of 20+ standalone programs that demonstrate exactly how to accomplish specific automation tasks, from the mundane (taking screenshots) to the sophisticated (handling proxy authentication with custom headers).
Technical Insight
The chromedp/examples repository is organized as a collection of independent Go programs, each residing in its own directory with a focused purpose. This architecture choice is deliberate—rather than creating a monolithic example application, each program can be copied directly into your project as a starting template. Let’s examine how the examples reveal chromedp’s design philosophy.
At its core, chromedp uses a context-based approach where browser actions are composed as a chain of operations. Here’s a simplified version of the screenshot example that captures this pattern:
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"),
chromedp.WaitVisible("#readme", chromedp.ByID),
chromedp.Screenshot("#readme", &buf, chromedp.NodeVisible, chromedp.ByID),
); err != nil {
log.Fatal(err)
}
if err := os.WriteFile("screenshot.png", buf, 0644); err != nil {
log.Fatal(err)
}
}
This example reveals several architectural decisions. First, chromedp uses Go’s context package not just for cancellation, but as a handle to the browser instance itself. The chromedp.Run() function accepts a variadic list of Actions—this is how you compose complex browser interactions from simple primitives. Notice how actions like Navigate, WaitVisible, and Screenshot are chained together in a declarative style.
The pdf example demonstrates a more advanced pattern—listening to browser events. Generating a PDF requires configuring print parameters and handling the download event:
var buf []byte
if err := chromedp.Run(ctx,
chromedp.Navigate("https://www.chromestatus.com"),
chromedp.ActionFunc(func(ctx context.Context) error {
buf, _, err = page.PrintToPDF().WithPrintBackground(false).Do(ctx)
return err
}),
); err != nil {
log.Fatal(err)
}
Here we see the escape hatch: when chromedp’s high-level actions aren’t sufficient, you can drop down to the underlying CDP domains (like page.PrintToPDF()) using ActionFunc. This layered API design—convenience functions for common tasks, raw protocol access for edge cases—is what makes chromedp both approachable and powerful.
The emulate example showcases device emulation, critical for testing responsive designs:
chromedp.Emulate(device.IPhone11Pro),
chromedp.Navigate("https://github.com/chromedp/chromedp"),
chromedp.WaitVisible("#readme"),
With a single action, you can simulate any device profile—viewport size, user agent, touch support, and pixel density. The examples repository includes demonstrations for both predefined devices and custom configurations.
Perhaps most valuable are the examples showing how to handle authentication and sessions. The cookies example demonstrates persisting login state across browser restarts, while the proxy-auth example shows how to inject authentication headers for proxy servers—a common requirement in corporate environments that’s surprisingly tricky to get right.
The remote example is particularly instructive for production deployments. It shows how to connect to a Chrome instance running in a separate container, enabling a microservices architecture where your automation service doesn’t need to bundle Chrome itself. This separation concerns pattern—one container for Chrome, another for your Go application—is crucial for scaling browser automation workloads.
Gotcha
The biggest limitation isn’t in the examples themselves, but in their dependency on external websites. Several examples target live sites like GitHub, Google, or specific demo pages. When these sites redesign their HTML structure, change their CSS selectors, or implement new anti-bot measures, the examples break. There’s no automated testing that validates whether these examples still work, so you might clone the repository only to find that half the examples fail on first run. This is inherent to the nature of browser automation examples, but it means you can’t treat this repository as a reliable reference without verifying each example still functions.
The examples also prioritize clarity over production readiness. Error handling is minimal—most examples simply log.Fatal() on any error rather than demonstrating retry logic, graceful degradation, or proper cleanup. Timeouts are often hardcoded or omitted entirely, which works fine for demonstrations but will cause problems in production when network latency varies or pages load slowly. The selector strategies shown are typically simple CSS or XPath queries without fallbacks, whereas production automation needs defensive selectors that can handle DOM variations. You’ll need to significantly harden any example before deploying it in a production service that needs reliability.
Another subtlety: the examples assume Chrome or Chromium is installed on your system and available in your PATH. Several examples specifically demonstrate using headless-shell (a minimal Chrome build for automation), but setting up these environments, especially in containers, requires knowledge not covered in the examples themselves. The Docker and Podman examples help, but they’re minimal—you won’t find guidance on resource limits, security sandboxing, or managing Chrome’s memory consumption at scale.
Verdict
Use if: You’re building backend automation in Go and need practical, copy-paste starting points for specific tasks like screenshots, PDF generation, or form automation. These examples cut through chromedp’s learning curve dramatically—instead of deciphering API documentation, you can see working code for your exact use case. Also use this if you’re migrating from Puppeteer or Selenium to chromedp and need to understand the idiomatic patterns for common operations. Skip if: You need production-ready automation code with comprehensive error handling and resilience patterns (you’ll need to build that layer yourself), you’re working in a language other than Go (look at Puppeteer for Node.js or Playwright for multi-language support), or you need examples that are guaranteed to work without modification (the reliance on live external sites means examples frequently need selector updates). Also skip if you’re looking for advanced scenarios like parallel browser instances, resource interception, or WebSocket debugging—the examples focus on common use cases rather than edge cases.