Skip to content

Commit 2ec034d

Browse files
EranSchNimaSoroush
authored andcommitted
Add support for accessing detailed test info via toMatchSnapshot callback (#83)
* [#82] Add support for accessing detailed test values via toMatchSnapshot Updates `toMatchSnapshot` to accept a second argument, a callback. Upon evaluation of the test, the test result is set on the class instance. If `toMatchSnapshot` is given a callback, the callback is invoked upon completion of the test and passed the full result data. - Include additional test result data in return for verbosity - Store result on instance and invoke callback with data if present - Add verbosity to error and success return values * [#82] Update tests and documentation to reflect toMatchSnapshot changes * [#82] Add description of arguments for toMatchSnapshot
1 parent f167714 commit 2ec034d

File tree

7 files changed

+193
-14
lines changed

7 files changed

+193
-14
lines changed

API.md

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
|Method|Arguments|description|
1313
|------|---------|-----------|
14-
|`toMatchSnapshot`|no argument|It matches the captured screenshot with reference screenshot|
14+
|`toMatchSnapshot`|`image` or <br /> `image, callback` or <br />` callback` | Pass an image object to compare it to the snapshot. Optionally, pass a callback to receive [details from the comparison](#detailed-result-information). Alternatively, just pass a callback to receive details of the snapshot currently in the chain. |
1515
|`result`|`Object`|A function that returns response object of previous step when on chained mode|
1616
|`launch`|`Object` [puppeteer.launch options](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteerlaunchoptions)|launches new browser and returns browser object|
1717
|`connect`|`Object` [puppeteer.connect options](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteerconnectoptions)|Attaches to an existing browser instance and returns browser object|
@@ -167,6 +167,88 @@ In this example, differencify will launch a browser instance and share same brow
167167
```
168168
In this example, after calling `result` function it will return the previous step result as an object.
169169

170+
## Detailed Result Information
171+
172+
For programmatic use cases where more information is required than simply whether or not
173+
a test passed, a callback function may be passed to `toMatchSnapshot` which will be invoked
174+
after the test and passed additional details.
175+
176+
```js
177+
(async () => {
178+
await differencify
179+
.init()
180+
.newPage()
181+
.setViewport({ width: 1600, height: 1200 })
182+
.goto('https://github.com/NimaSoroush/differencify')
183+
.screenshot()
184+
.toMatchSnapshot((resultDetail) => {
185+
console.log(resultDetail);
186+
/*
187+
Example output:
188+
{
189+
testConfig: {
190+
chain: false,
191+
testNameProvided: true,
192+
testName: 'TestName',
193+
'testId': 2,
194+
'isUpdate': false,
195+
'isJest': false,
196+
'newWindow': true
197+
},
198+
testResult: {
199+
diffPath: '/parent/__image_snapshots__/__differencified_output__/test.differencified.png',
200+
matched: false,
201+
diffPercent: 0.02,
202+
distance: 0,
203+
snapshotPath: '/parent/__image_snapshots__/test.snap.png',
204+
}
205+
}
206+
*/
207+
})
208+
.close()
209+
.end();
210+
})();
211+
```
212+
213+
Similarly, the callback may be passed as a second argument when unchained:
214+
215+
```js
216+
(async () => {
217+
const target = differencify.init({ chain: false });
218+
await target.launch();
219+
const page = await target.newPage();
220+
await page.goto('https://github.com/NimaSoroush/differencify');
221+
await page.setViewport({ width: 1600, height: 1200 });
222+
await page.waitFor(1000);
223+
const image = await page.screenshot();
224+
await target.toMatchSnapshot(image, (resultDetail) => {
225+
console.log(resultDetail);
226+
/*
227+
Example output:
228+
{
229+
testConfig: {
230+
chain: false,
231+
testNameProvided: true,
232+
testName: 'TestName',
233+
'testId': 2,
234+
'isUpdate': false,
235+
'isJest': false,
236+
'newWindow': true
237+
},
238+
testResult: {
239+
diffPath: '/parent/__image_snapshots__/__differencified_output__/test.differencified.png',
240+
matched: false,
241+
diffPercent: 0.02,
242+
distance: 0,
243+
snapshotPath: '/parent/__image_snapshots__/test.snap.png',
244+
}
245+
}
246+
*/
247+
});
248+
await page.close();
249+
await target.close();
250+
})();
251+
```
170252

171253
## Context switching when chained
172254

src/compareImage.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,22 +45,22 @@ const compareImage = async (capturedImage, globalConfig, testConfig) => {
4545
} catch (error) {
4646
prefixedLogger.error(`failed to read reference image: ${snapshotPath}`);
4747
prefixedLogger.trace(error);
48-
return { matched: false };
48+
return { error: 'Failed to read reference image', matched: false };
4949
}
5050
let testImage;
5151
try {
5252
testImage = await Jimp.read(capturedImage);
5353
} catch (error) {
5454
prefixedLogger.error('failed to read current screenshot image');
5555
prefixedLogger.trace(error);
56-
return { matched: false };
56+
return { error: 'Failed to read current screenshot image', matched: false };
5757
}
5858
prefixedLogger.log('comparing...');
5959
const distance = Jimp.distance(snapshotImage, testImage);
6060
const diff = Jimp.diff(snapshotImage, testImage, globalConfig.mismatchThreshold);
6161
if (distance < globalConfig.mismatchThreshold && diff.percent < globalConfig.mismatchThreshold) {
6262
prefixedLogger.log('no mismatch found ✅');
63-
return { matched: true };
63+
return { snapshotPath, distance, diffPercent: diff.percent, matched: true };
6464
}
6565
if (globalConfig.saveDifferencifiedImage) {
6666
try {
@@ -84,7 +84,7 @@ const compareImage = async (capturedImage, globalConfig, testConfig) => {
8484
diff: ${diff.percent}
8585
misMatchThreshold: ${globalConfig.mismatchThreshold}
8686
`);
87-
return { diffPath, matched: false };
87+
return { snapshotPath, distance, diffPercent: diff.percent, diffPath, matched: false };
8888
}
8989
prefixedLogger.log(`screenshot saved in -> ${snapshotPath}`);
9090
if (fs.existsSync(diffPath)) {

src/compareImage.test.js

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,10 @@ describe('Compare Image', () => {
130130
Jimp.read.mockReturnValueOnce(Promise.reject('error1'));
131131
fs.existsSync.mockReturnValueOnce(true);
132132
const result = await compareImage(Object, mockConfig, mockTestConfig);
133-
expect(result).toEqual({ matched: false });
133+
expect(result).toEqual({
134+
error: 'Failed to read reference image',
135+
matched: false,
136+
});
134137
expect(mockTrace).toHaveBeenCalledWith('error1');
135138
expect(mockError).toHaveBeenCalledWith('failed to read reference image: /parent/__image_snapshots__/test.snap.png');
136139
});
@@ -139,7 +142,12 @@ describe('Compare Image', () => {
139142
expect.assertions(2);
140143
fs.existsSync.mockReturnValueOnce(true);
141144
const result = await compareImage(Object, mockConfig, mockTestConfig);
142-
expect(result).toEqual({ matched: true });
145+
expect(result).toEqual({
146+
diffPercent: 0,
147+
distance: 0,
148+
matched: true,
149+
snapshotPath: '/parent/__image_snapshots__/test.snap.png',
150+
});
143151
expect(mockLog).toHaveBeenCalledWith('no mismatch found ✅');
144152
});
145153

@@ -157,6 +165,9 @@ describe('Compare Image', () => {
157165
expect(result).toEqual({
158166
diffPath: '/parent/__image_snapshots__/__differencified_output__/test.differencified.png',
159167
matched: false,
168+
diffPercent: 0.02,
169+
distance: 0,
170+
snapshotPath: '/parent/__image_snapshots__/test.snap.png',
160171
});
161172
expect(mockError).toHaveBeenCalledWith(`mismatch found❗
162173
Result:
@@ -181,6 +192,9 @@ describe('Compare Image', () => {
181192
expect(result).toEqual({
182193
diffPath: '/parent/__image_snapshots__/__differencified_output__/test.differencified.png',
183194
matched: false,
195+
diffPercent: 0,
196+
distance: 0.02,
197+
snapshotPath: '/parent/__image_snapshots__/test.snap.png',
184198
});
185199
expect(mockError).toHaveBeenCalledWith(`mismatch found❗
186200
Result:
@@ -205,6 +219,9 @@ describe('Compare Image', () => {
205219
expect(result).toEqual({
206220
diffPath: '/parent/__image_snapshots__/__differencified_output__/test.differencified.png',
207221
matched: false,
222+
diffPercent: 0.02,
223+
distance: 0.02,
224+
snapshotPath: '/parent/__image_snapshots__/test.snap.png',
208225
});
209226
expect(mockError).toHaveBeenCalledWith(`mismatch found❗
210227
Result:
@@ -234,6 +251,9 @@ describe('Compare Image', () => {
234251
expect(result).toEqual({
235252
diffPath: '/parent/__image_snapshots__/__differencified_output__/test.differencified.png',
236253
matched: false,
254+
diffPercent: 0.02,
255+
distance: 0.02,
256+
snapshotPath: '/parent/__image_snapshots__/test.snap.png',
237257
});
238258
expect(mockWrite)
239259
.toHaveBeenCalledWith('/parent/__image_snapshots__/__differencified_output__/test.differencified.png',
25.5 KB
Loading
25.5 KB
Loading

src/integration.tests/integration.test.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,70 @@ describe('Differencify', () => {
7474
.close()
7575
.end();
7676
}, 30000);
77+
it('Using toMatchSnapshot callback for result details', async () => {
78+
await differencify
79+
.init()
80+
.newPage()
81+
.setViewport({ width: 1600, height: 1200 })
82+
.goto('http://example.com/')
83+
.waitFor(1000)
84+
.title()
85+
.screenshot()
86+
.toMatchSnapshot((resultDetail) => {
87+
expect(resultDetail).toEqual({
88+
testConfig: {
89+
chain: true,
90+
imageType: 'png',
91+
isJest: true,
92+
isUpdate: false,
93+
testId: 6,
94+
testName: 'Differencify Using toMatchSnapshot callback for result details',
95+
testNameProvided: false,
96+
testPath: '/differencify/src/integration.tests/integration.test.js',
97+
},
98+
testResult: {
99+
diffPercent: 0,
100+
distance: 0,
101+
matched: true,
102+
snapshotPath: '/differencify/src/integration.tests/__image_snapshots__/Differencify Using toMatchSnapshot callback for result details.snap.png',
103+
},
104+
});
105+
})
106+
.close()
107+
.end();
108+
}, 30000);
109+
it('Using toMatchSnapshot callback for result details when unchained', async () => {
110+
const target = differencify.init({ chain: false });
111+
await target.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'] });
112+
const page = await target.newPage();
113+
await page.goto('http://example.com/');
114+
await page.setViewport({ width: 1600, height: 1200 });
115+
await page.waitFor(1000);
116+
const image = await page.screenshot();
117+
await target.toMatchSnapshot(image, (resultDetail) => {
118+
expect(resultDetail).toEqual({
119+
testConfig: {
120+
chain: false,
121+
imageType: undefined,
122+
isJest: true,
123+
isUpdate: false,
124+
newWindow: true,
125+
testId: 7,
126+
testName: 'Differencify Using toMatchSnapshot callback for result details when unchained',
127+
testNameProvided: false,
128+
testPath: '/differencify/src/integration.tests/integration.test.js',
129+
},
130+
testResult: {
131+
diffPercent: 0,
132+
distance: 0,
133+
matched: true,
134+
snapshotPath: '/differencify/src/integration.tests/__image_snapshots__/Differencify Using toMatchSnapshot callback for result details when unchained.snap.png',
135+
},
136+
});
137+
});
138+
await page.close();
139+
await target.close();
140+
}, 30000);
77141
it('Context switching when chained', async () => {
78142
await differencify
79143
.init()

src/target.js

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export default class Target {
1818
this.error = null;
1919
this.image = null;
2020
this.testId = 0;
21+
this.result = null;
2122
}
2223

2324
_logError(error) {
@@ -199,9 +200,15 @@ export default class Target {
199200
}
200201
}
201202

202-
async toMatchSnapshot(image) {
203-
if (image) {
203+
async toMatchSnapshot(image, callback) {
204+
let resultCallback;
205+
if (image && !isFunc(image)) {
204206
this.image = image;
207+
} else if (isFunc(image)) {
208+
resultCallback = image;
209+
}
210+
if (callback && isFunc(callback)) {
211+
resultCallback = callback;
205212
}
206213
if (this.testConfig.isJest && !this.testConfig.testNameProvided) {
207214
this.testConfig.testName = this.testId
@@ -213,14 +220,20 @@ export default class Target {
213220
: this.testConfig.testName;
214221
}
215222
this.testId += 1;
216-
return await this._evaluateResult();
223+
const result = await this._evaluateResult();
224+
if (resultCallback) {
225+
resultCallback({
226+
testConfig: this.testConfig,
227+
testResult: this.result,
228+
});
229+
}
230+
return result;
217231
}
218232

219233
async _evaluateResult() {
220234
if (!this.error) {
221-
let result;
222235
try {
223-
result = await compareImage(this.image, this.globalConfig, this.testConfig);
236+
this.result = await compareImage(this.image, this.globalConfig, this.testConfig);
224237
} catch (error) {
225238
this._logError(error);
226239
}
@@ -230,9 +243,9 @@ export default class Target {
230243
if (this.testConfig.isJest === true) {
231244
const toMatchImageSnapshot = jestMatchers.toMatchImageSnapshot;
232245
expect.extend({ toMatchImageSnapshot });
233-
expect(result).toMatchImageSnapshot(this.testStats);
246+
expect(this.result).toMatchImageSnapshot(this.testStats);
234247
}
235-
if (result.matched || result.updated || result.added) {
248+
if (this.result.matched || this.result.updated || this.result.added) {
236249
return true;
237250
}
238251
}

0 commit comments

Comments
 (0)