Skip to content

Commit 62b1dfc

Browse files
authored
Merge pull request #19 from NimaSoroush/refactor-compare-image
Refactor compare image
2 parents 2d0dbe0 + fb25644 commit 62b1dfc

File tree

11 files changed

+170
-75
lines changed

11 files changed

+170
-75
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,5 @@ dist/
3232
# Built docs
3333
docs/
3434
.vscode/
35-
dist/
3635
differencify_report/
3736
screenshots/

.npmignore

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,13 @@
11
src/
22
examples/
3+
.circleci/
4+
.vscode/
5+
.idea/
6+
.babelrc
7+
.editorconfig
8+
.eslintignore
9+
.gitignore
10+
.eslintrc
11+
.nvmrc
12+
_config.yml
13+
Dockerfile

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828

2929
## [0.0.5] - 2017-07-06
3030
### Changed
31-
- Decreased default threashhold to 0.01
31+
- Decreased default threshold to 0.01
3232
- Updated logging to log difference error
3333
- Update Readme.md
3434
- Added a Dockerfile for local/CI usage

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ const differencify = new Differencify(GlobalOptions);
3030
```js
3131
async () => {
3232
const result = await differencify.update(TestOptions);
33-
console.log(result); //true if update succeded
33+
console.log(result); //true if update succeeded
3434
}
3535
```
3636
### Validate your changes

src/chromyRunner.js

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,10 @@ import actions from './actions';
55
import { configTypes } from './defaultConfig';
66

77
const saveImage = (filename, image, testType, screenshotsPath, testReportPath) => {
8-
if (testType === configTypes.test) {
9-
logger.log(`screenshot saved in -> ${testReportPath}/${filename}.png`);
10-
return fs.writeFileSync(`${testReportPath}/${filename}.png`, image);
11-
}
12-
logger.log(`screenshot saved in -> ${screenshotsPath}/${filename}.png`);
13-
return fs.writeFileSync(`${screenshotsPath}/${filename}.png`, image);
8+
const directory = testType === configTypes.test ? testReportPath : screenshotsPath;
9+
const filePath = `${directory}/${filename}.png`;
10+
logger.log(`screenshot saved in -> ${filePath}`);
11+
return fs.writeFileSync(filePath, image);
1412
};
1513

