blob: 0146f585d505238e879b0b74f8dd077105f4dfa5 [file] [log] [blame]
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package screenshot
import (
"context"
"encoding/base64"
"image"
_ "image/png" // PNG decoder
"io"
"io/ioutil"
"os"
"strings"
"chromiumos/tast/common/testexec"
"chromiumos/tast/errors"
"chromiumos/tast/local/chrome"
"chromiumos/tast/local/chrome/ash"
"chromiumos/tast/local/coords"
)
// Capture takes a screenshot and saves it as a PNG image to the specified file
// path. It will use the CLI screenshot command to perform the screen capture.
func Capture(ctx context.Context, path string) error {
cmd := testexec.CommandContext(ctx, "screenshot", path)
if err := cmd.Run(testexec.DumpLogOnError); err != nil {
return errors.Errorf("failed running %q", strings.Join(cmd.Args, " "))
}
return nil
}
// CaptureWithStderr differs from Capture in that it returns the stderr when
// capturing a screenshot fails. This is useful for verification on whether turning display
// on/off is successful by matching with the message, "CRTC not found. Is the screen on?".
func CaptureWithStderr(ctx context.Context, path string) error {
_, stderr, err := testexec.CommandContext(ctx, "screenshot", path).SeparatedOutput()
if err != nil {
return errors.Wrapf(err, "failed running %q", stderr)
}
return nil
}
// CaptureChrome takes a screenshot of the primary display and saves it as a PNG
// image to the specified file path. It will use Chrome to perform the screen capture.
func CaptureChrome(ctx context.Context, cr *chrome.Chrome, path string) error {
tconn, err := cr.TestAPIConn(ctx)
if err != nil {
return err
}
return captureInternal(ctx, path, func(code string, out interface{}) error {
return tconn.Eval(ctx, code, out)
})
}
const (
// Do not use tast.promisify(), because this may be evaluated on the connection
// other than TestAPIConn.
takeScreenshot = `new Promise(function(resolve, reject) {
chrome.autotestPrivate.takeScreenshot(function(base64PNG) {
if (chrome.runtime.lastError === undefined) {
resolve(base64PNG);
} else {
reject(chrome.runtime.lastError.message);
}
});
})`
)
// CaptureChromeImage takes a screenshot of the primary display and returns
// it as an image.Image. It will use Chrome to perform the screen capture.
func CaptureChromeImage(ctx context.Context, cr *chrome.Chrome) (image.Image, error) {
tconn, err := cr.TestAPIConn(ctx)
if err != nil {
return nil, err
}
return CaptureChromeImageWithTestAPI(ctx, tconn)
}
// CaptureChromeImageWithTestAPI takes a screenshot of the primary display and
// returns it as an image.Image. It will use Test API to perform the screen
// capture.
func CaptureChromeImageWithTestAPI(ctx context.Context, tconn *chrome.TestConn) (image.Image, error) {
var base64PNG string
if err := tconn.Eval(ctx, takeScreenshot, &base64PNG); err != nil {
return nil, err
}
sr := strings.NewReader(base64PNG)
img, _, err := image.Decode(base64.NewDecoder(base64.StdEncoding, sr))
return img, err
}
// CaptureCDP takes a screenshot and saves it as a PNG image at path, similar to
// CaptureChrome.
// The diff from CaptureChrome is that this function takes *cdputil.Conn, which
// is used by chrome.Conn. Thus, CaptureChrome records logs in case of error,
// while this does not. XXX
func CaptureCDP(ctx context.Context, conn *ash.DevtoolsConn, path string) error {
return captureInternal(ctx, path, func(code string, out interface{}) error {
_, err := conn.Eval(ctx, code, true /* awaitPromise */, out)
return err
})
}
func captureInternal(ctx context.Context, path string, eval func(code string, out interface{}) error) error {
var base64PNG string
if err := eval(takeScreenshot, &base64PNG); err != nil {
return err
}
f, err := os.Create(path)
if err != nil {
return err
}
defer f.Close()
sr := strings.NewReader(base64PNG)
if _, err = io.Copy(f, base64.NewDecoder(base64.StdEncoding, sr)); err != nil {
return err
}
return nil
}
// CaptureChromeForDisplay takes a screenshot for a given displayID and saves it as a PNG
// image to the specified file path. It will use Chrome to perform the screen capture.
func CaptureChromeForDisplay(ctx context.Context, cr *chrome.Chrome, displayID, path string) error {
tconn, err := cr.TestAPIConn(ctx)
if err != nil {
return err
}
var base64PNG string
if err := tconn.Call(ctx, &base64PNG, "tast.promisify(chrome.autotestPrivate.takeScreenshotForDisplay)", displayID); err != nil {
return err
}
f, err := os.Create(path)
if err != nil {
return err
}
defer f.Close()
sr := strings.NewReader(base64PNG)
if _, err = io.Copy(f, base64.NewDecoder(base64.StdEncoding, sr)); err != nil {
return err
}
return nil
}
// GrabAndCropScreenshot grabs a screenshot and crops it to the specified bounds.
func GrabAndCropScreenshot(ctx context.Context, cr *chrome.Chrome, bounds coords.Rect) (image.Image, error) {
img, err := GrabScreenshot(ctx, cr)
if err != nil {
return nil, err
}
subImage := img.(interface {
SubImage(r image.Rectangle) image.Image
}).SubImage(image.Rect(bounds.Left, bounds.Top, bounds.Right(), bounds.Bottom()))
return subImage, nil
}
// GrabScreenshot creates a screenshot and returns an image.Image.
// The path of the image is generated ramdomly in /tmp.
func GrabScreenshot(ctx context.Context, cr *chrome.Chrome) (image.Image, error) {
fd, err := ioutil.TempFile("", "screenshot")
if err != nil {
return nil, errors.Wrap(err, "error opening screenshot file")
}
defer os.Remove(fd.Name())
defer fd.Close()
if err := CaptureChrome(ctx, cr, fd.Name()); err != nil {
return nil, errors.Wrap(err, "failed to capture screenshot")
}
img, _, err := image.Decode(fd)
if err != nil {
return nil, errors.Wrap(err, "error decoding image file")
}
return img, nil
}