cypress-image-diff-js
Version:
Visual regression testing tool with cypress
417 lines (335 loc) • 17 kB
JavaScript
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _typeof = require("@babel/runtime/helpers/typeof");
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
var _fsExtra = _interopRequireDefault(require("fs-extra"));
var _pixelmatch = _interopRequireDefault(require("pixelmatch"));
var _pngjs = require("pngjs");
var _path = _interopRequireDefault(require("path"));
var _utils = require("./utils");
var _config = _interopRequireWildcard(require("./config"));
var _testStatus = _interopRequireDefault(require("./reporter/test-status"));
var _reporter = require("./reporter");
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) { symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); } keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { (0, _defineProperty2["default"])(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
var testStatuses = [];
var setupFolders = function setupFolders() {
(0, _utils.createDir)([_config["default"].dir.baseline, _config["default"].dir.comparison, _config["default"].dir.diff, _config["default"].reportDir]);
};
var tearDownDirs = function tearDownDirs() {
(0, _utils.cleanDir)([_config["default"].dir.comparison, _config["default"].dir.diff]);
};
var generateReport = function generateReport() {
var instance = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
if (testStatuses.length > 0) {
(0, _reporter.createReport)({
tests: JSON.stringify(testStatuses),
instance: instance
});
}
return true;
};
var deleteReport = function deleteReport(args) {
testStatuses = testStatuses.filter(function (testStatus) {
return testStatus.name !== args.testName;
});
return true;
};
var copyScreenshot = function copyScreenshot(args) {
// If baseline does not exist, copy comparison image to baseline folder
if (!_fsExtra["default"].existsSync(_config["default"].image.baseline(args.testName))) {
_fsExtra["default"].copySync(_config["default"].image.comparison(args.testName), _config["default"].image.baseline(args.testName));
}
return true;
}; // Delete screenshot from comparison and diff directories
var deleteScreenshot = function deleteScreenshot(args) {
if (_fsExtra["default"].existsSync(_config["default"].image.comparison(args.testName))) {
_fsExtra["default"].unlinkSync(_config["default"].image.comparison(args.testName));
}
if (_fsExtra["default"].existsSync(_config["default"].image.diff(args.testName))) {
_fsExtra["default"].unlinkSync(_config["default"].image.diff(args.testName));
}
return true;
};
var getStatsComparisonAndPopulateDiffIfAny = /*#__PURE__*/function () {
var _ref = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee(args) {
var baselineImg, comparisonImg, diff, baselineFullCanvas, comparisonFullCanvas, pixelMismatchResult, percentage, testFailed, stream;
return _regenerator["default"].wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.prev = 0;
_context.next = 3;
return (0, _utils.parseImage)(_config["default"].image.baseline(args.testName));
case 3:
baselineImg = _context.sent;
_context.next = 9;
break;
case 6:
_context.prev = 6;
_context.t0 = _context["catch"](0);
return _context.abrupt("return", args.failOnMissingBaseline ? {
percentage: 1,
testFailed: true
} : {
percentage: 0,
testFailed: false
});
case 9:
_context.prev = 9;
_context.next = 12;
return (0, _utils.parseImage)(_config["default"].image.comparison(args.testName));
case 12:
comparisonImg = _context.sent;
_context.next = 18;
break;
case 15:
_context.prev = 15;
_context.t1 = _context["catch"](9);
return _context.abrupt("return", {
percentage: 1,
testFailed: true
});
case 18:
diff = new _pngjs.PNG({
width: Math.max(comparisonImg.width, baselineImg.width),
height: Math.max(comparisonImg.height, baselineImg.height)
});
_context.next = 21;
return (0, _utils.adjustCanvas)(baselineImg, diff.width, diff.height);
case 21:
baselineFullCanvas = _context.sent;
_context.next = 24;
return (0, _utils.adjustCanvas)(comparisonImg, diff.width, diff.height);
case 24:
comparisonFullCanvas = _context.sent;
pixelMismatchResult = (0, _pixelmatch["default"])(baselineFullCanvas.data, comparisonFullCanvas.data, diff.data, diff.width, diff.height, _config.userConfig.COMPARISON_OPTIONS);
percentage = Math.pow(pixelMismatchResult / diff.width / diff.height, 0.5);
testFailed = percentage > args.testThreshold;
if (!testFailed) {
_context.next = 33;
break;
}
_fsExtra["default"].ensureFileSync(_config["default"].image.diff(args.testName));
stream = diff.pack().pipe(_fsExtra["default"].createWriteStream(_config["default"].image.diff(args.testName))); // make sure the diff image fully populated before proceeding further
_context.next = 33;
return new Promise(function (resolve, reject) {
stream.once('finish', resolve);
stream.once('error', reject);
});
case 33:
return _context.abrupt("return", {
percentage: percentage,
testFailed: testFailed
});
case 34:
case "end":
return _context.stop();
}
}
}, _callee, null, [[0, 6], [9, 15]]);
}));
return function getStatsComparisonAndPopulateDiffIfAny(_x) {
return _ref.apply(this, arguments);
};
}();
function compareSnapshotsPlugin(_x2) {
return _compareSnapshotsPlugin.apply(this, arguments);
}
function _compareSnapshotsPlugin() {
_compareSnapshotsPlugin = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee4(args) {
var _yield$getStatsCompar, percentage, testFailed, newTest, _yield$Promise$all, _yield$Promise$all2, baselineDataUrl, diffDataUrl, comparisonDataUrl;
return _regenerator["default"].wrap(function _callee4$(_context4) {
while (1) {
switch (_context4.prev = _context4.next) {
case 0:
_context4.next = 2;
return getStatsComparisonAndPopulateDiffIfAny(args);
case 2:
_yield$getStatsCompar = _context4.sent;
percentage = _yield$getStatsCompar.percentage;
testFailed = _yield$getStatsCompar.testFailed;
// Saving test status object to build report if task is triggered
newTest = new _testStatus["default"]({
status: !testFailed,
name: args.testName,
percentage: percentage,
failureThreshold: args.testThreshold,
specFilename: args.specFilename,
specPath: args.specPath,
baselinePath: (0, _utils.getRelativePathFromCwd)(_config["default"].image.baseline(args.testName)),
diffPath: (0, _utils.getRelativePathFromCwd)(_config["default"].image.diff(args.testName)),
comparisonPath: (0, _utils.getRelativePathFromCwd)(_config["default"].image.comparison(args.testName))
});
if (!args.inlineAssets) {
_context4.next = 15;
break;
}
_context4.next = 9;
return Promise.all([(0, _utils.toBase64)(newTest.baselinePath), (0, _utils.toBase64)(newTest.diffPath), (0, _utils.toBase64)(newTest.comparisonPath)]);
case 9:
_yield$Promise$all = _context4.sent;
_yield$Promise$all2 = (0, _slicedToArray2["default"])(_yield$Promise$all, 3);
baselineDataUrl = _yield$Promise$all2[0];
diffDataUrl = _yield$Promise$all2[1];
comparisonDataUrl = _yield$Promise$all2[2];
newTest = _objectSpread(_objectSpread({}, newTest), {}, {
baselineDataUrl: baselineDataUrl,
diffDataUrl: diffDataUrl,
comparisonDataUrl: comparisonDataUrl
});
case 15:
testStatuses.push(newTest);
return _context4.abrupt("return", percentage);
case 17:
case "end":
return _context4.stop();
}
}
}, _callee4);
}));
return _compareSnapshotsPlugin.apply(this, arguments);
}
var generateJsonReport = /*#__PURE__*/function () {
var _ref2 = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee2(results) {
var testsMappedBySpecPath, suites, totalPassed, stats, jsonFilename, jsonPath;
return _regenerator["default"].wrap(function _callee2$(_context2) {
while (1) {
switch (_context2.prev = _context2.next) {
case 0:
testsMappedBySpecPath = testStatuses.reduce(function (map, item) {
if (map[item.specPath] === undefined) {
// eslint-disable-next-line no-param-reassign
map[item.specPath] = {
name: item.specFilename,
path: item.specPath,
tests: []
};
}
map[item.specPath].tests.push(item);
return map;
}, {});
suites = Object.values(testsMappedBySpecPath);
totalPassed = testStatuses.filter(function (t) {
return t.status === 'pass';
}).length;
stats = {
total: testStatuses.length,
totalPassed: totalPassed,
totalFailed: testStatuses.length - totalPassed,
totalSuites: suites.length,
suites: suites,
startedAt: results.startedTestsAt,
endedAt: results.endedTestsAt,
duration: results.totalDuration,
browserName: results.browserName,
browserVersion: results.browserVersion,
cypressVersion: results.cypressVersion
};
jsonFilename = _config.userConfig.JSON_REPORT.FILENAME ? "".concat(_config.userConfig.JSON_REPORT.FILENAME, ".json") : "report_".concat((0, _utils.getCleanDate)(stats.startedAt), ".json");
jsonPath = _path["default"].join(_config["default"].reportDir, jsonFilename);
if (!_config.userConfig.JSON_REPORT.OVERWRITE) {
_context2.next = 11;
break;
}
_context2.next = 9;
return _fsExtra["default"].writeFile(jsonPath, JSON.stringify(stats, null, 2));
case 9:
_context2.next = 13;
break;
case 11:
_context2.next = 13;
return (0, _utils.writeFileIncrement)(jsonPath, JSON.stringify(stats, null, 2));
case 13:
case "end":
return _context2.stop();
}
}
}, _callee2);
}));
return function generateJsonReport(_x3) {
return _ref2.apply(this, arguments);
};
}();
var generateJsonReportTask = function generateJsonReportTask(result) {
generateJsonReport(result || {});
return true;
};
var getCompareSnapshotsPlugin = function getCompareSnapshotsPlugin(on, config) {
// Create folder structure
setupFolders(); // Delete comparison and diff images to ensure a clean run
tearDownDirs(); // Force screenshot resolution to keep consistency of test runs across machines
on('before:browser:launch', function (browser, launchOptions) {
var width = config.viewportWidth || '1280';
var height = config.viewportHeight || '720';
if (browser.name === 'chrome') {
launchOptions.args.push("--window-size=".concat(width, ",").concat(height));
launchOptions.args.push('--force-device-scale-factor=1');
}
if (browser.name === 'electron') {
// eslint-disable-next-line no-param-reassign
launchOptions.preferences.width = Number.parseInt(width, 10); // eslint-disable-next-line no-param-reassign
launchOptions.preferences.height = Number.parseInt(height, 10);
}
if (browser.name === 'firefox') {
launchOptions.args.push("--width=".concat(width));
launchOptions.args.push("--height=".concat(height));
}
return launchOptions;
}); // Intercept cypress screenshot and create a new image with our own
// name convention and file structure for simplicity and consistency
on('after:screenshot', function (details) {
// A screenshot could be taken automatically due to a test failure
// and not a call to cy.compareSnapshot / cy.screenshot. These files
// should be left alone
if (details.testFailure) {
return;
} // Change screenshots file permission so it can be moved from drive to drive
(0, _utils.setFilePermission)(details.path, 511);
(0, _utils.setFilePermission)(_config["default"].image.comparison(details.name), 511);
if (config.env.preserveOriginalScreenshot === true) {
(0, _utils.renameAndCopyFile)(details.path, _config["default"].image.comparison(details.name));
} else {
(0, _utils.renameAndMoveFile)(details.path, _config["default"].image.comparison(details.name));
}
});
on('after:run', /*#__PURE__*/function () {
var _ref3 = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee3(results) {
return _regenerator["default"].wrap(function _callee3$(_context3) {
while (1) {
switch (_context3.prev = _context3.next) {
case 0:
if (!_config.userConfig.JSON_REPORT) {
_context3.next = 3;
break;
}
_context3.next = 3;
return generateJsonReport(results);
case 3:
case "end":
return _context3.stop();
}
}
}, _callee3);
}));
return function (_x4) {
return _ref3.apply(this, arguments);
};
}());
on('task', {
compareSnapshotsPlugin: compareSnapshotsPlugin,
copyScreenshot: copyScreenshot,
deleteScreenshot: deleteScreenshot,
generateReport: generateReport,
deleteReport: deleteReport,
generateJsonReport: generateJsonReportTask
}); // eslint-disable-next-line no-param-reassign
config.env.cypressImageDiff = _config.userConfig;
return config;
};
module.exports = getCompareSnapshotsPlugin;
;