From dc956758886941601506bda61e3ab50672d747ea Mon Sep 17 00:00:00 2001 From: Jamal Carvalho Date: Fri, 17 Dec 2021 14:37:02 +0000 Subject: [PATCH] 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 TryBot-Result: Gopher Robot Reviewed-by: Jonathan Amsterdam Trust: Jamal Carvalho --- internal/screentest/screentest.go | 105 ++++++++++++++----------- internal/screentest/screentest_test.go | 62 ++++++++++----- 2 files changed, 101 insertions(+), 66 deletions(-) diff --git a/internal/screentest/screentest.go b/internal/screentest/screentest.go index 49e432c7..d34991e6 100644 --- a/internal/screentest/screentest.go +++ b/internal/screentest/screentest.go @@ -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 { diff --git a/internal/screentest/screentest_test.go b/internal/screentest/screentest_test.go index f0c90e36..d5eb01d7 100644 --- a/internal/screentest/screentest_test.go +++ b/internal/screentest/screentest_test.go @@ -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") +}