Skip to content

Commit

Permalink
Implement AutoRecord for new snapshots.
Browse files Browse the repository at this point in the history
Provides an autoRecord parameter. If set, new tests that do not have a reference image will still execute, will return a failure, and will provide a fail image which is a snapshot of the view. This provides the opportunity for the user to review the image and decide whether to set it as a reference image. If recordMode == true, autoRecord is ignored and behavior is as previously.
  • Loading branch information
babbage committed Apr 8, 2018
1 parent 80c91d9 commit c2663ae
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 11 deletions.
6 changes: 6 additions & 0 deletions FBSnapshotTestCase/FBSnapshotTestCase.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@
*/
@property (readwrite, nonatomic, assign) BOOL recordMode;

/**
When YES, a test will run and fail when no reference image exists, without needing to run
recordMode first. The fail image is stored and can be reviewed and accepted as a reference.
*/
@property (readwrite, nonatomic, assign) BOOL autoRecord;

/**
When @c YES appends the name of the device model and OS to the snapshot file name.
The default value is @c NO.
Expand Down
20 changes: 17 additions & 3 deletions FBSnapshotTestCase/FBSnapshotTestCase.m
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,17 @@ - (void)setRecordMode:(BOOL)recordMode
_snapshotController.recordMode = recordMode;
}

- (BOOL)autoRecord
{
return _snapshotController.autoRecord;
}

- (void)setAutoRecord:(BOOL)autoRecord
{
NSAssert1(_snapshotController, @"%s cannot be called before [super setUp]", __FUNCTION__);
_snapshotController.autoRecord = autoRecord;
}

- (BOOL)isDeviceAgnostic
{
return _snapshotController.deviceAgnostic;
Expand Down Expand Up @@ -121,12 +132,15 @@ - (NSString *)snapshotVerifyViewOrLayer:(id)viewOrLayer
}
}

if (!testSuccess) {
return [NSString stringWithFormat:@"Snapshot comparison failed: %@", errors.firstObject];
}
if (self.recordMode) {
return @"Test ran in record mode. Reference image is now saved. Disable record mode to perform an actual snapshot comparison!";
}
else if (!testSuccess && !self.autoRecord) {
return [NSString stringWithFormat:@"Snapshot comparison failed: %@", errors.firstObject];
}
else if (!testSuccess && self.autoRecord) {
return [NSString stringWithFormat:@"No previous reference image. New image has been stored for approval."];
}

return nil;
}
Expand Down
5 changes: 5 additions & 0 deletions FBSnapshotTestCase/FBSnapshotTestController.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ extern NSString *const FBDiffedImageKey;
*/
@interface FBSnapshotTestController : NSObject

/**
Auto record snapshots on first run of new tests.
*/
@property (readwrite, nonatomic, assign) BOOL autoRecord;

