Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Blank or cut screenshots when using high resolutions #1137

Closed
amrap030 opened this issue Aug 17, 2022 · 13 comments
Closed

Blank or cut screenshots when using high resolutions #1137

amrap030 opened this issue Aug 17, 2022 · 13 comments

Comments

@amrap030
Copy link

What versions are you running?

$ go list -m github.com/chromedp/chromedp
github.com/chromedp/chromedp v0.8.4
$ google-chrome --version
104.0.5112.79
$ go version
go version go1.19 darwin/amd64

What did you do? Include clear steps.

I am trying to take a screenshot of a page that has some .svg content and some text and provide this functionality via REST API.

// Command screenshot is a chromedp example demonstrating how to take a
// screenshot of a specific element and of the entire browser viewport.
package main

import (
	"context"
	"io/ioutil"
	"net/http"
	"log"
	"github.com/chromedp/chromedp"
)

func createScreenshot(w http.ResponseWriter, r *http.Request) {
	// create context
	ctx, cancel := chromedp.NewContext(
		context.Background(),
		// chromedp.WithDebugf(log.Printf),
	)
	defer cancel()
	// capture screenshot of an element
	var buf []byte

	chromedp.WindowSize(7086, 9448)
	// capture entire browser viewport
	if err := chromedp.Run(ctx, chromedp.Navigate("...url"), chromedp.EmulateViewport(7086, 9448), chromedp.WaitVisible(`mondrian`, chromedp.ByID), chromedp.FullScreenshot(&buf, 100)); err != nil {
		log.Fatal(err)
	}
	if err := ioutil.WriteFile("fullScreenshot.png", buf, 0o644); err != nil {
		log.Fatal(err)
	}

	w.WriteHeader(http.StatusOK)
	w.Header().Set("Content-Type", "application/octet-stream")
	w.Write(buf)
}

func main() {
	handler := http.HandlerFunc(createScreenshot)
	http.Handle("/screenshot", handler)
	http.ListenAndServe("localhost:3333", nil)
}

What did you expect to see?

I would expect, that the API always returns a correct screenshot with all of the content that I would expect to see.

What did you see instead?

When using chromedp.EmulateViewport(7086, 9448) it very often returns blank images or images where the content is cut off like the following:

fullScreenshot_1

When I remove chromedp.EmulateViewport(7086, 9448) I haven't noticed any issues so far, but I am not sure if this was just luck. I need to reliably wait until everything on the screen is visible before taking a Screenshot. I don't know why it sometimes works and sometimes doesn't.

@ZekeLu
Copy link
Member

ZekeLu commented Aug 17, 2022

Please provide the URL they can be used to reproduce the issue.

@ZekeLu
Copy link
Member

ZekeLu commented Aug 17, 2022

I can get the expected screenshot on both Ubuntu and iMac with this demo:

package main

import (
	"context"
	"io/ioutil"
	"log"

	"github.com/chromedp/chromedp"
)

func main() {
	// create context
	ctx, cancel := chromedp.NewContext(
		context.Background(),
		// chromedp.WithDebugf(log.Printf),
	)
	defer cancel()

	var buf []byte

	// capture entire browser viewport
	if err := chromedp.Run(ctx,
		chromedp.Navigate("https://magic-mondrian.netlify.app/screenshot?mintAddress=0xe3bbf29f034fA780407Fd11dac7A0B3938b1bc6a&tokenId=1&timestamp=1660606604&url=ipfs://bafybeienieybucxgxkw6tspsjoxb5jj4kbsljbckck2e4r3pymyiilhzza/5.svg"),
		chromedp.EmulateViewport(7086, 9448),
		chromedp.WaitVisible(`mondrian`, chromedp.ByID),
		chromedp.FullScreenshot(&buf, 100),
	); err != nil {
		log.Fatal(err)
	}
	if err := ioutil.WriteFile("fullScreenshot.png", buf, 0o644); err != nil {
		log.Fatal(err)
	}
}

Did I miss anything?

@amrap030
Copy link
Author

I just tried your code and already the first try was again almost completely blank and the second try was the following:

fullScreenshot

Have you tried it multiple times? Because sometimes it also works for me but the majority of times it doesn't work.

@amrap030
Copy link
Author

I tested this on an M1 Macbook Pro btw.

@ZekeLu
Copy link
Member

ZekeLu commented Aug 18, 2022

That's because the image is not fully loaded before you capture the screenshot. You have to wait until the image is fully loaded. Please note that chromedp.WaitVisible("mondrian", chromedp.ByID) does not work in this case. And please note that the demo below does not work for all kinds of pages. You have to study each page and find a way to determine whether that page is fully loaded yourself.

