Is it possible to get a screenshot of the full page, even when setting a specific width and height to emulate a mobile device?
For example, here is a screenshot using dev tools and an iphone 6.
I'm looking to achieve the same result.
http://i.imgur.com/0Je8sgT.png
So far, I'm getting an image which is cut off at the specified height.
Here's what I'm using. As a bonus it also handles page scaling.
Click to expand
func main() {
/// ...
var buf []byte
err = c.Run(ctxt, cdp.Tasks{
cdp.Navigate(url),
cdp.WaitEventLoad(), // (see PR #79 here - You'll need to wait for the pageload somehow)
setViewportAndScale(708, 250, 0.7),
cdp.CaptureScreenshot(&buf),
screenshotSave("foo.png", &buf),
})
if err != nil {
log.Fatal(err)
}
}
func screenshotSave(fileName string, buf *[]byte) cdp.ActionFunc {
return func(ctxt context.Context, h cdptypes.Handler) error {
log.Printf("Write %v", fileName)
return ioutil.WriteFile(fileName, *buf, 0644)
}
}
func setViewportAndScale(w, h int64, scale float64) cdp.ActionFunc {
return func(ctxt context.Context, ha cdptypes.Handler) error {
err := emulation.SetVisibleSize(w, h).Do(ctxt, ha)
if err != nil {
return err
}
sw, sh := int64(float64(w)/scale), int64(float64(h)/scale)
err = emulation.SetDeviceMetricsOverride(sw, sh, scale, false, false).WithScale(scale).Do(ctxt, ha)
if err != nil {
return err
}
return nil
}
}
Awesome. I'll take a look at this tomorrow and will report back.
You may want to tweak the inputs for width and height going into SetVisibleSize and SetDeviceMetricsOverride to achieve the effect you're looking for. I'm uncertain how easy it is to get a "full page" screenshot.
I have this working perfectly via nodejs.
Doesn't matter what url I throw at it, it always brings back the correct height of the body and takes the correct screenshot.
I've ported it over to the best of my ability, but no matter what I try I get this:
error: could not perform (childNodeCountUpdated) operation on node 47 (wait node): timeout waiting for node `47`
I'm thinking it's the WaitVisible entry in the task list. So with this failing, the correct height is never returned.
My code below, would appreciate if anyone can tell me where I'm going wrong. Thanks!
package main
import (
"context"
"io/ioutil"
"log"
//"time"
cdp "github.com/knq/chromedp"
cdptypes "github.com/knq/chromedp/cdp"
"github.com/knq/chromedp/cdp/dom"
"github.com/knq/chromedp/cdp/emulation"
)
// google-chrome --headless --disable-gpu --hide-scrollbars --remote-debugging-port=9222 &
func main() {
var err error
var width, height int64
// create context
ctxt, cancel := context.WithCancel(context.Background())
defer cancel()
// create chrome instance
//c, err := cdp.New(ctxt, cdp.WithLog(log.Printf))
c, err := cdp.New(ctxt)
if err != nil {
log.Fatal(err)
}
fullPage := true
width = 480
height = 800
metrics := emulation.SetDeviceMetricsOverride(width, height, 0, false, false)
if err = c.Run(ctxt, metrics); err != nil {
log.Fatal(err)
}
vs := emulation.SetVisibleSize(width, height)
if err = c.Run(ctxt, vs); err != nil {
log.Fatal(err)
}
var buf []byte
url := "https://www.eff.org"
err = c.Run(ctxt, cdp.Tasks{
cdp.Navigate(url),
cdp.WaitVisible(`#topbar`, cdp.ByID),
setVisibleSize(width, height, fullPage),
cdp.CaptureScreenshot(&buf),
screenshotSave("foo.png", &buf),
})
if err != nil {
log.Fatal(err)
}
}
func screenshotSave(fileName string, buf *[]byte) cdp.ActionFunc {
return func(ctxt context.Context, h cdptypes.Handler) error {
log.Printf("Write %v", fileName)
return ioutil.WriteFile(fileName, *buf, 0644)
}
}
func setVisibleSize(w, h int64, fullPage bool) cdp.ActionFunc {
return func(ctxt context.Context, ha cdptypes.Handler) error {
if fullPage {
root, err := ha.GetRoot(ctxt)
if err != nil {
return err
}
model, err := dom.GetBoxModel(root.NodeID).Do(ctxt, ha)
if err != nil {
return err
}
h = model.Height
}
log.Printf("Height is %v", h)
err := emulation.SetVisibleSize(w, h).Do(ctxt, ha)
if err != nil {
return err
}
// This forceViewport call ensures that content outside the viewport is
// rendered, otherwise it shows up as grey. Possibly a bug?
err = emulation.ForceViewport(0.0, 0.0, 1.0).Do(ctxt, ha)
if err != nil {
return err
}
return nil
}
}
Nice trick to get the document height. I think you might be hitting #75 for which PR #77 may help.
I've tried applying #64 got a bunch of these:
2017/06/30 10:20:27 error: could not perform (childNodeCountUpdated) operation on node 26 (wait node): timeout waiting for node `26`
then
2017/06/30 10:20:27 error: could not perform (childNodeCountUpdated) operation on node 26 (wait node): timeout waiting for node `26`
2017/06/30 10:20:27 error: could not perform (attributeModified) operation on node 26 (wait node): timeout waiting for node `26`
and it hangs
then removed that and applied #77 first and then #79. All I get is it hanging.
I'm going to give up for now and wait until this project has moved on past these issues.
Why was this closed ?
Could you please help get a screenshot of the full page? Because examples above looks like outdated.
Any updates?
For anyone still looking for a solution, the code below seems to work for me with the latest version of chromedp:
chromedp.ActionFunc(func(ctxt context.Context, h cdp.Executor) error {
_, viewLayout, contentRect, err := page.GetLayoutMetrics().Do(ctxt, h)
if err != nil {
return err
}
v := page.Viewport{
X: contentRect.X,
Y: contentRect.Y,
Width: viewLayout.ClientWidth, // or contentRect.Width,
Height: contentRect.Height,
Scale: 1,
}
log.Printf("Capture %#v", v)
buf, err := page.CaptureScreenshot().WithClip(&v).Do(ctxt, h)
if err != nil {
return err
}
log.Printf("Write %v", outPath)
return ioutil.WriteFile(outPath, buf, 0644)
})
If you want to hide scrollbars, be sure to specify runner.Flag("hide-scrollbars", true)
@ajbouh 's solution above is a little out of date I think. I had to tweak it a bit to get it to run. Here is my solution:
package main
import (
"context"
"io/ioutil"
"log"
"github.com/chromedp/cdproto/page"
"github.com/chromedp/chromedp"
)
func main() {
ctx, cancel := chromedp.NewContext(context.Background())
defer cancel()
urlStr := "http://reddit.com"
outPath := "screenshot.png"
chromedp.Run(ctx,
chromedp.Navigate(urlStr),
chromedp.ActionFunc(func(ctxt context.Context) error {
_, _, contentRect, err := page.GetLayoutMetrics().Do(ctxt)
if err != nil {
return err
}
v := page.Viewport{
X: contentRect.X,
Y: contentRect.Y,
Width: contentRect.Width,
Height: contentRect.Height,
Scale: 1,
}
log.Printf("Capture %#v", v)
buf, err := page.CaptureScreenshot().WithClip(&v).Do(ctxt)
if err != nil {
return err
}
log.Printf("Write %v", outPath)
return ioutil.WriteFile(outPath, buf, 0644)
}))
}
To be perfectly honest, this is comically hard for something that seems like it should be built-in.
The Chrome devtools API has changed a lot over the last two years, as has chromedp. Let's add and maintain a simple example for this.
// fullScreenshot takes a screenshot of the entire browser viewport, after
// waiting for sel to be visible.
//
// Liberally copied from puppeteer's source.
//
// Note: this will override the emulation settings.
func fullScreenshot(urlstr string, quality int64, res *[]byte) chromedp.Tasks {
return chromedp.Tasks{
chromedp.Navigate(urlstr),
chromedp.ActionFunc(func(ctx context.Context) error {
// get layout metrics
_, _, contentSize, err := page.GetLayoutMetrics().Do(ctx)
if err != nil {
return err
}
width, height := int64(math.Ceil(contentSize.Width)), int64(math.Ceil(contentSize.Height))
// force viewport emulation
err = emulation.SetDeviceMetricsOverride(width, height, 1, false).
WithScreenOrientation(&emulation.ScreenOrientation{
Type: emulation.OrientationTypePortraitPrimary,
Angle: 0,
}).
Do(ctx)
if err != nil {
return err
}
// capture screenshot
*res, err = page.CaptureScreenshot().
WithQuality(quality).
WithClip(&page.Viewport{
X: contentSize.X,
Y: contentSize.Y,
Width: contentSize.Width,
Height: contentSize.Height,
Scale: 1,
}).Do(ctx)
if err != nil {
return err
}
return nil
}),
}
}
I've updated the examples:
https://github.com/chromedp/examples/blob/master/screenshot/main.go
Let me know if there were any mistakes in the implementation.
So the high-level api chromedp.CaptureScreenshot is not usable in headless mode for now?
@kenshaw I have nil pointer error on contentSize, which is nil in recent chromedp / chromium versions, what I'm doing wrong?
@kenshaw我的指针指针错误
contentSize为nil,在最近的chromedp / chrome版本中为nil,我在做什么错?
go.mod github.com/chromedp/cdproto v0.0.0-20210222063305-a3ac505ff0bd
@kenshaw我的指针指针错误
contentSize为nil,在最近的chromedp / chrome版本中为nil,我在做什么错?go.mod github.com/chromedp/cdproto v0.0.0-20210222063305-a3ac505ff0bd
I had the same problem. I just used the example and didn't make any changes.

