-
Notifications
You must be signed in to change notification settings - Fork 200
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow to generate diff for images with different sizes #42
Allow to generate diff for images with different sizes #42
Conversation
Does it actually work comparing differently sized images? I thought Edit: nvm I see what you are doing |
Hi @sergeybekrin! So I guess my first question is why are the image snapshots you are generating different in size sometimes? @jking90 @PixnBits what are your thoughts? |
@anescobar1991 we're using it to test visual regression based on screenshots — due to environment's factor we have to crop it first, resulting in different snapshot size if styling and/or content has changed. I agree this might sound specific, but since we don't have an access to baseline image which we need to align in size in some case too, I don't see any other way to do it. |
@sergeybekrin It sounds like in the case that the screenshot changes due to environmental factors you should regenerate the snapshots ( |
Oh, I didn't explained that part, my bad:
Update: Sorry for confusion and let me know if you have additional feedback ✌️ |
So all you really want is for the matcher to behave as usual (fail and produce a diff image) when given an image with different dimensions? If you remove this line without doing anything else does that work for your use case? (That is assuming I understand your use case correctly) |
Yep, correct
pixelmatch would fail in that case, they also have open issue about comparing different sized images and suggestion is to prepare it manually |
@sergeybekrin I don't think this is the right solution for your use case. What you should do is resize your image before you pass it to You can use the |
@anescobar1991 I don't see how that helps in case if received image is bigger. By cropping to smaller size we potentially missing regression. If you think this is to specific, maybe we should implement a way to optionally preprocess both images before comparison? |
@sergeybekrin I don't mean crop. You can actual resize the image using something like sharp pretty easily. As for the preprocess idea that is interesting... How would you imagine that to be implemented? |
@anescobar1991 alright, I would create a separate PR with an idea and PoC. |
@sergeybekrin Did my idea work for you? |
@anescobar1991 unfortunately not, quick try shown that that approach results in unreadable diff because of different proportions after resizing. I've attached a reference for better explanation. |
@anescobar1991 I've created #44 to continue discussion here |
@anescobar1991 I would like to ask a quick question about this size thing.
I believe you will then won't be able to get a diff right ? It will complain both images have not the same size ? Do I get that wrong ? Do you see any workaround we could use to make it work ? Can we expect pixelmatch to help in such matter? Thanks in advance for any help / thoughts, and also big 💌 thank you for this lib ! Really love it & use it extensively ! |
@thomasbertet For the sake of simplicity I am going off of the assumption that you care about the entirety of the page and that your tests cannot be broken up into smaller more focused snapshots (which is what I would suggest as a best practice). If that is the case then if the image has changed (which is what a height change would be) then what you would want is to see a failing test (with a diff) and then you can either update the snapshots or update your code at test to get back to the original snapshot state right? I can see that removing the image dimensions check would allow for that. @sergeybekrin this would help you out as well too right? Now that I understand this better I don't think this change would cause any issues. |
@thomasbertet @anescobar1991 removing that check wouldn't be enough since |
Thanks for the quick reply @anescobar1991 ! You understood me perfectly. Yes at first this seems to be enough for me to remove the check. But, I'm not sure to know what The ideal case would be, as you said @anescobar1991, to be able to generate some kind of a diff.
This way, we would be able to have two kinds of "failure":
Having nothing to visually compare is sooo sad, I feel like we might just generate the comparison image using a very naive algorithm. Even simplier, just generate the two images side-by-side as diff output for a first step would be quite helpful. What do you think ? Is this somewhere you would like to go ? I may help if you will. |
@sergeybekrin the comparison would not be accurate but it would still occur and Remembering back to what we talked about earlier though, your use case is different than @thomasbertet's in that you would actually want the comparison between differently sized images to be valid. And sadly that, as I mentioned to you earlier, we cannot support unless |
Thanks @anescobar1991 yep, if it does like you said, that's all I need to be very happy :) I tried and it seemed to not work as expected. I got an error: bitblt reading outside image and it did not generate the diff :( |
🤦♂️ I think because when I ran it locally the baseline image was larger than the new image to compare. But if you reverse the scenario it fails with that the message you got... So back to step 1 then... Let me think about this for a bit. |
@anescobar1991 I actually needed just a pretty diff and that's exactly my case, you can check my first PR with a solution which doesn't pass test but fails with outputting a valid diff updated: key changes were this and this, |
@sergeybekrin opening this back up as discussed in #49. Let me know if you are still willing to work on this, otherwise I can go ahead and pick this up. If you are still willing to work on it I just have a few comments: I don't think we need the HOWEVER, I think we need to always fail the test if the size is different (while still generating that diff of course). Just in case someone has a large failure threshold and the diff is not large enough (but should still fail since the images are different sizes and thus not the same image). The only thing about that is that we still need to generate some type of output image in case |
@anescobar1991 awesome, thanks! Sure I'll work on it. Good point regarding case when there's no diff but size is different, I'll check what we can do for it. |
1410859
to
e8c756c
Compare
@sergeybekrin do you know when you will be getting to this? Do you think it will be by the end of this week? Wondering if I should wait for this to be merged before publishing next version or not. |
@anescobar1991 yeah, I'll continue on it today. Should be ready by the end of the week. |
e8c756c
to
32861a2
Compare
src/index.js
Outdated
message = () => `Expected image to match or be a close match to snapshot but was ${differencePercentage}% different from snapshot (${result.diffPixelCount} differing pixels).\n` | ||
+ `${chalk.bold.red('See diff for details:')} ${chalk.red(result.diffOutputPath)}`; | ||
+ `${chalk.bold.red('See diff for details:')} ${chalk.red(result.diffOutputPath.replace(cwd, '.'))}`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one is handy change converts absolute diff path to relative which is easier to find (which also allows using .toThrowErrorMatchingSnapshot
in jest)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure it is easier to find/read the path this way.
Maybe it is just me but I think that seeing:
See diff for details /Users/username/path/to/__tests__/__image_snapshots__/diff.png
is easier to read and more clear than:
See diff for details ./__tests__/__image_snapshots__/diff.png
Can you remove this change and just implement something like it at the test level? (in order to allow usage of .toThrowErrorMatchingSnapshot()
I think a change like this for the user experience should be its own PR where a discussion could happen.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Haha, here's tricky part: if I add NODE_ENV === 'test'
check it reduces coverage since it's always set to test
in Jest env. I'll switch to regex option for now.
src/diff-snapshot.js
Outdated
@@ -69,7 +127,7 @@ function diffImageToSnapshot(options) { | |||
const totalPixels = imageWidth * imageHeight; | |||
const diffRatio = diffPixelCount / totalPixels; | |||
|
|||
let pass = false; | |||
let pass = !hasSizeMismatch; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This marks test as failed, but this is not necessary since resized images are always has diff at this point and would fail the test
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what do you mean by but this is not necessary since resized images are always has diff at this point and would fail the test
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah nevermid, just realised it's actually a valid case then size difference is small
src/diff-snapshot.js
Outdated
); | ||
// Align images in size if different | ||
if (hasSizeMismatch) { | ||
const resizedImages = alignImagesToSameSize(receivedImage, baselineImage); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Key change, this resolves both images in same size and with new filled area after resize
* Fills diff area with black transparent color for meaningful diff | ||
*/ | ||
/* eslint-disable no-plusplus, no-param-reassign, no-bitwise */ | ||
const fillSizeDifference = (width, height) => (image) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function just fills new area added after resize with transparent black color. I like idea of checker board pattern, but it seems to be too complicated to implement considering how low-level pngjs
API is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah the worry we had was that just filling it with color would confuse the user as they would think that the color was part of their passed in image.
Since you made it transparent though when I was running it locally it seemed clear enough that the black color was not a part of the image so this should be fine from my POV at least.
@anescobar1991 done, I've left some comments for easier review. Cheers! |
src/diff-snapshot.js
Outdated
receivedImage.height !== baselineImage.height || receivedImage.width !== baselineImage.width | ||
) { | ||
throw new Error('toMatchImageSnapshot(): Received image size must match baseline snapshot size in order to make comparison.'); | ||
let receivedImage = PNG.sync.read(receivedImageBuffer); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather keep these as const
and maybe just rename them rawReceivedImage
or something like that like you had before. Then you can have different variables for the "processed" images that will actually be diffed by pixelmatch
.
I think it will make it easier to follow and debug this code in the future.
/** | ||
* Fills diff area with black transparent color for meaningful diff | ||
*/ | ||
/* eslint-disable no-plusplus, no-param-reassign, no-bitwise */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am okay with disabling no-plusplus
since in a loop that is a pretty standard way to iterate that has been around since the beginning of programming. And no-bitwise
since you do need it.
However you should definitely not disable no-param-reassign
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no-param-reassign
required to mute errors for reassigning image.data
value
Alright I made my comments, for the most part it looks good @sergeybekrin! @10xLaCroixDrinker @PixnBits can you guys review as well? |
32861a2
to
b68935a
Compare
@anescobar1991 thanks for review, I've addressed your feedback |
@@ -70,7 +127,9 @@ function diffImageToSnapshot(options) { | |||
const diffRatio = diffPixelCount / totalPixels; | |||
|
|||
let pass = false; | |||
if (failureThresholdType === 'pixel') { | |||
if (hasSizeMismatch) { | |||
// Always fail test on image size mismatch |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we explicitly set pass
to false
for clarity of what the intent is?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright, done
b68935a
to
f74df4e
Compare
@sergeybekrin merged! Thanks for your patience with this PR, I know there was a lot of back and forth! |
Thanks @sergeybekrin for bringing this up in the first place & @anescobar1991 for staying open-minded on the subject 👍 Can't wait to try it 💯 |
Alright, perfect! Thanks everyone for taking part in this ✌️ |
Hi everyone, Sorry for re-opening this issue almost 2 years after the last comment, but i think it makes sense to be in here. I'm currently evaluating visual testing, using cypress, however when running different browsers we experience that even though the result images are the same, they have different sizes (with the same ratio, just scaled down/up). Instead of trying to align the images so that the smallest image is filled with transparent pixels, what if it would scale down the biggest image to match the smallest one? For instance we have an image A which is 50x50 and another B which 100x100. We could take image B and scale it down to 50x50 and then diff them. Also, this approach only makes sense if both images have the same ratio. Is this something which make sense for this project? I wouldn't mind tackling it if would suit the project. Do you also have any feedback on this matter? I'm fairly new to visual testing so it might be that i'm overlooking something. Thanks! |
I think doing so makes sense in your case where the ratios are the same but would not make sense to implement in jest-image-snapshot as we cannot make that assumption. What I would do if I was you is manipulate the images to make them the same size prior to passing them to jest-image-snapshot. |
Hey there 👋
I've noticed that in some cases image snapshots might be different in size, so 1px difference fails test without helpful diff.
This PR adds new opt-in
failOnSizeMismatch
configuration flag which istrue
by default and keeps current behaviour. With set tofalse
it would generate diff snapshot even for non-identical in size images which is helpful. Also, exact images with different transparent padding on right and bottom would also pass with that flag w/o cropping.Thanks!