/**
Record snapshots.
*/
Expand Down
17 changes: 12 additions & 5 deletions FBSnapshotTestCase/FBSnapshotTestController.m
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ - (UIImage *)referenceImageForSelector:(SEL)selector
UIImage *image = [UIImage imageWithContentsOfFile:filePath];
if (nil == image && NULL != errorPtr) {
BOOL exists = [_fileManager fileExistsAtPath:filePath];
if (!exists) {
if (!exists && !self.autoRecord) {
*errorPtr = [NSError errorWithDomain:FBSnapshotTestControllerErrorDomain
code:FBSnapshotTestControllerErrorCodeNeedsRecord
userInfo:@{
Expand Down Expand Up @@ -177,7 +177,7 @@ - (BOOL)saveFailedReferenceImage:(UIImage *)referenceImage
return NO;
}

if (![referencePNGData writeToFile:referencePath options:NSDataWritingAtomic error:errorPtr]) {
if (!self.autoRecord && ![referencePNGData writeToFile:referencePath options:NSDataWritingAtomic error:errorPtr]) {
return NO;
}

Expand All @@ -196,7 +196,7 @@ - (BOOL)saveFailedReferenceImage:(UIImage *)referenceImage
UIImage *diffImage = [referenceImage fb_diffWithImage:testImage];
NSData *diffImageData = UIImagePNGRepresentation(diffImage);

if (![diffImageData writeToFile:diffPath options:NSDataWritingAtomic error:errorPtr]) {
if (!self.autoRecord && ![diffImageData writeToFile:diffPath options:NSDataWritingAtomic error:errorPtr]) {
return NO;
}

Expand Down Expand Up @@ -280,9 +280,16 @@ - (BOOL)_performPixelComparisonWithViewOrLayer:(id)viewOrLayer
error:(NSError **)errorPtr
{
UIImage *referenceImage = [self referenceImageForSelector:selector identifier:identifier error:errorPtr];
if (nil != referenceImage) {
if (nil != referenceImage || self.autoRecord == true) {
UIImage *snapshot = [self _imageForViewOrLayer:viewOrLayer];
BOOL imagesSame = [self compareReferenceImage:referenceImage toImage:snapshot tolerance:tolerance error:errorPtr];
BOOL imagesSame;

if (self.autoRecord) {
imagesSame = NO;
} else {
imagesSame = [self compareReferenceImage:referenceImage toImage:snapshot tolerance:tolerance error:errorPtr];
}

if (!imagesSame) {
NSError *saveError = nil;
if ([self saveFailedReferenceImage:referenceImage testImage:snapshot selector:selector identifier:identifier error:&saveError] == NO) {
Expand Down
36 changes: 33 additions & 3 deletions FBSnapshotTestCase/SwiftSupport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,18 @@
if let envReferenceImageDirectory = envReferenceImageDirectory {
for suffix in suffixes {
let referenceImagesDirectory = "\(envReferenceImageDirectory)\(suffix)"
var referenceImageExists = true
if viewOrLayer.isKind(of: UIView.self) {
do {
try compareSnapshot(of: viewOrLayer as! UIView, referenceImagesDirectory: referenceImagesDirectory, identifier: identifier, tolerance: tolerance)
comparisonSuccess = true
} catch let error1 as NSError {
error = error1
comparisonSuccess = false
if error1.code == 1 {
referenceImageExists = false
} else {
error = error1
}
}
} else if viewOrLayer.isKind(of: CALayer.self) {
do {
Expand All @@ -39,6 +44,11 @@
} catch let error1 as NSError {
error = error1
comparisonSuccess = false
if error1.code == 1 {
referenceImageExists = false
} else {
error = error1
}
}
} else {
assertionFailure("Only UIView and CALayer classes can be snapshotted")
Expand All @@ -49,7 +59,13 @@
if comparisonSuccess || recordMode {
break
}

assert(self.autoRecord == false || referenceImageExists, message: "No previous reference image. New image has been stored for approval.", file: file, line: line)

if self.autoRecord && !referenceImageExists {
break
}

assert(comparisonSuccess, message: "Snapshot comparison failed: \(String(describing: error))", file: file, line: line)
}
} else {
Expand Down Expand Up @@ -86,16 +102,24 @@ public extension FBSnapshotTestCase {
try compareSnapshotOfView(viewOrLayer as! UIView, referenceImagesDirectory: referenceImagesDirectory, identifier: identifier, tolerance: tolerance)
comparisonSuccess = true
} catch let error1 as NSError {
error = error1
comparisonSuccess = false
if error1.code == 1 {
referenceImageExists = false
} else {
error = error1
}
}
} else if viewOrLayer.isKindOfClass(CALayer) {
do {
try compareSnapshotOfLayer(viewOrLayer as! CALayer, referenceImagesDirectory: referenceImagesDirectory, identifier: identifier, tolerance: tolerance)
comparisonSuccess = true
} catch let error1 as NSError {
error = error1
comparisonSuccess = false
if error1.code == 1 {
referenceImageExists = false
} else {
error = error1
}
}
} else {
assertionFailure("Only UIView and CALayer classes can be snapshotted")
Expand All @@ -107,6 +131,12 @@ public extension FBSnapshotTestCase {
break
}

assert(self.autoRecord == false || referenceImageExists, message: "No previous reference image. New image has been stored for approval.", file: file, line: line)

if self.autoRecord && !referenceImageExists {
break
}

assert(comparisonSuccess, message: "Snapshot comparison failed: \(error)", file: file, line: line)
}
} else {
Expand Down

0 comments on commit c2663ae

Please sign in to comment.