Of course it will panic. I don't know why page.GetLayoutMetrics() can't get contentSize. I try to use WaitVisible for wait html loading completed, but it didn't work.
Operating system macOS.
github.com/chromedp/cdproto v0.0.0-20210318231247-733a37e2c059
github.com/chromedp/chromedp v0.6.9
@kenshaw @paulm17
@LinkinStars Can you attach the debug log here? BTW, can you provide the version of the browser?
Enable debug logging by setting the chromedp.WithDebugf option. For example, ctx, cancel := chromedp.NewContext(context.Background(), chromedp.WithDebugf(log.Printf))
@LinkinStars Can you attach the debug log here? BTW, can you provide the version of the browser?
Enable debug logging by setting the
chromedp.WithDebugfoption. For example,ctx, cancel := chromedp.NewContext(context.Background(), chromedp.WithDebugf(log.Printf))
These are the last few lines of the log.
2021/03/22 18:54:17 <- {"method":"Page.loadEventFired","params":{"timestamp":601210.500372},"sessionId":"BDCFF0725CDE33E5BC7BBC5BF0C1DCE4"}
2021/03/22 18:54:17 <- {"method":"Page.lifecycleEvent","params":{"frameId":"7FBD0050FA125D4FE80DD2F01E14986B","loaderId":"700CF0F9C600378CE775D7415DD18EC9","name":"load","timestamp":601210.500372},"sessionId":"BDCFF0725CDE33E5BC7BBC5BF0C1DCE4"}
2021/03/22 18:54:17 <- {"method":"Page.frameStoppedLoading","params":{"frameId":"7FBD0050FA125D4FE80DD2F01E14986B"},"sessionId":"BDCFF0725CDE33E5BC7BBC5BF0C1DCE4"}
2021/03/22 18:54:17 -> {"id":17,"sessionId":"BDCFF0725CDE33E5BC7BBC5BF0C1DCE4","method":"Page.getLayoutMetrics"}
2021/03/22 18:54:17 <- {"id":17,"result":{"layoutViewport":{"pageX":46,"pageY":0,"clientWidth":800,"clientHeight":600},"visualViewport":{"offsetX":0,"offsetY":0,"pageX":46,"pageY":0,"clientWidth":800,"clientHeight":600,"scale":1,"zoom":1},"contentSize":{"x":0,"y":0,"width":1250,"height":600}},"sessionId":"BDCFF0725CDE33E5BC7BBC5BF0C1DCE4"}
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x10 pc=0x13e6c6f]
goroutine 1 [running]:
main.fullScreenshot.func1(0x15342a0, 0xc00020c360, 0x0, 0x0)
/Users/linkinstar/main.go:49 +0x8f
github.com/chromedp/chromedp.ActionFunc.Do(0xc0000b22d0, 0x15342a0, 0xc00020c360, 0x0, 0x0)
/Users/linkinstar/go/pkg/mod/github.com/chromedp/[email protected]/chromedp.go:606 +0x3a
github.com/chromedp/chromedp.Tasks.Do(0xc0000c82e0, 0x2, 0x2, 0x15342a0, 0xc00020c360, 0x100f4b8, 0x30)
/Users/linkinstar/go/pkg/mod/github.com/chromedp/[email protected]/chromedp.go:616 +0x72
github.com/chromedp/chromedp.Tasks.Do(0xc0000c1f60, 0x1, 0x1, 0x15342a0, 0xc00020c360, 0xc0003a4000, 0x15342a0)
/Users/linkinstar/go/pkg/mod/github.com/chromedp/[email protected]/chromedp.go:616 +0x72
github.com/chromedp/chromedp.Run(0x15342a0, 0xc00012e7b0, 0xc0000c1f60, 0x1, 0x1, 0x2, 0x2)
/Users/linkinstar/go/pkg/mod/github.com/chromedp/[email protected]/chromedp.go:274 +0xdb
main.main()
/Users/linkinstar/main.go:24 +0x18f
My local Chrome browser version is 89.0.4389.90. I don't know if it's the version you want.
I have tested with the same version of Chrome and it works.
Your log shows that the response of Page.getLayoutMetrics contains the contentSize field (see the response with "id":17), which is expected. Maybe you have a wrong dependency package. Can you provide go.mod and go.sum here?
I have tested with the same version of Chrome and it works.
Your log shows that the response of
Page.getLayoutMetricscontains thecontentSizefield (see the response with"id":17), which is expected. Maybe you have a wrong dependency package. Can you providego.modandgo.sumhere?
github.com/chromedp/cdproto v0.0.0-20210318231247-733a37e2c059
github.com/chromedp/chromedp v0.6.9
github.com/chromedp/cdproto v0.0.0-20210318231247-733a37e2c059 h1:mlK+5AZ3PhFQDGR/xpEb8x8BsxreV2em3VCfEnZviMI=
github.com/chromedp/cdproto v0.0.0-20210318231247-733a37e2c059/go.mod h1:At5TxYYdxkbQL0TSefRjhLE3Q0lgvqKKMSFUglJ7i1U=
github.com/chromedp/chromedp v0.6.9 h1:vJHNDF/lv4v3JFfZ5ZanoLfbI5A8DJF13q6lyGkdLBU=
github.com/chromedp/chromedp v0.6.9/go.mod h1:gu2vg0pL6XBSo3ilFexSe7Yrg9WwXujaMAx2D7EbEXw=
github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic=
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
Sorry, I just tested with the latest packages and has reproduced the issue.
The breaking change is brought by this commit https://github.com/chromedp/cdproto/commit/4e41b2544bc6b51d93c38037963943178eaee449 (check the changes in page/page.go, contentSize is renamed to cssContentSize). I have tested the latest package with the docker image chromedp/headless-shell:91.0.4442.4, and it works. Here is the response of Page.getLayoutMetrics in chromedp/headless-shell:91.0.4442.4:
2021/03/22 13:11:15 <- {"id":17,"result":{"layoutViewport":{"pageX":46,"pageY":0,"clientWidth":800,"clientHeight":600},"visualViewport":{"offsetX":0,"offsetY":0,"pageX":46,"pageY":0,"clientWidth":800,"clientHeight":600,"scale":1,"zoom":1},"contentSize":{"x":0,"y":0,"width":1250,"height":600},"cssLayoutViewport":{"pageX":46,"pageY":0,"clientWidth":800,"clientHeight":600},"cssVisualViewport":{"offsetX":0,"offsetY":0,"pageX":46,"pageY":0,"clientWidth":800,"clientHeight":600,"scale":1,"zoom":1},"cssContentSize":{"x":0,"y":0,"width":1250,"height":600}},"sessionId":"27EEFE1B14B793C7E48B3355D12843B0"}
You can either upgrade to Chrome 91 or stick with github.com/chromedp/[email protected] (the latest version before the breaking change).
If you want to stick with github.com/chromedp/[email protected], you can use the replace directive in the go.mod file like this:
replace github.com/chromedp/cdproto v0.0.0-20210318231247-733a37e2c059 => github.com/chromedp/cdproto v0.0.0-20210305224431-50b9f457e822
And then run go mod download github.com/chromedp/[email protected].
LGTM, THX.
Sorry, I just tested with the latest packages and has reproduced the issue.
The breaking change is brought by this commit chromedp/cdproto@4e41b25 (check the changes in
page/page.go,contentSizeis renamed tocssContentSize). I have tested the latest package with the docker imagechromedp/headless-shell:91.0.4442.4, and it works. Here is the response ofPage.getLayoutMetricsinchromedp/headless-shell:91.0.4442.4:2021/03/22 13:11:15 <- {"id":17,"result":{"layoutViewport":{"pageX":46,"pageY":0,"clientWidth":800,"clientHeight":600},"visualViewport":{"offsetX":0,"offsetY":0,"pageX":46,"pageY":0,"clientWidth":800,"clientHeight":600,"scale":1,"zoom":1},"contentSize":{"x":0,"y":0,"width":1250,"height":600},"cssLayoutViewport":{"pageX":46,"pageY":0,"clientWidth":800,"clientHeight":600},"cssVisualViewport":{"offsetX":0,"offsetY":0,"pageX":46,"pageY":0,"clientWidth":800,"clientHeight":600,"scale":1,"zoom":1},"cssContentSize":{"x":0,"y":0,"width":1250,"height":600}},"sessionId":"27EEFE1B14B793C7E48B3355D12843B0"}You can either upgrade to Chrome 91 or stick with
github.com/chromedp/[email protected](the latest version before the breaking change).If you want to stick with
github.com/chromedp/[email protected], you can use thereplacedirective in thego.modfile like this:replace github.com/chromedp/cdproto v0.0.0-20210318231247-733a37e2c059 => github.com/chromedp/cdproto v0.0.0-20210305224431-50b9f457e822And then run
go mod download github.com/chromedp/[email protected].
Thank you very much for your help. You're right, that's the problem. I downgraded the version of cdproto. Your answer is very professional.
For what it's worth, I have zero idea why the Chrome DevTools team decided to change this stable API. Nothing in the API was changed other than prefixing the names with css, which is really fundamentally confusing. This creates problems (obviously) for static code generators that would like to be backwards and forwards compatible with CDP versions. I'm looking into what the easiest fix for this is. Apologies.
I've decided that I'm just going to force the browser version used to the old version. This is such a breaking change that I can't imagine this is going to land in a stable version of the browser. If it v91 does become stable with this massive, breaking change, I'll address it then. I'll push a fix to cdproto shortly. In the interim, if you want to use Chromium v91, you'll need to keep it at the current broken version, and/or just don't use this functionality. Alternately, one can always run a direct message and decode the results.
I just tagged a new chromedp with old cdproto definitions. Unless you're using a dev build of chromium, the examples should work now. go get -u should fix everything. Again, apologies -- when the change came through I had not scrutinized this change enough using the automated tools I have built.
I just tagged a new
chromedpwith oldcdprotodefinitions. Unless you're using adevbuild of chromium, the examples should work now.go get -ushould fix everything. Again, apologies -- when the change came through I had not scrutinized this change enough using the automated tools I have built.
Well, as you said, the problem has been solved. Thank you very much for your quick processing, which helped a lot. 👍
Most helpful comment
Here's what I'm using. As a bonus it also handles page scaling.
Click to expand