Skip to content

Commit 1b4ed37

Browse files
authored
Merge pull request #1 from puneet0191/master
PMM-3410 Moving the component
2 parents 8528762 + d50dbef commit 1b4ed37

File tree

4 files changed

+382
-0
lines changed

4 files changed

+382
-0
lines changed

.gitignore

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
8+
# Runtime data
9+
pids
10+
*.pid
11+
*.seed
12+
*.pid.lock
13+
14+
# Directory for instrumented libs generated by jscoverage/JSCover
15+
lib-cov
16+
17+
# Coverage directory used by tools like istanbul
18+
coverage
19+
20+
# nyc test coverage
21+
.nyc_output
22+
23+
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24+
.grunt
25+
26+
# Bower dependency directory (https://bower.io/)
27+
bower_components
28+
29+
# node-waf configuration
30+
.lock-wscript
31+
32+
# Compiled binary addons (https://nodejs.org/api/addons.html)
33+
build/Release
34+
35+
# Dependency directories
36+
node_modules/
37+
jspm_packages/
38+
39+
# TypeScript v1 declaration files
40+
typings/
41+
42+
# Optional npm cache directory
43+
.npm
44+
45+
# Optional eslint cache
46+
.eslintcache
47+
48+
# Optional REPL history
49+
.node_repl_history
50+
51+
# Output of 'npm pack'
52+
*.tgz
53+
54+
# Yarn Integrity file
55+
.yarn-integrity
56+
57+
# dotenv environment variables file
58+
.env
59+
60+
# next.js build output
61+
.next

