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 for setDeviceMetrics.

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.

Summary

If you can get your service up and running or match the Chromium/chromedp versions successfully, it's a great tool. Chromedp also is bound to keep up to date with the protocol as it changes and seems to be backed by a company that relies on it.

Tagged with: #Chrome Debugging Protocol #chromedp #go #godet #golang #screenshot #testing

Thank you for reading! If you have any comments, additions or questions, please tweet or toot them at me!