Watch
1
0
Fork
You've already forked golang-github-chromedp-chromedp
0
No description
  • Go 99.1%
  • JavaScript 0.8%
  • Shell 0.1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Daniel Baumann fd0241ec01
Releasing fastforward version 0.19.1-1~ffwd13+u1.
Signed-off-by: Daniel Baumann <daniel@debian.org>
2026-10-06 10:40:25 +02:00
.agents/skills Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
.claude/skills Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
.github Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
contrib Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
debian Releasing fastforward version 0.19.1-1~ffwd13+u1. 2026-10-06 10:40:25 +02:00
device Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
docs Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
internal Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
js Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
kb Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
remote Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
test Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
testdata Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
.gitattributes Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
.gitignore Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
action.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
AGENTS.md Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
allocate.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
allocate_darwin.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
allocate_detach_unix.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
allocate_detach_windows.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
allocate_linux.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
allocate_linux_test.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
allocate_other.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
allocate_pipe.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
allocate_test.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
browser.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
browser_test.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
call.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
chromedp.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
chromedp_test.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
CLAUDE.md Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
console.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
console_test.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
CONTRIBUTING.md Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
drag.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
drag_test.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
emulate.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
emulate_test.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
errors.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
eval.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
eval_test.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
event_test.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
example_test.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
execpath_test.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
expose.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
expose_test.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
frame.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
go.mod Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
go.sum Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
input.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
input_test.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
js.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
keepopen.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
keepopen_linux_test.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
keepopen_test.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
LICENSE Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
nav.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
nav_test.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
newwindow_test.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
node.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
pdf.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
pdf_test.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
pipe.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
pipe_test.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
pipe_unix.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
pipe_unix_test.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
pipe_windows.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
pipe_windows_test.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
poll.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
poll_test.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
query.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
query_test.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
README.md Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
screenshot.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
session.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
skills-lock.json Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
target.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
target_test.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
util.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
util_test.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00
visible_test.go Merging upstream version 0.19.1. 2026-10-06 10:40:16 +02:00

About chromedp

chromedp logo

Package chromedp drives browsers that speak the Chrome DevTools Protocol from Go. It needs no external driver.

Unit Tests Go Reference Releases Discord Discussion

Installing

Install the package with go get. The module needs Go 1.27 or newer. Version v0.18.0 uses the typed cdproto v0.157.4 and has the generic and iterator API. The earlier versions of cdproto, v0.157.0, v0.157.1 and v0.157.2, have the old API.

go get -u github.com/chromedp/chromedp

The core module uses only the Go standard library and cdproto. It starts a browser and talks to it through a pipe. Code that needs a websocket is in a second module, github.com/chromedp/chromedp/remote. Install it only when you connect to a browser that runs already, when you want the exec allocator to use a websocket, or when you keep the browser open after the program ends:

go get -u github.com/chromedp/chromedp/remote

Usage

An action is a func that runs against a browser tab and returns a value. chromedp.Do runs actions that return nothing, and chromedp.Run runs one action and returns its value:

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

if err := chromedp.Do(ctx, chromedp.Navigate(`https://pkg.go.dev/`)); err != nil {
	log.Fatal(err)
}
title, err := chromedp.Run(ctx, chromedp.Title())
if err != nil {
	log.Fatal(err)
}
fmt.Println(title)

A page event is an iterator. chromedp.Events subscribes when it returns, so a program can subscribe, trigger the event, and then read it:

loaded := chromedp.Events(ctx, page.LoadEventFired)
if err := chromedp.Do(ctx, chromedp.Navigate(`https://pkg.go.dev/`)); err != nil {
	log.Fatal(err)
}
for ev, err := range loaded {
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(ev.Timestamp)
	break
}

docs/API.md describes the API. It has 14 examples that show the old code and the new code side by side. docs/MIGRATION.md lists every renamed and removed name.

See the Go reference for the documentation and examples.

More examples

The examples repository has 43 programs for larger tasks. Each program is one main.go file. Run one with go run, for example go run github.com/chromedp/examples/tabs@latest. Every program takes the flag -v to print the protocol messages. Every program except remote takes the flag -visible to show the browser window. Most programs take the flag -visible-on-terminal to draw the page in the terminal while they run, with termcast. Every program except fast reads a local test site and needs no internet. Use the flag -url to read another site.

Read pages and fill forms:

  • click clicks an element that a selector finds.
  • text reads the text of an element.
  • eval runs JavaScript in the page and decodes the result.
  • structeval decodes JavaScript results into Go structs, slices and maps, and shows the errors.
  • logic mixes actions and Go code to read a list.
  • subtree walks a subtree of the DOM.
  • selectors shows the typed selectors side by side.
  • frames reaches elements inside an iframe and a shadow root.
  • submit fills out and submits a form.
  • keys sends key events to an element.
  • upload uploads a file on a form.
  • visible waits until an element is visible.
  • dragdrop drags and drops with the mouse and with HTML5 drag and drop.

Network, sessions and files:

  • cookie sets cookies on requests.
  • headers adds extra HTTP headers.
  • proxy signs in to a proxy server that needs a password.
  • intercept blocks, mocks and changes requests with the Fetch domain.
  • session saves a login session and restores it in another browser.
  • har writes a HAR file from the network events of a page.
  • download_file downloads a file with a headless browser.
  • download_image downloads an image from the network response.

Events and the protocol:

  • eventsiter reads the events of a page with iterators, and waits for the network to be idle.
  • console reads the console and the uncaught exceptions of a page.
  • dialogs answers alert, confirm, prompt and beforeunload dialogs.
  • popups works with popups and several targets, and with browser contexts.
  • exposefunc calls Go functions from the page.
  • extension loads a browser extension, uBlock Origin Lite, from a folder on disk and shows that it blocks the ads of a page.
  • rawcall sends protocol commands that have no action, such as timezone, locale, geolocation and throttling.

Screens and devices:

  • screenshot takes a screenshot of an element and of the whole page.
  • pdf prints a page to a PDF file.
  • pdfoptions prints a page to PDF files with different options.
  • pdfstream reads a printed PDF as a stream.
  • emulate emulates a device, such as an iPhone.
  • screencast saves the frames of a page as JPEG files.
  • termcast plays an animated SVG and streams the screen of the browser to the terminal with termcast.

Several tabs and browsers:

  • tabs uses several tabs of one browser, with or without a window for each tab, and switches between them.
  • workers runs many jobs at the same time in one browser with a pool of goroutines.
  • multi uses the headless-shell image in a container.
  • remote connects to a browser that is already running, with the module remote.

The program fast reads a live site, fast.com, and draws an image in the terminal. It can fail when the site changes. The programs forecast and geoip also draw an image in the terminal. Run these programs in a terminal that can show images. The tags of the examples repository follow the tags of chromedp.

Show the browser in the terminal

termcast streams the screen of a chromedp page to a terminal that can show images (Kitty, iTerm2 or Sixel). It uses the screencast of the Chrome DevTools Protocol, so it works with a headless browser, also over ssh. Start the browser first, and then start the stream with the context.

ctx, cancel := chromedp.NewContext(context.Background())
defer cancel()
chromedp.Do(ctx, chromedp.Navigate("https://example.com")) // starts the browser
s, err := termcast.Start(ctx)                               // draws 4 frames each second
if err != nil {
	log.Fatal(err) // termcast.ErrNoGraphics when the terminal has no graphics
}
defer s.Stop()

The stream clears the terminal at each redraw. It holds the log lines while it runs, and prints them after the final frame. The type termcast.Flags adds the flags -visible-on-terminal and -terminal-fps to a program, as the programs in the examples repository have them.

Visible browser

By default, chromedp runs Chrome in headless mode, so no window opens. To see the browser, add WithVisibleWindow. Set the variable CHROMEDP_VISIBLEWINDOW=1 to get the same result with no change in the code. On Linux, a visible window needs DISPLAY or WAYLAND_DISPLAY. Without them, the first Run returns chromedp.ErrNoDisplay.

ctx, cancel := chromedp.NewContext(context.Background(), chromedp.WithVisibleWindow())
defer cancel()
chromedp.Do(ctx, chromedp.Navigate("https://example.com"))
chromedp.WaitClosed(ctx) // block until the user closes the window

To leave the browser open after the program ends, add remote.WithKeepOpen from the module github.com/chromedp/chromedp/remote. The program can print the address, and another program can attach to it with remote.NewAllocator. The profile directory stays on disk, and you must delete it yourself.

ctx, _ := chromedp.NewContext(context.Background(), chromedp.WithVisibleWindow(), remote.WithKeepOpen())
chromedp.Do(ctx, chromedp.Navigate("https://example.com"))
wsURL, profile := chromedp.KeptOpen(ctx)
fmt.Println("attach to", wsURL, "profile", profile)
// The program ends here. The browser stays open.

Remote browser