README.md

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# codeceptjs-resemblehelper
2+
Helper for resemble.js, used for Image comparison in Tests with WebdriverIO
3+
4+
codeceptjs-resemblehelper is [CodeceptJS](https://codecept.io/) helper which can be used to compare screenshots and make the tests fail/pass based on the tolerance allowed
5+
6+
If two screenshot comparisons have difference greater then the tolerance provided, the test will fail.
7+
8+
NPM package: https://www.npmjs.com/package/codeceptjs-resemblehelper
9+
10+
To install the package, just run `npm install codeceptjs-resemblehelper`
11+
12+
### Configuration
13+
14+
This helper should be added in codecept.json/codecept.conf.js
15+
16+
Example:
17+
18+
```json
19+
{
20+
"helpers": {
21+
"ResembleHelper" : {
22+
"require": "codeceptjs-resemblehelper",
23+
"screenshotFolder" : "./tests/output/",
24+
"baseFolder": "./tests/screenshots/base/",
25+
"diffFolder": "./tests/screenshots/diff/"
26+
}
27+
}
28+
}
29+
```
30+
To use the Helper, users must provide the three parameters:
31+
`screenshotFolder` : This will always have the same value as `output` in Codecept configuration, this is the folder where webdriverIO
32+
saves a screenshot when using `I.saveScreenshot` method
33+
34+
`baseFolder`: This is the folder for base images, which will be used with screenshot for comparison
35+
36+
`diffFolder`: This will the folder where resemble would try to store the difference image, which can be viewed later,
37+
Please remember to create empty folder if you don't have one already
38+
39+
Usage, these are major functions that help in visual testing
40+
41+
First one is the `verifyMisMatchPercentage` which basically takes several parameters including tolerance and PrepareBase
42+
```js
43+
/**
44+
* Mis Match Percentage Verification
45+
* @param baseImage Name of the Base Image (Base Image path is taken from Configuration)
46+
* @param screenShotImage Name of the screenshot Image (Screenshot Image Path is taken from Configuration)
47+
* @param diffImageName Name of the Diff Image which will be saved after comparison (Diff Image path is taken from Configuration)
48+
* @param tolerance Tolerance Percentage, default value 10
49+
* @param prepareBase True | False, depending on the requirement if the base images are missing
50+
* @param selector CSS|XPath|id, If provided locator will be used to fetch Bounding Box of the element and only that element is compared on two images
51+
* @param options Resemble JS Options, read more here: https://github.com/rsmbl/Resemble.js
52+
* @returns {Promise<void>}
53+
*/
54+
async verifyMisMatchPercentage(baseImage, screenShotImage, diffImageName, tolerance = 10, prepareBase = false, selector, options){
55+
```
56+
Second one is the `PrepareBase` which basically prepares all the base images in case they are not available
57+
```js
58+
/**
59+
* Function to prepare Base Images from Screenshots
60+
*
61+
* @param baseImage Name of the Base Image (Base Image path is taken from Configuration)
62+
* @param screenShotImage Name of the screenshot Image (Screenshot Image Path is taken from Configuration)
63+
*/
64+
prepareBaseImage(baseImage, screenShotImage) {}
65+
```
66+
Third function is to fetch the boundingBox of an element using selector, this boundingBox is then provided to resemble
67+
so that only that element is compared on the images.
68+
69+
```js
70+
/**
71+
* Function to fetch Bounding box for an element, fetched using selector
72+
*
73+
* @param selector CSS|XPath|ID locators
74+
* @returns {Promise<{boundingBox: {left: *, top: *, right: *, bottom: *}}>}
75+
*/
76+
async getBoundingBox(selector){
77+
```
78+
Users can make use of the boundingBox feature by providing a selector to `verifyMisMatchPercentage` function, it will internally
79+
check if a locator is provided, fetch it's bounding-box and compare only that element on both the images.
80+
81+
Finally to use the helper in your test, you can write something like this:
82+
83+
```
84+
Feature('to verify monitoried Remote Db instances');
85+
86+
Scenario('Open the System Overview Dashboard', async (I, adminPage, loginPage) => {
87+
adminPage.navigateToDashboard("OS", "System Overview");
88+
adminPage.applyTimer("1m");
89+
adminPage.viewMetric("CPU Usage");
90+
I.saveScreenshot("System_Overview_CPU_Usage.png");
91+
});
92+
93+
Scenario('Compare CPU Usage Images', async (I) => {
94+
95+
// passing TRUE to let the helper know to prepare base images
96+
I.verifyMisMatchPercentage("System_Overview_CPU_Usage.png", "System_Overview_CPU_Usage.png", "DiffImage_SystemOverview_CPU_USAGE_Dashboard", 10, true);
97+
98+
// passing a selector, to only compare that element on both the images now
99+
I.verifyMisMatchPercentage("System_Overview_CPU_Usage.png", "System_Overview_CPU_Usage.png", "DiffImage_SystemOverview_CPU_USAGE_Panel", 10, false, "//div[@class='panel-container']");
100+
});
101+
```

index.js

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
'use strict';
2+
3+
// use any assertion library you like
4+
const resemble = require("resemblejs");
5+
const fs = require('fs');
6+
let assert = require('assert');
7+
const mkdirp = require('mkdirp');
8+
const getDirName = require('path').dirname;
9+
/**
10+
* Resemble.js helper class for CodeceptJS, this allows screen comparison
11+
* @author Puneet Kala
12+
*/
13+
class ResembleHelper extends Helper {
14+
15+
constructor(config) {
16+
super(config);
17+
}
18+
19+
/**
20+
*
21+
* @param image1
22+
* @param image2
23+
* @param diffImage
24+
* @param tolerance
25+
* @param options
26+
* @returns {Promise<any | never>}
27+
*/
28+
async _compareImages (image1, image2, diffImage, tolerance, options) {
29+
image1 = this.config.baseFolder + image1;
30+
image2 = this.config.screenshotFolder + image2;
31+
32+
return new Promise((resolve, reject) => {
33+
if (options !== undefined)
34+
{
35+
resemble.outputSettings({
36+
boundingBox: options.boundingBox
37+
});
38+
}
39+
resemble.compare(image1, image2, options, (err, data) => {
40+
if (err) {
41+
reject(err);
42+
} else {
43+
resolve(data);
44+
if (data.misMatchPercentage >= tolerance) {
45+
mkdirp(getDirName(this.config.diffFolder + diffImage), function (err) {
46+
if (err) return cb(err);
47+
});
48+
fs.writeFile(this.config.diffFolder + diffImage + '.png', data.getBuffer(), (err, data) => {
49+
if (err) {
50+
throw new Error(this.err);
51+
}
52+
});
53+
}
54+
}
55+
});
56+
}).catch((error) => {
57+
console.log('caught', error.message);
58+
});
59+
}
60+
61+
/**
62+
*
63+
* @param image1
64+
* @param image2
65+
* @param diffImage
66+
* @param tolerance
67+
* @param options
68+
* @returns {Promise<*>}
69+
*/
70+
async _fetchMisMatchPercentage (image1, image2, diffImage, tolerance, options) {
71+
var result = this._compareImages(image1, image2, diffImage, tolerance, options);
72+
var data = await Promise.resolve(result);
73+
return data.misMatchPercentage;
74+
}
75+
76+
/**
77+
* Mis Match Percentage Verification
78+
* @param baseImage Name of the Base Image (Base Image path is taken from Configuration)
79+
* @param screenShotImage Name of the screenshot Image (Screenshot Image Path is taken from Configuration)
80+
* @param diffImageName Name of the Diff Image which will be saved after comparison (Diff Image path is taken from Configuration)
81+
* @param tolerance Tolerance Percentage, default value 10
82+
* @param prepareBase True | False, depending on the requirement if the base images are missing
83+
* @param selector If set, passed selector will be used to fetch Bouding Box and compared on two images
84+
* @param options Resemble JS Options, read more here: https://github.com/rsmbl/Resemble.js
85+
* @returns {Promise<void>}
86+
*/
87+
async verifyMisMatchPercentage(baseImage, screenShotImage, diffImageName, tolerance = 10, prepareBase = false, selector, options){
88+
if (prepareBase)
89+
{
90+
await this.prepareBaseImage(baseImage, screenShotImage);
91+
}
92+
93+
if (selector !== undefined)
94+
{
95+
if (options !== undefined)
96+
{
97+
options.boundingBox = await this.getBoundingBox(selector);
98+
}
99+
else
100+
{
101+
var options = {};
102+
options.boundingBox = await this.getBoundingBox(selector);
103+
}
104+
}
105+
106+
var misMatch = await this._fetchMisMatchPercentage(baseImage, screenShotImage, diffImageName, tolerance, options);
107+
console.log("MisMatch Percentage Calculated is " + misMatch);
108+
assert.ok(misMatch < tolerance, "MissMatch Percentage " + misMatch);
109+
}
110+
111+
/**
112+
* Function to prepare Base Images from Screenshots
113+
*
114+
* @param baseImage Name of the Base Image (Base Image path is taken from Configuration)
115+
* @param screenShotImage Name of the screenshot Image (Screenshot Image Path is taken from Configuration)
116+
*/
117+
async prepareBaseImage(baseImage, screenShotImage) {
118+
var configuration = this.config;
119+
120+
await this._createDir(configuration.baseFolder + baseImage);
121+
122+
fs.access(configuration.screenshotFolder + screenShotImage, fs.constants.F_OK | fs.constants.W_OK, (err) => {
123+
if (err) {
124+
console.error(
125+
`${configuration.screenshotFolder + screenShotImage} ${err.code === 'ENOENT' ? 'does not exist' : 'is read-only'}`);
126+
}
127+
});
128+
129+
fs.access(configuration.baseFolder, fs.constants.F_OK | fs.constants.W_OK, (err) => {
130+
if (err) {
131+
console.error(
132+
`${configuration.baseFolder} ${err.code === 'ENOENT' ? 'does not exist' : 'is read-only'}`);
133+
}
134+
});
135+
136+
fs.copyFileSync(configuration.screenshotFolder + screenShotImage, configuration.baseFolder + baseImage);
137+
}
138+
139+
/**
140+
* Function to create Directory
141+
* @param directory
142+
* @returns {Promise<void>}
143+
* @private
144+
*/
145+
async _createDir (directory) {
146+
mkdirp.sync(getDirName(directory));
147+
}
148+
149+
/**
150+
* Function to fetch Bounding box for an element, fetched using selector
151+
*
152+
* @param selector CSS|XPath|ID selector
153+
* @returns {Promise<{boundingBox: {left: *, top: *, right: *, bottom: *}}>}
154+
*/
155+
async getBoundingBox(selector){
156+
const browser = this._getBrowser();
157+
158+
var ele = await browser.element(selector)
159+
.then((res) => {
160+
return res;
161+
})
162+
.catch((err) => {
163+
// Catch the error because webdriver.io throws if the element could not be found
164+
// Source: https://github.com/webdriverio/webdriverio/blob/master/lib/protocol/element.js
165+
return null;
166+
});
167+
var location = await browser.getLocation(selector);
168+
var size = await browser.getElementSize(selector);
169+
var bottom = size.height + location.y;
170+
var right = size.width + location.x;
171+
var boundingBox = {
172+
left: location.x,
173+
top: location.y,
174+
right: right,
175+
bottom: bottom
176+
};
177+
178+
return boundingBox;
179+
}
180+
181+
_getBrowser() {
182+
if (this.helpers['WebDriver']) {
183+
return this.helpers['WebDriver'].browser;
184+
}
185+
if (this.helpers['Appium']) {
186+
return this.helpers['Appium'].browser;
187+
}
188+
if (this.helpers['WebDriverIO']) {
189+
return this.helpers['WebDriverIO'].browser;
190+
}
191+
throw new Error('No matching helper found. Supported helpers: WebDriver/Appium/WebDriverIO');
192+
}
193+
}
194+
195+
module.exports = ResembleHelper;

package.json

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"name": "codeceptjs-resemblehelper",
3+
"version": "1.2.1",
4+
"description": "Resemble Js helper for CodeceptJS, with WebdriverIO",
5+
"repository": {
6+
"type": "git",
7+
"url": "[email protected]:Percona-Lab/codeceptjs-resemblehelper.git"
8+
},
9+
"dependencies": {
10+
"assert": "^1.4.1",
11+
"canvas": "^2.2.0",
12+
"mz": "^2.7.0",
13+
"resemblejs": "^3.0.0",
14+
"mkdirp": "^0.5.1",
15+
"path": "^0.12.7"
16+
},
17+
"keywords": [
18+
"codeceptJS",
19+
"codeceptjs",
20+
"resemblejs",
21+
"codeceptjs-resemble"
22+
],
23+
"author": "Puneet Kala <[email protected]>",
24+
"license": "MIT"
25+
}

0 commit comments

Comments
 (0)