internal/screentest: cleanup implementation

Updated the test reader to generate outfile paths
prior to test runs so that it is easier to read/write
to a cache of images in a future CL.

Change-Id: I3f27194c3fb7d215d5d258fe24da0b2b8ff53a5f
Reviewed-on: https://go-review.googlesource.com/c/website/+/373476
Run-TryBot: Jamal Carvalho <jamal@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
Trust: Jamal Carvalho <jamalcarvalho@google.com>
This commit is contained in:
Jamal Carvalho 2021-12-17 14:37:02 +00:00 коммит произвёл Jamal Carvalho
Родитель c471c5cea7
Коммит dc95675888
2 изменённых файлов: 101 добавлений и 66 удалений

Просмотреть файл

@ -137,11 +137,11 @@ func CheckHandler(glob string) error {
for _, test := range tests {
if err := runDiff(ctx, test, out); err != nil {
if !hdr {
fmt.Fprintf(&buf, "%s\n", file)
fmt.Fprintf(&buf, "inspect diffs at %s\n", out)
fmt.Fprintf(&buf, "%s\n\n", file)
hdr = true
}
fmt.Fprintf(&buf, "%v\n", err)
fmt.Fprintf(&buf, "inspect diff at %s\n\n", test.outDiff)
}
}
}
@ -223,15 +223,18 @@ const (
)
type testcase struct {
name string
pathame string
tasks chromedp.Tasks
originA string
originB string
viewportWidth int
viewportHeight int
screenshotType screenshotType
screenshotElement string
name string
tasks chromedp.Tasks
urlA, urlB string
outImgA, outImgB, outDiff string
viewportWidth int
viewportHeight int
screenshotType screenshotType
screenshotElement string
}
func (t *testcase) String() string {
return t.name
}
// readTests parses the testcases from a text file.
@ -315,12 +318,19 @@ func readTests(file string) ([]*testcase, error) {
if pathname == "" {
return nil, fmt.Errorf("missing pathname for capture on line %d", lineNo)
}
urlA, err := url.Parse(originA + pathname)
if err != nil {
return nil, fmt.Errorf("url.Parse(%q): %w", originA+pathname, err)
}
urlB, err := url.Parse(originB + pathname)
if err != nil {
return nil, fmt.Errorf("url.Parse(%q): %w", originB+pathname, err)
}
test := &testcase{
name: testName,
pathame: pathname,
tasks: tasks,
originA: originA,
originB: originB,
name: testName,
tasks: tasks,
urlA: urlA.String(),
urlB: urlB.String(),
// Default to viewportScreenshot
screenshotType: viewportScreenshot,
viewportWidth: width,
@ -339,15 +349,23 @@ func readTests(file string) ([]*testcase, error) {
if err != nil {
return nil, fmt.Errorf("splitDimensions(%q): %w", args, err)
}
test.name = testName + fmt.Sprintf(" %dx%d", w, h)
test.name += fmt.Sprintf(" %dx%d", w, h)
test.viewportWidth = w
test.viewportHeight = h
}
case "ELEMENT":
test.name = testName + fmt.Sprintf(" %s", args)
test.name += fmt.Sprintf(" %s", args)
test.screenshotType = elementScreenshot
test.screenshotElement = args
}
out, err := outDir(file)
if err != nil {
return nil, fmt.Errorf("outDir(%q): %w", file, err)
}
outfile := filepath.Join(out, sanitized(test.name))
test.outImgA = outfile + "." + sanitized(urlA.Host) + ".png"
test.outImgB = outfile + "." + sanitized(urlB.Host) + ".png"
test.outDiff = outfile + ".diff.png"
default:
// We should never reach this error.
return nil, fmt.Errorf("invalid syntax on line %d: %q", lineNo, line)
@ -391,27 +409,14 @@ func splitDimensions(text string) (width, height int, err error) {
// a diff if the screenshots do not match.
func runDiff(ctx context.Context, test *testcase, out string) error {
fmt.Printf("test %s\n", test.name)
urlA, err := url.Parse(test.originA + test.pathame)
screenA, err := captureScreenshot(ctx, test.urlA, test)
if err != nil {
return fmt.Errorf("url.Parse(%q): %w", test.originA+test.pathame, err)
return fmt.Errorf("captureScreenshot(ctx, %q, %q): %w", test.urlA, test, err)
}
urlB, err := url.Parse(test.originB + test.pathame)
screenB, err := captureScreenshot(ctx, test.urlB, test)
if err != nil {
return fmt.Errorf("url.Parse(%q): %w", test.originB+test.pathame, err)
return fmt.Errorf("captureScreenshot(ctx, %q, %q): %w", test.urlB, test, err)
}
screenA, err := captureScreenshot(ctx, urlA, test)
if err != nil {
return fmt.Errorf("fullScreenshot(ctx, %q, %q): %w", urlA, test, err)
}
screenB, err := captureScreenshot(ctx, urlB, test)
if err != nil {
return fmt.Errorf("fullScreenshot(ctx, %q, %q): %w", urlB, test, err)
}
if bytes.Equal(screenA, screenB) {
fmt.Printf("%s == %s\n\n", urlA, urlB)
return nil
}
fmt.Printf("%s != %s\n", urlA, urlB)
imgA, _, err := image.Decode(bytes.NewReader(screenA))
if err != nil {
return fmt.Errorf("image.Decode(...): %w", err)
@ -420,31 +425,35 @@ func runDiff(ctx context.Context, test *testcase, out string) error {
if err != nil {
return fmt.Errorf("image.Decode(...): %w", err)
}
outfile := filepath.Join(out, sanitized(test.name))
result := imgdiff.Diff(imgA, imgB, &imgdiff.Options{
Threshold: 0.1,
DiffImage: true,
})
if result.Equal {
fmt.Printf("%s == %s\n\n", test.urlA, test.urlB)
return nil
}
fmt.Printf("%s != %s\n", test.urlA, test.urlB)
var errs errgroup.Group
errs.Go(func() error {
out := imgdiff.Diff(imgA, imgB, &imgdiff.Options{
Threshold: 0.1,
DiffImage: true,
})
return writePNG(&out.Image, outfile+".diff")
return writePNG(&result.Image, test.outDiff)
})
errs.Go(func() error {
return writePNG(&imgA, outfile+"."+sanitized(urlA.Host))
return writePNG(&imgA, test.outImgA)
})
errs.Go(func() error {
return writePNG(&imgB, outfile+"."+sanitized(urlB.Host))
return writePNG(&imgB, test.outImgB)
})
if err := errs.Wait(); err != nil {
return fmt.Errorf("writePNG(...): %w", errs.Wait())
}
fmt.Printf("wrote diff to %s\n\n", out)
return fmt.Errorf("%s != %s", urlA, urlB)
return fmt.Errorf("%s != %s", test.urlA, test.urlB)
}
// captureScreenshot runs a series of browser actions and takes a screenshot
// of the resulting webpage in an instance of headless chrome.
func captureScreenshot(ctx context.Context, u *url.URL, test *testcase) ([]byte, error) {
func captureScreenshot(ctx context.Context, url string, test *testcase) ([]byte, error) {
var buf []byte
ctx, cancel := chromedp.NewContext(ctx)
defer cancel()
@ -452,7 +461,7 @@ func captureScreenshot(ctx context.Context, u *url.URL, test *testcase) ([]byte,
defer cancel()
tasks := chromedp.Tasks{
chromedp.EmulateViewport(int64(test.viewportWidth), int64(test.viewportHeight)),
chromedp.Navigate(u.String()),
chromedp.Navigate(url),
waitForEvent("networkIdle"),
test.tasks,
}
@ -472,9 +481,9 @@ func captureScreenshot(ctx context.Context, u *url.URL, test *testcase) ([]byte,
// writePNG writes image data to a png file.
func writePNG(i *image.Image, filename string) error {
f, err := os.Create(filename + ".png")
f, err := os.Create(filename)
if err != nil {
return fmt.Errorf("os.Create(%q): %w", filename+".png", err)
return fmt.Errorf("os.Create(%q): %w", filename, err)
}
err = png.Encode(f, *i)
if err != nil {

Просмотреть файл

@ -19,6 +19,11 @@ func TestReadTests(t *testing.T) {
type args struct {
filename string
}
d, err := os.UserCacheDir()
if err != nil {
t.Errorf("os.UserCacheDir(): %v", err)
}
cache := filepath.Join(d, "screentest")
tests := []struct {
name string
args args
@ -33,64 +38,76 @@ func TestReadTests(t *testing.T) {
want: []*testcase{
{
name: "go.dev homepage",
originA: "https://go.dev",
originB: "http://localhost:6060/go.dev",
urlA: "https://go.dev/",
urlB: "http://localhost:6060/go.dev/",
outImgA: filepath.Join(cache, "readtests-txt", "go-dev-homepage.go-dev.png"),
outImgB: filepath.Join(cache, "readtests-txt", "go-dev-homepage.localhost-6060.png"),
outDiff: filepath.Join(cache, "readtests-txt", "go-dev-homepage.diff.png"),
viewportWidth: 1536,
viewportHeight: 960,
screenshotType: fullScreenshot,
pathame: "/",
},
{
name: "go.dev homepage 540x1080",
originA: "https://go.dev",
originB: "http://localhost:6060/go.dev",
urlA: "https://go.dev/",
urlB: "http://localhost:6060/go.dev/",
outImgA: filepath.Join(cache, "readtests-txt", "go-dev-homepage-540x1080.go-dev.png"),
outImgB: filepath.Join(cache, "readtests-txt", "go-dev-homepage-540x1080.localhost-6060.png"),
outDiff: filepath.Join(cache, "readtests-txt", "go-dev-homepage-540x1080.diff.png"),
viewportWidth: 540,
viewportHeight: 1080,
screenshotType: fullScreenshot,
pathame: "/",
},
{
name: "about page",
originA: "https://go.dev",
originB: "http://localhost:6060/go.dev",
urlA: "https://go.dev/about",
urlB: "http://localhost:6060/go.dev/about",
outImgA: filepath.Join(cache, "readtests-txt", "about-page.go-dev.png"),
outImgB: filepath.Join(cache, "readtests-txt", "about-page.localhost-6060.png"),
outDiff: filepath.Join(cache, "readtests-txt", "about-page.diff.png"),
screenshotType: fullScreenshot,
viewportWidth: 1536,
viewportHeight: 960,
pathame: "/about",
},
{
name: "pkg.go.dev homepage .go-Carousel",
originA: "https://pkg.go.dev",
originB: "https://beta.pkg.go.dev",
urlA: "https://pkg.go.dev/",
urlB: "https://beta.pkg.go.dev/",
outImgA: filepath.Join(cache, "readtests-txt", "pkg-go-dev-homepage--go-Carousel.pkg-go-dev.png"),
outImgB: filepath.Join(cache, "readtests-txt", "pkg-go-dev-homepage--go-Carousel.beta-pkg-go-dev.png"),
outDiff: filepath.Join(cache, "readtests-txt", "pkg-go-dev-homepage--go-Carousel.diff.png"),
screenshotType: elementScreenshot,
screenshotElement: ".go-Carousel",
viewportWidth: 1536,
viewportHeight: 960,
pathame: "/",
tasks: chromedp.Tasks{
chromedp.Click(".go-Carousel-dot"),
},
},
{
name: "net package doc",
originA: "https://pkg.go.dev",
originB: "https://beta.pkg.go.dev",
urlA: "https://pkg.go.dev/net",
urlB: "https://beta.pkg.go.dev/net",
outImgA: filepath.Join(cache, "readtests-txt", "net-package-doc.pkg-go-dev.png"),
outImgB: filepath.Join(cache, "readtests-txt", "net-package-doc.beta-pkg-go-dev.png"),
outDiff: filepath.Join(cache, "readtests-txt", "net-package-doc.diff.png"),
screenshotType: viewportScreenshot,
viewportWidth: 1536,
viewportHeight: 960,
pathame: "/net",
tasks: chromedp.Tasks{
chromedp.WaitReady(`[role="treeitem"][aria-expanded="true"]`),
},
},
{
name: "net package doc 540x1080",
originA: "https://pkg.go.dev",
originB: "https://beta.pkg.go.dev",
urlA: "https://pkg.go.dev/net",
urlB: "https://beta.pkg.go.dev/net",
outImgA: filepath.Join(cache, "readtests-txt", "net-package-doc-540x1080.pkg-go-dev.png"),
outImgB: filepath.Join(cache, "readtests-txt", "net-package-doc-540x1080.beta-pkg-go-dev.png"),
outDiff: filepath.Join(cache, "readtests-txt", "net-package-doc-540x1080.diff.png"),
screenshotType: viewportScreenshot,
viewportWidth: 540,
viewportHeight: 1080,
pathame: "/net",
tasks: chromedp.Tasks{
chromedp.WaitReady(`[role="treeitem"][aria-expanded="true"]`),
},
@ -179,3 +196,12 @@ func TestCheckHandler(t *testing.T) {
})
}
}
func TestTestHandler(t *testing.T) {
// Skip this test if Google Chrome is not installed.
_, err := exec.LookPath("google-chrome")
if err != nil {
t.Skip()
}
TestHandler(t, "testdata/pass.txt")
}