diff --git a/.gitignore b/.gitignore
index 8b2754cd9d4..2c7c3259832 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 d2416bdad87..a61af08bf07 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 00000000000..e35d349b562
--- /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 00000000000..f541bce8e00
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 00000000000..fabe5b85fca
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 00000000000..34f65c760b5
--- /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);
+}