package main

import (
	"context"
	"io/ioutil"
	"log"
	"sync"

	"github.com/chromedp/cdproto/page"
	"github.com/chromedp/chromedp"
)

func main() {
	ctx, cancel := chromedp.NewContext(
		context.Background(),
		// chromedp.WithDebugf(log.Printf),
	)
	defer cancel()

	var wg sync.WaitGroup

	wg.Add(1)

	chromedp.ListenTarget(ctx, func(ev interface{}) {
		if ev, ok := ev.(*page.EventLifecycleEvent); ok {
			if ev.Name == "firstMeaningfulPaint" {
				wg.Done()
			}
		}
	})

	var buf []byte

	// capture entire browser viewport
	if err := chromedp.Run(ctx,
		chromedp.Navigate("https://magic-mondrian.netlify.app/screenshot?mintAddress=0xe3bbf29f034fA780407Fd11dac7A0B3938b1bc6a&tokenId=1&timestamp=1660606604&url=ipfs://bafybeienieybucxgxkw6tspsjoxb5jj4kbsljbckck2e4r3pymyiilhzza/5.svg"),
		chromedp.EmulateViewport(7086, 9448),
		chromedp.ActionFunc(func(ctx context.Context) error {
			wg.Wait()
			return nil
		}),
		chromedp.FullScreenshot(&buf, 100),
	); err != nil {
		log.Fatal(err)
	}
	if err := ioutil.WriteFile("fullScreenshot.png", buf, 0o644); err != nil {
		log.Fatal(err)
	}
}

@amrap030
Copy link
Author

amrap030 commented Aug 18, 2022

Not really sure what to do now, the code you posted has the same result. How can I reliably wait? I am also not really familiar with golang, but I wanted to try it since I used puppeteer for node js before, but I had the exact same problem.

I find it also really strange that texts are cut off, because this is something that doesn't need to be loaded via an API call. Text information should be there immediately. It feels like the image is generated from top to bottom and if it is saved too early, it gets cut off at the point where the "image generator" was.

@ZekeLu
Copy link
Member

ZekeLu commented Aug 18, 2022

Sorry that I haven't provided the correct event to wait for. Like I said before, you have to study the page to find a way to determine whether that page is fully loaded. Maybe you can wait for the svg file is received. Please try the next demo:

package main

import (
	"context"
	"io/ioutil"
	"log"
	"sync"

	"github.com/chromedp/cdproto/network"
	"github.com/chromedp/chromedp"
)

func main() {
	ctx, cancel := chromedp.NewContext(
		context.Background(),
		// chromedp.WithDebugf(log.Printf),
	)
	defer cancel()

	var wg sync.WaitGroup

	wg.Add(1)

	chromedp.ListenTarget(ctx, func(ev interface{}) {
		if ev, ok := ev.(*network.EventResponseReceived); ok {
			if ev.Response.URL == "https://cloudflare-ipfs.com/ipfs/bafybeienieybucxgxkw6tspsjoxb5jj4kbsljbckck2e4r3pymyiilhzza/5.svg" {
				wg.Done()
			}
		}
	})

	var buf []byte

	// capture entire browser viewport
	if err := chromedp.Run(ctx,
		chromedp.Navigate("https://magic-mondrian.netlify.app/screenshot?mintAddress=0xe3bbf29f034fA780407Fd11dac7A0B3938b1bc6a&tokenId=1&timestamp=1660606604&url=ipfs://bafybeienieybucxgxkw6tspsjoxb5jj4kbsljbckck2e4r3pymyiilhzza/5.svg"),
		chromedp.EmulateViewport(7086, 9448),
		chromedp.ActionFunc(func(ctx context.Context) error {
			wg.Wait()
			return nil
		}),
		chromedp.FullScreenshot(&buf, 100),
	); err != nil {
		log.Fatal(err)
	}
	if err := ioutil.WriteFile("fullScreenshot.png", buf, 0o644); err != nil {
		log.Fatal(err)
	}
}

You can umcomment this line chromedp.WithDebugf(log.Printf) to enable the debug log and find which event to wait for.

@amrap030
Copy link
Author

amrap030 commented Aug 18, 2022

Thank you, but still the same behaviour. Would it be possible to implement something in the frontend that definitely tells chromedp the page has been fully finished?

I would imagine some variable that is set and I am going to wait for that variable to be true or something like that?

It definitely has something to do with the viewport size. When I comment out chromedp.EmulateViewport(7086, 9448) it works completely fine, but I unfortunately need this resolution. Maybe when setting it to this resolution it rerenders the page and while rerendering, chromedp makes the screenshot? Could this be the reason?

