Taking Screenshots with Headless, The Chrome Debuggping Protocol (CDP) and Golang
There's multiple Go drivers to connect to the Chrome Debugging Protocol in order to either run automated tests or to take responsive screenshots of websites. Let's explore some of the options that are available for taking screenshots and how to use them from within Go.
Godet
Godet the smaller packages, which I started playing with in the beginning. I'm not actively maintaining it and it seems to have manually added API specs for the CDP. I'm only linking to my github repository because I'm still waiting for the PR to be accepted that allows the support forsetDeviceMetrics
.
The images produced look something like this:
This expects to have a running headless instance by the way:
google-chrome --headless --hide-scrollbars --remote-debugging-port=9222 --disable-gpu &
My working example for mobile (iPhone 7) screenshots with Go looks like this:
package main
import "fmt"
import "time"
import "github.com/jonathanmh/godet"
func main() {
// connect to Chrome instance
remote, _ := godet.Connect("localhost:9222", true)
// disconnect when done
defer remote.Close()
// get browser and protocol version
version, _ := remote.Version()
fmt.Println(version)
// install some callbacks
remote.CallbackEvent(godet.EventClosed, func(params godet.Params) {
fmt.Println("RemoteDebugger connection terminated.")
})
remote.CallbackEvent("Network.requestWillBeSent", func(params godet.Params) {
fmt.Println("requestWillBeSent",
params["type"],
params["documentURL"],
params["request"].(map[string]interface{})["url"])
})
remote.CallbackEvent("Network.responseReceived", func(params godet.Params) {
fmt.Println("responseReceived",
params["type"],
params["response"].(map[string]interface{})["url"])
})
remote.CallbackEvent("Log.entryAdded", func(params godet.Params) {
entry := params["entry"].(map[string]interface{})
fmt.Println("LOG", entry["type"], entry["level"], entry["text"])
})
// enable event processing
remote.RuntimeEvents(true)
remote.NetworkEvents(true)
remote.PageEvents(true)
remote.DOMEvents(true)
remote.LogEvents(true)
// create new tab
tab, _ := remote.NewTab("https://jonathanmh.com")
fmt.Println(tab)
remote.SetVisibleSize(375, 667)
remote.SetDeviceMetricsOverride(375, 667, 3, true, false)
time.Sleep(time.Second * 3)
// take a screenshot
_ = remote.SaveScreenshot("screenshot.png", 0644, 0, true)
// or save page as PDF
// _ = remote.SavePDF("page.pdf", 0644, godet.PortraitMode(), godet.Scale(0.5), godet.Dimensions(6.0, 2.0))
}
Chromedp
Chromedp is a really cool project, very much geared towards automatic testing, but also supporting screenshots. There's a wrapper around it on github: web2image that works as a command line uitility. Another bonus is that the package takes care of starting a chrome/chromium instance for you.There's a small caveat when you're not 100% sure which Chrome/Chromium version you have installed at the moment though:
If your Chrome is not up to date with the protocol definition that is generated from the chromium source, this package will not work for you. You'll need to make sure the version of Chromedp fits your install Chrome Headless version if the API changed recently.
Example: SetDeviceMetricsOverride
:
err := emulation.SetDeviceMetricsOverride(sw, sh, scale, false).WithScale(scale).Do(ctxt, ha)
if err != nil {
return err
}
will throw:
2017/10/01 10:57:37 <- {"id":12,"method":"Emulation.setDeviceMetricsOverride","params":{"width":1011,"height":357,"deviceScaleFactor":0.7,"mobile":false,"scale":0.7}}
2017/10/01 10:57:37 -> {"error":{"code":-32602,"message":"Invalid parameters","data":"fitWindow: boolean value expected"},"id":12}
because older Chromiums expect another parameter. However if you try to use that parameter with the up to date Go package, it will not compile, because the parameter is not part of that function any longer.