Skip to content

Commit 3d9974d

Browse files
Prepare for Release V1.9.4 (#92)
* 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]>
1 parent 1cc3b60 commit 3d9974d

File tree

3 files changed

+129
-52
lines changed

3 files changed

+129
-52
lines changed

README.md

+18
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ To use the Helper, users may provide the parameters:
3636

3737
`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.
3838

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

4041
### Usage
4142

@@ -183,6 +184,23 @@ The resultant output image will be uploaded in a folder named "*output*" and dif
183184
If the `prepareBaseImage` option is marked `true`, then the generated base image will be uploaded to a folder named "*base*" in the S3 bucket.
184185
> 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.
185186
187+
### Compare with custom image
188+
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.
189+
190+
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.
191+
```js
192+
I.seeVisualDiffForElement("#element", "image.png", {compareWithImage: "dashboard.png"});
193+
I.seeVisualDiffForElement("#element", "image.png", {compareWithImage: "account.png"});
194+
```
195+
196+
Or, in some cases there are intended visual differences for different browsers or operating systems:
197+
```js
198+
const os = "win32" === process.platform ? "win" : "mac";
199+
200+
// Compare "image.png" either with "image-win.png" or "image-mac.png":
201+
I.seeVisualDiff("image.png", {compareWithImage: `image-${os}.png`});
202+
```
203+
186204
### Known Issues:
187205

188206
> Issue in Windows where the image comparison is not carried out, and therefore no Mismatch Percentage is shown. See 'loadImageData' function in resemble.js

index.js

+110-51
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,13 @@ class ResembleHelper extends Helper {
3333
* Compare Images
3434
*
3535
* @param image
36-
* @param diffImage
3736
* @param options
3837
* @returns {Promise<resolve | reject>}
3938
*/
40-
async _compareImages(image, diffImage, options) {
41-
const baseImage = this.baseFolder + image;
42-
const actualImage = this.screenshotFolder + image;
39+
async _compareImages(image, options) {
40+
const baseImage = this._getBaseImagePath(image, options);
41+
const actualImage = this._getActualImagePath(image);
42+
const diffImage = this._getDiffImagePath(image);
4343

4444
// check whether the base and the screenshot images are present.
4545
fs.access(baseImage, fs.constants.F_OK | fs.constants.R_OK, (err) => {
@@ -83,11 +83,11 @@ class ResembleHelper extends Helper {
8383
}
8484
resolve(data);
8585
if (data.misMatchPercentage >= tolerance) {
86-
if (!fs.existsSync(getDirName(this.diffFolder + diffImage))) {
87-
fs.mkdirSync(getDirName(this.diffFolder + diffImage));
86+
if (!fs.existsSync(getDirName(diffImage))) {
87+
fs.mkdirSync(getDirName(diffImage));
8888
}
89-
fs.writeFileSync(this.diffFolder + diffImage + '.png', data.getBuffer());
90-
const diffImagePath = path.join(process.cwd(), this.diffFolder + diffImage + '.png');
89+
fs.writeFileSync(diffImage, data.getBuffer());
90+
const diffImagePath = path.join(process.cwd(), diffImage);
9191
this.debug(`Diff Image File Saved to: ${diffImagePath}`);
9292
}
9393
}
@@ -102,8 +102,7 @@ class ResembleHelper extends Helper {
102102
* @returns {Promise<*>}
103103
*/
104104
async _fetchMisMatchPercentage(image, options) {
105-
const diffImage = "Diff_" + image.split(".")[0];
106-
const result = this._compareImages(image, diffImage, options);
105+
const result = this._compareImages(image, options);
107106
const data = await Promise.resolve(result);
108107
return data.misMatchPercentage;
109108
}
@@ -144,40 +143,38 @@ class ResembleHelper extends Helper {
144143
* This method attaches image attachments of the base, screenshot and diff to the allure reporter when the mismatch exceeds tolerance.
145144
* @param baseImage
146145
* @param misMatch
147-
* @param tolerance
146+
* @param options
148147
* @returns {Promise<void>}
149148
*/
150149

151-
async _addAttachment(baseImage, misMatch, tolerance) {
150+
async _addAttachment(baseImage, misMatch, options) {
152151
const allure = codeceptjs.container.plugins('allure');
153-
const diffImage = "Diff_" + baseImage.split(".")[0] + ".png";
154152

155-
if (allure !== undefined && misMatch >= tolerance) {
156-
allure.addAttachment('Base Image', fs.readFileSync(this.baseFolder + baseImage), 'image/png');
157-
allure.addAttachment('Screenshot Image', fs.readFileSync(this.screenshotFolder + baseImage), 'image/png');
158-
allure.addAttachment('Diff Image', fs.readFileSync(this.diffFolder + diffImage), 'image/png');
153+
if (allure !== undefined && misMatch >= options.tolerance) {
154+
allure.addAttachment('Base Image', fs.readFileSync(this._getBaseImagePath(baseImage, options)), 'image/png');
155+
allure.addAttachment('Screenshot Image', fs.readFileSync(this._getActualImagePath(baseImage)), 'image/png');
156+
allure.addAttachment('Diff Image', fs.readFileSync(this._getDiffImagePath(baseImage)), 'image/png');
159157
}
160158
}
161159

162160
/**
163161
* This method attaches context, and images to Mochawesome reporter when the mismatch exceeds tolerance.
164162
* @param baseImage
165163
* @param misMatch
166-
* @param tolerance
164+
* @param options
167165
* @returns {Promise<void>}
168166
*/
169167

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

174-
if (mocha !== undefined && misMatch >= tolerance) {
171+
if (mocha !== undefined && misMatch >= options.tolerance) {
175172
await mocha.addMochawesomeContext("Base Image");
176-
await mocha.addMochawesomeContext(this.baseFolder + baseImage);
173+
await mocha.addMochawesomeContext(this._getBaseImagePath(baseImage, options));
177174
await mocha.addMochawesomeContext("ScreenShot Image");
178-
await mocha.addMochawesomeContext(this.screenshotFolder + baseImage);
175+
await mocha.addMochawesomeContext(this._getActualImagePath(baseImage));
179176
await mocha.addMochawesomeContext("Diff Image");
180-
await mocha.addMochawesomeContext(this.diffFolder + diffImage);
177+
await mocha.addMochawesomeContext(this._getDiffImagePath(baseImage));
181178
}
182179
}
183180

@@ -189,18 +186,18 @@ class ResembleHelper extends Helper {
189186
* @param region
190187
* @param bucketName
191188
* @param baseImage
192-
* @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.
189+
* @param options
193190
* @returns {Promise<void>}
194191
*/
195192

196-
async _upload(accessKeyId, secretAccessKey, region, bucketName, baseImage, ifBaseImage) {
193+
async _upload(accessKeyId, secretAccessKey, region, bucketName, baseImage, options) {
197194
console.log("Starting Upload... ");
198195
const s3 = new AWS.S3({
199196
accessKeyId: accessKeyId,
200197
secretAccessKey: secretAccessKey,
201198
region: region
202199
});
203-
fs.readFile(this.screenshotFolder + baseImage, (err, data) => {
200+
fs.readFile(this._getActualImagePath(baseImage), (err, data) => {
204201
if (err) throw err;
205202
let base64data = new Buffer(data, 'binary');
206203
const params = {
@@ -213,7 +210,7 @@ class ResembleHelper extends Helper {
213210
console.log(`Screenshot Image uploaded successfully at ${uData.Location}`);
214211
});
215212
});
216-
fs.readFile(this.diffFolder + "Diff_" + baseImage, (err, data) => {
213+
fs.readFile(this._getDiffImagePath(baseImage), (err, data) => {
217214
if (err) console.log("Diff image not generated");
218215
else {
219216
let base64data = new Buffer(data, 'binary');
@@ -228,14 +225,18 @@ class ResembleHelper extends Helper {
228225
});
229226
}
230227
});
231-
if (ifBaseImage) {
232-
fs.readFile(this.baseFolder + baseImage, (err, data) => {
228+
229+
// 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.
230+
if (this._getPrepareBaseImage(options)) {
231+
const baseImageName = this._getBaseImageName(baseImage, options);
232+
233+
fs.readFile(this._getBaseImagePath(baseImage, options), (err, data) => {
233234
if (err) throw err;
234235
else {
235236
let base64data = new Buffer(data, 'binary');
236237
const params = {
237238
Bucket: bucketName,
238-
Key: `base/${baseImage}`,
239+
Key: `base/${baseImageName}`,
239240
Body: base64data
240241
};
241242
s3.upload(params, (uErr, uData) => {
@@ -256,25 +257,27 @@ class ResembleHelper extends Helper {
256257
* @param region
257258
* @param bucketName
258259
* @param baseImage
260+
* @param options
259261
* @returns {Promise<void>}
260262
*/
261263

262-
_download(accessKeyId, secretAccessKey, region, bucketName, baseImage) {
264+
_download(accessKeyId, secretAccessKey, region, bucketName, baseImage, options) {
263265
console.log("Starting Download...");
266+
const baseImageName = this._getBaseImageName(baseImage, options);
264267
const s3 = new AWS.S3({
265268
accessKeyId: accessKeyId,
266269
secretAccessKey: secretAccessKey,
267270
region: region
268271
});
269272
const params = {
270273
Bucket: bucketName,
271-
Key: `base/${baseImage}`
274+
Key: `base/${baseImageName}`
272275
};
273276
return new Promise((resolve) => {
274277
s3.getObject(params, (err, data) => {
275278
if (err) console.error(err);
276-
console.log(this.baseFolder + baseImage);
277-
fs.writeFileSync(this.baseFolder + baseImage, data.Body);
279+
console.log(this._getBaseImagePath(baseImage, options));
280+
fs.writeFileSync(this._getBaseImagePath(baseImage, options), data.Body);
278281
resolve("File Downloaded Successfully");
279282
});
280283
});
@@ -308,24 +311,22 @@ class ResembleHelper extends Helper {
308311
options.tolerance = 0;
309312
}
310313

311-
const prepareBaseImage = options.prepareBaseImage !== undefined
312-
? options.prepareBaseImage
313-
: (this.prepareBaseImage === true)
314314
const awsC = this.config.aws;
315-
if (awsC !== undefined && prepareBaseImage === false) {
316-
await this._download(awsC.accessKeyId, awsC.secretAccessKey, awsC.region, awsC.bucketName, baseImage);
317-
}
318-
if (options.prepareBaseImage !== undefined && options.prepareBaseImage) {
319-
await this._prepareBaseImage(baseImage);
315+
316+
if (this._getPrepareBaseImage(options)) {
317+
await this._prepareBaseImage(baseImage, options);
318+
} else if (awsC !== undefined) {
319+
await this._download(awsC.accessKeyId, awsC.secretAccessKey, awsC.region, awsC.bucketName, baseImage, options);
320320
}
321+
321322
if (selector) {
322323
options.boundingBox = await this._getBoundingBox(selector);
323324
}
324325
const misMatch = await this._fetchMisMatchPercentage(baseImage, options);
325-
this._addAttachment(baseImage, misMatch, options.tolerance);
326-
this._addMochaContext(baseImage, misMatch, options.tolerance);
326+
this._addAttachment(baseImage, misMatch, options);
327+
this._addMochaContext(baseImage, misMatch, options);
327328
if (awsC !== undefined) {
328-
await this._upload(awsC.accessKeyId, awsC.secretAccessKey, awsC.region, awsC.bucketName, baseImage, options.prepareBaseImage)
329+
await this._upload(awsC.accessKeyId, awsC.secretAccessKey, awsC.region, awsC.bucketName, baseImage, options)
329330
}
330331

331332
this.debug("MisMatch Percentage Calculated is " + misMatch + " for baseline " + baseImage);
@@ -339,14 +340,18 @@ class ResembleHelper extends Helper {
339340
* Function to prepare Base Images from Screenshots
340341
*
341342
* @param screenShotImage Name of the screenshot Image (Screenshot Image Path is taken from Configuration)
343+
* @param options
342344
*/
343-
async _prepareBaseImage(screenShotImage) {
344-
await this._createDir(this.baseFolder + screenShotImage);
345+
async _prepareBaseImage(screenShotImage, options) {
346+
const baseImage = this._getBaseImagePath(screenShotImage, options);
347+
const actualImage = this._getActualImagePath(screenShotImage);
348+
349+
await this._createDir(baseImage);
345350

346-
fs.access(this.screenshotFolder + screenShotImage, fs.constants.F_OK | fs.constants.W_OK, (err) => {
351+
fs.access(actualImage, fs.constants.F_OK | fs.constants.W_OK, (err) => {
347352
if (err) {
348353
throw new Error(
349-
`${this.screenshotFolder + screenShotImage} ${err.code === 'ENOENT' ? 'does not exist' : 'is read-only'}`);
354+
`${actualImage} ${err.code === 'ENOENT' ? 'does not exist' : 'is read-only'}`);
350355
}
351356
});
352357

@@ -357,7 +362,7 @@ class ResembleHelper extends Helper {
357362
}
358363
});
359364

360-
fs.copyFileSync(this.screenshotFolder + screenShotImage, this.baseFolder + screenShotImage);
365+
fs.copyFileSync(actualImage, baseImage);
361366
}
362367

363368
/**
@@ -452,6 +457,60 @@ class ResembleHelper extends Helper {
452457

453458
throw new Error('No matching helper found. Supported helpers: Playwright/WebDriver/Appium/Puppeteer/TestCafe');
454459
}
460+
461+
/**
462+
* Returns the final name of the expected base image, without a path
463+
* @param image Name of the base-image, without path
464+
* @param options Helper options
465+
* @returns {string}
466+
*/
467+
_getBaseImageName(image, options) {
468+
return (options.compareWithImage ? options.compareWithImage : image);
469+
}
470+
471+
/**
472+
* Returns the path to the expected base image
473+
* @param image Name of the base-image, without path
474+
* @param options Helper options
475+
* @returns {string}
476+
*/
477+
_getBaseImagePath(image, options) {
478+
return this.baseFolder + this._getBaseImageName(image, options);
479+
}
480+
481+
/**
482+
* Returns the path to the actual screenshot image
483+
* @param image Name of the image, without path
484+
* @returns {string}
485+
*/
486+
_getActualImagePath(image) {
487+
return this.screenshotFolder + image;
488+
}
489+
490+
/**
491+
* Returns the path to the image that displays differences between base and actual image.
492+
* @param image Name of the image, without path
493+
* @returns {string}
494+
*/
495+
_getDiffImagePath(image) {
496+
const diffImage = "Diff_" + image.split(".")[0] + ".png";
497+
return this.diffFolder + diffImage;
498+
}
499+
500+
/**
501+
* Returns the final `prepareBaseImage` flag after evaluating options and config values
502+
* @param options Helper options
503+
* @returns {boolean}
504+
*/
505+
_getPrepareBaseImage(options) {
506+
if ('undefined' !== typeof options.prepareBaseImage) {
507+
// Cast to bool with `!!` for backwards compatibility
508+
return !! options.prepareBaseImage;
509+
} else {
510+
// Compare with `true` for backwards compatibility
511+
return true === this.prepareBaseImage;
512+
}
513+
}
455514
}
456515

457516
module.exports = ResembleHelper;

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "codeceptjs-resemblehelper",
3-
"version": "1.9.3",
3+
"version": "1.9.4",
44
"description": "Resemble Js helper for CodeceptJS, with Support for Playwright, Webdriver, TestCafe, Puppeteer & Appium",
55
"repository": {
66
"type": "git",

0 commit comments

Comments
 (0)