Automate a cloud browser with chromedp
Use Steel with chromedp to connect over CDP, navigate to Hacker News, extract the top stories, and capture a screenshot.
Scaffolds a starter project locally. Requires the Steel CLI.
chromedp speaks the Chrome DevTools Protocol over a websocket and never shells out to a local Chrome. A Steel session exposes exactly that websocket, so chromedp.NewRemoteAllocator points at the remote browser and every chromedp.Run step executes in the cloud, behind Steel's stealth, proxies, and live viewer. No browser on your machine.
cdpURL := fmt.Sprintf("%s&apiKey=%s", sess.WebsocketURL, apiKey)allocCtx, cancelAlloc := chromedp.NewRemoteAllocator(ctx, cdpURL, chromedp.NoModifyURL)defer cancelAlloc()browserCtx, cancelBrowser := chromedp.NewContext(allocCtx)defer cancelBrowser()
NoModifyURL is the one detail that matters here. By default chromedp probes /json/version and rewrites the websocket it gets back. Steel already hands you the exact browser endpoint with its auth query string attached, so rewriting it breaks the connection. The flag tells chromedp to dial the URL verbatim.
After that it is plain chromedp. run builds one task list and ships it in a single chromedp.Run: navigate, wait for the story rows, pull data out, screenshot.
err = chromedp.Run(runCtx,chromedp.Navigate("https://news.ycombinator.com"),chromedp.WaitVisible("tr.athing", chromedp.ByQuery),chromedp.Evaluate(extractTopStories, &raw),chromedp.FullScreenshot(&screenshot, 90),)
The extraction step is the part worth reading. chromedp's Evaluate decodes a JS return value into a Go variable, but a list of structs does not map cleanly across that boundary. The reliable pattern is to have the page-side script JSON.stringify its result into a string, then json.Unmarshal it into a typed []story on the Go side. The extractTopStories constant holds that script: it reads the top five tr.athing rows and returns title, link, and points for each.
Run it
cd examples/chromedpcp .env.example .env # set STEEL_API_KEYgo mod tidygo run .
Get a key at app.steel.dev/settings/api-keys. The program prints a session viewer URL as it starts. Open it in another tab to watch the run live. It writes hackernews.png to the working directory on the way out.
Your output varies. Structure looks like this:
Creating Steel session...Session created. Watch it live at https://app.steel.dev/sessions/ab12cd34Navigating to Hacker News...Top 5 Hacker News Stories:1. A tiny font renderer that fits in your CPU cacheLink: https://example.com/font-rendererPoints: 6422. Show HN: I rebuilt my home network on a single Raspberry PiLink: https://news.ycombinator.com/item?id=43990011Points: 318Saved screenshot to hackernews.pngReleasing session...
A run costs a few cents of browser time. Steel bills per session-minute, so the deferred client.Sessions.Release is not optional. The defer sits right after the create call, which means the session is released whether run returns clean or errors out partway through. Drop it and the browser stays up until the default five-minute timeout, on your dime.
Make it yours
- Swap the target. Change the
chromedp.NavigateURL, theWaitVisibleselector, and theextractTopStoriesscript. Session setup and cleanup stay identical. The JSON-string bridge works for any shape: define a matching Go struct and unmarshal. - Add steps. chromedp tasks compose, so append
chromedp.Click,chromedp.SendKeys, orchromedp.SetValueto theRunlist to fill forms or paginate before you extract. - Turn on stealth.
SessionCreateParamstakes pointers likeBlockAds,SolveCaptcha, andUseProxyfor sites with anti-bot, plusTimeoutto extend the session past five minutes. Set the field to the address of a value (v := true; params.BlockAds = &v) since they are all optional. - Tune the screenshot.
FullScreenshotcaptures the whole scroll height at the given JPEG quality (0 to 100). Swap it forchromedp.CaptureScreenshotto grab only the viewport.
Related
Playwright version and Python Playwright connect over CDP the same way with a different driver. Rod is the other Go option, with a fluent page API instead of a task list. chromedp's own examples cover clicks, downloads, and network interception.
Related recipes
Run a Steel browser job with Trigger.dev
Queue a Trigger.dev task that creates a Steel session, drives Playwright over CDP, saves artifacts, and releases the browser in cleanup.
Run a durable browser workflow with Temporal
Build a Temporal TypeScript Workflow that schedules retryable Steel browser Activities to capture page summaries, screenshots, and Markdown artifacts.
Automate a cloud browser with headless_chrome
Use Steel with headless_chrome, the synchronous Rust equivalent of Puppeteer, to connect over CDP and scrape quotes with element handles.