diff --git a/.gitignore b/.gitignore index 8b2754cd..2c7c3259 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /node_modules/ +/test/output /.local-chromium/ /.dev_profile* .DS_Store diff --git a/package.json b/package.json index d2416bda..a61af08b 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,9 @@ "main": "index.js", "scripts": { "test-puppeteer": "jasmine test/test.js", + "test-golden": "jasmine test/goldentest.js", "test-phantom": "python third_party/phantomjs/test/run-tests.py", - "test": "npm run test-puppeteer && npm run test-phantom", + "test": "npm run test-puppeteer && npm run test-golden && npm run test-phantom", "install": "node install.js" }, "author": "The Chromium Authors", @@ -22,9 +23,10 @@ "chromium_revision": "478524" }, "devDependencies": { - "ncp": "^2.0.0", - "minimist": "^1.2.0", "deasync": "^0.1.9", - "jasmine": "^2.6.0" + "jasmine": "^2.6.0", + "minimist": "^1.2.0", + "ncp": "^2.0.0", + "pixelmatch": "^4.0.2" } } diff --git a/test/assets/grid.html b/test/assets/grid.html new file mode 100644 index 00000000..e35d349b --- /dev/null +++ b/test/assets/grid.html @@ -0,0 +1,44 @@ + + + diff --git a/test/golden/screenshot-clip-rect.png b/test/golden/screenshot-clip-rect.png new file mode 100644 index 00000000..f541bce8 Binary files /dev/null and b/test/golden/screenshot-clip-rect.png differ diff --git a/test/golden/screenshot-sanity.png b/test/golden/screenshot-sanity.png new file mode 100644 index 00000000..fabe5b85 Binary files /dev/null and b/test/golden/screenshot-sanity.png differ diff --git a/test/goldentest.js b/test/goldentest.js new file mode 100644 index 00000000..34f65c76 --- /dev/null +++ b/test/goldentest.js @@ -0,0 +1,136 @@ +/** + * Copyright 2017 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +var fs = require('fs'); +var path = require('path'); +var rm = require('rimraf').sync; +var Browser = require('../lib/Browser'); +var StaticServer = require('./StaticServer'); +var PNG = require('pngjs').PNG; +var pixelmatch = require('pixelmatch'); + +var PORT = 8907; +var STATIC_PREFIX = 'http://localhost:' + PORT; +var GOLDEN_DIR = path.join(__dirname, 'golden'); +var OUTPUT_DIR = path.join(__dirname, 'output'); + +describe('GoldenTests', function() { + var browser; + var staticServer; + var page; + + beforeAll(function() { + browser = new Browser(); + staticServer = new StaticServer(path.join(__dirname, 'assets'), PORT); + if (fs.existsSync(OUTPUT_DIR)) + rm(OUTPUT_DIR); + }); + + afterAll(function() { + browser.close(); + staticServer.stop(); + }); + + beforeEach(SX(async function() { + page = await browser.newPage(); + })); + + afterEach(function() { + page.close(); + }); + + imageTest('screenshot-sanity.png', async function() { + await page.setViewportSize({width: 500, height: 500}); + await page.navigate(STATIC_PREFIX + '/grid.html'); + return page.screenshot('png'); + }); + + imageTest('screenshot-clip-rect.png', async function() { + await page.setViewportSize({width: 500, height: 500}); + await page.navigate(STATIC_PREFIX + '/grid.html'); + return page.screenshot('png', { + x: 50, + y: 100, + width: 150, + height: 100 + }); + }); +}); + +/** + * @param {string} fileName + * @param {function():!Promise} runner + */ +function imageTest(fileName, runner) { + var expectedPath = path.join(GOLDEN_DIR, fileName); + var actualPath = path.join(OUTPUT_DIR, fileName); + var expected = null; + if (fs.existsSync(expectedPath)) { + var buffer = fs.readFileSync(expectedPath); + expected = PNG.sync.read(buffer); + } + it(fileName, SX(async function() { + var imageBuffer = await runner(); + if (!imageBuffer || !(imageBuffer instanceof Buffer)) { + fail(fileName + ' test did not return Buffer with image.'); + return; + } + var actual = PNG.sync.read(imageBuffer); + if (!expected) { + ensureOutputDir(); + fs.writeFileSync(addSuffix(actualPath, '-actual'), imageBuffer); + fail(fileName + ' is missing in golden results.'); + return; + } + if (expected.width !== actual.width || expected.height !== actual.height) { + ensureOutputDir(); + fs.writeFileSync(addSuffix(actualPath, '-actual'), imageBuffer); + fail(`Sizes differ: expected image ${expected.width}px X ${expected.height}px, but got ${actual.width}px X ${actual.height}px`); + return; + } + var diff = new PNG({width: expected.width, height: expected.height}); + var count = pixelmatch(expected.data, actual.data, diff.data, expected.width, expected.height, {threshold: 0.1}); + if (count > 0) { + ensureOutputDir(); + fs.writeFileSync(addSuffix(actualPath, '-actual'), imageBuffer); + fs.writeFileSync(addSuffix(actualPath, '-diff'), PNG.sync.write(diff)); + fail(fileName + ' mismatch!'); + } + })); +} + +function ensureOutputDir() { + if (!fs.existsSync(OUTPUT_DIR)) + fs.mkdirSync(OUTPUT_DIR); +} + +/** + * @param {string} filePath + * @param {string} suffix + * @return {string} + */ +function addSuffix(filePath, suffix) { + var dirname = path.dirname(filePath); + var ext = path.extname(filePath); + var name = path.basename(filePath, ext); + return path.join(dirname, name + suffix + ext); +} + +// Since Jasmine doesn't like async functions, they should be wrapped +// in a SX function. +function SX(fun) { + return done => Promise.resolve(fun()).then(done).catch(done.fail); +}