UNPKG

puppeteer-screenshot-tester

Version:

Small library that allows us to compare screenshots generated by puppeteer in our tests

182 lines (170 loc) 7.74 kB
const resemble = require('nodejs-resemble'); const fs = require('fs'); const parentModule = require('parent-module'); const path = require('path'); const sharp = require('sharp'); const DEFAULT_COMPRESSION = 85; const DEFAULT_PNG_COMPRESSION = 8; // currying everywhere, that allows us to create one setup and then use tester without copying config each time const ScreenTestFactory = function( threshold = 0, includeAA = false, ignoreColors = false, matchingBox = { ignoreRectangles: [], includeRectangles: [] }, errorSettings = { errorColor: { red: 255, green: 0, blue: 255 }, errorType: 'flat', transparency: 0.7 }, outputSettings = { forceExt: null, compressionLevel: null, overrides: {} }) { if(Array.isArray(matchingBox)) { console.error(`You're using old version of API, please refer to https://github.com/burnpiro/puppeteer-screenshot-tester/releases/tag/1.3.0`); matchingBox = { ignoreRectangles: matchingBox, includeRectangles: [] } } resemble.outputSettings(errorSettings); // get path to called directory // cannot use __directory because it returns module directory instead of caller const folderPath = path.dirname(parentModule()); return new Promise( resolve => { resolve(async (page, name = 'test', screenshotOptions = {}) => { let saveFolder = folderPath; let ext = screenshotOptions.type ? `.${screenshotOptions.type}` : '.png'; if(screenshotOptions.path != null) { const puppeteerExt = path.extname(screenshotOptions.path); const puppeteerPath = path.dirname(screenshotOptions.path); const puppeteerFileName = path.basename(screenshotOptions.path, puppeteerExt); if(typeof puppeteerFileName === 'string' && puppeteerFileName.length > 0 && typeof puppeteerPath === 'string' && puppeteerPath.length > 0) { saveFolder = puppeteerPath; name = puppeteerFileName; ext = puppeteerExt || '.png'; } delete screenshotOptions.path; } // get existing image, might return undefined const oldImage = await getOldImageData(saveFolder, name, ext); // get page object from puppeteer and create screenshot without path to receive Buffer const screenShot = await page.screenshot(screenshotOptions); if (oldImage !== undefined) { // call comparison between images const comparisonResult = resemble(screenShot) .compareTo(oldImage); // Add extra options if specified if (!includeAA) { comparisonResult.ignoreAntialiasing() } if (ignoreColors) { comparisonResult.ignoreColors() } if (Array.isArray(matchingBox.ignoreRectangles) && matchingBox.ignoreRectangles.length > 0) { comparisonResult.ignoreRectangles(matchingBox.ignoreRectangles) } if (Array.isArray(matchingBox.includeRectangles) && matchingBox.includeRectangles.length > 0) { comparisonResult.includeRectangles(matchingBox.includeRectangles) } // await for a comparison to be completed and return resolved value return await new Promise(resolve => { comparisonResult.onComplete((data) => { // check if images are the same dimensions and mismatched pixels are below threshold if (data.isSameDimensions === false || Number(data.misMatchPercentage) > threshold) { // save diff to test folder with '-diff' postfix const storeExt = outputSettings.forceExt != null ? outputSettings.forceExt : ext.substring(ext.lastIndexOf(".")+1); const overrides = outputSettings.overrides || {}; const extFormatter = { 'jpeg': () => sharp().jpeg({ quality: outputSettings.compressionLevel || DEFAULT_COMPRESSION, ...overrides }), 'jpg': () => sharp().jpeg({ quality: outputSettings.compressionLevel || DEFAULT_COMPRESSION, ...overrides }), 'png': () => sharp().png({ compressionLevel: outputSettings.compressionLevel || DEFAULT_PNG_COMPRESSION, ...overrides }), 'webp': () => sharp().webp({ quality: outputSettings.compressionLevel || DEFAULT_COMPRESSION, ...overrides }) } data.getDiffImage().pack() .pipe(extFormatter[storeExt]()) .pipe(fs.createWriteStream(`${saveFolder}/${name}-diff${ext}`)); // optionally save the new image to the test directory if (screenshotOptions.saveNewImageOnError || screenshotOptions.overwriteImageOnChange) { const newFilePath = screenshotOptions.overwriteImageOnChange ? `${saveFolder}/${name}${ext}` : `${saveFolder}/${name}-new${ext}`; switch (storeExt) { case 'jpeg': case 'jpg': sharp(screenShot) .jpeg({quality: outputSettings.compressionLevel || DEFAULT_COMPRESSION, ...overrides}) .toFile(newFilePath); break; case 'webp': sharp(screenShot) .webp({quality: outputSettings.compressionLevel || DEFAULT_COMPRESSION, ...overrides}) .toFile(newFilePath); break; default: sharp(screenShot) .png({compressionLevel: outputSettings.compressionLevel || DEFAULT_PNG_COMPRESSION, ...overrides}) .toFile(newFilePath); } } resolve(false) } else { resolve(true) } }); }); } else { // if there is no old image we cannot compare two images so just write existing screenshot as default image // fs.writeFileSync(`${saveFolder}/${name}${ext}`, screenShot); const storeExt = outputSettings.forceExt != null ? outputSettings.forceExt : ext.substring(ext.lastIndexOf(".")+1); const overrides = outputSettings.overrides || {}; switch (storeExt) { case 'jpeg': case 'jpg': await sharp(screenShot) .jpeg({ quality: outputSettings.compressionLevel || DEFAULT_COMPRESSION, ...overrides }) .toFile(`${saveFolder}/${name}${ext}`); break; case 'webp': await sharp(screenShot) .webp({ quality: outputSettings.compressionLevel || DEFAULT_COMPRESSION, ...overrides }) .toFile(`${saveFolder}/${name}${ext}`); break; default: await sharp(screenShot) .png({ compressionLevel: outputSettings.compressionLevel || DEFAULT_PNG_COMPRESSION, ...overrides }) .toFile(`${saveFolder}/${name}${ext}`); } console.log('There was nothing to compare, current screens saved as default'); return true; } }) }) } // returns promise which resolves with undefined or PNG object const getOldImageData = function(folderPath, name = 'test', ext = 'png') { return new Promise((resolve) => { fs.stat(`${folderPath}/${name}${ext}`, (error) => { if (error) { // if there is an error resolve with undefined resolve(); } else { // if file exists just get file and pipe it into PNG fs.readFile(`${folderPath}/${name}${ext}`, (err, data) => { if (err || !data instanceof Buffer) { resolve(); } else { resolve(data); } }) } }) }) } module.exports = ScreenTestFactory