Files
gio/cmd/gogio/js_test.go
T
Daniel Martí ed8a0c4909 cmd/gogio: extract endtoend driver base into a type
This type contains all the common bits, such as *testing.T, as well as
the channel and method used to wait for blocking until a frame is ready.

It also allows us to initialise this base separately from Start, which
keeps the exported method simpler to understand.

The base type is embedded into the specific driver types, so that the
code remains simple. While at it, start embedding *testing.T too, so
that we can write d.Fatalf instead of d.t.Fatalf. The drivers will only
have a small number of exported methods as per the interface, so it's
easy to keep those from colliding with the method set on T.

Signed-off-by: Daniel Martí <mvdan@mvdan.cc>
2020-02-06 08:15:32 +01:00

153 lines
3.7 KiB
Go

// SPDX-License-Identifier: Unlicense OR MIT
package main_test
import (
"bytes"
"context"
"errors"
"image"
"image/png"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"os/exec"
"strings"
"github.com/chromedp/cdproto/runtime"
"github.com/chromedp/chromedp"
_ "gioui.org/unit" // the build tool adds it to go.mod, so keep it there
)
type JSTestDriver struct {
driverBase
// ctx is the chromedp context.
ctx context.Context
}
func (d *JSTestDriver) Start(path string, width, height int) {
if raceEnabled {
d.Skipf("js/wasm doesn't support -race; skipping")
}
// First, build the app.
dir, err := ioutil.TempDir("", "gio-endtoend-js")
if err != nil {
d.Fatal(err)
}
d.Cleanup(func() { 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, path)
if out, err := cmd.CombinedOutput(); err != nil {
d.Fatalf("could not build app: %s:\n%s", err, out)
}
// Second, start Chrome.
opts := append(chromedp.DefaultExecAllocatorOptions[:],
chromedp.Flag("headless", *headless),
// The default would be use-gl=desktop when there's a GPU we can
// use, falling back to use-gl=swiftshader otherwise or when we
// are running in headless mode. Swiftshader allows full WebGL
// support with just a CPU.
//
// Unfortunately, many Linux distros like Arch and Alpine
// package Chromium without Swiftshader, so we can't rely on the
// defaults above. use-gl=egl works on any machine with a GPU,
// even if we run Chrome in headless mode, which is OK for now.
//
// TODO(mvdan): remove all of this once these issues are fixed:
//
// https://bugs.archlinux.org/task/64307
// https://gitlab.alpinelinux.org/alpine/aports/issues/10920
chromedp.Flag("use-gl", "egl"),
)
actx, cancel := chromedp.NewExecAllocator(context.Background(), opts...)
d.Cleanup(cancel)
ctx, cancel := chromedp.NewContext(actx,
// Send all logf/errf calls to t.Logf
chromedp.WithLogf(d.Logf),
)
d.Cleanup(cancel)
d.ctx = ctx
if err := chromedp.Run(ctx); err != nil {
if errors.Is(err, exec.ErrNotFound) {
d.Skipf("test requires Chrome to be installed: %v", err)
return
}
d.Fatal(err)
}
chromedp.ListenTarget(ctx, func(ev interface{}) {
switch ev := ev.(type) {
case *runtime.EventConsoleAPICalled:
if ev.Type == "log" && len(ev.Args) == 1 &&
// Note that the argument values are JSON.
string(ev.Args[0].Value) == `"frame ready"` {
d.frameNotifs <- true
// These logs are expected. Don't show them.
break
}
switch ev.Type {
case "log", "info", "warning", "error":
var args strings.Builder
for i, arg := range ev.Args {
if i > 0 {
args.WriteString(", ")
}
args.Write(arg.Value)
}
d.Logf("console %s: %s", ev.Type, args.String())
}
}
})
// Third, serve the app folder, set the browser tab dimensions, and
// navigate to the folder.
ts := httptest.NewServer(http.FileServer(http.Dir(dir)))
d.Cleanup(ts.Close)
if err := chromedp.Run(ctx,
chromedp.EmulateViewport(int64(width), int64(height)),
chromedp.Navigate(ts.URL),
); err != nil {
d.Fatal(err)
}
// Wait for the gio app to render.
d.waitForFrame()
}
func (d *JSTestDriver) Screenshot() image.Image {
var buf []byte
if err := chromedp.Run(d.ctx,
chromedp.CaptureScreenshot(&buf),
); err != nil {
d.Fatal(err)
}
img, err := png.Decode(bytes.NewReader(buf))
if err != nil {
d.Fatal(err)
}
return img
}
func (d *JSTestDriver) Click(x, y int) {
if err := chromedp.Run(d.ctx,
chromedp.MouseClickXY(float64(x), float64(y)),
); err != nil {
d.Fatal(err)
}
// Wait for the gio app to render after this click.
d.waitForFrame()
}