To connect to a browser that runs already, such as a container or a hosted service, use remote.NewAllocator with the websocket address of the browser:

allocCtx, cancel := remote.NewAllocator(context.Background(), "ws://127.0.0.1:9222/")
defer cancel()
ctx, cancel := chromedp.NewContext(allocCtx)
defer cancel()
title, err := chromedp.Run(ctx, chromedp.Title())

A hosted service can need a header on the websocket request. Give the full address of the browser and the options remote.NoModifyURL and remote.WithDialHTTPHeader.

Frequently Asked Questions

I cannot see any Chrome browser window

By default, chromedp runs Chrome in headless mode. See Visible browser. See also DefaultExecAllocatorOptions, and see an example that overrides the default options.

I see "context canceled" errors

When the connection to the browser is lost, chromedp cancels the context. This can cause the error. It happens, for example, when someone closes the browser by hand, or when something kills the browser process. When the browser process dies on its own, the error also holds the exit error of the process. Use errors.As with an *exec.ExitError to read the signal or the exit status.

How does chromedp talk to the browser it starts?

By default, through a pipe. chromedp starts Chrome with --remote-debugging-pipe and two pipes, so Chrome opens no debugging port. On Windows the pipes are handles that the switch --remote-debugging-io-pipes names. To use a websocket and a debugging port instead, add the remote.WebSocket option of the module github.com/chromedp/chromedp/remote to the exec allocator. A remote-debugging-port or remote-debugging-address flag also selects the websocket, and so does KeepOpen, and they all need remote.WebSocket. A program for Windows needs no remote module for the default pipe.

Chrome exits as soon as my Go program finishes

On Linux, chromedp kills the Chrome child processes that it started, so that no resources leak. To leave Chrome open, add remote.WithKeepOpen. See Visible browser. You can also start Chrome yourself and connect with remote.NewAllocator.

Calling an action or a command results in "invalid context"

chromedp.Do, chromedp.Run and chromedp.Call need a context that came from chromedp.NewContext, because the context holds the browser and the tab. Any other context gives ErrInvalidContext.

How do I send a protocol command that has no action?

Call it with chromedp.Call, which runs the command on the tab of the context:

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

res, err := chromedp.Call(ctx, page.GetFrameTree, cdp.Empty{})

Inside an action, call cdp.Call(ctx, t, command, params) with the target t that the action receives. The target is the tab of the context. The commands and their parameter structs are in github.com/chromedp/cdproto.

I have an action of the old kind

Wrap it with chromedp.Legacy. The old kind is a value with a Do(context.Context) error method.

Is it safe to use one context from several goroutines?

Contexts that share one browser run in separate tabs, and they are safe to use in parallel. Call chromedp.Run once on a parent context so that it has a browser, and then make a child context for each goroutine with chromedp.NewContext. A child of a context that has no browser yet starts a browser of its own.

Do not share one context between goroutines. The actions of one context run in one tab, so they can race. For example, two Navigate actions on the same tab can fail. The first Run on a context starts the browser, and it must not run at the same time as another Run on that context.

ctx, cancel := chromedp.NewContext(context.Background())
defer cancel()
if err := chromedp.Do(ctx); err != nil { // starts the browser
	log.Fatal(err)
}
var wg sync.WaitGroup
for _, url := range urls {
	wg.Go(func() {
		tabCtx, cancel := chromedp.NewContext(ctx) // a new tab
		defer cancel()
		if err := chromedp.Do(tabCtx, chromedp.Navigate(url)); err != nil {
			log.Println(url, err)
		}
	})
}
wg.Wait()

I cannot reach an element or the network events of an iframe

Chrome runs an iframe from another site (a cross-site iframe) in its own process, as a separate target of the type iframe. The DOM tree of the page does not hold its content. The node of the iframe element has no ContentDocument, so a query with FromNode finds nothing and waits until the context ends. chromedp does not attach to the iframe target by itself, so the network events of the iframe do not reach the context of the page. An iframe from the same site is not affected.

To work in the iframe, find its target with chromedp.Targets and attach to it with chromedp.WithTargetID. The new context runs actions in the iframe and gets its events. The parent context must have run once, so that it has a browser.

infos, err := chromedp.Targets(ctx)
if err != nil {
	log.Fatal(err)
}
for _, info := range infos {
	if info.Type == "iframe" && strings.HasPrefix(info.URL, "https://other.example/") {
		frameCtx, cancel := chromedp.NewContext(ctx, chromedp.WithTargetID(info.TargetID))
		defer cancel()
		text, err := chromedp.Run(frameCtx, chromedp.Text(chromedp.CSS("#inner")))
		// ...
	}
}

