Browser Automation in Go: Selenium, chromedp, Playwright, ZenRows
Selenium, chromedp, Playwright, ZenRows - in Go.
Choosing the right browser automation stack and webscraping in Go affects speed, maintenance, and where your code runs.
This overview compares chromedp, Playwright for Go, Selenium (Go client), and ZenRows from Go-with code examples for each-so you can pick the best fit for scraping, E2E tests, or scheduled automation.

TL;DR - Quick comparison
| Tool | Browser scope | Runtime | Best for |
|---|---|---|---|
| chromedp | Chrome/Chromium | Pure Go, no server | Scraping, lightweight automation |
| Playwright Go | Chromium, Firefox, WebKit | Playwright binaries | E2E tests, cross-browser, scraping |
| Selenium (Go) | Any WebDriver | Driver or grid | Legacy suites, broad ecosystem |
| ZenRows | Cloud (API/Browser) | HTTP from Go | Scraping with proxies/anti-bot |
When to use which
chromedp - Idiomatic Go library that drives Chrome/Chromium via the Chrome DevTools Protocol (CDP). No WebDriver or Selenium server; no external runtime. Ideal for scraping and lightweight automation when Chrome-only is acceptable. The main difference from Playwright for Go is that chromedp is pure Go and Chrome-only, while Playwright supports multiple browsers and requires installing browser binaries.
Playwright for Go - Community-maintained Go bindings for Microsoft Playwright. One API for Chromium, Firefox, and WebKit; auto-waiting for elements; modern selectors and features. Use it when you need cross-browser E2E tests or a test-focused API and are fine with an extra install step for browsers.
Selenium (Go) - The classic WebDriver approach: a Go client talks to a browser driver (ChromeDriver, GeckoDriver, etc.). Selenium does support Go; you run a driver process or connect to a grid. Use it for legacy suites or when you need the broadest ecosystem; for new Go projects, chromedp or Playwright for Go often simplify setup.
ZenRows - Not a driver library but a Scraper API (and optional Scraping Browser) you call from Go over HTTP. ZenRows handles headless browsers, JS rendering, residential proxies, anti-bot bypass, and CAPTCHA. Use it when your goal is scraping and you hit blocks or rate limits; for local E2E tests, chromedp or Playwright are usually enough.
For a quick reference of Go tooling and structure, see Go Project Structure: Practices & Patterns; keeping automation in a dedicated package fits well with internal/ or pkg/.
chromedp: pure Go, Chrome-only
chromedp requires no third-party binaries: it implements the CDP in Go and launches (or connects to) Chrome/Chromium. Install:
go get -u github.com/chromedp/chromedp
Example: navigate, read title, and extract text by selector. All actions run inside chromedp.Run; use chromedp.ByQuery for CSS selectors.
package main
import (
"context"
"fmt"
"log"
"github.com/chromedp/chromedp"
)
func main() {
ctx, cancel := chromedp.NewContext(context.Background())
defer cancel()
var title string
var bodyText string
err := chromedp.Run(ctx,
chromedp.Navigate("https://example.com"),
chromedp.Title(&title),
chromedp.Text("h1", &bodyText, chromedp.ByQuery),
)
if err != nil {
log.Fatal(err)
}
fmt.Println("Title:", title)
fmt.Println("Heading:", bodyText)
}
Example: click and read updated HTML. Use chromedp.Click and chromedp.OuterHTML (or chromedp.InnerHTML) with chromedp.ByQuery. Replace targetURL with your page (e.g. a test server or any URL):
targetURL := "https://example.com"
var outerBefore, outerAfter string
err := chromedp.Run(ctx,
chromedp.Navigate(targetURL),
chromedp.OuterHTML("#content", &outerBefore, chromedp.ByQuery),
chromedp.Click("#content", chromedp.ByQuery),
chromedp.OuterHTML("#content", &outerAfter, chromedp.ByQuery),
)
By default, Chrome runs headless. To show a window or change flags, use a custom allocator (see the chromedp ExecAllocator example). For Docker or CI, the chromedp/headless-shell image provides a smaller headless Chrome build that chromedp can use out of the box-so you can run chromedp in headless environments without installing Chrome on the host.
More examples (screenshots, PDFs, forms, cookies) are in the chromedp/examples repository.
Playwright for Go: cross-browser, auto-wait
Playwright for Go gives you the same multi-browser and auto-wait features as Playwright in other languages. Install the library and then the browser binaries:
go get -u github.com/playwright-community/playwright-go
go run github.com/playwright-community/playwright-go/cmd/playwright@latest install --with-deps
Example: launch Chromium, open a page, take a screenshot. You can use pw.Firefox or pw.WebKit for other engines.
package main
import (
"log"
"github.com/playwright-community/playwright-go"
)
func main() {
pw, err := playwright.Run()
if err != nil {
log.Fatalf("could not launch playwright: %v", err)
}
defer pw.Stop()
browser, err := pw.Chromium.Launch(playwright.BrowserTypeLaunchOptions{Headless: playwright.Bool(true)})
if err != nil {
log.Fatalf("could not launch Chromium: %v", err)
}
defer browser.Close()
page, err := browser.NewPage()
if err != nil {
log.Fatalf("could not create page: %v", err)
}
_, err = page.Goto("https://example.com")
if err != nil {
log.Fatalf("could not goto: %v", err)
}
_, err = page.Screenshot(playwright.PageScreenshotOptions{Path: playwright.String("example.png")})
if err != nil {
log.Fatalf("could not screenshot: %v", err)
}
}
Example: fill a form and get text. Playwright auto-waits for elements to be actionable, which reduces flakiness compared to raw CDP or Selenium without explicit waits.
page.Goto("https://example.com/login")
page.Locator("#username").Fill("user")
page.Locator("#password").Fill("secret")
page.Locator("button[type=submit]").Click()
content, _ := page.Locator("h1").TextContent()
fmt.Println(content)
You can use Playwright for Go for web scraping as well as testing: navigate, click, extract HTML or text, and optionally drive ZenRows’ Scraping Browser via its CDP/Playwright-compatible endpoint when you need anti-bot or proxies.
Selenium (Go client)
Selenium’s WebDriver API is available in Go via community clients (e.g. tebeka/selenium). You run a browser driver (ChromeDriver, GeckoDriver) or connect to a grid; the Go code sends WebDriver commands. So yes, Selenium does support Go-you just need to manage the driver process or use a cloud grid.
Example: connect to ChromeDriver, navigate, get title. The driver must be running (e.g. chromedriver --port=4444 or Selenium Manager in Selenium 4).
package main
import (
"fmt"
"log"
"github.com/tebeka/selenium"
)
func main() {
caps := selenium.Capabilities{"browserName": "chrome"}
wd, err := selenium.NewRemote(caps, "http://localhost:4444/wd/hub")
if err != nil {
log.Fatal(err)
}
defer wd.Quit()
err = wd.Get("https://example.com")
if err != nil {
log.Fatal(err)
}
title, err := wd.Title()
if err != nil {
log.Fatal(err)
}
fmt.Println("Title:", title)
}
For new Go projects, chromedp (no driver) or Playwright for Go (multi-browser, auto-wait) are often easier; use Selenium when you already have WebDriver suites or need a specific grid.
ZenRows: Scraper API from Go
ZenRows exposes a Scraper API (HTTP) and an optional Scraping Browser (CDP/Playwright-compatible). From Go you typically use the Scraper API: send a GET (or POST) with the target URL and options; ZenRows returns the rendered HTML or other formats. No local browser to manage. Use ZenRows when scraping is the goal and you need proxies, anti-bot bypass, or CAPTCHA handling; for simple local automation, chromedp or Playwright are enough.
Install the official Go SDK:
go get github.com/zenrows/zenrows-go-sdk/service/api
Example: simple GET with the ZenRows Scraper API. Set your API key via the client or ZENROWS_API_KEY env var.
package main
import (
"context"
"fmt"
"log"
scraperapi "github.com/zenrows/zenrows-go-sdk/service/api"
)
func main() {
client := scraperapi.NewClient(
scraperapi.WithAPIKey("YOUR_API_KEY"),
)
response, err := client.Get(context.Background(), "https://example.com", nil)
if err != nil {
log.Fatal(err)
}
if err := response.Error(); err != nil {
log.Fatal(err)
}
fmt.Println("Body length:", len(response.Body()))
fmt.Println("Status:", response.Status())
}
Example: JS rendering and premium proxies. Customize behavior with RequestParameters:
params := &scraperapi.RequestParameters{
JSRender: true,
UsePremiumProxies: true,
ProxyCountry: "US",
}
response, err := client.Get(context.Background(), "https://example.com", params)
if err != nil {
log.Fatal(err)
}
if err := response.Error(); err != nil {
log.Fatal(err)
}
html := response.String()
The SDK supports concurrency limits (WithMaxConcurrentRequests), retries (WithMaxRetryCount, WithRetryWaitTime), and other options; see the ZenRows Scraper API Go SDK docs.
Summary
- chromedp: Pure Go, CDP, Chrome-only; no driver. Use for fast, low-overhead automation and scraping. Run in Docker with chromedp/headless-shell if needed.
- Playwright for Go: Multi-browser, auto-wait, test-friendly. Use for E2E tests or when you want one API for Chromium, Firefox, and WebKit; also fine for scraping.
- Selenium (Go): WebDriver from Go; driver or grid required. Use when maintaining existing Selenium suites or when you need a specific grid.
- ZenRows: Scraper API (and Scraping Browser) from Go. Use when scraping is the focus and you need resilience to blocks, rate limits, and anti-bot.
For more Go practices-linters, project layout, and dependency injection-see Go Linters: Essential Tools for Code Quality, Dependency Injection in Go: Patterns & Best Practices, and the Go Cheatsheet. If you combine browser automation with LLM pipelines in Go, the Go SDKs for Ollama and Reranking with Ollama and Qwen3 Embedding in Go are useful references.
Related posts
- Go Cheatsheet
- Go Linters: Essential Tools for Code Quality
- Go Project Structure: Practices & Patterns
- Dependency Injection in Go: Patterns & Best Practices
- Go SDKs for Ollama - comparison with examples
- Reranking text documents with Ollama and Qwen3 Embedding model - in Go
- chromedp - Go package
- chromedp examples
- Playwright for Go
- playwright-go GitHub
- ZenRows Scraper API Go SDK
- ZenRows documentation
- Chrome DevTools Protocol