From c4cd7e1dda0662276db24bcbe4cc572889e217c8 Mon Sep 17 00:00:00 2001 From: s_ashiotis Date: Tue, 22 Oct 2019 15:00:41 +0300 Subject: [PATCH 1/4] formatting fix issue where if codecept is run outside the test root, relative paths for baseScreenshots would not be found --- index.js | 228 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 117 insertions(+), 111 deletions(-) diff --git a/index.js b/index.js index 2e1c854..b1f019b 100644 --- a/index.js +++ b/index.js @@ -14,17 +14,31 @@ const sizeOf = require('image-size'); class ResembleHelper extends Helper { + constructor(config) { + super(config); + this.baseFolder = this.resolvePath(config.baseFolder); + this.diffFolder = this.resolvePath(config.diffFolder); + this.screenshotFolder = global.output_dir + "/"; + } + + resolvePath(folderPath) { + if (!path.isAbsolute(folderPath)) { + return path.resolve(global.codecept_dir, folderPath) + "/"; // custom helper + } + return folderPath; + } + /** * Compare Images - * + * * @param image * @param diffImage * @param options * @returns {Promise} */ async _compareImages(image, diffImage, options) { - const image1 = this.config.baseFolder + image; - const image2 = this.config.screenshotFolder + image; + const image1 = this.baseFolder + image; + const image2 = this.screenshotFolder + image; // check whether the base and the screenshot images are present. fs.access(image1, fs.constants.F_OK | fs.constants.W_OK, (err) => { @@ -42,7 +56,7 @@ class ResembleHelper extends Helper { }); return new Promise((resolve, reject) => { - + resemble.outputSettings({ boundingBox: options.boundingBox, ignoredBox: options.ignoredBox @@ -55,23 +69,22 @@ class ResembleHelper extends Helper { if (err) { reject(err); } else { - if(!data.isSameDimensions) { + if (!data.isSameDimensions) { let dimensions1 = sizeOf(image1); let dimensions2 = sizeOf(image2); - reject(new Error("The image1 is of " +dimensions1.height + " X " + dimensions1.width + " and image2 is of " + dimensions2.height + " X " + dimensions2.width + ". Please use images of same dimensions so as to avoid any unexpected results.")); + reject(new Error("The image1 is of " + dimensions1.height + " X " + dimensions1.width + " and image2 is of " + dimensions2.height + " X " + dimensions2.width + ". Please use images of same dimensions so as to avoid any unexpected results.")); } resolve(data); if (data.misMatchPercentage >= tolerance) { - mkdirp(getDirName(this.config.diffFolder + diffImage), function (err) { + mkdirp(getDirName(this.diffFolder + diffImage), function (err) { if (err) return cb(err); }); - fs.writeFile(this.config.diffFolder + diffImage + '.png', data.getBuffer(), (err, data) => { + fs.writeFile(this.diffFolder + diffImage + '.png', data.getBuffer(), (err, data) => { if (err) { throw new Error(this.err); - } - else { - const diffImagePath = path.join(process.cwd(), this.config.diffFolder + diffImage + '.png'); - this.debug("Diff Image File Saved to: " + diffImagePath); + } else { + const diffImagePath = path.join(process.cwd(), this.diffFolder + diffImage + '.png'); + this.debug("Diff Image File Saved to: " + diffImagePath); } }); } @@ -95,13 +108,13 @@ class ResembleHelper extends Helper { /** * Take screenshot of individual element. - * @param selector selector of the element to be screenshotted + * @param selector selector of the element to be screenshotted * @param name name of the image - * @returns {Promise} + * @returns {Promise} */ async screenshotElement(selector, name) { const helper = this._getHelper(); - if(this.helpers['Puppeteer']){ + if (this.helpers['Puppeteer']) { await helper.waitForVisible(selector); const els = await helper._locate(selector); if (!els.length) throw new Error(`Element ${selector} couldn't be located`); @@ -111,16 +124,13 @@ class ResembleHelper extends Helper { path: global.output_dir + "/" + name + '.png' }); } else if (this.helpers['WebDriver']) { - const configuration = this.config; - await helper.waitForVisible(selector); const els = await helper._locate(selector); if (!els.length) throw new Error(`Element ${selector} couldn't be located`); const el = els[0]; - await el.saveScreenshot(configuration.screenshotFolder + name + '.png'); - } - else throw new Error("Method only works with Puppeteer and WebDriver helpers."); + await el.saveScreenshot(this.screenshotFolder + name + '.png'); + } else throw new Error("Method only works with Puppeteer and WebDriver helpers."); } /** @@ -135,12 +145,12 @@ class ResembleHelper extends Helper { const allure = codeceptjs.container.plugins('allure'); const diffImage = "Diff_" + baseImage.split(".")[0] + ".png"; - if(allure !== undefined && misMatch >= tolerance) { - allure.addAttachment('Base Image', fs.readFileSync(this.config.baseFolder + baseImage), 'image/png'); - allure.addAttachment('Screenshot Image', fs.readFileSync(this.config.screenshotFolder + baseImage), 'image/png'); - allure.addAttachment('Diff Image', fs.readFileSync(this.config.diffFolder + diffImage), 'image/png'); + if (allure !== undefined && misMatch >= tolerance) { + allure.addAttachment('Base Image', fs.readFileSync(this.baseFolder + baseImage), 'image/png'); + allure.addAttachment('Screenshot Image', fs.readFileSync(this.screenshotFolder + baseImage), 'image/png'); + allure.addAttachment('Diff Image', fs.readFileSync(this.diffFolder + diffImage), 'image/png'); } - } + } /** * This method uploads the diff and screenshot images into the bucket with diff image under bucketName/diff/diffImage and the screenshot image as @@ -155,60 +165,59 @@ class ResembleHelper extends Helper { */ async _upload(accessKeyId, secretAccessKey, region, bucketName, baseImage, ifBaseImage) { - console.log("Starting Upload... "); - const s3 = new AWS.S3({ - accessKeyId: accessKeyId, - secretAccessKey: secretAccessKey, - region: region + console.log("Starting Upload... "); + const s3 = new AWS.S3({ + accessKeyId: accessKeyId, + secretAccessKey: secretAccessKey, + region: region + }); + fs.readFile(this.screenshotFolder + baseImage, (err, data) => { + if (err) throw err; + let base64data = new Buffer(data, 'binary'); + const params = { + Bucket: bucketName, + Key: `output/${baseImage}`, + Body: base64data + }; + s3.upload(params, (uerr, data) => { + if (uerr) throw uerr; + console.log(`Screenshot Image uploaded successfully at ${data.Location}`); }); - fs.readFile(this.config.screenshotFolder + baseImage, (err, data) => { - if(err) throw err; + }); + fs.readFile(this.diffFolder + "Diff_" + baseImage, (err, data) => { + if (err) console.log("Diff image not generated"); + else { + let base64data = new Buffer(data, 'binary'); + const params = { + Bucket: bucketName, + Key: `diff/Diff_${baseImage}`, + Body: base64data + }; + s3.upload(params, (uerr, data) => { + if (uerr) throw uerr; + console.log(`Diff Image uploaded successfully at ${data.Location}`) + }); + } + }); + if (ifBaseImage) { + fs.readFile(this.baseFolder + baseImage, (err, data) => { + if (err) throw err; + else { let base64data = new Buffer(data, 'binary'); const params = { - Bucket: bucketName, - Key: `output/${baseImage}`, - Body: base64data + Bucket: bucketName, + Key: `base/${baseImage}`, + Body: base64data }; s3.upload(params, (uerr, data) => { - if(uerr) throw uerr; - console.log(`Screenshot Image uploaded successfully at ${data.Location}`); + if (uerr) throw uerr; + console.log(`Base Image uploaded at ${data.Location}`) }); + } }); - fs.readFile(this.config.diffFolder + "Diff_" + baseImage, (err, data) => { - if(err) console.log("Diff image not generated"); - else { - let base64data = new Buffer(data, 'binary'); - const params = { - Bucket: bucketName, - Key: `diff/Diff_${baseImage}`, - Body: base64data - }; - s3.upload(params, (uerr, data) => { - if(uerr) throw uerr; - console.log(`Diff Image uploaded successfully at ${data.Location}`) - }); - } - }); - if(ifBaseImage) { - fs.readFile(this.config.baseFolder + baseImage, (err, data) => { - if(err) throw err; - else { - let base64data = new Buffer(data, 'binary'); - const params = { - Bucket: bucketName, - Key: `base/${baseImage}`, - Body: base64data - }; - s3.upload(params, (uerr, data) => { - if(uerr) throw uerr; - console.log(`Base Image uploaded at ${data.Location}`) - }); - } - }); - } - else { - console.log("Not Uploading base Image"); - } + } else { + console.log("Not Uploading base Image"); + } } /** @@ -222,24 +231,24 @@ class ResembleHelper extends Helper { */ _download(accessKeyId, secretAccessKey, region, bucketName, baseImage) { - console.log("Starting Download..."); - const s3 = new AWS.S3({ - accessKeyId: accessKeyId, - secretAccessKey: secretAccessKey, - region: region - }); - const params = { - Bucket: bucketName, - Key: `base/${baseImage}` - }; - return new Promise((resolve, reject) => { - s3.getObject(params, (err, data) => { - if(err) console.error(err); - console.log(this.config.baseFolder + baseImage); - fs.writeFileSync(this.config.baseFolder + baseImage, data.Body); - resolve("File Downloaded Successfully"); - }); + console.log("Starting Download..."); + const s3 = new AWS.S3({ + accessKeyId: accessKeyId, + secretAccessKey: secretAccessKey, + region: region + }); + const params = { + Bucket: bucketName, + Key: `base/${baseImage}` + }; + return new Promise((resolve, reject) => { + s3.getObject(params, (err, data) => { + if (err) console.error(err); + console.log(this.baseFolder + baseImage); + fs.writeFileSync(this.baseFolder + baseImage, data.Body); + resolve("File Downloaded Successfully"); }); + }); } /** @@ -255,10 +264,9 @@ class ResembleHelper extends Helper { } const awsC = this.config.aws; - this.config.screenshotFolder = global.output_dir + "/"; if (awsC !== undefined && options.prepareBaseImage === false) { - await this._download(awsC.accessKeyId, awsC.secretAccessKey, awsC.region, awsC.bucketName, baseImage); + await this._download(awsC.accessKeyId, awsC.secretAccessKey, awsC.region, awsC.bucketName, baseImage); } if (options.prepareBaseImage !== undefined && options.prepareBaseImage) { @@ -269,9 +277,9 @@ class ResembleHelper extends Helper { this._addAttachment(baseImage, misMatch, options.tolerance); - if(awsC !== undefined) { - let ifUpload = options.prepareBaseImage === false ? false : true; - await this._upload(awsC.accessKeyId, awsC.secretAccessKey, awsC.region, awsC.bucketName, baseImage, ifUpload) + if (awsC !== undefined) { + let ifUpload = options.prepareBaseImage === false ? false : true; + await this._upload(awsC.accessKeyId, awsC.secretAccessKey, awsC.region, awsC.bucketName, baseImage, ifUpload) } this.debug("MisMatch Percentage Calculated is " + misMatch); @@ -294,10 +302,9 @@ class ResembleHelper extends Helper { } const awsC = this.config.aws; - this.config.screenshotFolder = global.output_dir + "/"; if (awsC !== undefined && options.prepareBaseImage === false) { - await this._download(awsC.accessKeyId, awsC.secretAccessKey, awsC.region, awsC.bucketName, baseImage); + await this._download(awsC.accessKeyId, awsC.secretAccessKey, awsC.region, awsC.bucketName, baseImage); } if (options.prepareBaseImage !== undefined && options.prepareBaseImage) { @@ -308,10 +315,10 @@ class ResembleHelper extends Helper { const misMatch = await this._fetchMisMatchPercentage(baseImage, options); this._addAttachment(baseImage, misMatch, options.tolerance); - - if(awsC !== undefined) { - let ifUpload = options.prepareBaseImage === false ? false : true; - await this._upload(awsC.accessKeyId, awsC.secretAccessKey, awsC.region, awsC.bucketName, baseImage, ifUpload) + + if (awsC !== undefined) { + let ifUpload = options.prepareBaseImage === false ? false : true; + await this._upload(awsC.accessKeyId, awsC.secretAccessKey, awsC.region, awsC.bucketName, baseImage, ifUpload) } this.debug("MisMatch Percentage Calculated is " + misMatch); @@ -324,25 +331,23 @@ class ResembleHelper extends Helper { * @param screenShotImage Name of the screenshot Image (Screenshot Image Path is taken from Configuration) */ async _prepareBaseImage(screenShotImage) { - const configuration = this.config; - - await this._createDir(configuration.baseFolder + screenShotImage); + await this._createDir(this.baseFolder + screenShotImage); - fs.access(configuration.screenshotFolder + screenShotImage, fs.constants.F_OK | fs.constants.W_OK, (err) => { + fs.access(this.screenshotFolder + screenShotImage, fs.constants.F_OK | fs.constants.W_OK, (err) => { if (err) { throw new Error( - `${configuration.screenshotFolder + screenShotImage} ${err.code === 'ENOENT' ? 'does not exist' : 'is read-only'}`); + `${this.screenshotFolder + screenShotImage} ${err.code === 'ENOENT' ? 'does not exist' : 'is read-only'}`); } }); - fs.access(configuration.baseFolder, fs.constants.F_OK | fs.constants.W_OK, (err) => { + fs.access(this.baseFolder, fs.constants.F_OK | fs.constants.W_OK, (err) => { if (err) { throw new Error( - `${configuration.baseFolder} ${err.code === 'ENOENT' ? 'does not exist' : 'is read-only'}`); + `${this.baseFolder} ${err.code === 'ENOENT' ? 'does not exist' : 'is read-only'}`); } }); - fs.copyFileSync(configuration.screenshotFolder + screenShotImage, configuration.baseFolder + screenShotImage); + fs.copyFileSync(this.screenshotFolder + screenShotImage, this.baseFolder + screenShotImage); } /** @@ -371,15 +376,15 @@ class ResembleHelper extends Helper { let location, size; if (this.helpers['Puppeteer']) { - const box = await el.boundingBox(); + const box = await el.boundingBox(); size = location = box; } if (this.helpers['WebDriver'] || this.helpers['Appium']) { location = await el.getLocation(); size = await el.getSize(); - } - + } + if (this.helpers['WebDriverIO']) { location = await helper.browser.getLocation(selector); size = await helper.browser.getElementSize(selector); @@ -417,4 +422,5 @@ class ResembleHelper extends Helper { throw new Error('No matching helper found. Supported helpers: WebDriver/Appium/Puppeteer'); } } + module.exports = ResembleHelper; \ No newline at end of file From e4a9017d9c0323c5cda243bdc74b94b2cb13f9bf Mon Sep 17 00:00:00 2001 From: s_ashiotis Date: Tue, 22 Oct 2019 15:27:15 +0300 Subject: [PATCH 2/4] remove comment --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index b1f019b..770b845 100644 --- a/index.js +++ b/index.js @@ -23,7 +23,7 @@ class ResembleHelper extends Helper { resolvePath(folderPath) { if (!path.isAbsolute(folderPath)) { - return path.resolve(global.codecept_dir, folderPath) + "/"; // custom helper + return path.resolve(global.codecept_dir, folderPath) + "/"; } return folderPath; } From 1d5a2378c702ebf84e4f8f4141bb4dca4543d062 Mon Sep 17 00:00:00 2001 From: s_ashiotis Date: Wed, 23 Oct 2019 12:12:15 +0300 Subject: [PATCH 3/4] add dev dependencies on codecept and allure to allow IDE to resolve those calls. Replace fs.writeFile with fs.writeFileSync to ensure the diff image is written before attached to allure. Fix JSDoc warnings Fix "err" scoping warning Change undefined checks to check if element is either null or undefined Remove unused parameter Add check to ensure size is populated fix overcomplicated ternary statement --- index.js | 81 +++++++++++++++++++--------------------------------- package.json | 4 +++ 2 files changed, 34 insertions(+), 51 deletions(-) diff --git a/index.js b/index.js index 770b845..5615c62 100644 --- a/index.js +++ b/index.js @@ -34,7 +34,7 @@ class ResembleHelper extends Helper { * @param image * @param diffImage * @param options - * @returns {Promise} + * @returns {Promise} */ async _compareImages(image, diffImage, options) { const image1 = this.baseFolder + image; @@ -76,17 +76,12 @@ class ResembleHelper extends Helper { } resolve(data); if (data.misMatchPercentage >= tolerance) { - mkdirp(getDirName(this.diffFolder + diffImage), function (err) { - if (err) return cb(err); - }); - fs.writeFile(this.diffFolder + diffImage + '.png', data.getBuffer(), (err, data) => { - if (err) { - throw new Error(this.err); - } else { - const diffImagePath = path.join(process.cwd(), this.diffFolder + diffImage + '.png'); - this.debug("Diff Image File Saved to: " + diffImagePath); - } + mkdirp(getDirName(this.diffFolder + diffImage), function (error) { + if (error) return cb(error); }); + fs.writeFileSync(this.diffFolder + diffImage + '.png', data.getBuffer()); + const diffImagePath = path.join(process.cwd(), this.diffFolder + diffImage + '.png'); + this.debug("Diff Image File Saved to: " + diffImagePath); } } }); @@ -179,9 +174,9 @@ class ResembleHelper extends Helper { Key: `output/${baseImage}`, Body: base64data }; - s3.upload(params, (uerr, data) => { - if (uerr) throw uerr; - console.log(`Screenshot Image uploaded successfully at ${data.Location}`); + s3.upload(params, (uErr, uData) => { + if (uErr) throw uErr; + console.log(`Screenshot Image uploaded successfully at ${uData.Location}`); }); }); fs.readFile(this.diffFolder + "Diff_" + baseImage, (err, data) => { @@ -193,9 +188,9 @@ class ResembleHelper extends Helper { Key: `diff/Diff_${baseImage}`, Body: base64data }; - s3.upload(params, (uerr, data) => { - if (uerr) throw uerr; - console.log(`Diff Image uploaded successfully at ${data.Location}`) + s3.upload(params, (uErr, uData) => { + if (uErr) throw uErr; + console.log(`Diff Image uploaded successfully at ${uData.Location}`) }); } }); @@ -209,9 +204,9 @@ class ResembleHelper extends Helper { Key: `base/${baseImage}`, Body: base64data }; - s3.upload(params, (uerr, data) => { - if (uerr) throw uerr; - console.log(`Base Image uploaded at ${data.Location}`) + s3.upload(params, (uErr, uData) => { + if (uErr) throw uErr; + console.log(`Base Image uploaded at ${uData.Location}`) }); } }); @@ -241,7 +236,7 @@ class ResembleHelper extends Helper { Bucket: bucketName, Key: `base/${baseImage}` }; - return new Promise((resolve, reject) => { + return new Promise((resolve) => { s3.getObject(params, (err, data) => { if (err) console.error(err); console.log(this.baseFolder + baseImage); @@ -258,32 +253,7 @@ class ResembleHelper extends Helper { * @returns {Promise} */ async seeVisualDiff(baseImage, options) { - if (options == undefined) { - options = {}; - options.tolerance = 0; - } - - const awsC = this.config.aws; - - if (awsC !== undefined && options.prepareBaseImage === false) { - await this._download(awsC.accessKeyId, awsC.secretAccessKey, awsC.region, awsC.bucketName, baseImage); - } - - if (options.prepareBaseImage !== undefined && options.prepareBaseImage) { - await this._prepareBaseImage(baseImage); - } - - const misMatch = await this._fetchMisMatchPercentage(baseImage, options); - - this._addAttachment(baseImage, misMatch, options.tolerance); - - if (awsC !== undefined) { - let ifUpload = options.prepareBaseImage === false ? false : true; - await this._upload(awsC.accessKeyId, awsC.secretAccessKey, awsC.region, awsC.bucketName, baseImage, ifUpload) - } - - this.debug("MisMatch Percentage Calculated is " + misMatch); - assert(misMatch <= options.tolerance, "MissMatch Percentage " + misMatch); + await this._assertVisualDiff(undefined, baseImage, options); } /** @@ -295,8 +265,11 @@ class ResembleHelper extends Helper { * @returns {Promise} */ async seeVisualDiffForElement(selector, baseImage, options) { + await this._assertVisualDiff(selector, baseImage, options); + } - if (options == undefined) { + async _assertVisualDiff(selector, baseImage, options) { + if (!options) { options = {}; options.tolerance = 0; } @@ -311,14 +284,16 @@ class ResembleHelper extends Helper { await this._prepareBaseImage(baseImage); } - options.boundingBox = await this._getBoundingBox(selector); + if (selector) { + options.boundingBox = await this._getBoundingBox(selector); + } + const misMatch = await this._fetchMisMatchPercentage(baseImage, options); this._addAttachment(baseImage, misMatch, options.tolerance); if (awsC !== undefined) { - let ifUpload = options.prepareBaseImage === false ? false : true; - await this._upload(awsC.accessKeyId, awsC.secretAccessKey, awsC.region, awsC.bucketName, baseImage, ifUpload) + await this._upload(awsC.accessKeyId, awsC.secretAccessKey, awsC.region, awsC.bucketName, baseImage, options.prepareBaseImage) } this.debug("MisMatch Percentage Calculated is " + misMatch); @@ -390,6 +365,10 @@ class ResembleHelper extends Helper { size = await helper.browser.getElementSize(selector); } + if (!size) { + throw new Error("Cannot get element size!"); + } + const bottom = size.height + location.y; const right = size.width + location.x; const boundingBox = { diff --git a/package.json b/package.json index cf04fb6..e12ee5f 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,10 @@ "aws-sdk": "^2.476.0", "image-size": "^0.7.4" }, + "devDependencies": { + "allure-commandline": "^2.13.0", + "codeceptjs": "^2.3.5" + }, "keywords": [ "codeceptJS", "codeceptjs", From 349fe8cb25dcc88f6256958617ddeb783d404935 Mon Sep 17 00:00:00 2001 From: s_ashiotis Date: Thu, 31 Oct 2019 09:55:22 +0200 Subject: [PATCH 4/4] fix error message for image dimensions to be exact on which image is which --- index.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/index.js b/index.js index 5615c62..3fa0c5e 100644 --- a/index.js +++ b/index.js @@ -37,21 +37,21 @@ class ResembleHelper extends Helper { * @returns {Promise} */ async _compareImages(image, diffImage, options) { - const image1 = this.baseFolder + image; - const image2 = this.screenshotFolder + image; + const baseImage = this.baseFolder + image; + const actualImage = this.screenshotFolder + image; // check whether the base and the screenshot images are present. - fs.access(image1, fs.constants.F_OK | fs.constants.W_OK, (err) => { + fs.access(baseImage, fs.constants.F_OK | fs.constants.W_OK, (err) => { if (err) { throw new Error( - `${image1} ${err.code === 'ENOENT' ? 'base image does not exist' : 'is read-only'}`); + `${baseImage} ${err.code === 'ENOENT' ? 'base image does not exist' : 'is read-only'}`); } }); - fs.access(image2, fs.constants.F_OK | fs.constants.W_OK, (err) => { + fs.access(actualImage, fs.constants.F_OK | fs.constants.W_OK, (err) => { if (err) { throw new Error( - `${image2} ${err.code === 'ENOENT' ? 'screenshot image does not exist' : 'is read-only'}`); + `${actualImage} ${err.code === 'ENOENT' ? 'screenshot image does not exist' : 'is read-only'}`); } }); @@ -65,14 +65,14 @@ class ResembleHelper extends Helper { this.debug("Tolerance Level Provided " + options.tolerance); const tolerance = options.tolerance; - resemble.compare(image1, image2, options, (err, data) => { + resemble.compare(baseImage, actualImage, options, (err, data) => { if (err) { reject(err); } else { if (!data.isSameDimensions) { - let dimensions1 = sizeOf(image1); - let dimensions2 = sizeOf(image2); - reject(new Error("The image1 is of " + dimensions1.height + " X " + dimensions1.width + " and image2 is of " + dimensions2.height + " X " + dimensions2.width + ". Please use images of same dimensions so as to avoid any unexpected results.")); + let dimensions1 = sizeOf(baseImage); + let dimensions2 = sizeOf(actualImage); + reject(new Error("The base image is of " + dimensions1.height + " X " + dimensions1.width + " and actual image is of " + dimensions2.height + " X " + dimensions2.width + ". Please use images of same dimensions so as to avoid any unexpected results.")); } resolve(data); if (data.misMatchPercentage >= tolerance) {