The other way is to turn off site isolation, so that the iframe stays in the process of the page. Add Flag("disable-features", "SitePerProcess,IsolateOrigins") and Flag("disable-site-isolation-trials", true) to the options of the exec allocator. Then FromNode and the events of the page reach the iframe. This turns off a security feature of Chrome. Use it only for pages that you trust. See docs/decisions/2026-10-04-keep-site-isolation-on.md.

SendKeys with a new line inserts two line breaks

SendKeys sends the character \n as the Enter key, in the form \r. That is a keyDown event, a char event with the text \r, and a keyUp event. The key kb.Enter is the same as \r, so it sends the same events and does not help. An editor that handles Enter on keydown and cancels the default, such as Lexical, can insert two line breaks, because the char event inserts one more. To send the Enter key with no char event, send a rawKeyDown event and a keyUp event with input.DispatchKeyEvent, and send the text before and after it with SendKeys:

enter := chromedp.Func(func(ctx context.Context, t *chromedp.Target) error {
	for _, typ := range []input.DispatchKeyEventType{
		input.DispatchKeyEventTypeRawKeyDown, input.DispatchKeyEventTypeKeyUp,
	} {
		_, err := cdp.Call(ctx, t, input.DispatchKeyEvent, input.DispatchKeyEventParams{
			Type: typ, Key: "Enter", Code: "Enter",
			WindowsVirtualKeyCode: 13, NativeVirtualKeyCode: 13,
		})
		if err != nil {
			return err
		}
	}
	return nil
})
err := chromedp.Do(ctx,
	chromedp.SendKeys(sel, "Hello"),
	enter,
	chromedp.SendKeys(sel, "World"),
)

The browser closes when the timeout of my context ends

The first Run on a context starts the browser, and it binds the life of the browser to the context that you pass to that call. When that context ends, the browser stops. So a context from context.WithTimeout that you use for the first Run closes the browser when the timeout ends, and every later call on the parent context fails with context canceled.

To limit one action, start the browser first with a context that has no timeout. Then run the action with a context that you derive from it. When the timeout ends, Run returns an error that wraps context.DeadlineExceeded. The tab and the browser stay open, and ctx still works. Run has no option for the timeout of one action.

ctx, cancel := chromedp.NewContext(context.Background())
defer cancel()
if err := chromedp.Do(ctx); err != nil { // starts the browser, with no timeout
	log.Fatal(err)
}

tctx, tcancel := context.WithTimeout(ctx, 5*time.Second)
defer tcancel()
err := chromedp.Do(tctx, chromedp.Navigate(url)) // only this call has the timeout

Why does chromedp pass disable-dev-shm-usage, and can I turn it off?

DefaultExecAllocatorOptions sets disable-dev-shm-usage to true. Chrome then keeps the files of its shared memory in the temporary directory, such as /tmp, and not in /dev/shm. The flag is on because /dev/shm is small in many containers (64 MB in a default Docker container), and Chrome crashes when it fills the space.

The cost is that the shared memory files are file-backed memory in the temporary directory. They can be slower, and a program that runs for a long time and opens many pages can see growing memory. If /dev/shm is large enough, for example a memory volume that you mount in a container, turn the flag off after the default options:

opts := append(chromedp.DefaultExecAllocatorOptions[:],
	chromedp.Flag("disable-dev-shm-usage", false),
)
allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), opts...)
defer cancel()

I want to use chromedp on a headless environment

Run the Go program that uses chromedp inside the chromedp/headless-shell image. The image has headless-shell, a smaller headless build of Chrome. chromedp finds it by default.

Contributing

Read CONTRIBUTING.md before you send a change. AGENTS.md holds the rules for people and coding agents. The tests need Chrome or the headless-shell image. The repository holds three modules, and AGENTS.md tells how to test each one.

These documents are in docs/:

Document Holds
docs/PLAN.md the purpose, the architecture and the open questions
docs/PROGRESS.md where the work stands
docs/BACKLOG.md known work that is not done
docs/API.md the new API, with old and new code side by side
docs/MIGRATION.md every renamed and removed name
docs/decisions/README.md the index of every recorded decision

Questions and ideas

Ask a question, or suggest a feature, in Discussions. The issue tracker is for bugs. You can also chat on Discord.

Resources