Skip to content

Commit

Permalink
Prepare for Release V1.9.4 (#92)
Browse files Browse the repository at this point in the history
* New option `compareWithImage` to compare a screenshot with a custom file (#90) (#91)

* Add option `compareWithImage`

* Cleanup changes from previous commit

* Document changes for `compareWithImage` option

* Improve `compareWithImage` documentation

* Fix issues from previous commit

1. Code ignored the flag `config.prepareBaseImage`
2. There was a typo in the function name `_getBaseImageName()`

Co-authored-by: Philipp Stracker <[email protected]>

* Update Version

Co-authored-by: Philipp Stracker <[email protected]>
  • Loading branch information
puneet0191 and stracker-phil authored Aug 10, 2021
1 parent 1cc3b60 commit 3d9974d
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 52 deletions.
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ To use the Helper, users may provide the parameters:

`prepareBaseImage`: Optional. When `true` then the system replaces all of the baselines related to the test case(s) you ran. This is equivalent of setting the option `prepareBaseImage: true` in all verifications of the test file.

`compareWithImage`: Optional. A custom filename to compare the screenshot with. The `compareWithImage` file must be located inside the `baseFolder`.

### Usage

Expand Down Expand Up @@ -183,6 +184,23 @@ The resultant output image will be uploaded in a folder named "*output*" and dif
If the `prepareBaseImage` option is marked `true`, then the generated base image will be uploaded to a folder named "*base*" in the S3 bucket.
> Note: The tests may take a bit longer to run when the AWS configuration is provided as determined by the internet speed to upload/download images.
### Compare with custom image
Usually, every screenshot needs to have the same filename as an existing image inside the `baseFolder` directory. To change this behavior, you can use the `compareWithImage` option and specify a different image inside the `baseFolder` directory.

This is useful, if you want to compare a single screenshot against multiple base images - for example, when you want to validate that the main menu element is identical on all app pages.
```js
I.seeVisualDiffForElement("#element", "image.png", {compareWithImage: "dashboard.png"});
I.seeVisualDiffForElement("#element", "image.png", {compareWithImage: "account.png"});
```

Or, in some cases there are intended visual differences for different browsers or operating systems:
```js
const os = "win32" === process.platform ? "win" : "mac";

// Compare "image.png" either with "image-win.png" or "image-mac.png":
I.seeVisualDiff("image.png", {compareWithImage: `image-${os}.png`});
```

### Known Issues:

> Issue in Windows where the image comparison is not carried out, and therefore no Mismatch Percentage is shown. See 'loadImageData' function in resemble.js
161 changes: 110 additions & 51 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ class ResembleHelper extends Helper {
* Compare Images
*
* @param image
* @param diffImage
* @param options
* @returns {Promise<resolve | reject>}
*/
async _compareImages(image, diffImage, options) {
const baseImage = this.baseFolder + image;
const actualImage = this.screenshotFolder + image;
async _compareImages(image, options) {
const baseImage = this._getBaseImagePath(image, options);
const actualImage = this._getActualImagePath(image);
const diffImage = this._getDiffImagePath(image);

// check whether the base and the screenshot images are present.
fs.access(baseImage, fs.constants.F_OK | fs.constants.R_OK, (err) => {
Expand Down Expand Up @@ -83,11 +83,11 @@ class ResembleHelper extends Helper {
}
resolve(data);
if (data.misMatchPercentage >= tolerance) {
if (!fs.existsSync(getDirName(this.diffFolder + diffImage))) {
fs.mkdirSync(getDirName(this.diffFolder + diffImage));
if (!fs.existsSync(getDirName(diffImage))) {
fs.mkdirSync(getDirName(diffImage));
}
fs.writeFileSync(this.diffFolder + diffImage + '.png', data.getBuffer());
const diffImagePath = path.join(process.cwd(), this.diffFolder + diffImage + '.png');
fs.writeFileSync(diffImage, data.getBuffer());
const diffImagePath = path.join(process.cwd(), diffImage);
this.debug(`Diff Image File Saved to: ${diffImagePath}`);
}
}
Expand All @@ -102,8 +102,7 @@ class ResembleHelper extends Helper {
* @returns {Promise<*>}
*/
async _fetchMisMatchPercentage(image, options) {
const diffImage = "Diff_" + image.split(".")[0];
const result = this._compareImages(image, diffImage, options);
const result = this._compareImages(image, options);
const data = await Promise.resolve(result);
return data.misMatchPercentage;
}
Expand Down Expand Up @@ -144,40 +143,38 @@ class ResembleHelper extends Helper {
* This method attaches image attachments of the base, screenshot and diff to the allure reporter when the mismatch exceeds tolerance.
* @param baseImage
* @param misMatch
* @param tolerance
* @param options
* @returns {Promise<void>}
*/

async _addAttachment(baseImage, misMatch, tolerance) {
async _addAttachment(baseImage, misMatch, options) {
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.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');
if (allure !== undefined && misMatch >= options.tolerance) {
allure.addAttachment('Base Image', fs.readFileSync(this._getBaseImagePath(baseImage, options)), 'image/png');
allure.addAttachment('Screenshot Image', fs.readFileSync(this._getActualImagePath(baseImage)), 'image/png');
allure.addAttachment('Diff Image', fs.readFileSync(this._getDiffImagePath(baseImage)), 'image/png');
}
}

/**
* This method attaches context, and images to Mochawesome reporter when the mismatch exceeds tolerance.
* @param baseImage
* @param misMatch
* @param tolerance
* @param options
* @returns {Promise<void>}
*/

async _addMochaContext(baseImage, misMatch, tolerance) {
async _addMochaContext(baseImage, misMatch, options) {
const mocha = this.helpers['Mochawesome'];
const diffImage = "Diff_" + baseImage.split(".")[0] + ".png";

if (mocha !== undefined && misMatch >= tolerance) {
if (mocha !== undefined && misMatch >= options.tolerance) {
await mocha.addMochawesomeContext("Base Image");
await mocha.addMochawesomeContext(this.baseFolder + baseImage);
await mocha.addMochawesomeContext(this._getBaseImagePath(baseImage, options));
await mocha.addMochawesomeContext("ScreenShot Image");
await mocha.addMochawesomeContext(this.screenshotFolder + baseImage);
await mocha.addMochawesomeContext(this._getActualImagePath(baseImage));
await mocha.addMochawesomeContext("Diff Image");
await mocha.addMochawesomeContext(this.diffFolder + diffImage);
await mocha.addMochawesomeContext(this._getDiffImagePath(baseImage));
}
}

Expand All @@ -189,18 +186,18 @@ class ResembleHelper extends Helper {
* @param region
* @param bucketName
* @param baseImage
* @param ifBaseImage - tells if the prepareBaseImage is true or false. If false, then it won't upload the baseImage. However, this parameter is not considered if the config file has a prepareBaseImage set to true.
* @param options
* @returns {Promise<void>}
*/

async _upload(accessKeyId, secretAccessKey, region, bucketName, baseImage, ifBaseImage) {
async _upload(accessKeyId, secretAccessKey, region, bucketName, baseImage, options) {
console.log("Starting Upload... ");
const s3 = new AWS.S3({
accessKeyId: accessKeyId,
secretAccessKey: secretAccessKey,
region: region
});
fs.readFile(this.screenshotFolder + baseImage, (err, data) => {
fs.readFile(this._getActualImagePath(baseImage), (err, data) => {
if (err) throw err;
let base64data = new Buffer(data, 'binary');
const params = {
Expand All @@ -213,7 +210,7 @@ class ResembleHelper extends Helper {
console.log(`Screenshot Image uploaded successfully at ${uData.Location}`);
});
});
fs.readFile(this.diffFolder + "Diff_" + baseImage, (err, data) => {
fs.readFile(this._getDiffImagePath(baseImage), (err, data) => {
if (err) console.log("Diff image not generated");
else {
let base64data = new Buffer(data, 'binary');
Expand All @@ -228,14 +225,18 @@ class ResembleHelper extends Helper {
});
}
});
if (ifBaseImage) {
fs.readFile(this.baseFolder + baseImage, (err, data) => {

// If prepareBaseImage is false, then it won't upload the baseImage. However, this parameter is not considered if the config file has a prepareBaseImage set to true.
if (this._getPrepareBaseImage(options)) {
const baseImageName = this._getBaseImageName(baseImage, options);

fs.readFile(this._getBaseImagePath(baseImage, options), (err, data) => {
if (err) throw err;
else {
let base64data = new Buffer(data, 'binary');
const params = {
Bucket: bucketName,
Key: `base/${baseImage}`,
Key: `base/${baseImageName}`,
Body: base64data
};
s3.upload(params, (uErr, uData) => {
Expand All @@ -256,25 +257,27 @@ class ResembleHelper extends Helper {
* @param region
* @param bucketName
* @param baseImage
* @param options
* @returns {Promise<void>}
*/

_download(accessKeyId, secretAccessKey, region, bucketName, baseImage) {
_download(accessKeyId, secretAccessKey, region, bucketName, baseImage, options) {
console.log("Starting Download...");
const baseImageName = this._getBaseImageName(baseImage, options);
const s3 = new AWS.S3({
accessKeyId: accessKeyId,
secretAccessKey: secretAccessKey,
region: region
});
const params = {
Bucket: bucketName,
Key: `base/${baseImage}`
Key: `base/${baseImageName}`
};
return new Promise((resolve) => {
s3.getObject(params, (err, data) => {
if (err) console.error(err);
console.log(this.baseFolder + baseImage);
fs.writeFileSync(this.baseFolder + baseImage, data.Body);
console.log(this._getBaseImagePath(baseImage, options));
fs.writeFileSync(this._getBaseImagePath(baseImage, options), data.Body);
resolve("File Downloaded Successfully");
});
});
Expand Down Expand Up @@ -308,24 +311,22 @@ class ResembleHelper extends Helper {
options.tolerance = 0;
}

const prepareBaseImage = options.prepareBaseImage !== undefined
? options.prepareBaseImage
: (this.prepareBaseImage === true)
const awsC = this.config.aws;
if (awsC !== undefined && prepareBaseImage === false) {
await this._download(awsC.accessKeyId, awsC.secretAccessKey, awsC.region, awsC.bucketName, baseImage);
}
if (options.prepareBaseImage !== undefined && options.prepareBaseImage) {
await this._prepareBaseImage(baseImage);

if (this._getPrepareBaseImage(options)) {
await this._prepareBaseImage(baseImage, options);
} else if (awsC !== undefined) {
await this._download(awsC.accessKeyId, awsC.secretAccessKey, awsC.region, awsC.bucketName, baseImage, options);
}

if (selector) {
options.boundingBox = await this._getBoundingBox(selector);
}
const misMatch = await this._fetchMisMatchPercentage(baseImage, options);
this._addAttachment(baseImage, misMatch, options.tolerance);
this._addMochaContext(baseImage, misMatch, options.tolerance);
this._addAttachment(baseImage, misMatch, options);
this._addMochaContext(baseImage, misMatch, options);
if (awsC !== undefined) {
await this._upload(awsC.accessKeyId, awsC.secretAccessKey, awsC.region, awsC.bucketName, baseImage, options.prepareBaseImage)
await this._upload(awsC.accessKeyId, awsC.secretAccessKey, awsC.region, awsC.bucketName, baseImage, options)
}

this.debug("MisMatch Percentage Calculated is " + misMatch + " for baseline " + baseImage);
Expand All @@ -339,14 +340,18 @@ class ResembleHelper extends Helper {
* Function to prepare Base Images from Screenshots
*
* @param screenShotImage Name of the screenshot Image (Screenshot Image Path is taken from Configuration)
* @param options
*/
async _prepareBaseImage(screenShotImage) {
await this._createDir(this.baseFolder + screenShotImage);
async _prepareBaseImage(screenShotImage, options) {
const baseImage = this._getBaseImagePath(screenShotImage, options);
const actualImage = this._getActualImagePath(screenShotImage);

await this._createDir(baseImage);

fs.access(this.screenshotFolder + screenShotImage, 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(
`${this.screenshotFolder + screenShotImage} ${err.code === 'ENOENT' ? 'does not exist' : 'is read-only'}`);
`${actualImage} ${err.code === 'ENOENT' ? 'does not exist' : 'is read-only'}`);
}
});

Expand All @@ -357,7 +362,7 @@ class ResembleHelper extends Helper {
}
});

fs.copyFileSync(this.screenshotFolder + screenShotImage, this.baseFolder + screenShotImage);
fs.copyFileSync(actualImage, baseImage);
}

/**
Expand Down Expand Up @@ -452,6 +457,60 @@ class ResembleHelper extends Helper {

throw new Error('No matching helper found. Supported helpers: Playwright/WebDriver/Appium/Puppeteer/TestCafe');
}

/**
* Returns the final name of the expected base image, without a path
* @param image Name of the base-image, without path
* @param options Helper options
* @returns {string}
*/
_getBaseImageName(image, options) {
return (options.compareWithImage ? options.compareWithImage : image);
}

/**
* Returns the path to the expected base image
* @param image Name of the base-image, without path
* @param options Helper options
* @returns {string}
*/
_getBaseImagePath(image, options) {
return this.baseFolder + this._getBaseImageName(image, options);
}

/**
* Returns the path to the actual screenshot image
* @param image Name of the image, without path
* @returns {string}
*/
_getActualImagePath(image) {
return this.screenshotFolder + image;
}

/**
* Returns the path to the image that displays differences between base and actual image.
* @param image Name of the image, without path
* @returns {string}
*/
_getDiffImagePath(image) {
const diffImage = "Diff_" + image.split(".")[0] + ".png";
return this.diffFolder + diffImage;
}

/**
* Returns the final `prepareBaseImage` flag after evaluating options and config values
* @param options Helper options
* @returns {boolean}
*/
_getPrepareBaseImage(options) {
if ('undefined' !== typeof options.prepareBaseImage) {
// Cast to bool with `!!` for backwards compatibility
return !! options.prepareBaseImage;
} else {
// Compare with `true` for backwards compatibility
return true === this.prepareBaseImage;
}
}
}

module.exports = ResembleHelper;
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "codeceptjs-resemblehelper",
"version": "1.9.3",
"version": "1.9.4",
"description": "Resemble Js helper for CodeceptJS, with Support for Playwright, Webdriver, TestCafe, Puppeteer & Appium",
"repository": {
"type": "git",
Expand Down

0 comments on commit 3d9974d

Please sign in to comment.