1614
const run = async (chromy, options, test) => {
@@ -32,7 +30,7 @@ const run = async (chromy, options, test) => {
3230
try {
3331
logger.log('Capturing screenshot of whole DOM');
3432
const png = await chromy.screenshotDocument();
35-
await saveImage(test.name, png, test.type, options.screenshots, options.testReportPath);
33+
saveImage(test.name, png, test.type, options.screenshots, options.testReportPath);
3634
} catch (error) {
3735
logger.error(error);
3836
return false;
@@ -42,7 +40,7 @@ const run = async (chromy, options, test) => {
4240
try {
4341
logger.log('Capturing screenshot of chrome window');
4442
const png = await chromy.screenshot();
45-
await saveImage(test.name, png, test.type, options.screenshots, options.testReportPath);
43+
saveImage(test.name, png, test.type, options.screenshots, options.testReportPath);
4644
} catch (error) {
4745
logger.error(error);
4846
return false;
@@ -52,7 +50,7 @@ const run = async (chromy, options, test) => {
5250
try {
5351
logger.log(`Capturing screenshot of ${action.value} selector`);
5452
const png = await chromy.screenshotSelector(action.value);
55-
await saveImage(test.name, png, test.type, options.screenshots, options.testReportPath);
53+
saveImage(test.name, png, test.type, options.screenshots, options.testReportPath);
5654
} catch (error) {
5755
logger.error(error);
5856
return false;
@@ -62,8 +60,6 @@ const run = async (chromy, options, test) => {
6260
break;
6361
case actions.test:
6462
try {
65-
logger.log(`comparing -> ${options.testReportPath}/${test.name}.png
66-
and ${options.screenshots}/${test.name}.png`);
6763
const result = await compareImage(options, test.name);
6864
logger.log(result);
6965
} catch (error) {

src/chromyRunner.test.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,6 @@ describe('ChromyRunner', () => {
6969
expect(loggerCalls[0]).toEqual('goto -> www.example.com');
7070
expect(loggerCalls[1]).toEqual('Capturing screenshot of whole DOM');
7171
expect(loggerCalls[2]).toEqual('screenshot saved in -> ./differencify_report/default.png');
72-
expect(loggerCalls[4]).toEqual('Saving the diff image to disk');
7372
expect(writeFileSyncCalls).toEqual(['./differencify_report/default.png', 'png file']);
7473
});
7574
it('Step runner: update action', async () => {

src/compareImage.js

Lines changed: 43 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,48 @@
1-
21
import Jimp from 'jimp';
32
import logger from './logger';
43

5-
const compareImage = (options, testName) =>
6-
new Promise((resolve, reject) => {
7-
const referenceFile = `${options.screenshots}/${testName}.png`;
8-
const testFile = `${options.testReportPath}/${testName}.png`;
9-
Jimp.read(referenceFile).then((referenceImage) => {
10-
Jimp.read(testFile).then((testImage) => {
11-
const distance = Jimp.distance(referenceImage, testImage);
12-
const diff = Jimp.diff(referenceImage, testImage, options.mismatchThreshold);
13-
if (distance < options.mismatchThreshold || diff.percent < options.mismatchThreshold) {
14-
return resolve('No mismatch found!');
15-
}
16-
if (options.saveDifferencifiedImage) {
17-
logger.log('Saving the diff image to disk');
18-
diff.image.write(`${options.testReportPath}/${testName}_differencified.png`);
19-
}
20-
logger.error(`Result -> distance:${distance} diff:${diff.percent}
21-
misMatchThreshold:${options.mismatchThreshold}`);
22-
return reject('Mismatch found!');
23-
}).catch((err) => {
24-
logger.error(err);
25-
return reject('Failed to read test image!');
26-
});
27-
}).catch((err) => {
28-
logger.error(err);
29-
return reject('Failed to read reference image!');
30-
});
31-
});
4+
const compareImage = async (options, testName) => {
5+
const referenceFile = `${options.screenshots}/${testName}.png`;
6+
const testFile = `${options.testReportPath}/${testName}.png`;
7+
8+
logger.log(`${testName}: comparing ${referenceFile} and ${testFile}`);
9+
10+
let referenceImage;
11+
try {
12+
referenceImage = await Jimp.read(referenceFile);
13+
} catch (err) {
14+
throw new Error(`${testName}: failed to read reference image ${err}`);
15+
}
16+
17+
let testImage;
18+
try {
19+
testImage = await Jimp.read(testFile);
20+
} catch (err) {
21+
throw new Error(`${testName}: failed to read test image ${err}`);
22+
}
23+
24+
const distance = Jimp.distance(referenceImage, testImage);
25+
const diff = Jimp.diff(referenceImage, testImage, options.mismatchThreshold);
26+
if (distance < options.mismatchThreshold || diff.percent < options.mismatchThreshold) {
27+
return `${testName}: no mismatch found ✅`;
28+
}
29+
30+
if (options.saveDifferencifiedImage) {
31+
try {
32+
const diffPath = `${options.testReportPath}/${testName}_differencified.png`;
33+
diff.image.write(diffPath);
34+
logger.log(`${testName}: saved the diff image to disk at ${diffPath}`);
35+
} catch (err) {
36+
throw new Error(`${testName}: failed to save the diff image ${err}`);
37+
}
38+
}
39+
40+
throw new Error(`${testName}: mismatch found❗
41+
Result:
42+
distance: ${distance}
43+
diff: ${diff.percent}
44+
misMatchThreshold: ${options.mismatchThreshold}
45+
`);
46+
};
3247

3348
export default compareImage;

src/compareImage.test.js

Lines changed: 98 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,108 @@
11
import Jimp from 'jimp';
22
import compareImage from './compareImage';
3-
import logger from './logger';
4-
import { globalConfig } from './defaultConfig';
53

6-
Jimp.read = path =>
7-
new Promise((resolve, reject) => {
8-
if (path === './screenshots/test1.png' || path === './differencify_report/test1.png') {
9-
return resolve('image');
4+
const mockConfig = {
5+
screenshots: './screenshots',
6+
testReportPath: './differencify_report',
7+
saveDifferencifiedImage: false,
8+
mismatchThreshold: 0.01,
9+
};
10+
11+
jest.mock('jimp', () => ({
12+
read: jest.fn(),
13+
distance: jest.fn(),
14+
diff: jest.fn(),
15+
}));
16+
17+
18+
jest.mock('./logger', () => ({
19+
log: jest.fn(),
20+
}));
21+
22+
describe('Compare Image', () => {
23+
beforeEach(() => {
24+
Jimp.distance.mockReturnValue(0);
25+
Jimp.diff.mockReturnValue({ percent: 0 });
26+
});
27+
28+
it('calls Jimp with correct image names', async () => {
29+
expect.assertions(2);
30+
await compareImage(mockConfig, 'test');
31+
expect(Jimp.read).toHaveBeenCalledWith('./differencify_report/test.png');
32+
expect(Jimp.read).toHaveBeenCalledWith('./screenshots/test.png');
33+
});
34+
35+
it('throws correct error if it cannot read image', async () => {
36+
expect.assertions(2);
37+
Jimp.read
38+
.mockReturnValueOnce(Promise.reject('error1'))
39+
.mockReturnValueOnce(Promise.resolve())
40+
.mockReturnValueOnce(Promise.reject('error2'));
41+
42+
try {
43+
await compareImage(mockConfig, 'test1');
44+
} catch (err) {
45+
expect(err.message).toEqual('test1: failed to read reference image error1');
46+
}
47+
48+
try {
49+
expect(await compareImage(mockConfig, 'test2')).toThrow();
50+
} catch (err) {
51+
expect(err.message).toEqual('test2: failed to read test image error2');
1052
}
11-
return reject('error');
1253
});
1354

14-
Jimp.distance = (referenceImage, testImage) => {
15-
if (referenceImage === 'image' && testImage === 'image') {
16-
return 0;
17-
}
18-
return 1;
19-
};
55+
it('returns correct value if difference below threshold', async () => {
56+
const result = await compareImage(mockConfig, 'test');
57+
expect(result).toEqual('test: no mismatch found ✅');
58+
});
2059

21-
Jimp.diff = () => jest.fn();
60+
it('returns correct value if only difference above threshold', async () => {
61+
Jimp.diff.mockReturnValue({ percent: 0.02 });
2262

23-
let loggerCalls = [];
24-
logger.log = (...args) => {
25-
loggerCalls.push(...args);
26-
};
63+
const result = await compareImage(mockConfig, 'test');
64+
expect(result).toEqual('test: no mismatch found ✅');
65+
});
2766

28-
describe('Compare Image', () => {
29-
afterEach(() => {
30-
loggerCalls = [];
31-
});
32-
it('returns correct values', async () =>
33-
compareImage(globalConfig, 'test1')
34-
.catch((result) => {
35-
expect(result).toEqual('Failed to read test image!');
36-
expect(loggerCalls[0]).toEqual('Saving the diff image to disk');
37-
}));
67+
it('returns correct value if only distance above threshold', async () => {
68+
Jimp.distance.mockReturnValue(0.02);
69+
70+
const result = await compareImage(mockConfig, 'test');
71+
expect(result).toEqual('test: no mismatch found ✅');
72+
});
73+
74+
it('throws error if distance and difference are above threshold', async () => {
75+
expect.assertions(1);
76+
Jimp.distance.mockReturnValue(0.02);
77+
Jimp.diff.mockReturnValue({ percent: 0.02 });
78+
79+
try {
80+
await compareImage(mockConfig, 'test');
81+
} catch (err) {
82+
expect(err.message).toEqual(`test: mismatch found❗
83+
Result:
84+
distance: 0.02
85+
diff: 0.02
86+
misMatchThreshold: 0.01
87+
`);
88+
}
89+
});
90+
91+
it('writes to disk diff image if saveDifferencifiedImage is true', async () => {
92+
expect.assertions(1);
93+
Jimp.distance.mockReturnValue(0.02);
94+
const mockWrite = jest.fn();
95+
Jimp.diff.mockReturnValue({
96+
percent: 0.02,
97+
image: {
98+
write: mockWrite,
99+
},
100+
});
101+
try {
102+
// eslint-disable-next-line prefer-object-spread/prefer-object-spread
103+
await compareImage(Object.assign({}, mockConfig, { saveDifferencifiedImage: true }), 'test');
104+
} catch (err) {
105+
expect(mockWrite).toHaveBeenCalledWith('./differencify_report/test_differencified.png');
106+
}
107+
});
38108
});

src/index.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export default class Differencify {
2929
createDir(this.configuration.screenshots);
3030
createDir(this.configuration.testReportPath);
3131
}
32+
3233
_createChromeInstance(testConfig) {
3334
const width = testConfig.resolution.width || CHROME_WIDTH;
3435
const height = testConfig.resolution.height || CHROME_HEIGHT;
@@ -41,19 +42,22 @@ export default class Differencify {
4142
});
4243
return chromy;
4344
}
45+
4446
_updateChromeInstances(id, chromy) {
4547
this.chromeInstances[id] = chromy;
4648
this.chromeInstancesId += 1;
4749
}
50+
4851
async _closeChrome(id, chromy) {
4952
try {
5053
logger.log('closing browser');
51-
chromy.close();
54+
await chromy.close();
5255
delete this.chromeInstances[id];
5356
} catch (error) {
5457
logger.error(error);
5558
}
5659
}
60+
5761
async _run(config, type, step) {
5862
const testConfig = sanitiseTestConfiguration(config);
5963
const chromy = this._createChromeInstance(testConfig);
@@ -67,13 +71,16 @@ export default class Differencify {
6771
await this._closeChrome(testId, chromy);
6872
return result;
6973
}
74+
7075
async update(config) {
7176
return await this._run(config, configTypes.update, null);
7277
}
78+
7379
async test(config) {
7480
const testStep = { name: actions.test, value: this.configuration.testReportPath };
7581
return await this._run(config, configTypes.test, testStep);
7682
}
83+
7784
async cleanup() {
7885
await Promise.all(
7986
Object.values(this.chromeInstances).map(chromeInstance => chromeInstance.close()),

src/index.test.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import Differencify from './index';
44
import logger from './logger';
55

66
let chromyCloseCallsCounter = 0;
7-
jest.mock('chromy', () => jest.fn().mockImplementation(() =>
7+
jest.mock('chromy', () => jest.fn(() =>
88
({
99
goto: jest.fn(),
1010
close: jest.fn(() => { chromyCloseCallsCounter += 1; }),
@@ -78,7 +78,6 @@ describe('Differencify', () => {
7878
expect(loggerCalls[0]).toEqual('goto -> www.example.com');
7979
expect(loggerCalls[1]).toEqual('Capturing screenshot of whole DOM');
8080
expect(loggerCalls[2]).toEqual('screenshot saved in -> ./differencify_report/default.png');
81-
expect(loggerCalls[4]).toEqual('Saving the diff image to disk');
8281
expect(writeFileSyncCalls).toEqual(['./differencify_report/default.png', 'png file']);
8382
});
8483
it('cleanup fn', async () => {

0 commit comments

Comments
 (0)