@amrap030
Copy link
Author

I tried to move the line chromedp.EmulateViewport(7086, 9448) right before chromedp.FullScreenshot(&buf, 100) and so far it seems to be working. I will try it several more times and give feedback.

package main

import (
	"context"
	"io/ioutil"
	"log"
	"sync"

	"github.com/chromedp/cdproto/page"
	"github.com/chromedp/chromedp"
)

func main() {
	ctx, cancel := chromedp.NewContext(
		context.Background(),
		chromedp.WithDebugf(log.Printf),
	)
	defer cancel()

	var wg sync.WaitGroup

	wg.Add(1)

	chromedp.ListenTarget(ctx, func(ev interface{}) {
		if ev, ok := ev.(*page.EventLifecycleEvent); ok {
			if ev.Name == "firstMeaningfulPaint" {
				wg.Done()
			}
		}
	})

	var buf []byte

	// capture entire browser viewport
	if err := chromedp.Run(ctx,
		chromedp.Navigate("https://magic-mondrian.netlify.app/screenshot?mintAddress=0xe3bbf29f034fA780407Fd11dac7A0B3938b1bc6a&tokenId=1&timestamp=1660606604&url=ipfs://bafybeienieybucxgxkw6tspsjoxb5jj4kbsljbckck2e4r3pymyiilhzza/5.svg"),
		chromedp.ActionFunc(func(ctx context.Context) error {
			wg.Wait()
			return nil
		}),
		chromedp.EmulateViewport(7086, 9448),
		chromedp.FullScreenshot(&buf, 100),
	); err != nil {
		log.Fatal(err)
	}
	if err := ioutil.WriteFile("fullScreenshot.png", buf, 0o644); err != nil {
		log.Fatal(err)
	}
}

@amrap030
Copy link
Author

amrap030 commented Aug 18, 2022

It really works! But now I am not able to create a docker container. I can run it, but it says 2022/08/18 14:21:28 exec: "google-chrome": executable file not found in $PATH

I am using this Dockerfile:

# Start from golang base image
FROM golang:alpine

# Install git.
# Git is required for fetching the dependencies.
RUN apk update && apk add --no-cache git && apk add --no-cach bash && apk add build-base

# Setup folders
RUN mkdir /app
WORKDIR /app

# Copy the source from the current directory to the working Directory inside the container
COPY . .

# Download all the dependencies
RUN go get -d -v ./...

# Install the package
RUN go install -v ./...

# Build the Go app
RUN go build -o /build

# Expose port 8080 to the outside world
EXPOSE 8080

# Run the executable
CMD [ "/build" ]

I also found this: #377 but I haven't been able to figure out how to use this. I tried this Dockerfile:

# =============== build stage ===============
FROM golang:1.16.5-buster AS build
WORKDIR /app

COPY go.* ./
RUN go mod download -x all

COPY . ./
# ldflags:
#  -s: disable symbol table
#  -w: disable DWARF generation
# run `go tool link -help` to get the full list of ldflags
#RUN go build -ldflags "-s -w" -o screenly -v ./main.go
RUN go build -ldflags "-s -w" -o screen-api -v main.go
# =============== final stage ===============
FROM chromedp/headless-shell:93.0.4535.3 AS final

WORKDIR /app
COPY --from=build /app/screen-api ./
ENTRYPOINT ["/app/screen-api"]

Then I ran: docker build -t screenly . and docker run -p 3333:3333 screenly but I get the error: exec /app/screen-api: no such file or directory

@amrap030
Copy link
Author

amrap030 commented Aug 18, 2022

With this Dockerfile it works:

FROM golang:1.16.5 AS build-env

RUN apt update && apt -y upgrade 

RUN apt -y install chromium

WORKDIR /app

ADD ./ ./

# Download all the dependencies
RUN go get -d -v ./...

# Install the package
RUN go install -v ./...

# Build the Go app
RUN go build -o /build

# Expose port 8080 to the outside world
EXPOSE 3333

# Run the executable
CMD [ "/build" ]

But now the fonts are wrong. It drives me crazy^^. One problem solved and then there is the next problem.

Should look like this:

Bildschirmfoto 2022-08-18 um 17 16 51

But it looks like this:

Bildschirmfoto 2022-08-18 um 17 17 11

Not sure why, because the fonts are actually hosted by our application.

@amrap030
Copy link
Author

Ok, I got the fonts working, but I have another issue. Since the actual issue is fixed, I am going to open another one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants