diff --git a/FBSnapshotTestCase.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/FBSnapshotTestCase.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/FBSnapshotTestCase.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/FBSnapshotTestCase.xcodeproj/project.xcworkspace/xcshareddata/FBSnapshotTestCase.xcscmblueprint b/FBSnapshotTestCase.xcodeproj/project.xcworkspace/xcshareddata/FBSnapshotTestCase.xcscmblueprint new file mode 100644 index 0000000..6f43335 --- /dev/null +++ b/FBSnapshotTestCase.xcodeproj/project.xcworkspace/xcshareddata/FBSnapshotTestCase.xcscmblueprint @@ -0,0 +1,30 @@ +{ + "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "542E5EAD47EA299C9005C3138923F693160B998F", + "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { + + }, + "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { + "542E5EAD47EA299C9005C3138923F693160B998F" : 9223372036854775807, + "C29DD0E89C17000393FD71820E9E7C86451DD28A" : 9223372036854775807 + }, + "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "61F27B42-711F-4F72-989C-E02DC0D613E8", + "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { + "542E5EAD47EA299C9005C3138923F693160B998F" : "FBSnapshotTestCase\/", + "C29DD0E89C17000393FD71820E9E7C86451DD28A" : ".." + }, + "DVTSourceControlWorkspaceBlueprintNameKey" : "FBSnapshotTestCase", + "DVTSourceControlWorkspaceBlueprintVersion" : 204, + "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "FBSnapshotTestCase.xcodeproj", + "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ + { + "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/thorthugnasty\/ios-snapshot-test-case.git", + "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", + "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "542E5EAD47EA299C9005C3138923F693160B998F" + }, + { + "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "ssh:\/\/git.corp.squareup.com\/ios\/appointments.git", + "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", + "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "C29DD0E89C17000393FD71820E9E7C86451DD28A" + } + ] +} \ No newline at end of file diff --git a/FBSnapshotTestCase/Categories/UIImage+Compare.h b/FBSnapshotTestCase/Categories/UIImage+Compare.h index 9091d62..1f3075a 100644 --- a/FBSnapshotTestCase/Categories/UIImage+Compare.h +++ b/FBSnapshotTestCase/Categories/UIImage+Compare.h @@ -32,6 +32,6 @@ @interface UIImage (Compare) -- (BOOL)fb_compareWithImage:(UIImage *)image tolerance:(CGFloat)tolerance; +- (BOOL)fb_compareWithImage:(UIImage *)image tolerance:(CGFloat)tolerance colorTolerance:(CGFloat)colorTolerance; @end diff --git a/FBSnapshotTestCase/Categories/UIImage+Compare.m b/FBSnapshotTestCase/Categories/UIImage+Compare.m index c997f57..f25034e 100644 --- a/FBSnapshotTestCase/Categories/UIImage+Compare.m +++ b/FBSnapshotTestCase/Categories/UIImage+Compare.m @@ -44,7 +44,7 @@ @implementation UIImage (Compare) -- (BOOL)fb_compareWithImage:(UIImage *)image tolerance:(CGFloat)tolerance +- (BOOL)fb_compareWithImage:(UIImage *)image tolerance:(CGFloat)tolerance colorTolerance:(CGFloat)colorTolerance { NSAssert(CGSizeEqualToSize(self.size, image.size), @"Images must be same size."); @@ -97,7 +97,7 @@ - (BOOL)fb_compareWithImage:(UIImage *)image tolerance:(CGFloat)tolerance BOOL imageEqual = YES; // Do a fast compare if we can - if (tolerance == 0) { + if (tolerance == 0 && colorTolerance == 0) { imageEqual = (memcmp(referenceImagePixels, imagePixels, referenceImageSizeBytes) == 0); } else { // Go through each pixel in turn and see if it is different @@ -111,12 +111,30 @@ - (BOOL)fb_compareWithImage:(UIImage *)image tolerance:(CGFloat)tolerance // If this pixel is different, increment the pixel diff count and see // if we have hit our limit. if (p1->raw != p2->raw) { - numDiffPixels ++; - - CGFloat percent = (CGFloat)numDiffPixels / pixelCount; - if (percent > tolerance) { - imageEqual = NO; - break; + BOOL pixelsEqual = YES; + if (colorTolerance == 0) { + pixelsEqual = NO; + } else { + long dr = (long)p1->pixels.red - (long)p2->pixels.red; + long dg = (long)p1->pixels.green - (long)p2->pixels.green; + long db = (long)p1->pixels.blue - (long)p2->pixels.blue; + + double distanceSquared = (float) (dr * dr + dg * dg + db * db); + distanceSquared /= (255.0 * 255.0); // Scale each channel from 0 - 255 to 0 - 1.0 + + if (distanceSquared > colorTolerance * colorTolerance) { + pixelsEqual = NO; + } + } + + if (!pixelsEqual) { + numDiffPixels++; + + CGFloat percent = (CGFloat)numDiffPixels / pixelCount; + if (percent > tolerance) { + imageEqual = NO; + break; + } } } diff --git a/FBSnapshotTestCase/FBSnapshotTestCase.h b/FBSnapshotTestCase/FBSnapshotTestCase.h index 72abc3c..6b28d09 100644 --- a/FBSnapshotTestCase/FBSnapshotTestCase.h +++ b/FBSnapshotTestCase/FBSnapshotTestCase.h @@ -142,12 +142,46 @@ tolerance:(CGFloat)tolerance error:(NSError **)errorPtr; +/** + Performs the comparison or records a snapshot of the layer if recordMode is YES. + @param layer The Layer to snapshot + @param referenceImagesDirectory The directory in which reference images are stored. + @param identifier An optional identifier, used if there are multiple snapshot tests in a given -test method. + @param tolerance The percentage difference to still count as identical - 0 mean pixel perfect, 1 means I don't care + @param colorTolerance The euclidean distance allowable between two colors (with each channel 0-1.0) to consider pixels to be "identical". colorTolerance is applied before calculating percentage of pixels that are different. + @param errorPtr An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc). + @returns YES if the comparison (or saving of the reference image) succeeded. + */ +- (BOOL)compareSnapshotOfLayer:(CALayer *)layer + referenceImagesDirectory:(NSString *)referenceImagesDirectory + identifier:(NSString *)identifier + tolerance:(CGFloat)tolerance + colorTolerance:(CGFloat)colorTolerance + error:(NSError **)errorPtr; + +/** + Performs the comparison or records a snapshot of the view if recordMode is YES. + @param view The view to snapshot + @param referenceImagesDirectory The directory in which reference images are stored. + @param identifier An optional identifier, used if there are multiple snapshot tests in a given -test method. + @param tolerance The percentage difference to still count as identical - 0 mean pixel perfect, 1 means I don't care + @param errorPtr An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc). + @returns YES if the comparison (or saving of the reference image) succeeded. + */ +- (BOOL)compareSnapshotOfView:(UIView *)view + referenceImagesDirectory:(NSString *)referenceImagesDirectory + identifier:(NSString *)identifier + tolerance:(CGFloat)tolerance + error:(NSError **)errorPtr; + + /** Performs the comparison or records a snapshot of the view if recordMode is YES. @param view The view to snapshot @param referenceImagesDirectory The directory in which reference images are stored. @param identifier An optional identifier, used if there are multiple snapshot tests in a given -test method. @param tolerance The percentage difference to still count as identical - 0 mean pixel perfect, 1 means I don't care + @param colorTolerance The euclidean distance allowable between two colors (with each channel 0-1.0) to consider pixels to be "identical". colorTolerance is applied before calculating percentage of pixels that are different. @param errorPtr An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc). @returns YES if the comparison (or saving of the reference image) succeeded. */ @@ -155,6 +189,7 @@ referenceImagesDirectory:(NSString *)referenceImagesDirectory identifier:(NSString *)identifier tolerance:(CGFloat)tolerance + colorTolerance:(CGFloat)colorTolerance error:(NSError **)errorPtr; /** diff --git a/FBSnapshotTestCase/FBSnapshotTestCase.m b/FBSnapshotTestCase/FBSnapshotTestCase.m index f44458c..1d9be1f 100644 --- a/FBSnapshotTestCase/FBSnapshotTestCase.m +++ b/FBSnapshotTestCase/FBSnapshotTestCase.m @@ -87,7 +87,7 @@ - (NSString *)snapshotVerifyViewOrLayer:(id)viewOrLayer if (self.recordMode) { NSString *referenceImagesDirectory = [NSString stringWithFormat:@"%@%@", referenceImageDirectory, suffixes.firstObject]; - BOOL referenceImageSaved = [self _compareSnapshotOfViewOrLayer:viewOrLayer referenceImagesDirectory:referenceImagesDirectory identifier:(identifier) tolerance:tolerance error:&error]; + BOOL referenceImageSaved = [self _compareSnapshotOfViewOrLayer:viewOrLayer referenceImagesDirectory:referenceImagesDirectory identifier:(identifier) tolerance:tolerance colorTolerance: 0.0 error:&error]; if (!referenceImageSaved) { [errors addObject:error]; } @@ -97,7 +97,7 @@ - (NSString *)snapshotVerifyViewOrLayer:(id)viewOrLayer BOOL referenceImageAvailable = [self referenceImageRecordedInDirectory:referenceImagesDirectory identifier:(identifier) error:&error]; if (referenceImageAvailable) { - BOOL comparisonSuccess = [self _compareSnapshotOfViewOrLayer:viewOrLayer referenceImagesDirectory:referenceImagesDirectory identifier:identifier tolerance:tolerance error:&error]; + BOOL comparisonSuccess = [self _compareSnapshotOfViewOrLayer:viewOrLayer referenceImagesDirectory:referenceImagesDirectory identifier:identifier tolerance:tolerance colorTolerance: 0.0 error:&error]; [errors removeAllObjects]; if (comparisonSuccess) { testSuccess = YES; @@ -131,19 +131,54 @@ - (BOOL)compareSnapshotOfLayer:(CALayer *)layer referenceImagesDirectory:referenceImagesDirectory identifier:identifier tolerance:tolerance + colorTolerance:0 error:errorPtr]; } + + +- (BOOL)compareSnapshotOfLayer:(CALayer *)layer + referenceImagesDirectory:(NSString *)referenceImagesDirectory + identifier:(NSString *)identifier + tolerance:(CGFloat)tolerance + colorTolerance:(CGFloat)colorTolerance + error:(NSError **)errorPtr +{ + return [self _compareSnapshotOfViewOrLayer:layer + referenceImagesDirectory:referenceImagesDirectory + identifier:identifier + tolerance:tolerance + colorTolerance:colorTolerance + error:errorPtr]; +} + +- (BOOL)compareSnapshotOfView:(UIView *)view + referenceImagesDirectory:(NSString *)referenceImagesDirectory + identifier:(NSString *)identifier + tolerance:(CGFloat)tolerance + error:(NSError **)errorPtr +{ + return [self _compareSnapshotOfViewOrLayer:view + referenceImagesDirectory:referenceImagesDirectory + identifier:identifier + tolerance:tolerance + colorTolerance:0 + error:errorPtr]; +} + + - (BOOL)compareSnapshotOfView:(UIView *)view referenceImagesDirectory:(NSString *)referenceImagesDirectory identifier:(NSString *)identifier tolerance:(CGFloat)tolerance + colorTolerance:(CGFloat)colorTolerance error:(NSError **)errorPtr { return [self _compareSnapshotOfViewOrLayer:view referenceImagesDirectory:referenceImagesDirectory identifier:identifier tolerance:tolerance + colorTolerance:colorTolerance error:errorPtr]; } @@ -179,6 +214,7 @@ - (BOOL)_compareSnapshotOfViewOrLayer:(id)viewOrLayer referenceImagesDirectory:(NSString *)referenceImagesDirectory identifier:(NSString *)identifier tolerance:(CGFloat)tolerance + colorTolerance:(CGFloat)colorTolerance error:(NSError **)errorPtr { _snapshotController.referenceImagesDirectory = referenceImagesDirectory; @@ -186,6 +222,7 @@ - (BOOL)_compareSnapshotOfViewOrLayer:(id)viewOrLayer selector:self.invocation.selector identifier:identifier tolerance:tolerance + colorTolerance:colorTolerance error:errorPtr]; } diff --git a/FBSnapshotTestCase/FBSnapshotTestController.h b/FBSnapshotTestCase/FBSnapshotTestController.h index 0000c71..3853a87 100644 --- a/FBSnapshotTestCase/FBSnapshotTestController.h +++ b/FBSnapshotTestCase/FBSnapshotTestController.h @@ -116,7 +116,24 @@ extern NSString *const FBDiffedImageKey; @param selector The test method being run. @param identifier An optional identifier, used is there are muliptle snapshot tests in a given -test method. @param tolerance The percentage of pixels that can differ and still be considered 'identical' - @param errorPtr An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc). + @param colorTolerance The euclidean distance allowable between two colors (with each channel 0-1.0) to consider pixels to be "identical". colorTolerance is applied before calculating percentage of pixels that are different. + @param error An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc). + @returns YES if the comparison (or saving of the reference image) succeeded. + */ +- (BOOL)compareSnapshotOfViewOrLayer:(id)viewOrLayer + selector:(SEL)selector + identifier:(NSString *)identifier + tolerance:(CGFloat)tolerance + colorTolerance:(CGFloat)colorTolerance + error:(NSError **)errorPtr; + +/** + Performs the comparison of a view or layer. + @param view The view or layer to snapshot. + @param selector The test method being run. + @param identifier An optional identifier, used is there are muliptle snapshot tests in a given -test method. + @param tolerance The percentage of pixels that can differ and still be considered 'identical' + @param error An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc). @returns YES if the comparison (or saving of the reference image) succeeded. */ - (BOOL)compareSnapshotOfViewOrLayer:(id)viewOrLayer @@ -149,6 +166,21 @@ extern NSString *const FBDiffedImageKey; tolerance:(CGFloat)tolerance error:(NSError **)errorPtr; +/** + Performs a pixel-by-pixel comparison of the two images with an allowable margin of error. + @param referenceImage The reference (correct) image. + @param image The image to test against the reference. + @param tolerance The percentage of pixels that can differ and still be considered 'identical' + @param colorTolerance The euclidean distance allowable between two colors (with each channel 0-1.0) to consider pixels to be "identical". colorTolerance is applied before calculating percentage of pixels that are different. + @param errorPtr An error that indicates why the comparison failed if it does. + @returns YES if the comparison succeeded and the images are the same(ish). + */ +- (BOOL)compareReferenceImage:(UIImage *)referenceImage + toImage:(UIImage *)image + tolerance:(CGFloat)tolerance + colorTolerance:(CGFloat)colorTolerance + error:(NSError **)errorPtr; + /** Saves the reference image and the test image to `failedOutputDirectory`. @param referenceImage The reference (correct) image. diff --git a/FBSnapshotTestCase/FBSnapshotTestController.m b/FBSnapshotTestCase/FBSnapshotTestController.m index 74c5a0a..37d97c2 100644 --- a/FBSnapshotTestCase/FBSnapshotTestController.m +++ b/FBSnapshotTestCase/FBSnapshotTestController.m @@ -91,11 +91,21 @@ - (BOOL)compareSnapshotOfViewOrLayer:(id)viewOrLayer identifier:(NSString *)identifier tolerance:(CGFloat)tolerance error:(NSError **)errorPtr +{ + return [self compareSnapshotOfViewOrLayer:viewOrLayer selector:selector identifier:identifier tolerance:tolerance colorTolerance:0.0 error:errorPtr]; +} + +- (BOOL)compareSnapshotOfViewOrLayer:(id)viewOrLayer + selector:(SEL)selector + identifier:(NSString *)identifier + tolerance:(CGFloat)tolerance + colorTolerance:(CGFloat)colorTolerance + error:(NSError **)errorPtr { if (self.recordMode) { return [self _recordSnapshotOfViewOrLayer:viewOrLayer selector:selector identifier:identifier error:errorPtr]; } else { - return [self _performPixelComparisonWithViewOrLayer:viewOrLayer selector:selector identifier:identifier tolerance:tolerance error:errorPtr]; + return [self _performPixelComparisonWithViewOrLayer:viewOrLayer selector:selector identifier:identifier tolerance:tolerance colorTolerance:colorTolerance error:errorPtr]; } } @@ -127,10 +137,18 @@ - (UIImage *)referenceImageForSelector:(SEL)selector - (BOOL)compareReferenceImage:(UIImage *)referenceImage toImage:(UIImage *)image tolerance:(CGFloat)tolerance + error:(NSError **)errorPtr { + return [self compareReferenceImage:referenceImage toImage:image tolerance:tolerance colorTolerance:0 error:errorPtr]; +} + +- (BOOL)compareReferenceImage:(UIImage *)referenceImage + toImage:(UIImage *)image + tolerance:(CGFloat)tolerance + colorTolerance:(CGFloat)colorTolerance error:(NSError **)errorPtr { BOOL sameImageDimensions = CGSizeEqualToSize(referenceImage.size, image.size); - if (sameImageDimensions && [referenceImage fb_compareWithImage:image tolerance:tolerance]) { + if (sameImageDimensions && [referenceImage fb_compareWithImage:image tolerance:tolerance colorTolerance:colorTolerance]) { return YES; } @@ -275,12 +293,13 @@ - (BOOL)_performPixelComparisonWithViewOrLayer:(id)viewOrLayer selector:(SEL)selector identifier:(NSString *)identifier tolerance:(CGFloat)tolerance + colorTolerance:(CGFloat)colorTolerance error:(NSError **)errorPtr { UIImage *referenceImage = [self referenceImageForSelector:selector identifier:identifier error:errorPtr]; if (nil != referenceImage) { UIImage *snapshot = [self _imageForViewOrLayer:viewOrLayer]; - BOOL imagesSame = [self compareReferenceImage:referenceImage toImage:snapshot tolerance:tolerance error:errorPtr]; + BOOL imagesSame = [self compareReferenceImage:referenceImage toImage:snapshot tolerance:tolerance colorTolerance:colorTolerance error:errorPtr]; if (!imagesSame) { NSError *saveError = nil; if ([self saveFailedReferenceImage:referenceImage testImage:snapshot selector:selector identifier:identifier error:&saveError] == NO) { diff --git a/FBSnapshotTestCase/SwiftSupport.swift b/FBSnapshotTestCase/SwiftSupport.swift index 471bb0d..84eb94f 100644 --- a/FBSnapshotTestCase/SwiftSupport.swift +++ b/FBSnapshotTestCase/SwiftSupport.swift @@ -67,15 +67,15 @@ } #else public extension FBSnapshotTestCase { - public func FBSnapshotVerifyView(view: UIView, identifier: String = "", suffixes: NSOrderedSet = FBSnapshotTestCaseDefaultSuffixes(), tolerance: CGFloat = 0, file: StaticString = #file, line: UInt = #line) { - FBSnapshotVerifyViewOrLayer(view, identifier: identifier, suffixes: suffixes, tolerance: tolerance, file: file, line: line) + public func FBSnapshotVerifyView(view: UIView, identifier: String = "", suffixes: NSOrderedSet = FBSnapshotTestCaseDefaultSuffixes(), tolerance: CGFloat = 0, colorTolerance: CGFloat = 0, file: StaticString = #file, line: UInt = #line) { + FBSnapshotVerifyViewOrLayer(view, identifier: identifier, suffixes: suffixes, tolerance: tolerance, colorTolerance: colorTolerance,file: file, line: line) } - public func FBSnapshotVerifyLayer(layer: CALayer, identifier: String = "", suffixes: NSOrderedSet = FBSnapshotTestCaseDefaultSuffixes(), tolerance: CGFloat = 0, file: StaticString = #file, line: UInt = #line) { - FBSnapshotVerifyViewOrLayer(layer, identifier: identifier, suffixes: suffixes, tolerance: tolerance, file: file, line: line) + public func FBSnapshotVerifyLayer(layer: CALayer, identifier: String = "", suffixes: NSOrderedSet = FBSnapshotTestCaseDefaultSuffixes(), tolerance: CGFloat = 0, colorTolerance: CGFloat = 0, file: StaticString = #file, line: UInt = #line) { + FBSnapshotVerifyViewOrLayer(layer, identifier: identifier, suffixes: suffixes, tolerance: tolerance, colorTolerance: colorTolerance, file: file, line: line) } - private func FBSnapshotVerifyViewOrLayer(viewOrLayer: AnyObject, identifier: String = "", suffixes: NSOrderedSet = FBSnapshotTestCaseDefaultSuffixes(), tolerance: CGFloat = 0, file: StaticString = #file, line: UInt = #line) { + private func FBSnapshotVerifyViewOrLayer(viewOrLayer: AnyObject, identifier: String = "", suffixes: NSOrderedSet = FBSnapshotTestCaseDefaultSuffixes(), tolerance: CGFloat = 0, colorTolerance: CGFloat = 0, file: StaticString = #file, line: UInt = #line) { let envReferenceImageDirectory = self.getReferenceImageDirectoryWithDefault(FB_REFERENCE_IMAGE_DIR) var error: NSError? var comparisonSuccess = false @@ -85,7 +85,7 @@ public extension FBSnapshotTestCase { let referenceImagesDirectory = "\(envReferenceImageDirectory)\(suffix)" if viewOrLayer.isKindOfClass(UIView) { do { - try compareSnapshotOfView(viewOrLayer as! UIView, referenceImagesDirectory: referenceImagesDirectory, identifier: identifier, tolerance: tolerance) + try compareSnapshotOfView(viewOrLayer as! UIView, referenceImagesDirectory: referenceImagesDirectory, identifier: identifier, tolerance: tolerance, colorTolerance: colorTolerance) comparisonSuccess = true } catch let error1 as NSError { error = error1 @@ -93,7 +93,7 @@ public extension FBSnapshotTestCase { } } else if viewOrLayer.isKindOfClass(CALayer) { do { - try compareSnapshotOfLayer(viewOrLayer as! CALayer, referenceImagesDirectory: referenceImagesDirectory, identifier: identifier, tolerance: tolerance) + try compareSnapshotOfLayer(viewOrLayer as! CALayer, referenceImagesDirectory: referenceImagesDirectory, identifier: identifier, tolerance: tolerance, colorTolerance: colorTolerance) comparisonSuccess = true } catch let error1 as NSError { error = error1