Files
gio/cmd/gogio/js_test.go
T
Daniel Martí 687ea833a3 cmd/gogio: add the first end-to-end test for js via chrome
This commit adds the first fully end-to-end test. It builds a very
simple Gio app, loads it on Chrome, and checks that it works.

To control Chrome, we use chromedp, a library in pure Go that takes care
of starting the browser and talking to it via the devtools protocol.

We add the test directly in the cmd module, since it mainly interacts
with the gogio tool, and also because the code might turn into some sort
of 'gogio test' command in the future. This does add chromedp and ui as
test dependencies to go.mod, but GOPROXY should allow a 'go get' of
gogio to not download their entire source code archives.

We don't replace ui with ../../ui in the go.mod, to ensure that testing
the cmd module works from anywhere without unintended differences.

The test app being used is inside a testdata directory, to ensure it's
not go-gettable, and that it doesn't otherwise affect the cmd module.

Finally, the test itself is pretty simple. The app just paints a red
background, and the test verifies that, once loaded, the background of
the browser viewport is indeed red.

The test does currently require Chrome or Chromium to be installed,
which is fine for now. It may also require a GPU, though I don't have a
headless machine to check for sure. The test uses Chrome in headless
mode though, so it doesn't open up any visible browser window.

All in all, the test succeeds in just over a second on my laptop with
Chromium 77.0.3865.75.

Signed-off-by: Daniel Martí <mvdan@mvdan.cc>
2019-09-23 21:23:31 +02:00

105 lines
2.9 KiB
Go

// SPDX-License-Identifier: Unlicense OR MIT
package main_test
import (
"bytes"
"context"
"image/png"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"os/exec"
"testing"
"github.com/chromedp/chromedp"
_ "gioui.org/ui" // the build tool adds it to go.mod, so keep it there
)
func TestJSOnChrome(t *testing.T) {
// First, build the app.
dir, err := ioutil.TempDir("", "gio-endtoend-js")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
// TODO(mvdan): This is inefficient, as we link the gogio tool every time.
// Consider options in the future. On the plus side, this is simple.
cmd := exec.Command("go", "run", ".", "-target=js", "-o="+dir, "testdata/red.go")
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("could not build app: %s:\n%s", err, out)
}
// Second, start Chrome.
opts := append(chromedp.DefaultExecAllocatorOptions[:],
// Uncomment to get the browser's GUI.
// chromedp.Flag("headless", false),
// We need use-gl=egl instead of the default of use-gl=desktop;
// "desktop" doesn't seem to work when we're in headless mode.
// TODO(mvdan): Does egl require a GPU? If so, consider
// use-gl=swiftshader, which will use CPU-based rendering. That
// might be necessary for some CI or headless environments.
chromedp.Flag("use-gl", "egl"),
)
actx, cancel := chromedp.NewExecAllocator(context.Background(), opts...)
defer cancel()
ctx, cancel := chromedp.NewContext(actx)
defer cancel()
if err := chromedp.Run(ctx); err != nil {
// TODO(mvdan): Skip the test if chrome/chromium/headless-shell
// aren't installed.
t.Fatal(err)
}
// Third, serve the app folder, set the browser tab dimensions, and
// navigate to the folder.
ts := httptest.NewServer(http.FileServer(http.Dir(dir)))
defer ts.Close()
if err := chromedp.Run(ctx,
// A small window with 2x HiDPI.
chromedp.EmulateViewport(300, 300, chromedp.EmulateScale(2.0)),
chromedp.Navigate(ts.URL),
); err != nil {
t.Fatal(err)
}
// Finally, run the test.
// 1: Once the canvas is ready, grab a screenshot to check that the
// entirety of the viewport is red, as per the background color.
var buf []byte
if err := chromedp.Run(ctx,
chromedp.WaitReady("canvas", chromedp.ByQuery),
chromedp.CaptureScreenshot(&buf),
); err != nil {
t.Fatal(err)
}
img, err := png.Decode(bytes.NewReader(buf))
if err != nil {
t.Fatal(err)
}
size := img.Bounds().Size()
wantSize := 600 // 300px at 2.0 scaling factor
if size.X != wantSize || size.Y != wantSize {
t.Fatalf("expected dimensions to be %d*%d, got %d*%d",
wantSize, wantSize, size.X, size.Y)
}
wantColor := func(x, y int, r, g, b, a uint32) {
color := img.At(x, y)
r_, g_, b_, a_ := color.RGBA()
if r_ != r || g_ != g || b_ != b || a_ != a {
t.Errorf("got 0x%04x%04x%04x%04x at (%d,%d), want 0x%04x%04x%04x%04x",
r_, g_, b_, a_, x, y, r, g, b, a)
}
}
wantColor(5, 5, 0xffff, 0x0, 0x0, 0xffff)
wantColor(595, 595, 0xffff, 0x0, 0x0, 0xffff)
}