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)
}
}
}
@ -224,16 +224,19 @@ const (
type testcase struct {
name string
pathame string
tasks chromedp.Tasks
originA string
originB string
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.
func readTests(file string) ([]*testcase, error) {
f, err := os.Open(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,
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))
var errs errgroup.Group
errs.Go(func() error {
out := imgdiff.Diff(imgA, imgB, &imgdiff.Options{
result := imgdiff.Diff(imgA, imgB, &imgdiff.Options{
Threshold: 0.1,
DiffImage: true,
})
return writePNG(&out.Image, outfile+".diff")
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 {
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")
}