Base Revision
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..ae319c7
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,23 @@
+# How to Contribute
+
+We'd love to accept your patches and contributions to this project. There are
+just a few small guidelines you need to follow.
+
+## Contributor License Agreement
+
+Contributions to this project must be accompanied by a Contributor License
+Agreement. You (or your employer) retain the copyright to your contribution,
+this simply gives us permission to use and redistribute your contributions as
+part of the project. Head over to <https://cla.developers.google.com/> to see
+your current agreements on file or to sign a new one.
+
+You generally only need to submit a CLA once, so if you've already submitted one
+(even if it was for a different project), you probably don't need to do it
+again.
+
+## Code reviews
+
+All submissions, including submissions by project members, require review. We
+use GitHub pull requests for this purpose. Consult
+[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
+information on using pull requests.
diff --git a/Classes/GTAxe.h b/Classes/GTAxe.h
new file mode 100644
index 0000000..f4b5f02
--- /dev/null
+++ b/Classes/GTAxe.h
@@ -0,0 +1,44 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+/**
+ *  Umbrella header for public GTX APIs.
+ */
+#import <UIKit/UIKit.h>
+
+//! Project version number for GTAxe.
+FOUNDATION_EXPORT double GTAxeVersionNumber;
+
+//! Project version string for GTAxe.
+FOUNDATION_EXPORT const unsigned char GTAxeVersionString[];
+
+#import <GTAxe/GTXAccessibilityTree.h>
+#import <GTAxe/GTXAnalytics.h>
+#import <GTAxe/GTXAnalyticsUtils.h>
+#import <GTAxe/GTXAssertions.h>
+#import <GTAxe/GTXAxeCore.h>
+#import <GTAxe/GTXCheckBlock.h>
+#import <GTAxe/GTXChecksCollection.h>
+#import <GTAxe/GTXCommon.h>
+#import <GTAxe/GTXElementBlacklist.h>
+#import <GTAxe/GTXErrorReporter.h>
+#import <GTAxe/GTXImageRGBAData.h>
+#import <GTAxe/GTXImageAndColorUtils.h>
+#import <GTAxe/GTXLogging.h>
+#import <GTAxe/GTXPluginXCTestCase.h>
+#import <GTAxe/GTXToolKit.h>
+#import <GTAxe/GTXTestSuite.h>
+#import <GTAxe/NSError+GTXAdditions.h>
diff --git a/Classes/GTXAccessibilityTree.h b/Classes/GTXAccessibilityTree.h
new file mode 100644
index 0000000..9a0855b
--- /dev/null
+++ b/Classes/GTXAccessibilityTree.h
@@ -0,0 +1,33 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ *  An enumerator for the accessibility trees navigated by accessibility services like VoiceOver.
+ */
+@interface GTXAccessibilityTree : NSEnumerator
+
+/**
+ *  Creates a new accessibility tree whose traversals will include the given @c rootElements.
+ */
+- (instancetype)initWithRootElements:(NSArray *)rootElements;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Classes/GTXAccessibilityTree.m b/Classes/GTXAccessibilityTree.m
new file mode 100644
index 0000000..c86ff1f
--- /dev/null
+++ b/Classes/GTXAccessibilityTree.m
@@ -0,0 +1,201 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import <UIKit/UIKit.h>
+
+#import "GTXAccessibilityTree.h"
+
+/**
+ * There seems to be errors in accessibility children reported by some UIKit classes especially
+ * UITextEffectsWindow which reports 9223372036854775807 possibly due to internal type conversions
+ * with -1, we use this bounds value to detect that case..
+ */
+const NSInteger kAccessibilityChildrenUpperBound = 50000;
+
+@implementation GTXAccessibilityTree {
+  // A queue of elements to be visited.
+  NSMutableArray *_queue;
+  // A queue of elements already visited.
+  NSMutableSet *_visitedElements;
+}
+
+- (instancetype)initWithRootElements:(NSArray *)rootElements {
+  self = [super init];
+  if (self) {
+    _queue = [[NSMutableArray alloc] initWithArray:rootElements];
+    _visitedElements = [[NSMutableSet alloc] init];
+  }
+  return self;
+}
+
+#pragma mark - NSEnumerator
+
+- (id)nextObject {
+  if ([_queue count] == 0) {
+    return nil;
+  }
+
+  id nextInQueue;
+  // Get the next "unvisited" element.
+  do {
+    id candidateNext = [_queue firstObject];
+    [_queue removeObjectAtIndex:0];
+    if (![_visitedElements containsObject:candidateNext]) {
+      if (![self gtx_isAccessibilityHiddenElement:candidateNext]) {
+        nextInQueue = candidateNext;
+      }
+    }
+  } while ([_queue count] > 0 && !nextInQueue);
+  if (!nextInQueue) {
+    return nil;
+  }
+
+  [_visitedElements addObject:nextInQueue];
+  if ([nextInQueue respondsToSelector:@selector(isAccessibilityElement)]) {
+    if (![nextInQueue isAccessibilityElement]) {
+      // nextInQueue could be an accessibility container, if so enqueue its children.
+      // There are two ways of getting the children of an accessibility container:
+      // First, using @selector(accessibilityElements)
+      NSArray *axElements = [self gtx_accessibilityElementsOfElement:nextInQueue];
+
+      // Second, using @selector(accessibilityElementAtIndex:)
+      NSArray *axElementsFromIndices =
+          [self gtx_accessibilityElementsFromIndicesOfElement:nextInQueue];
+
+      // Ensure that either the children are available only through one method or elements via both
+      // are the same. Otherwise we must fail as the the accessibility tree is inconsistent.
+      if (axElements && axElementsFromIndices) {
+        NSSet *accessibilityElementsSet = [NSSet setWithArray:axElements];
+        NSSet *accessibilityElementsFromIndicesSet = [NSSet setWithArray:axElementsFromIndices];
+        NSAssert([accessibilityElementsSet isEqualToSet:accessibilityElementsFromIndicesSet],
+                 @"Accessibility elements obtained from -accessibilityElements and"
+                 @" -accessibilityElementAtIndex: are different - they must not be. Either provide"
+                 @" elements via one method or provide the same elements.\nDetails:\nElements via"
+                 @" accessibilityElements:%@\nElements via accessibilityElementAtIndex:\n"
+                 @"accessibilityElementCount:%@\nElements:%@",
+                 accessibilityElementsSet,
+                 @([nextInQueue accessibilityElementCount]),
+                 accessibilityElementsFromIndicesSet);
+      } else {
+        // Set accessibilityElements to whichever is non nil or leave it as is.
+        axElements = axElementsFromIndices ? axElementsFromIndices : axElements;
+      }
+      if (![nextInQueue respondsToSelector:@selector(accessibilityElementsHidden)] ||
+          ![nextInQueue accessibilityElementsHidden]) {
+        for (id element in axElements) {
+          if (![_visitedElements containsObject:element]) {
+            [_queue addObject:element];
+          }
+        }
+      }
+
+      // nextInQueue could be a UIView subclass, if so enqueue its subviews.
+      NSArray *subViews;
+      if ([nextInQueue isKindOfClass:[UITableViewCell class]] ||
+          [nextInQueue isKindOfClass:[UICollectionViewCell class]]) {
+        subViews = [nextInQueue contentView].subviews;
+      } else if ([nextInQueue respondsToSelector:@selector(subviews)]) {
+        subViews = [nextInQueue subviews];
+      }
+      if ([nextInQueue respondsToSelector:@selector(isHidden)] &&
+          ![nextInQueue isHidden]) {
+        for (id child in subViews) {
+          if (![_visitedElements containsObject:child]) {
+            [_queue addObject:child];
+          }
+        }
+      }
+    }
+  }
+  return nextInQueue;
+}
+
+#pragma mark - NSExtendedEnumerator
+
+- (NSArray *)allObjects {
+  NSMutableArray *remainingObjects = [[NSMutableArray alloc] init];
+  id nextObject;
+  while ((nextObject = [self nextObject])) {
+    [remainingObjects addObject:nextObject];
+  }
+  return remainingObjects;
+}
+
+#pragma mark - Private
+
+
+/**
+ *  @return An array of accessible children of the given @c element as reported by the selector
+ *          -[NSObject(UIAccessibility) accessibilityElements].
+ */
+- (NSArray *)gtx_accessibilityElementsOfElement:(id)element {
+  if ([element respondsToSelector:@selector(accessibilityElements)]) {
+    return [element accessibilityElements];
+  }
+  return nil;
+}
+
+/**
+ *  @return An array of accessible children of the given @c element as reported by the selector
+ *          -[NSObject(UIAccessibility) accessibilityElementAtIndex:].
+ */
+- (NSArray *)gtx_accessibilityElementsFromIndicesOfElement:(id)element {
+  NSMutableArray *axElementsFromIndices;
+  if ([element respondsToSelector:@selector(accessibilityElementAtIndex:)] &&
+      [element respondsToSelector:@selector(accessibilityElementCount)]) {
+    NSInteger childrenCount = [element accessibilityElementCount];
+    // This is a workaround to deal with UIKit classes that are reporting incorrect
+    // accessibilityElementCount, see kAccessibilityChildrenUpperBound.
+    if (childrenCount > 0 && childrenCount < kAccessibilityChildrenUpperBound) {
+      axElementsFromIndices = [[NSMutableArray alloc] initWithCapacity:(NSUInteger)childrenCount];
+      for (NSInteger index = 0; index < childrenCount; index++) {
+        [axElementsFromIndices addObject:[element accessibilityElementAtIndex:index]];
+      }
+    }
+  }
+  return axElementsFromIndices;
+}
+
+/**
+ *  Elements are hidden from accessibility trees
+ *
+ *  @return @c YES if the element is hidden from accessibility tree @c NO otherwise.
+ */
+- (BOOL)gtx_isAccessibilityHiddenElement:(id)element {
+  BOOL isHidden = NO;
+  BOOL isAccessibilityHidden = NO;
+  BOOL isHiddenDueToAccessibilityFrame = NO;
+  BOOL isHiddenDueToFrame = NO;
+  if ([element respondsToSelector:@selector(isHidden)]) {
+    isHidden = [element isHidden];
+  }
+  if ([element respondsToSelector:@selector(accessibilityElementsHidden)]) {
+    isAccessibilityHidden = [element accessibilityElementsHidden];
+  }
+  if ([element respondsToSelector:@selector(accessibilityFrame)]) {
+    CGRect accessibilityFrame = [element accessibilityFrame];
+    isHiddenDueToAccessibilityFrame = (accessibilityFrame.size.width == 0 ||
+                                       accessibilityFrame.size.height == 0);
+  }
+  if ([element respondsToSelector:@selector(frame)]) {
+    CGRect frame = [element frame];
+    isHiddenDueToFrame = frame.size.width == 0 || frame.size.height == 0;
+  }
+  return (isHidden || isAccessibilityHidden ||
+          (isHiddenDueToFrame && isHiddenDueToAccessibilityFrame));
+}
+
+@end
diff --git a/Classes/GTXAnalytics.h b/Classes/GTXAnalytics.h
new file mode 100644
index 0000000..76a683d
--- /dev/null
+++ b/Classes/GTXAnalytics.h
@@ -0,0 +1,74 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import <UIKit/UIKit.h>
+
+#import "GTXChecking.h"
+#import "GTXCheckBlock.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+
+/**
+ Enum of all possible analytics events handleed by GTAxe.
+ */
+typedef NS_ENUM(NSUInteger, GTXAnalyticsEvent) {
+  /**
+   Enum for GTAxe checks being invoked.
+   */
+  GTXAnalyticsEventChecksPerformed,
+
+  /**
+   Enum for GTAxe checks failure detection.
+   */
+  GTXAnalyticsEventChecksFailed,
+};
+
+
+/**
+ Typedef for Analytics handler.
+
+ @param event The analytics event to be handled.
+ */
+typedef void(^GTXAnalyticsHandlerBlock)(GTXAnalyticsEvent event);
+
+
+/**
+ Class that handles all analytics in GTAxe.
+ */
+@interface GTXAnalytics : NSObject
+
+/**
+ Boolean property that specifies if analytics is enabled or not.
+ */
+@property (class, nonatomic, assign) BOOL enabled;
+
+/**
+ Current analytics handler, users can override this for custom handling of analytics events.
+ */
+@property (class, nonatomic) GTXAnalyticsHandlerBlock handler;
+
+
+/**
+ Feeds an analytics event to be handled.
+
+ @param event The event to be handled.
+ */
++ (void)invokeAnalyticsEvent:(GTXAnalyticsEvent)event;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Classes/GTXAnalytics.m b/Classes/GTXAnalytics.m
new file mode 100644
index 0000000..28972bf
--- /dev/null
+++ b/Classes/GTXAnalytics.m
@@ -0,0 +1,90 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "GTXAnalytics.h"
+#import "GTXAnalyticsUtils.h"
+#import "GTXAssertions.h"
+
+/**
+ Storage for GTXAnalytics.enabled property.
+ */
+static BOOL gEnabled = YES;
+
+/**
+ Storage for GTXAnalytics.handler property.
+ */
+static GTXAnalyticsHandlerBlock gHandler;
+
+/**
+ The Analytics tracking ID that receives GTAxe usage data.
+ */
+static NSString *const kGTXAnalyticsTrackingID = @"UA-113761703-1";
+
+#pragma mark - Implementation
+
+@implementation GTXAnalytics
+
++ (void)load {
+  gHandler = ^(GTXAnalyticsEvent event) {
+    GTX_ASSERT(gEnabled, @"Invoking default handler when analytics is enabled!");
+    [GTXAnalyticsUtils sendEventHitWithTrackingID:kGTXAnalyticsTrackingID
+                                         clientID:[GTXAnalyticsUtils clientID]
+                                         category:@"GTAxeLib"
+                                           action:[self _NSStringFromAnalyticsEvent:event]
+                                            value:@"1"];
+  };
+}
+
++ (void)setHandler:(GTXAnalyticsHandlerBlock)handler {
+  NSParameterAssert(handler);
+  gHandler = handler;
+}
+
++ (GTXAnalyticsHandlerBlock)handler {
+  return gHandler;
+}
+
++ (void)setEnabled:(BOOL)enabled {
+  gEnabled = enabled;
+}
+
++ (BOOL)enabled {
+  return gEnabled;
+}
+
++ (void)invokeAnalyticsEvent:(GTXAnalyticsEvent)event {
+  if (self.enabled) {
+    self.handler(event);
+  }
+}
+
+#pragma mark - Private
+
+/**
+ Appropriate NSString value for the given GTXAnalyticsEvent.
+
+ @param event The event whose string value is needed.
+ @return String value for the given GTXAnalyticsEvent.
+ */
++ (NSString *)_NSStringFromAnalyticsEvent:(GTXAnalyticsEvent)event {
+  switch (event) {
+    case GTXAnalyticsEventChecksPerformed: return @"checkRan";
+    case GTXAnalyticsEventChecksFailed: return @"checkFailed";
+  }
+}
+
+@end
+
diff --git a/Classes/GTXAnalyticsUtils.h b/Classes/GTXAnalyticsUtils.h
new file mode 100644
index 0000000..878468e
--- /dev/null
+++ b/Classes/GTXAnalyticsUtils.h
@@ -0,0 +1,48 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import <UIKit/UIKit.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ Class that provides analytics utils for GTAxe analytics.
+ */
+@interface GTXAnalyticsUtils : NSObject
+
+/**
+ Util method to send an analytics event over to Google Analytics.
+
+ @param trackingID Google Analytics account tracking ID
+ @param clientID Client ID to use in the event.
+ @param category Event category to use in the event.
+ @param action Event action to use in the event.
+ @param value Event value to use in the event.
+ */
++ (void)sendEventHitWithTrackingID:(NSString *)trackingID
+                           clientID:(NSString *)clientID
+                           category:(NSString *)category
+                             action:(NSString *)action
+                              value:(NSString *)value;
+
+/**
+ @return The an appropriate clientID for analytics that is based on hash of App's bundle ID.
+ */
++ (NSString *)clientID;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Classes/GTXAnalyticsUtils.m b/Classes/GTXAnalyticsUtils.m
new file mode 100644
index 0000000..93deea0
--- /dev/null
+++ b/Classes/GTXAnalyticsUtils.m
@@ -0,0 +1,105 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "GTXAnalyticsUtils.h"
+#import "GTXLogging.h"
+
+#import <CommonCrypto/CommonDigest.h>
+
+/**
+ The endpoint that receives GTAxe usage data.
+ */
+static NSString *const kGTXTrackingEndPoint = @"https://ssl.google-analytics.com/collect";
+
+#pragma mark - Implementation
+
+@implementation GTXAnalyticsUtils
+
+/**
+ @return The clientID to be used by GTAxe analytics, this is a hash of App's bundle ID.
+ */
++ (NSString *)clientID {
+  static NSString *clientID;
+  static dispatch_once_t onceToken;
+  dispatch_once(&onceToken, ^{
+    NSString *bundleIDMD5 = [[NSBundle mainBundle] bundleIdentifier];
+    if (!bundleIDMD5) {
+      // If bundle ID is not available we use a placeholder.
+      bundleIDMD5 = @"<Missing Bundle ID>";
+    }
+
+    // Get MD5 value of the given string.
+    unsigned char md5Value[CC_MD5_DIGEST_LENGTH];
+    const char *stringCPtr = [bundleIDMD5 UTF8String];
+    CC_MD5(stringCPtr, (CC_LONG)strlen(stringCPtr), md5Value);
+
+    // Parse MD5 value into individual hex values.
+    NSMutableString *stringWithHexMd5Values = [[NSMutableString alloc] init];
+    for (int i = 0; i < CC_MD5_DIGEST_LENGTH; i++) {
+      [stringWithHexMd5Values appendFormat:@"%02x", md5Value[i]];
+    }
+    clientID = [stringWithHexMd5Values copy];
+  });
+
+  return clientID;
+}
+
+/**
+ Util method to send an analytics event over to Google Analytics.
+
+ @param trackingID Google Analytics account tracking ID
+ @param clientID Client ID to use in the event.
+ @param category Event category to use in the event.
+ @param action Event action to use in the event.
+ @param value Event value to use in the event.
+ */
++ (void)sendEventHitWithTrackingID:(NSString *)trackingID
+                          clientID:(NSString *)clientID
+                          category:(NSString *)category
+                            action:(NSString *)action
+                             value:(NSString *)value {
+  // Initialize the payload with version(=1), tracking ID, client ID, category, action, and value.
+  NSMutableString *payload = [[NSMutableString alloc] initWithFormat:@"v=1"
+                                                                     @"&t=event"
+                                                                     @"&tid=%@"
+                                                                     @"&cid=%@"
+                                                                     @"&ec=%@"
+                                                                     @"&ea=%@"
+                                                                     @"&ev=%@",
+                                                                     trackingID,
+                                                                     clientID,
+                                                                     category,
+                                                                     action,
+                                                                     value];
+
+  NSURLComponents *components = [[NSURLComponents alloc] initWithString:kGTXTrackingEndPoint];
+  [components setQuery:payload];
+  NSURL *url = [components URL];
+
+  [[[NSURLSession sharedSession] dataTaskWithURL:url
+                               completionHandler:^(NSData *data,
+                                                   NSURLResponse *response,
+                                                   NSError *error) {
+    if (error) {
+      // Failed to send analytics data, but since the test might be running in a sandboxed
+      // environment it's not a good idea to freeze or throw assertions, let's just log and
+      // move on.
+      GTX_LOG(@"Failed to send analytics data due to: %@", error);
+    }
+  }] resume];
+}
+
+@end
diff --git a/Classes/GTXAssertions.h b/Classes/GTXAssertions.h
new file mode 100644
index 0000000..5ea026f
--- /dev/null
+++ b/Classes/GTXAssertions.h
@@ -0,0 +1,24 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "GTXLogging.h"
+
+#ifndef GTX_ASSERTIONS_INCLUDED
+#define GTX_ASSERTIONS_INCLUDED
+
+#define GTX_ASSERT(...) NSAssert(__VA_ARGS__)
+
+#endif  // GTX_ASSERTIONS_INCLUDED
diff --git a/Classes/GTXAxeCore.h b/Classes/GTXAxeCore.h
new file mode 100644
index 0000000..50a6753
--- /dev/null
+++ b/Classes/GTXAxeCore.h
@@ -0,0 +1,119 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "GTXAnalytics.h"
+#import "GTXAccessibilityTree.h"
+#import "GTXCheckBlock.h"
+#import "GTXChecksCollection.h"
+#import "GTXCommon.h"
+#import "GTXElementBlacklist.h"
+#import "GTXErrorReporter.h"
+#import "GTXTestSuite.h"
+#import "NSError+GTXAdditions.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ *  Name of the notification that must be fired when testcase begins - i.e. before test method is
+ *  executed but after setup has been.
+ */
+extern NSString *const gtxTestCaseDidBeginNotification;
+
+/**
+ *  Name of the notification that must be fired when testcase ends - i.e. after test method is
+ *  executed but before tearDown has been.
+ */
+extern NSString *const gtxTestCaseDidEndNotification;
+
+/**
+ *  Name of the notification that must be fired when a test interaction (tap/swipe etc) begins.
+ */
+extern NSString *const gtxTestInteractionDidBeginNotification;
+
+/**
+ *  Name of the notification that must be fired after a test interaction (tap/swipe etc) has ended.
+ */
+extern NSString *const gtxTestInteractionDidEndNotification;
+
+/**
+ *  Name of the user info key for the notifications that must be set to the name of the currently
+ *  running test class.
+ */
+extern NSString *const gtxTestClassUserInfoKey;
+
+/**
+ *  Name of the user info key for the notifications that must be set to the current test invocation.
+ */
+extern NSString *const gtxTestInvocationUserInfoKey;
+
+/**
+ *  Block type for GTAxe failure handlers, this is invoked with the error detected.
+ */
+typedef void(^GTAxeFailureHandler)(NSError *error);
+
+/**
+ *  Primary class that allows for installing checks, creating checks and blacklists etc.
+ */
+@interface GTAxe : NSObject
+
+/**
+ Install checks on all test cases of a given test suite.
+
+ @param suite Suite of all test cases where checks are to be installed.
+ @param checks Array of checks to be installed.
+ @param blacklists Array of element blacklists to be skipped from checks.
+ */
++ (void)installOnTestSuite:(GTXTestSuite *)suite
+                    checks:(NSArray<id<GTXChecking>> *)checks
+         elementBlacklists:(NSArray<GTXElementBlacklist *> *)blacklists;
+
+/**
+ Creates a check with the given name and block.
+
+ @param name Name of the check
+ @param block Block that performs the check, the block must return NO if the check failed, YES
+              otherwise.
+ @return The newly created check.
+ */
++ (id<GTXChecking>)checkWithName:(NSString *)name block:(GTXCheckHandlerBlock)block;
+
+/**
+ Creates a blacklist matcher that skips elements of the given class from all checks.
+
+ @param className The class name of the element.
+ @return The newly created blacklist matcher.
+ */
++ (GTXElementBlacklist *)blacklistForElementsOfClassNamed:(NSString *)className;
+
+/**
+ Creates a blacklist matcher that skips elements of the given class from the given check.
+
+ @param className The class name of the element.
+ @param skipCheckName The name of the check from which to skip the elements.
+ @return The newly created blacklist matcher.
+ */
++ (GTXElementBlacklist *)blacklistForElementsOfClassNamed:(NSString *)className
+                                            forCheckNamed:(NSString *)skipCheckName;
+
+/**
+ The failure handler to be invoked when checks fail, by default if checks fail an Assertion is
+ raised.
+ */
+@property (class, nonatomic, strong) GTAxeFailureHandler failureHandler;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Classes/GTXAxeCore.m b/Classes/GTXAxeCore.m
new file mode 100644
index 0000000..56c16cd
--- /dev/null
+++ b/Classes/GTXAxeCore.m
@@ -0,0 +1,271 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "GTXAxeCore.h"
+
+#import "GTXToolKit.h"
+#import "GTXAssertions.h"
+#import "GTXChecking.h"
+#import "GTXChecksCollection.h"
+#import "GTXLogging.h"
+#import "GTXPluginXCTestCase.h"
+#import "NSError+GTXAdditions.h"
+
+#pragma mark - Global definitions.
+
+NSString *const gtxTestCaseDidBeginNotification = @"gtxTestCaseDidBeginNotification";
+NSString *const gtxTestCaseDidEndNotification = @"gtxTestCaseDidEndNotification";
+NSString *const gtxTestClassUserInfoKey = @"gtxTestClassUserInfoKey";
+NSString *const gtxTestInvocationUserInfoKey = @"gtxTestInvocationUserInfoKey";
+NSString *const gtxTestInteractionDidBeginNotification = @"gtxTestInteractionDidBeginNotification";
+NSString *const gtxTestInteractionDidEndNotification = @"gtxTestInteractionDidEndNotification";
+
+@interface GTXInstallOptions : NSObject
+
+@property (nonatomic, strong) NSArray *checks;
+@property (nonatomic, strong) NSArray *elementBlacklist;
+@property (nonatomic, strong) GTXTestSuite *suite;
+
+@end
+
+@implementation GTXInstallOptions
+@end
+
+/**
+ The GTXToolKit instance used to handle accessibility checking in GTAxe class.
+ */
+static GTXToolKit *gToolkit;
+
+/**
+ The an array of installation options specified by the user so far.
+ */
+static NSMutableArray *gIntsallOptions;
+
+/**
+ The pointer to current options being used by GTAxe.
+ */
+static GTXInstallOptions *gCurrentOptions;
+
+/**
+ The failure handler block.
+ */
+static GTAxeFailureHandler gFailureHandler;
+
+/**
+ Boolean that indicates if GTAxe has detected an on-going interaction.
+ */
+static BOOL gIsInInteraction;
+
+/**
+ Boolean that indicates if GTAxe has detected a test case tearDown.
+ */
+static BOOL gIsInTearDown;
+
+
+#pragma mark - Implementation
+
+@implementation GTAxe
+
++ (void)installOnTestSuite:(GTXTestSuite *)suite
+                    checks:(NSArray<id<GTXChecking>> *)checks
+         elementBlacklists:(NSArray<GTXElementBlacklist *> *)blacklists {
+  [GTXPluginXCTestCase installPlugin];
+  if (!gIntsallOptions) {
+    gIntsallOptions = [[NSMutableArray alloc] init];
+  }
+
+  GTXInstallOptions *options = [[GTXInstallOptions alloc] init];
+  options.checks = checks;
+  options.elementBlacklist = blacklists;
+  options.suite = suite;
+
+  // Assert that this suite has no test cases also specified in other install calls.
+  for (GTXInstallOptions *existing in gIntsallOptions) {
+    GTXTestSuite *intersection = [existing.suite intersection:suite];
+    NSAssert(intersection.tests.count == 0,
+             @"Error! Attempting to install GTXChecks multiple times on the same test cases: %@",
+             intersection);
+  }
+  [gIntsallOptions addObject:options];
+
+  static dispatch_once_t onceToken;
+  dispatch_once(&onceToken, ^{
+    [[NSNotificationCenter defaultCenter] addObserver:self
+                                             selector:@selector(_testCaseDidBegin:)
+                                                 name:gtxTestCaseDidBeginNotification
+                                               object:nil];
+    [[NSNotificationCenter defaultCenter] addObserver:self
+                                             selector:@selector(_testCaseDidTearDown:)
+                                                 name:gtxTestCaseDidEndNotification
+                                               object:nil];
+    [[NSNotificationCenter defaultCenter] addObserver:self
+                                             selector:@selector(_testInteractionDidBegin:)
+                                                 name:gtxTestInteractionDidBeginNotification
+                                               object:nil];
+    [[NSNotificationCenter defaultCenter] addObserver:self
+                                             selector:@selector(_testInteractionDidFinish:)
+                                                 name:gtxTestInteractionDidEndNotification
+                                               object:nil];
+  });
+}
+
++ (void)setFailureHandler:(GTAxeFailureHandler)handler {
+  gFailureHandler = handler;
+}
+
++ (GTAxeFailureHandler)failureHandler {
+  return gFailureHandler ?: ^(NSError *error) {
+    NSString *formattedError =
+        [NSString stringWithFormat:@"\n\n%@\n\n",
+                                   error.localizedDescription];
+    [[NSAssertionHandler currentHandler] handleFailureInMethod:_cmd
+                                                        object:self
+                                                          file:@(__FILE__)
+                                                    lineNumber:__LINE__
+                                                   description:@"%@", formattedError];
+  };
+}
+
++ (id<GTXChecking>)checkWithName:(NSString *)name block:(GTXCheckHandlerBlock)block {
+  return [GTXToolKit checkWithName:name block:block];
+}
+
++ (GTXElementBlacklist *)blacklistForElementsOfClassNamed:(NSString *)className {
+  return [[GTXElementBlacklist alloc] initWithElementClassName:className
+                                                     checkName:nil];
+}
+
++ (GTXElementBlacklist *)blacklistForElementsOfClassNamed:(NSString *)className
+                                            forCheckNamed:(NSString *)skipCheckName {
+  return [[GTXElementBlacklist alloc] initWithElementClassName:className
+                                                     checkName:skipCheckName];
+}
+
+#pragma mark - Private
+
+/**
+ Executes the currently installed checks on the given element. In case of failures, the failure
+ handler is invoked.
+
+ @param element The element on which the checks need to be executed.
+ @return @c NO if any of the checks failed, @c YES otherwise.
+ */
++ (BOOL)_checkElement:(id)element {
+  NSError *error;
+  BOOL success = [gToolkit checkElement:element error:&error];
+  if (error) {
+    self.failureHandler(error);
+  }
+  return success;
+}
+
+/**
+ Executes the currently installed checks on the all elements of the accessibility tree under the
+ given root elements. In case of failures, the failure handler is invoked.
+
+ @param rootElements An array of root elements.
+ @return @c NO if any of the checks failed, @c YES otherwise.
+ */
++ (BOOL)_checkAllElementsFromRootElements:(NSArray *)rootElements {
+  NSError *error;
+  BOOL success = [gToolkit checkAllElementsFromRootElements:rootElements error:&error];
+  if (error) {
+    self.failureHandler(error);
+  }
+  return success;
+}
+
+/**
+ Notification handler for handling gtxTestCaseDidBeginNotification.
+
+ @param notification The notification that was posted.
+ */
++ (void)_testCaseDidBegin:(NSNotification *)notification {
+  // check if new test class has started
+  gIsInTearDown = NO;
+  Class currentTestClass = notification.userInfo[gtxTestClassUserInfoKey];
+  SEL currentTestSelector =
+      ((NSInvocation *)notification.userInfo[gtxTestInvocationUserInfoKey]).selector;
+  GTXInstallOptions *currentTestCaseOptions = nil;
+  for (GTXInstallOptions *options in gIntsallOptions) {
+    if ([options.suite hasTestCaseWithClass:currentTestClass
+                                 testMethod:currentTestSelector]) {
+      currentTestCaseOptions = options;
+      break;
+    }
+  }
+  if (gCurrentOptions != currentTestCaseOptions) {
+    gCurrentOptions = currentTestCaseOptions;
+    if (gCurrentOptions) {
+      gToolkit = [[GTXToolKit alloc] init];
+      NSAssert(gCurrentOptions.checks && gCurrentOptions.checks.count > 0,
+               @"At least one check must be installed!");
+      for (id<GTXChecking> check in gCurrentOptions.checks) {
+        [gToolkit registerCheck:check];
+      }
+      for (GTXElementBlacklist *blacklist in gCurrentOptions.elementBlacklist) {
+        if (blacklist.checkName) {
+          [gToolkit ignoreElementsOfClassNamed:blacklist.elementClassName
+                                 forCheckNamed:blacklist.checkName];
+        } else {
+          [gToolkit ignoreElementsOfClassNamed:blacklist.elementClassName];
+        }
+      }
+    }
+  }
+}
+
+/**
+ Notification handler for handling gtxTestCaseDidEndNotification.
+
+ @param notification The notification that was posted.
+ */
++ (void)_testCaseDidTearDown:(NSNotification *)notification {
+  if (gIsInTearDown) {
+    return;
+  }
+  gIsInTearDown = YES;
+
+  if (gCurrentOptions) {
+    // Run all the checks.
+    UIWindow *window = [UIApplication sharedApplication].keyWindow;
+    if (window) {
+      [self _checkAllElementsFromRootElements:@[window]];
+    }
+  }
+}
+
+/**
+ Notification handler for handling gtxTestInteractionDidBeginNotification.
+
+ @param notification The notification that was posted.
+ */
++ (void)_testInteractionDidBegin:(NSNotification *)notification {
+  if (!gIsInInteraction) {
+    [self _checkAllElementsFromRootElements:@[[UIApplication sharedApplication].keyWindow.window]];
+  }
+  gIsInInteraction = YES;
+}
+
+/**
+ Notification handler for handling gtxTestInteractionDidEndNotification.
+
+ @param notification The notification that was posted.
+ */
++ (void)_testInteractionDidFinish:(NSNotification *)notification {
+  gIsInInteraction = NO;
+}
+@end
diff --git a/Classes/GTXCheckBlock.h b/Classes/GTXCheckBlock.h
new file mode 100644
index 0000000..f635f55
--- /dev/null
+++ b/Classes/GTXCheckBlock.h
@@ -0,0 +1,58 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import <UIKit/UIKit.h>
+
+#import "GTXChecking.h"
+#import "GTXCommon.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ *  Block that performs a single accessibility check on the given element.
+ *
+ *  @param      element    The target element on which GTX check is to be performed.
+ *  @param[out] errorOrNil The error set on failure. The error returned can be @c nil, signifying
+ *                         that the check succeeded.
+ *
+ *  @return @c YES if the check performed succeeded without any errors, else @c NO.
+ */
+typedef BOOL (^GTXCheckHandlerBlock)(id element, GTXErrorRefType errorOrNil);
+
+/**
+ *  A block based interface for creating accessiblity checks.
+ */
+@interface GTXCheckBlock : NSObject<GTXChecking>
+
+/**
+ *  GTXCheckBlock::init is disabled, instead use GTXCheckBlock::GTXCheckWithName:block: method
+ *  to create GTXChecks.
+ */
+- (instancetype)init __attribute__((unavailable("Use GTXCheckWithName:block: instead.")));
+
+/**
+ *  Creates an GTXCheck with the given @c name and @c block that performs the check.
+ *
+ *  @param name  The name of the check.
+ *  @param block A block that performs the check.
+ *
+ *  @return An GTXCheck object.
+ */
++ (id<GTXChecking>)GTXCheckWithName:(NSString *)name block:(GTXCheckHandlerBlock)block;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Classes/GTXCheckBlock.m b/Classes/GTXCheckBlock.m
new file mode 100644
index 0000000..a6957bc
--- /dev/null
+++ b/Classes/GTXCheckBlock.m
@@ -0,0 +1,58 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "GTXCheckBlock.h"
+
+#import "NSError+GTXAdditions.h"
+
+@implementation GTXCheckBlock {
+  NSString *_name;
+  GTXCheckHandlerBlock _block;
+}
+
++ (id<GTXChecking>)GTXCheckWithName:(NSString *)name
+                              block:(GTXCheckHandlerBlock)block {
+  return [[GTXCheckBlock alloc] initWithName:name block:block];
+}
+
+- (instancetype)initWithName:(NSString *)name
+                       block:(GTXCheckHandlerBlock)block {
+  NSParameterAssert(name);
+  NSParameterAssert(block);
+
+  self = [super init];
+  if (self) {
+    _name = [name copy];
+    _block = [block copy];
+  }
+  return self;
+}
+
+- (NSString *)description {
+  return [NSString stringWithFormat:@"%@ %p: %@", [self class], self, _name];
+}
+
+#pragma mark - GTXCheck
+
+- (BOOL)check:(id)element error:(GTXErrorRefType)errorOrNil {
+  return _block(element, errorOrNil);
+}
+
+- (NSString *)name {
+  return _name;
+}
+
+@end
diff --git a/Classes/GTXChecking.h b/Classes/GTXChecking.h
new file mode 100644
index 0000000..e2ba2ef
--- /dev/null
+++ b/Classes/GTXChecking.h
@@ -0,0 +1,47 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import <UIKit/UIKit.h>
+
+#import "GTXCommon.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ *  Protocol for performing accessibility checks.
+ */
+@protocol GTXChecking<NSObject>
+
+/**
+ *  Performs the check on the given @c element.
+ *
+ *  @param      element    The target element on which GTX check is to be performed.
+ *  @param[out] errorOrNil The error set on failure. The error returned can be @c nil, signifying
+ *                         that the check succeeded.
+ *
+ *  @return @c YES if the check performed succeeded without any errors, else @c NO.
+ */
+- (BOOL)check:(id)element error:(GTXErrorRefType)errorOrNil;
+
+/**
+ *  @return A name of this GTXCheck, this will be used in the logs and or reports and so must
+ *          uniquely identify the check.
+ */
+- (NSString *)name;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Classes/GTXChecksCollection.h b/Classes/GTXChecksCollection.h
new file mode 100644
index 0000000..dd9b831
--- /dev/null
+++ b/Classes/GTXChecksCollection.h
@@ -0,0 +1,94 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import <UIKit/UIKit.h>
+
+#import "GTXChecking.h"
+
+FOUNDATION_EXTERN NSString *const kGTXCheckNameAccessibilityLabelPresent;
+FOUNDATION_EXTERN NSString *const kGTXCheckNameAccessibilityLabelNotPunctuated;
+FOUNDATION_EXTERN NSString *const kGTXCheckNameAccessibilityTraitsDontConflict;
+FOUNDATION_EXTERN NSString *const kGTXCheckNameMinimumTappableArea;
+FOUNDATION_EXTERN NSString *const kGTXCheckNameMinimumContrastRatio;
+
+/**
+ *  An enumeration of various versions of GTXSystems supported by GTX.
+ *
+ *  Ideally we would like everyone to use one stable version but since several users are using GTX
+ *  and adding new checks can require work on part of those users - new checks for example, may
+ *  expose new accessibility issues in those apps that will need to be fixed to get all tests to
+ *  pass. Versions allow for easy development and deployment of those checks. All the users are on
+ *  "stable" version and all new checks are developed under "latest" version when ready, the
+ *  checks are moved into the "stable" version so that everyone gets it. Users can easily test (and
+ *  debug/fix) their apps with latest checks by switching to the "latest" version.
+ */
+typedef NS_ENUM(NSUInteger, GTXSystemVersion) {
+
+  /**
+   *  The default version of checker uses all the latest *stable* checks.
+   */
+  GTXSystemVersionStable = 0,
+
+  /**
+   *  The version of the checker that uses *all* the latest checks.
+   */
+  GTXSystemVersionLatest,
+
+  /**
+   *  A placeholder for determining maximum count of the versions available.
+   */
+  GTXSystemVersionMax,
+};
+
+/**
+ *  Organizes all GTX checks supported by GTXSystem.
+ */
+@interface GTXChecksCollection : NSObject
+
+/**
+ *  @return An array of all supported GTXChecks.
+ */
++ (NSArray *)allGTXChecks;
+
+/**
+ *  @return An array of GTXChecks under the given version.
+ */
++ (NSArray *)checksWithVersion:(GTXSystemVersion)version;
+
+/**
+ *  The GTX check instance that has the specified @c name, or @c nil if no such check exists.
+ *
+ *  @param name The name of the GTX check.
+ *
+ *  @return The GTX check instance that has the specified @c name.
+ */
++ (id<GTXChecking>)GTXCheckWithName:(NSString *)name;
+
+/**
+ *  Registers the provided check and makes it available to all @c GTXSystem objects.
+ *
+ *  @param check The check to be registered, must conform to @c GTXChecking protocol.
+ */
++ (void)registerCheck:(id<GTXChecking>)check;
+
+/**
+ *  Deregisters a previously registered check.
+ *
+ *  @param checkName The name of the check to be unregistered.
+ */
++ (void)deRegisterCheck:(NSString *)checkName;
+
+@end
diff --git a/Classes/GTXChecksCollection.m b/Classes/GTXChecksCollection.m
new file mode 100644
index 0000000..0d16fe8
--- /dev/null
+++ b/Classes/GTXChecksCollection.m
@@ -0,0 +1,499 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "GTXChecksCollection.h"
+
+#import "GTXCheckBlock.h"
+#import "GTXChecking.h"
+#import "GTXImageAndColorUtils.h"
+#import "NSError+GTXAdditions.h"
+
+#pragma mark - Externs
+
+NSString *const kGTXCheckNameAccessibilityLabelPresent = @"Accessibility Label Present";
+NSString *const kGTXCheckNameAccessibilityLabelNotPunctuated =
+    @"Accessibility Label Not Punctuated";
+NSString *const kGTXCheckNameAccessibilityTraitsDontConflict =
+    @"Accessibility Traits Don't Conflict";
+NSString *const kGTXCheckNameMinimumTappableArea = @"Element has Minimum Tappable Area";
+NSString *const kGTXCheckNameMinimumContrastRatio = @"Element has Minimum Contrast Ratio";
+
+#pragma mark - Globals
+
+/**
+ *  The minimum size (width or height) for a given element to be accessible as per iOS Accessibility
+ *  standards. For more info see go/ios-gtx-touch-target-ref.
+ */
+static const float kMinSizeForAccessibleElements = 48.0;
+
+/**
+ *  The minimum contrast ratio for any given text to be considered accessible. Note that smaller
+ *  text has even stricter requirement of 4.5:1.
+ */
+static const float kMinContrastRatioForAccessibleText = 3.0;
+
+/**
+ *  A global array of dictionaries mapping default GTX check names to their instances.
+ */
+NSArray *gGTXBuiltInChecks;
+
+/**
+ *  A global dictionary mapping custom GTX check names to their instances.
+ */
+NSMutableDictionary *gGTXCustomChecks;
+
+#pragma mark - Implementations
+
+@implementation GTXChecksCollection
+
++ (NSArray *)gGTXDefaultChecks {
+  static dispatch_once_t onceToken;
+  dispatch_once(&onceToken, ^{
+    NSMutableArray *mutableBuiltInChecks = [[NSMutableArray alloc] init];
+    for (NSUInteger i = 0; i < GTXSystemVersionMax; i++) {
+      [mutableBuiltInChecks addObject:[[NSMutableDictionary alloc] init]];
+    }
+    gGTXBuiltInChecks = mutableBuiltInChecks;
+  });
+  return gGTXBuiltInChecks;
+}
+
++ (NSMutableDictionary *)gGTXCustomChecks {
+  static dispatch_once_t onceToken;
+  dispatch_once(&onceToken, ^{
+    gGTXCustomChecks = [[NSMutableDictionary alloc] init];
+  });
+  return gGTXCustomChecks;
+}
+
++ (NSArray *)checksWithVersion:(GTXSystemVersion)version {
+  static dispatch_once_t onceToken;
+  dispatch_once(&onceToken, ^{
+    // Register the default checks.
+    // GTXSystemVersionLatest will have *all* checks.
+    [self registerDefaultCheck:[self GTXCheckForAXLabelPresent]
+                    forVersion:GTXSystemVersionLatest];
+    [self registerDefaultCheck:[self GTXCheckForAXLabelNotPunctuated]
+                    forVersion:GTXSystemVersionLatest];
+    [self registerDefaultCheck:[self GTXCheckForAXTraitDontConflict]
+                    forVersion:GTXSystemVersionLatest];
+    [self registerDefaultCheck:[self GTXCheckForMinimumTappableArea]
+                    forVersion:GTXSystemVersionLatest];
+    [self registerDefaultCheck:[self GTXCheckForContrastRatio]
+                    forVersion:GTXSystemVersionLatest];
+
+    // GTXSystemVersionStable currently has the following checks.
+    [self registerDefaultCheck:[self GTXCheckForAXLabelPresent]
+                    forVersion:GTXSystemVersionStable];
+    [self registerDefaultCheck:[self GTXCheckForAXLabelNotPunctuated]
+                    forVersion:GTXSystemVersionStable];
+    [self registerDefaultCheck:[self GTXCheckForAXTraitDontConflict]
+                    forVersion:GTXSystemVersionStable];
+  });
+  NSArray *defaultChecks = [[self gGTXDefaultChecks][version] allValues];
+  NSArray *customChecks = [[self gGTXCustomChecks] allValues];
+  return [defaultChecks arrayByAddingObjectsFromArray:customChecks];
+}
+
++ (NSArray *)allGTXChecks {
+  return [self checksWithVersion:GTXSystemVersionLatest];
+}
+
++ (id<GTXChecking>)GTXCheckWithName:(NSString *)name {
+  for (id<GTXChecking> check in [self allGTXChecks]) {
+    if ([name isEqualToString:[check name]]) {
+      return check;
+    }
+  }
+  return nil;
+}
+
++ (void)registerDefaultCheck:(id<GTXChecking>)check forVersion:(GTXSystemVersion)version {
+  NSMutableDictionary *defaultChecks = [self gGTXDefaultChecks][version];
+  NSAssert(!defaultChecks[[check name]],
+           @"GtxCheck with name %@ already registered.", [check name]);
+  defaultChecks[[check name]] = check;
+}
+
++ (void)registerCheck:(id<GTXChecking>)check {
+  NSAssert(![self gGTXCustomChecks][[check name]],
+           @"GtxCheck with name %@ already registered.", [check name]);
+  [self gGTXCustomChecks][[check name]] = check;
+}
+
++ (void)deRegisterCheck:(NSString *)checkName {
+  NSAssert([self gGTXCustomChecks][checkName],
+           @"GtxCheck with name %@ was not registered.", checkName);
+  [[self gGTXCustomChecks] removeObjectForKey:checkName];
+}
+
+#pragma mark - GTXChecks
+
++ (id<GTXChecking>)GTXCheckForAXLabelPresent {
+  id<GTXChecking> check = [GTXCheckBlock GTXCheckWithName:kGTXCheckNameAccessibilityLabelPresent
+                                   block:^BOOL(id element, GTXErrorRefType errorOrNil) {
+    if ([self gtx_isTextDisplayingElement:element]) {
+      // Elements that display text can use its text as an accessibility value making the
+      // accessibility label optional.
+      return YES;
+    }
+    NSError *error;
+    NSString *label = [self stringValueOfAccessibilityLabelForElement:element
+                                                                error:&error];
+    if (error) {
+      if (errorOrNil) {
+        *errorOrNil = error;
+      }
+      return NO;
+    }
+    label = [self trimmedStringFromString:label];
+    if ([label length] > 0) {
+      // Check passed.
+      return YES;
+    }
+    [NSError gtx_logOrSetGTXCheckFailedError:errorOrNil
+                                     element:element
+                                        name:kGTXCheckNameAccessibilityLabelPresent
+                                 description:@"Accessibility elements should have an appropriate "
+                                             @"accessibility label."];
+    return NO;
+  }];
+  return check;
+}
+
++ (id<GTXChecking>)GTXCheckForAXLabelNotPunctuated {
+  id<GTXChecking> check =
+      [GTXCheckBlock GTXCheckWithName:kGTXCheckNameAccessibilityLabelNotPunctuated
+                                block:^BOOL(id element, GTXErrorRefType errorOrNil) {
+    if ([self gtx_isTextDisplayingElement:element]) {
+      // This check is not applicable to text elements as accessibility labels can hold static text
+      // that can be punctuated and formatted like a string.
+      return YES;
+    }
+    NSError *error;
+    NSString *stringValue = [self stringValueOfAccessibilityLabelForElement:element
+                                                                      error:&error];
+    if (error) {
+      if (errorOrNil) {
+        *errorOrNil = error;
+      }
+      return NO;
+    }
+    NSString *label = [self trimmedStringFromString:stringValue];
+    // This check is not applicable for container elements that combine individual labels joined
+    // with commas.
+    if ([label rangeOfString:@","].location != NSNotFound) {
+      return YES;
+    }
+    if ([label length] > 0 && [label hasSuffix:@"."]) {
+      // Check failed.
+      NSString *errorDescription = @"Suggest removing the period at the end of this element's "
+                                   @"accessibility label. Accessibility labels are not sentences "
+                                   @"therefore they should not end in period. If the element "
+                                   @"visually displays text it should have the "
+                                   @"UIAccessibilityTraitStaticText trait similar to "
+                                   @"UITextView or UILabel.";
+      [NSError gtx_logOrSetGTXCheckFailedError:errorOrNil
+                                       element:element
+                                          name:kGTXCheckNameAccessibilityLabelNotPunctuated
+                                   description:errorDescription];
+      return NO;
+    }
+    return YES;
+  }];
+  return check;
+}
+
++ (id<GTXChecking>)GTXCheckForAXTraitDontConflict {
+  id<GTXChecking> check =
+      [GTXCheckBlock GTXCheckWithName:kGTXCheckNameAccessibilityTraitsDontConflict
+                                block:^BOOL(id element, GTXErrorRefType errorOrNil) {
+    if ([NSStringFromClass([element class]) isEqualToString:@"UIAccessibilityElementKBKey"]) {
+      // iOS keyboard keys are known to have conflicting traits skip them.
+      return YES;
+    }
+    UIAccessibilityTraits elementAXTraits = [element accessibilityTraits];
+    // Even though we can check for valid accessibility traits, we are not doing that because some
+    // undocumented UIKit controls, e.g., UINavigationItemButtonView, are known to have unknown
+    // values. Check b/29226386 for more details.
+    // Check mutually exclusive conflicts for the element's accessibility traits.
+    for (NSArray<NSNumber *> *traitsConflictRule in [self traitsMutuallyExclusiveRules]) {
+      NSMutableArray<NSString *> *conflictTraitsNameList = [[NSMutableArray alloc] init];
+      for (NSNumber *testTrait in traitsConflictRule) {
+        UIAccessibilityTraits testUITrait = [testTrait unsignedLongLongValue];
+        if ((BOOL)(elementAXTraits & testUITrait)) {
+          NSError *error;
+          NSString *stringValue = [self stringValueOfUIAccessibilityTraits:testUITrait
+                                                                     error:&error];
+          if (error) {
+            if (errorOrNil) {
+              *errorOrNil = error;
+            }
+            return NO;
+          }
+          [conflictTraitsNameList addObject:stringValue];
+        }
+      }
+      if ([conflictTraitsNameList count] > 1) {
+        NSString *stringOfConflictTraitsNameList =
+            [conflictTraitsNameList componentsJoinedByString:@", "];
+        NSString *description =
+            [NSString stringWithFormat:@"Suggest removing the trait conflict among %@ since they "
+                                       @"are mutually exclusive.", stringOfConflictTraitsNameList];
+        [NSError gtx_logOrSetGTXCheckFailedError:errorOrNil
+                                         element:element
+                                            name:kGTXCheckNameAccessibilityTraitsDontConflict
+                                     description:description];
+        return NO;
+      }
+    }
+    return YES;
+  }];
+  return check;
+}
+
++ (id<GTXChecking>)GTXCheckForMinimumTappableArea {
+  id<GTXChecking> check =
+      [GTXCheckBlock GTXCheckWithName:kGTXCheckNameMinimumTappableArea
+                                block:^BOOL(id element, GTXErrorRefType errorOrNil) {
+    if (![self gtx_isTappableNonLinkElement:element]) {
+      // Element is not tappable or is a link, links follow the font size of the text on page and
+      // are exempt from this check.
+      return YES;
+    }
+    if ([element respondsToSelector:@selector(accessibilityFrame)]) {
+      CGRect frame = [element accessibilityFrame];
+      BOOL hasSmallWidth = CGRectGetWidth(frame) < kMinSizeForAccessibleElements;
+      BOOL hasSmallHeight = CGRectGetHeight(frame) < kMinSizeForAccessibleElements;
+      if (hasSmallWidth || hasSmallHeight) {
+        NSString *dimensionsToBeFixed;
+        // Append a suggestion to the error description.
+        if (hasSmallWidth && hasSmallHeight) {
+          // Both width and height make the element inaccessible.
+          dimensionsToBeFixed = @"frame width and height";
+        } else if (hasSmallWidth) {
+          // Only width is making the element inaccessible.
+          dimensionsToBeFixed = @"frame width";
+        } else {
+          // Only height is making the element inaccessible.
+          dimensionsToBeFixed = @"frame height";
+        }
+        NSString *description =
+            [NSString stringWithFormat:@"Suggest increasing element's %@ to at least %d for "
+                                       @"a suggested tappable area of at least %dX%d",
+                                       dimensionsToBeFixed,
+                                       (int)kMinSizeForAccessibleElements,
+                                       (int)kMinSizeForAccessibleElements,
+                                       (int)kMinSizeForAccessibleElements];
+
+        [NSError gtx_logOrSetGTXCheckFailedError:errorOrNil
+                                         element:element
+                                            name:kGTXCheckNameMinimumTappableArea
+                                     description:description];
+        return NO;
+      }
+    }
+    return YES;
+  }];
+  return check;
+}
+
++ (id<GTXChecking>)GTXCheckForContrastRatio {
+  id<GTXChecking> check =
+      [GTXCheckBlock GTXCheckWithName:kGTXCheckNameMinimumContrastRatio
+                                block:^BOOL(id element, GTXErrorRefType errorOrNil) {
+    if (![element isKindOfClass:[UILabel class]]) {
+      return YES;
+    }
+    CGFloat ratio = [GTXImageAndColorUtils contrastRatioOfUILabel:element];
+    BOOL hasSufficientContrast =
+        (ratio >= kMinContrastRatioForAccessibleText - kContrastRatioAccuracy);
+    if (!hasSufficientContrast) {
+      NSString *description =
+          [NSString stringWithFormat:@"Suggest increasing this element's contrast ratio to at "
+                                     "least "
+                                     @"%.5f the actual ratio was computed as %.5f",
+                                     (float)kMinContrastRatioForAccessibleText, (float)ratio];
+      [NSError gtx_logOrSetGTXCheckFailedError:errorOrNil
+                                       element:element
+                                          name:kGTXCheckNameMinimumContrastRatio
+                                   description:description];
+    }
+    return hasSufficientContrast;
+  }];
+  return check;
+}
+
+
+#pragma mark - Private
+
+/**
+ *  @return The NSArray contains the mutually exclusive rules for accessibility traits.
+ *          For details, check go/gtx-ios.
+ */
++ (NSArray<NSArray<NSNumber *> *> *)traitsMutuallyExclusiveRules {
+  // Each item below consists of a mutually exclusive traits rule.
+  return @[
+    // Conflicting Rule No. 1
+    @[
+      @(UIAccessibilityTraitButton),
+      @(UIAccessibilityTraitLink),
+      @(UIAccessibilityTraitSearchField),
+      @(UIAccessibilityTraitKeyboardKey)
+    ],
+    // Conflicting Rule No. 2
+    @[
+      @(UIAccessibilityTraitButton),
+      @(UIAccessibilityTraitAdjustable)
+    ]
+  ];
+}
+
+/**
+ *  @return The UIAccessibilityTraits to NSString mapping dictionary as type
+ *          NSDictionary<NSNumber *, NSString *> *.
+ */
++ (NSDictionary<NSNumber *, NSString *> const *)traitsToStringDictionary {
+  // Each element below is an valid accessibility traits entity.
+  return @{
+    @(UIAccessibilityTraitNone):
+      @"UIAccessibilityTraitNone",
+    @(UIAccessibilityTraitButton):
+      @"UIAccessibilityTraitButton",
+    @(UIAccessibilityTraitLink):
+      @"UIAccessibilityTraitLink",
+    @(UIAccessibilityTraitSearchField):
+      @"UIAccessibilityTraitSearchField",
+    @(UIAccessibilityTraitImage):
+      @"UIAccessibilityTraitImage",
+    @(UIAccessibilityTraitSelected):
+      @"UIAccessibilityTraitSelected",
+    @(UIAccessibilityTraitPlaysSound):
+      @"UIAccessibilityTraitPlaysSound",
+    @(UIAccessibilityTraitKeyboardKey):
+      @"UIAccessibilityTraitKeyboardKey",
+    @(UIAccessibilityTraitStaticText):
+      @"UIAccessibilityTraitStaticText",
+    @(UIAccessibilityTraitSummaryElement):
+      @"UIAccessibilityTraitSummaryElement",
+    @(UIAccessibilityTraitNotEnabled):
+      @"UIAccessibilityTraitNotEnabled",
+    @(UIAccessibilityTraitUpdatesFrequently):
+      @"UIAccessibilityTraitUpdatesFrequently",
+    @(UIAccessibilityTraitStartsMediaSession):
+      @"UIAccessibilityTraitStartsMediaSession",
+    @(UIAccessibilityTraitAdjustable):
+      @"UIAccessibilityTraitAdjustable",
+    @(UIAccessibilityTraitAllowsDirectInteraction):
+      @"UIAccessibilityTraitAllowsDirectInteraction",
+    @(UIAccessibilityTraitCausesPageTurn):
+      @"UIAccessibilityTraitCausesPageTurn",
+    @(UIAccessibilityTraitHeader):
+      @"UIAccessibilityTraitHeader"
+  };
+}
+
+/**
+ *  @return The NSString value of the specified accessibility traits.
+ */
++ (NSString *)stringValueOfUIAccessibilityTraits:(UIAccessibilityTraits)traits
+                                           error:(GTXErrorRefType)errorOrNil {
+  NSString *stringValue = [[self traitsToStringDictionary]
+      objectForKey:[NSNumber numberWithUnsignedLongLong:traits]];
+  if (nil == stringValue) {
+    NSString *errorMessage =
+        [NSString stringWithFormat:@"This element defines accessibility traits 0x%016llx which may "
+                                   @"be invalid.", traits];
+    [NSError gtx_logOrSetError:errorOrNil
+                   description:errorMessage
+                          code:GTXCheckErrorCodeGenericError
+                      userInfo:nil];
+    return nil;
+  }
+  return stringValue;
+}
+
+/**
+ *  @return The NSString value of the @c element's accessibility label or @c nil if label was not
+ *          set or an error occurred extracting the label.
+ */
++ (NSString *)stringValueOfAccessibilityLabelForElement:(id)element
+                                                  error:(GTXErrorRefType)errorOrNil {
+  NSString *stringValue;
+  id accessibilityLabel = [element accessibilityLabel];
+  if ([accessibilityLabel isKindOfClass:[NSString class]]) {
+    stringValue = accessibilityLabel;
+  } else if ([accessibilityLabel respondsToSelector:@selector(string)]) {
+    stringValue = [accessibilityLabel string];
+  } else if (accessibilityLabel) {
+    NSString *errorMessage =
+        [NSString stringWithFormat:@"String value of accessibility label %@ of class"
+                                   @" %@ could not be extracted from element %@",
+                                   accessibilityLabel,
+                                   NSStringFromClass([accessibilityLabel class]),
+                                   element];
+    [NSError gtx_logOrSetError:errorOrNil
+                   description:errorMessage
+                          code:GTXCheckErrorCodeGenericError
+                      userInfo:nil];
+    return nil;
+  }
+  return stringValue;
+}
+
+/**
+ *  @return Returns the string obtained by removing whitespace and newlines present at the beginning
+ *          and the end of the specified @c string.
+ */
++ (NSString *)trimmedStringFromString:(NSString *)string {
+  return [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
+}
+
+/**
+ *  @return @c YES if @c element is tappable (for ex button) @c NO otherwise.
+ */
++ (BOOL)gtx_isTappableNonLinkElement:(id)element {
+  BOOL hasTappableTrait = NO;
+  if ([element respondsToSelector:@selector(accessibilityTraits)]) {
+    UIAccessibilityTraits traits = [element accessibilityTraits];
+    hasTappableTrait = ((traits & UIAccessibilityTraitButton) ||
+                        (traits & UIAccessibilityTraitLink) ||
+                        (traits & UIAccessibilityTraitSearchField) ||
+                        (traits & UIAccessibilityTraitPlaysSound) ||
+                        (traits & UIAccessibilityTraitKeyboardKey));
+  }
+  return hasTappableTrait;
+}
+
+/**
+ *  @return @c YES if @c element displays text @c NO otherwise.
+ */
++ (BOOL)gtx_isTextDisplayingElement:(id)element {
+  BOOL hasTextTrait = NO;
+  if ([element respondsToSelector:@selector(accessibilityTraits)]) {
+    UIAccessibilityTraits traits = [element accessibilityTraits];
+    hasTextTrait = ((traits & UIAccessibilityTraitStaticText) ||
+                    (traits & UIAccessibilityTraitLink) ||
+                    (traits & UIAccessibilityTraitSearchField) ||
+                    (traits & UIAccessibilityTraitKeyboardKey));
+  }
+  return ([element isKindOfClass:[UILabel class]] ||
+          [element isKindOfClass:[UITextView class]] ||
+          [element isKindOfClass:[UITextField class]] ||
+          hasTextTrait);
+}
+
+@end
diff --git a/Classes/GTXCommon.h b/Classes/GTXCommon.h
new file mode 100644
index 0000000..c957dba
--- /dev/null
+++ b/Classes/GTXCommon.h
@@ -0,0 +1,24 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import <UIKit/UIKit.h>
+
+/**
+ *  Defines a double pointer to NSError with nullability attributes to allow both the pointer and
+ *  the NSError object being pointed to be @c nil.
+ *  @todo Update __nullable to _Nullable when FoM moves to Xcode 7.0+.
+ */
+typedef NSError *__nullable __strong *__nullable GTXErrorRefType;
diff --git a/Classes/GTXElementBlacklist.h b/Classes/GTXElementBlacklist.h
new file mode 100644
index 0000000..7519465
--- /dev/null
+++ b/Classes/GTXElementBlacklist.h
@@ -0,0 +1,34 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import <UIKit/UIKit.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ *  Describes a blacklist of a elements of a given class and optionally for a given GTXCheck.
+ */
+@interface GTXElementBlacklist : NSObject
+
+- (instancetype)init NS_UNAVAILABLE;
+- (instancetype)initWithElementClassName:(NSString *)elementClassName
+                               checkName:(nullable NSString *)checkName;
+
+@property (nonatomic, copy) NSString *elementClassName;
+@property (nonatomic, nullable, copy) NSString *checkName;
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Classes/GTXElementBlacklist.m b/Classes/GTXElementBlacklist.m
new file mode 100644
index 0000000..154ad02
--- /dev/null
+++ b/Classes/GTXElementBlacklist.m
@@ -0,0 +1,32 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "GTXElementBlacklist.h"
+
+@implementation GTXElementBlacklist
+
+- (instancetype)initWithElementClassName:(NSString *)elementClassName
+                               checkName:(NSString *)checkName {
+  NSParameterAssert(elementClassName);
+  self = [super init];
+  if (self) {
+    _elementClassName = [elementClassName copy];
+    _checkName = [checkName copy];
+  }
+  return self;
+}
+
+@end
diff --git a/Classes/GTXErrorReporter.h b/Classes/GTXErrorReporter.h
new file mode 100644
index 0000000..f231d5c
--- /dev/null
+++ b/Classes/GTXErrorReporter.h
@@ -0,0 +1,51 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import <UIKit/UIKit.h>
+
+/**
+ *  Reports GTX errors and provides utils for reporting/logging errors found by GTX.
+ */
+@interface GTXErrorReporter : NSObject
+
+/**
+ *  Takes a screenshot of the current screen and outlines all the provided (failing) @c elements.
+ *
+ *  @param elements Array of elements with accessibility issues.
+ *
+ *  @return an image of the current screen with failing elements marked.
+ */
++ (UIImage *)screenshotWithFailingElements:(NSArray *)elements;
+
+/**
+ *  Takes a screenshot of the given @c element.
+ *
+ *  @param element element whose screenshot is needed.
+ *
+ *  @return an image of the element.
+ */
++ (UIImage *)screenshotView:(UIView *)element;
+
+/**
+ *  Takes a screenshot of the given @c element.
+ *
+ *  @param rootElements element whose screenshot is needed.
+ *
+ *  @return an image of the element.
+ */
++ (NSString *)hierarchyDescriptionOfRootElements:(NSArray *)rootElements;
+
+@end
diff --git a/Classes/GTXErrorReporter.m b/Classes/GTXErrorReporter.m
new file mode 100644
index 0000000..ad28701
--- /dev/null
+++ b/Classes/GTXErrorReporter.m
@@ -0,0 +1,268 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "GTXErrorReporter.h"
+
+#import "NSError+GTXAdditions.h"
+
+@implementation GTXErrorReporter
+
++ (UIImage *)screenshotWithFailingElements:(NSArray *)elements {
+  // Render a gray scale screen shot of the app screen. Gray scale allows the element outlines to
+  //  be seen clearly.
+  UIImage *image = [self gtx_takeScreenshot];
+  UIImage *grayScaleScreenShot = [self gtx_grayScaleImageFromImage:image];
+
+  // Outline the locations of elements on the gray scale screenshot.
+  UIScreen *mainScreen = [UIScreen mainScreen];
+  UIGraphicsBeginImageContextWithOptions(mainScreen.bounds.size, YES, mainScreen.scale);
+  CGContextRef context = UIGraphicsGetCurrentContext();
+  [grayScaleScreenShot drawInRect:mainScreen.bounds];
+
+  for (id element in elements) {
+    // Find the element's outline rect that needs to be marked on the screenshot.
+    CGRect elementOutline = [element accessibilityFrame];
+    if ([element isKindOfClass:[UIView class]]) {
+      UIView *view = element;
+      elementOutline = [view convertRect:view.bounds toView:nil];
+      elementOutline = [[view window] convertRect:elementOutline toWindow:nil];
+    }
+    // Draw a rectangle indicating the outline.
+    CGContextSetLineWidth(context, 3.0);
+    CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
+    CGContextStrokeRect(context, elementOutline);
+  }
+
+  // Save the currently rendered image.
+  image = UIGraphicsGetImageFromCurrentImageContext();
+  UIGraphicsEndImageContext();
+  return image;
+}
+
++ (UIImage *)screenshotView:(UIView *)element {
+  CGRect rect = CGRectStandardize(element.bounds);
+  rect.origin = CGPointZero;
+  UIGraphicsBeginImageContext(rect.size);
+  [element drawViewHierarchyInRect:rect afterScreenUpdates:NO];
+  UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
+  UIGraphicsEndImageContext();
+  return image;
+}
+
++ (NSString *)hierarchyDescriptionOfRootElements:(NSArray *)rootElements {
+  NSMutableArray *descriptions = [NSMutableArray new];
+  for (id rootElement in rootElements) {
+    [self gtx_getRecursiveHierarchyDescriptionOfRootElement:rootElement
+                                                     indent:nil
+                                             outDescription:descriptions];
+  }
+  return [descriptions componentsJoinedByString:@"\n"];
+}
+
+#pragma mark - Private
+
+/**
+ *  Traverses the view hierarchy starting from the given root @c element and fills @c description
+ *  with a list of all elements descriptions.
+ *
+ *  @param element     The root element to start traversal from.
+ *  @param indent      The indent to apply to the root element's description.
+ *  @param description A mutable array to be filled with elements descriptions.
+ */
++ (void)gtx_getRecursiveHierarchyDescriptionOfRootElement:(id)element
+                                                   indent:(NSString *)indent
+                                           outDescription:(NSMutableArray *)description {
+  indent = indent ? indent : @"";
+  NSMutableSet *axChildren = [NSMutableSet new];
+  NSMutableSet *subviews = [NSMutableSet new];
+  NSMutableSet *both = [NSMutableSet new];
+  // Get all a11y children.
+  NSArray *elements = [element accessibilityElements];
+  NSInteger count = [element accessibilityElementCount];
+  if (elements) {
+    [axChildren addObjectsFromArray:elements];
+  } else if (count > 0 && count != NSNotFound) {
+    for (NSInteger i = 0; i < count; i++) {
+      [axChildren addObject:[element accessibilityElementAtIndex:i]];
+    }
+  }
+
+  // Get all subviews
+  elements = [element respondsToSelector:@selector(subviews)] ? [element subviews] : nil;
+  for (id element in elements) {
+    if ([axChildren containsObject:element]) {
+      [both addObject:element];
+    } else {
+      [subviews addObject:element];
+    }
+  }
+  [axChildren minusSet:both];
+  [description addObject:[self gtx_hierarchyDescriptionOfSingleElement:element indent:indent]];
+  NSString *nextIndent = [indent stringByAppendingString:@" | "];
+  if ([both count]) {
+    [description addObject:
+     [NSString stringWithFormat:@"%@Subviews and accessibility elements(%d):",
+         indent,
+         (int)[both count]]];
+    for (id element in both) {
+      [self gtx_getRecursiveHierarchyDescriptionOfRootElement:element
+                                                       indent:nextIndent
+                                               outDescription:description];
+    }
+  }
+  if ([axChildren count]) {
+    [description addObject:[NSString stringWithFormat:@"%@Accessibility elements(%d):",
+                                                      indent,
+                                                      (int)[axChildren count]]];
+    for (id element in axChildren) {
+      [self gtx_getRecursiveHierarchyDescriptionOfRootElement:element
+                                                       indent:nextIndent
+                                               outDescription:description];
+    }
+  }
+  if ([subviews count]) {
+    [description addObject:[NSString stringWithFormat:@"%@Subviews(%d):",
+                                                      indent,
+                                                      (int)[subviews count]]];
+    for (id element in subviews) {
+      [self gtx_getRecursiveHierarchyDescriptionOfRootElement:element
+                                                       indent:nextIndent
+                                               outDescription:description];
+    }
+  }
+}
+
+/**
+ *  Returns a pretty printed description of various attributes the given element such as class,
+ *  a11y attributes etc.
+ *
+ *  @param element The element whose description is needed.
+ *  @param indent  The indent to apply to the root element's description.
+ *
+ *  @return The description of the given element prepended with the indent.
+ */
++ (NSString *)gtx_hierarchyDescriptionOfSingleElement:(id)element indent:(NSString *)indent {
+  NSMutableArray *descriptions = [NSMutableArray new];
+  [descriptions addObject:[NSString stringWithFormat:@"%@<%@:%p",
+                                                     indent,
+                                                     NSStringFromClass([element class]),
+                                                     element]];
+
+  // Adds a key=value pair to the description array.
+  void (^AddKeyValue)(NSString *key, NSString *value) = ^(NSString *key, NSString *value) {
+    [descriptions addObject:[NSString stringWithFormat:@"%@=%@",
+                                                       key,
+                                                       value ? value : @"<nil>"]];
+  };
+
+  // Add all the attributes needed in the description.
+  AddKeyValue(@"AX", [element isAccessibilityElement] ? @"Y": @"N");
+  AddKeyValue(@"AXID", [element accessibilityIdentifier]);
+  NSString *stringValue = [element accessibilityLabel];
+  AddKeyValue(@"AXLabel", stringValue ? [NSString stringWithFormat:@"\"%@\"", stringValue] : nil);
+  stringValue = [NSString stringWithFormat:@"(UIAccessibilityTrait)%@",
+      [self stringValueOfUIAccessibilityTraits:[element accessibilityTraits]]];
+  AddKeyValue(@"AXTraits", stringValue);
+  AddKeyValue(@"AXFrame", NSStringFromCGRect([element accessibilityFrame]));
+  stringValue = [element accessibilityHint];
+  AddKeyValue(@"AXHint", stringValue ? [NSString stringWithFormat:@"\"%@\"", stringValue] : nil);
+  return [descriptions componentsJoinedByString:@" "];
+}
+
+/**
+ *  @return The UIAccessibilityTraits to NSString mapping dictionary as type
+ *          NSDictionary<NSNumber *, NSString *> *.
+ */
++ (NSDictionary<NSNumber *, NSString *> const *)traitsToStringDictionary {
+  // Each element below is an valid accessibility traits entity.
+  return @{@(UIAccessibilityTraitNone): @"None",
+           @(UIAccessibilityTraitButton): @"Button",
+           @(UIAccessibilityTraitLink): @"Link",
+           @(UIAccessibilityTraitSearchField): @"SearchField",
+           @(UIAccessibilityTraitImage): @"Image",
+           @(UIAccessibilityTraitSelected): @"Selected",
+           @(UIAccessibilityTraitPlaysSound): @"PlaysSound",
+           @(UIAccessibilityTraitKeyboardKey): @"KeyboardKey",
+           @(UIAccessibilityTraitStaticText): @"StaticText",
+           @(UIAccessibilityTraitSummaryElement): @"SummaryElement",
+           @(UIAccessibilityTraitNotEnabled): @"NotEnabled",
+           @(UIAccessibilityTraitUpdatesFrequently): @"UpdatesFrequently",
+           @(UIAccessibilityTraitStartsMediaSession): @"StartsMediaSession",
+           @(UIAccessibilityTraitAdjustable): @"Adjustable",
+           @(UIAccessibilityTraitAllowsDirectInteraction): @"AllowsDirectInteraction",
+           @(UIAccessibilityTraitCausesPageTurn): @"CausesPageTurn",
+           @(UIAccessibilityTraitHeader): @"Header"
+           };
+}
+
+/**
+ *  @return The NSString value of the specified accessibility traits.
+ */
++ (NSString *)stringValueOfUIAccessibilityTraits:(UIAccessibilityTraits)traits {
+  if (traits == UIAccessibilityTraitNone) {
+    return @"None";
+  }
+
+  NSMutableArray *traitsStrings = [NSMutableArray new];
+  const NSDictionary<NSNumber *, NSString *> *traitsToString = [self traitsToStringDictionary];
+  for (NSNumber *trait in traitsToString.allKeys) {
+    if (traits & [trait unsignedLongLongValue]) {
+      [traitsStrings addObject:traitsToString[trait]];
+    }
+  }
+  return [traitsStrings componentsJoinedByString:@","];
+}
+
+/**
+ *  @return an image of the current screen.
+ */
++ (UIImage *)gtx_takeScreenshot {
+  UIGraphicsBeginImageContextWithOptions([UIScreen mainScreen].bounds.size,
+                                         YES,
+                                         [UIScreen mainScreen].scale);
+
+  for (UIWindow *window in [UIApplication sharedApplication].windows.reverseObjectEnumerator) {
+    [window drawViewHierarchyInRect:window.bounds afterScreenUpdates:NO];
+  }
+  UIImage *screenshot = UIGraphicsGetImageFromCurrentImageContext();
+  UIGraphicsEndImageContext();
+  return screenshot;
+}
+
+/**
+ *  @return a gray scale version of the provided @c image.
+ */
++ (UIImage *)gtx_grayScaleImageFromImage:(UIImage *)image {
+  // Create a bitmap context with gray scale color space.
+  CGColorSpaceRef grayScaleColorSpace = CGColorSpaceCreateDeviceGray();
+  CGContextRef context =
+      CGBitmapContextCreate(NULL, (size_t)image.size.width, (size_t)image.size.height,
+                            8, 0, grayScaleColorSpace, (uint32_t)kCGImageAlphaNone);
+
+  // Render the given image into it and create a UIImage with the current contents.
+  CGContextDrawImage(context, CGRectMake(0, 0, image.size.width, image.size.height),
+                     [image CGImage]);
+  CGImageRef grayScaleCGImage = CGBitmapContextCreateImage(context);
+  UIImage *grayScaleUIImage = [UIImage imageWithCGImage:grayScaleCGImage];
+
+  // Release all the created CF Objects.
+  CGColorSpaceRelease(grayScaleColorSpace);
+  CGContextRelease(context);
+  CFRelease(grayScaleCGImage);
+  return grayScaleUIImage;
+}
+
+@end
diff --git a/Classes/GTXImageAndColorUtils.h b/Classes/GTXImageAndColorUtils.h
new file mode 100644
index 0000000..34ce3c4
--- /dev/null
+++ b/Classes/GTXImageAndColorUtils.h
@@ -0,0 +1,64 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import <UIKit/UIKit.h>
+
+extern const CGFloat kContrastRatioAccuracy;
+
+/**
+ *  Collection of accessibility related utils that use colors and/or images.
+ */
+@interface GTXImageAndColorUtils : NSObject
+
+/**
+ *  Computes luminance for the color composed of the given RGB values.
+ *
+ *  @param red   Red component value.
+ *  @param blue  Blue component value.
+ *  @param green Green component value.
+ *
+ *  @return luminance value for the color consisting of the given RGB values.
+ */
++ (CGFloat)luminanceWithRed:(CGFloat)red blue:(CGFloat)blue green:(CGFloat)green;
+
+/**
+ *  @return luminance value for the given color.
+ */
++ (CGFloat)luminanceWithColor:(UIColor *)color;
+
+/**
+ *  Computes the contrast ratio from the given luminance values.
+ *
+ *  @param color1Luminance Luminance value for the first color.
+ *  @param color2Luminance Luminance value for the second color.
+ *
+ *  @return Contrast ratio for the given two luminance values.
+ */
++ (CGFloat)contrastRatioWithLuminaceOfFirstColor:(CGFloat)color1Luminance
+                       andLuminanceOfSecondColor:(CGFloat)color2Luminance;
+
+/**
+ *  Computes contrast ratio of the given label to its background. This method analyses the label's
+ *  text color in the context of its view hierarchy in order to compute the contrast ratio, because
+ *  of which the label must already be in a window before this method can be used.
+ *
+ *  @param label The label whose contrast ratio is to be computed.
+ *
+ *  @return The contrast ratio (proportional to 1.0) of the label.
+ */
++ (CGFloat)contrastRatioOfUILabel:(UILabel *)label;
+
+@end
diff --git a/Classes/GTXImageAndColorUtils.m b/Classes/GTXImageAndColorUtils.m
new file mode 100644
index 0000000..8fd91ff
--- /dev/null
+++ b/Classes/GTXImageAndColorUtils.m
@@ -0,0 +1,191 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "GTXImageAndColorUtils.h"
+
+#import "GTXImageRGBAData.h"
+
+/**
+ *  Accuracy of the contrast ratios provided by the APIs in this class.
+ */
+const CGFloat kContrastRatioAccuracy = 0.05f;
+
+@implementation GTXImageAndColorUtils
+
++ (CGFloat)luminanceWithRed:(CGFloat)red blue:(CGFloat)blue green:(CGFloat)green {
+  // Based on https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
+  CGFloat (^adjustedComponent)(CGFloat) = ^CGFloat(CGFloat component) {
+    if (component < 0.03928f) {
+      return component / 12.92f;
+    } else {
+      return (CGFloat)pow((component + 0.055f) / 1.055f, 2.4f);
+    }
+  };
+  return (0.2126f * adjustedComponent(red) +
+          0.0722f * adjustedComponent(blue) +
+          0.7152f * adjustedComponent(green));
+}
+
++ (CGFloat)luminanceWithColor:(UIColor *)color {
+  CGFloat red, blue, green;
+  [color getRed:&red green:&green blue:&blue alpha:NULL];
+  return [self luminanceWithRed:red blue:blue green:green];
+}
+
++ (CGFloat)contrastRatioWithLuminaceOfFirstColor:(CGFloat)color1Luminance
+                       andLuminanceOfSecondColor:(CGFloat)color2Luminance {
+  NSParameterAssert(color2Luminance >= 0);
+  NSParameterAssert(color1Luminance >= 0);
+  CGFloat brighterColorLuminance = MAX(color1Luminance, color2Luminance);
+  CGFloat darkerColorLuminance = MIN(color1Luminance, color2Luminance);
+  // Based on https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef
+  return (brighterColorLuminance + 0.05f) / (darkerColorLuminance + 0.05f);
+}
+
++ (CGFloat)contrastRatioOfUILabel:(UILabel *)label {
+  NSAssert(label.window, @"Label %@ must be part of view hierarchy to use this method, see API "
+                         @"docs for more info.", label);
+  // Create a block that can take snapshot of the given label.
+  UIImage *(^takeSnapshot)(void) = ^{
+    CGRect labelBounds = [label.window convertRect:label.bounds fromView:label];
+    UIWindow *window = label.window;
+    if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)]) {
+      UIGraphicsBeginImageContextWithOptions(labelBounds.size,
+                                             NO, [UIScreen mainScreen].scale);
+    } else {
+      UIGraphicsBeginImageContext(labelBounds.size);
+    }
+    CGRect screenRect = CGRectZero;
+    screenRect.origin = CGPointMake(-labelBounds.origin.x,
+                                    -labelBounds.origin.y);
+    screenRect.size = window.bounds.size;
+    [window drawViewHierarchyInRect:screenRect afterScreenUpdates:YES];
+    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
+    UIGraphicsEndImageContext();
+    return image;
+  };
+
+  // Take snapshot of the label as it exists.
+  UIImage *before = takeSnapshot();
+
+  // Update the text color and take another snapshot.
+  UIColor *prevColor = label.textColor;
+  label.textColor = [self gtx_shiftedColorWithColor:prevColor];
+  UIImage *after = takeSnapshot();
+  label.textColor = prevColor;
+
+  return [self gtx_contrastRatioWithTextElementImage:before textElementColorShiftedImage:after];
+}
+
+#pragma mark - Utils
+
+/**
+ *  Computes the contrast ratio for the text in the given image. This method also requires image of
+ *  the text element with the text color changed, by comparing both the image pixels its possible to
+ *  determine which pixels belong to text (will change between images) and which ones belong to the
+ *  background (unchanged between images). The pixel values are used to determine contrast
+ *  ratio of the text on the given background.
+ *
+ *  @param original     The original image of the text element.
+ *  @param colorShifted Image of the text element with color of the text shifted (changed).
+ *
+ *  @return The contrast ratio (proportional to 1.0) of the label.
+ */
++ (CGFloat)gtx_contrastRatioWithTextElementImage:(UIImage *)original
+                    textElementColorShiftedImage:(UIImage *)colorShifted {
+  // Luminace of image is computed using Reinhard’s method:
+  // Luminace of image = Geometric Mean of luminance of individual pixels.
+  CGFloat textLogAverage = 0;
+  NSInteger textPixelCount = 0;
+  CGFloat backgroundLogAverage = 0;
+  NSInteger backgroundPixelCount = 0;
+  GTXImageRGBAData *beforeData = [[GTXImageRGBAData alloc] initWithUIImage:original];
+  GTXImageRGBAData *afterData = [[GTXImageRGBAData alloc] initWithUIImage:colorShifted];
+
+  // Geometric mean of n numbers is the nth root of the product of the numbers however to avoid
+  // issues with floating point accuracies we first compute the average of the logarithms and then
+  // compute e to the power of the average. Also, since luminances are in the range of 0 to 1 the
+  // logarithms will lie in the range negative infinity to 0 which will still cause inaccuracies
+  // when we sum them, to avoid this we first offset all luminances by 1.0 and scale them by
+  // e (~2.7182) causing their logarithms to fall in the range of 0 to 1 instead leading to better
+  // accuracy of the geometric mean.
+  const CGFloat luminanceOffset = 1.0f;
+  const CGFloat luminanceScale = (CGFloat)M_E;
+  for (NSUInteger column = 0; column < beforeData.width; column++) {
+    for (NSUInteger row = 0; row < beforeData.height; row++) {
+      unsigned char *beforeOffset =
+          beforeData.rgba + column * beforeData.bytesPerPixel + row * beforeData.bytesPerRow;
+      unsigned char *afterOffset =
+          afterData.rgba + column * afterData.bytesPerPixel + row * afterData.bytesPerRow;
+      CGFloat red = beforeOffset[0] / 255.0f;
+      CGFloat green = beforeOffset[1] / 255.0f;
+      CGFloat blue = beforeOffset[2] / 255.0f;
+      CGFloat alpha = beforeOffset[3];
+
+      if (alpha == 0) {
+        // Skip transparent pixels.
+        continue;
+      }
+
+      CGFloat logLuminance =
+          (CGFloat)log(luminanceOffset +
+                       luminanceScale * [self luminanceWithRed:red blue:blue green:green]);
+      if (beforeOffset[0] != afterOffset[0]) {
+        // This pixel has changed from before: it is part of the text.
+        textLogAverage += logLuminance;
+        textPixelCount += 1;
+      } else {
+        // This pixel has *not* changed from before: it is part of the text background.
+        backgroundLogAverage += logLuminance;
+        backgroundPixelCount += 1;
+      }
+    }
+  }
+
+  // Compute the geometric mean and scale the result back.
+  CGFloat textLuminance = 1.0f;
+  if (textPixelCount != 0) {
+    textLuminance =
+        (CGFloat)(exp(textLogAverage / textPixelCount) - luminanceOffset) / luminanceScale;
+  }
+  CGFloat backgroundLuminance = 1.0;
+  if (backgroundPixelCount != 0) {
+    backgroundLuminance =
+        (CGFloat)(exp(backgroundLogAverage / backgroundPixelCount) - luminanceOffset) /
+                  luminanceScale;
+  }
+  return [self contrastRatioWithLuminaceOfFirstColor:textLuminance
+                           andLuminanceOfSecondColor:backgroundLuminance];
+}
+
+/**
+ *  @return The color obtained by shifting (and rotating around 1.0 if needed) the RGBA values by
+ *          0.5.
+ */
++ (UIColor *)gtx_shiftedColorWithColor:(UIColor *)color {
+  CGFloat red, blue, green, alpha;
+  [color getRed:&red green:&green blue:&blue alpha:&alpha];
+  // Rotate red blue and green around 1.0.
+  red += 1.0f;
+  blue += 1.0f;
+  green += 1.0f;
+  red = red > 1.0f ? 1.0f - red : red;
+  blue = blue > 1.0f ? 1.0f - blue : blue;
+  green = green > 1.0f ? 1.0f - green : green;
+  return [UIColor colorWithRed:red green:green blue:blue alpha:alpha];
+}
+
+@end
diff --git a/Classes/GTXImageRGBAData.h b/Classes/GTXImageRGBAData.h
new file mode 100644
index 0000000..7e8c193
--- /dev/null
+++ b/Classes/GTXImageRGBAData.h
@@ -0,0 +1,54 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import <UIKit/UIKit.h>
+
+/**
+ *  Class for holding RGBA pixel data when processing images. This class allocates memory to hold
+ *  the buffer required for RGBA data deallocates it when object is released.
+ */
+@interface GTXImageRGBAData : NSObject
+
+- (instancetype)init NS_UNAVAILABLE;
+
+- (instancetype)initWithUIImage:(UIImage *)image;
+
+/**
+ *  Pointer to rgba data (in that order) of the provided image in row major order.
+ */
+@property(nonatomic, readonly) unsigned char *rgba;
+
+/**
+ *  Width of the image as available in the rgba data.
+ */
+@property(nonatomic, readonly) NSUInteger width;
+
+/**
+ *  Height of the image as available in the rgba data.
+ */
+@property(nonatomic, readonly) NSUInteger height;
+
+/**
+ *  Number of bytes used for storing a single pixel in the rgba data.
+ */
+@property(nonatomic, readonly) NSUInteger bytesPerPixel;
+
+/**
+ *  Number of bytes in a single row of rgba data.
+ */
+@property(nonatomic, readonly) NSUInteger bytesPerRow;
+
+@end
diff --git a/Classes/GTXImageRGBAData.m b/Classes/GTXImageRGBAData.m
new file mode 100644
index 0000000..c7f65b3
--- /dev/null
+++ b/Classes/GTXImageRGBAData.m
@@ -0,0 +1,93 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "GTXImageRGBAData.h"
+
+@implementation GTXImageRGBAData
+
+- (instancetype)initWithUIImage:(UIImage *)image {
+  self = [super init];
+  if (self) {
+    _rgba = [GTXImageRGBAData RGBADataFromImage:image
+                                       outWidth:&_width
+                                      outheight:&_height
+                               outbytesPerPixel:&_bytesPerPixel
+                                 outbytesPerRow:&_bytesPerRow];
+  }
+  return self;
+}
+
+/**
+ *  Provides RGBA pixel data for the given UIImage.
+ *
+ *  @param image The image whose RGBA data is to be extracted.
+ *  @param[out] outWidth Populated with the image width, if provided.
+ *  @param[out] outHeight Populated with the image height, if provided.
+ *  @param[out] outBytesPerPixel Populated with the byte count per pixel, if provided.
+ *  @param[out] outBytesPerRow Populated with the byte count per row, if provided.
+ *
+ *  @return pointer to RGBA data for the given image.
+ */
++ (unsigned char *)RGBADataFromImage:(UIImage *)image
+                            outWidth:(NSUInteger *)outWidth
+                           outheight:(NSUInteger *)outHeight
+                    outbytesPerPixel:(NSUInteger *)outBytesPerPixel
+                      outbytesPerRow:(NSUInteger *)outBytesPerRow {
+  // Extract the image dimentions.
+  CGImageRef imageRef = [image CGImage];
+  NSUInteger width = CGImageGetWidth(imageRef);
+  NSUInteger height = CGImageGetHeight(imageRef);
+
+  // Allocate an array to store the rgba values.
+  const NSUInteger bytesPerPixel = 4;
+  const NSUInteger bytesPerRow = bytesPerPixel * width;
+  const NSUInteger bitsPerComponent = 8;
+  unsigned char *rgba = (unsigned char *)malloc(height * width * bytesPerPixel);
+
+  // Create a bitmap context that uses the allocated array as a backing store and render the image
+  // onto it.
+  CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
+  CGContextRef context =
+  CGBitmapContextCreate(rgba, width, height, bitsPerComponent, bytesPerRow, colorSpace,
+                        kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
+  CGColorSpaceRelease(colorSpace);
+  CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
+  CGContextRelease(context);
+
+  // Set the out arguments.
+  if (outWidth) {
+    *outWidth = width;
+  }
+  if (outHeight) {
+    *outHeight = height;
+  }
+  if (outBytesPerPixel) {
+    *outBytesPerPixel = bytesPerPixel;
+  }
+  if (outBytesPerRow) {
+    *outBytesPerRow = bytesPerRow;
+  }
+  return rgba;
+}
+
+- (void)dealloc {
+  if (_rgba) {
+    free(_rgba);
+    _rgba = NULL;
+  }
+}
+
+@end
diff --git a/Classes/GTXLogging.h b/Classes/GTXLogging.h
new file mode 100644
index 0000000..1f3094a
--- /dev/null
+++ b/Classes/GTXLogging.h
@@ -0,0 +1,26 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef GTX_LOGGINIG_INCLUDED
+#define GTX_LOGGINIG_INCLUDED
+
+#if DEBUG
+#define GTX_LOG(...) NSLog(__VA_ARGS__)
+#else
+#define GTX_LOG(...) do { } while (0)
+#endif
+
+#endif  // GTX_LOGGINIG_INCLUDED
diff --git a/Classes/GTXPluginXCTestCase.h b/Classes/GTXPluginXCTestCase.h
new file mode 100644
index 0000000..70522d8
--- /dev/null
+++ b/Classes/GTXPluginXCTestCase.h
@@ -0,0 +1,32 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import <Foundation/Foundation.h>
+
+/**
+ Plugin for GTX to listen for XCTestCase's test tearDown events.
+ */
+@interface GTXPluginXCTestCase : NSObject
+
+/**
+ Installs XCTestCase plugin so that GTX can capture tearDown events.
+
+ @note that this plugin references XCTestCase dynamically. This method must be invoked in the
+ context of a test unless XCTest framework is also linked.
+ */
++ (void)installPlugin;
+
+@end
diff --git a/Classes/GTXPluginXCTestCase.m b/Classes/GTXPluginXCTestCase.m
new file mode 100644
index 0000000..e5425cb
--- /dev/null
+++ b/Classes/GTXPluginXCTestCase.m
@@ -0,0 +1,197 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "GTXPluginXCTestCase.h"
+
+#import "GTXAxeCore.h"
+#import "GTXAssertions.h"
+
+#import <objc/runtime.h>
+
+/**
+ Reference to XCTestCase class which will be dynamically set when needed, this allows for tests as
+ well as apps (which dont link to XCTest) use GTX.
+ */
+Class gXCTestCaseClass;
+
+@implementation GTXPluginXCTestCase
+
++ (void)installPlugin {
+  static dispatch_once_t onceToken;
+  dispatch_once(&onceToken, ^{
+    gXCTestCaseClass = NSClassFromString(@"XCTestCase");
+    NSAssert(gXCTestCaseClass, @"XCTestCase class was not found, either XCTest framework is not "
+                               @"being linked or not loaded yet.");
+    [self gtx_addInstanceMethod:@selector(gtx_tearDown)
+                      fromClass:self
+                        toClass:gXCTestCaseClass];
+    [self gtx_addInstanceMethod:@selector(gtx_invokeTest)
+                      fromClass:self
+                        toClass:gXCTestCaseClass];
+    [self gtx_swizzleInstanceMethod:@selector(invokeTest)
+                         withMethod:@selector(gtx_invokeTest)
+                            inClass:gXCTestCaseClass];
+  });
+}
+
+#pragma mark - XCTestCase Methods
+
+- (void)tearDown {
+  NSAssert(NO, @"This method was added only for a reference to the selector but must never be "
+               @"invoked directly.");
+}
+
+- (void)invokeTest {
+  NSAssert(NO, @"This method was added only for a reference to the selector but must never be "
+               @"invoked directly.");
+}
+
+#pragma mark - Private
+
+/**
+ Adds the given method from the given source to the given destination class.
+
+ @param methodSelector selector of the method to be added.
+ @param srcClass Source class from where to get the method implementation
+ @param destClass destination class where the method implementation is to be added.
+ */
++ (void)gtx_addInstanceMethod:(SEL)methodSelector
+                    fromClass:(Class)srcClass
+                      toClass:(Class)destClass {
+  Method instanceMethod = class_getInstanceMethod(srcClass, methodSelector);
+  const char *typeEncoding = method_getTypeEncoding(instanceMethod);
+  NSAssert(typeEncoding, @"Failed to get method type encoding.");
+
+  BOOL success = class_addMethod(destClass,
+                                 methodSelector,
+                                 method_getImplementation(instanceMethod),
+                                 typeEncoding);
+  NSAssert(success, @"Failed to add %@ from %@ to %@",
+                    NSStringFromSelector(methodSelector), srcClass, destClass);
+}
+
+/**
+ Swizzles the given methods in the given class.
+
+ @param methodSelector1 Selector for the original method.
+ @param methodSelector2 Selector for the method to be swizzled with.
+ @param clazz Target class to be swizzled in.
+ */
++ (void)gtx_swizzleInstanceMethod:(SEL)methodSelector1
+                       withMethod:(SEL)methodSelector2
+                          inClass:(Class)clazz {
+  Method method1 = class_getInstanceMethod(clazz, methodSelector1);
+  Method method2 = class_getInstanceMethod(clazz, methodSelector2);
+  // Only swizzle if both methods are found
+  if (method1 && method2) {
+    IMP imp1 = method_getImplementation(method1);
+    IMP imp2 = method_getImplementation(method2);
+
+    if (class_addMethod(clazz, methodSelector1, imp2, method_getTypeEncoding(method2))) {
+      class_replaceMethod(clazz, methodSelector2, imp1, method_getTypeEncoding(method1));
+    } else {
+      method_exchangeImplementations(method1, method2);
+    }
+  } else {
+    GTX_ASSERT(NO, @"Cannot swizzle %@", NSStringFromSelector(methodSelector1));
+  }
+}
+
+/**
+ @return Closest superclass to @c clazz that has -tearDown method.
+ */
++ (Class)gtx_classWithInstanceTearDown:(Class)clazz {
+  while (clazz != gXCTestCaseClass &&
+         ![clazz instanceMethodForSelector:@selector(tearDown)]) {
+    clazz = [clazz superclass];
+  }
+  return clazz;
+}
+
+/**
+ Wires up teardown in the given class so that -gtx_tearDown is invoked instead.
+
+ @param clazz The class to be modified.
+ */
++ (void)gtx_wireUpTeardownInClass:(Class)clazz {
+  Class baseClass = gXCTestCaseClass;
+
+  // If gtx_tearDown does not exists in clazz, add it first.
+  if (baseClass != clazz) {
+    Method defaultGTXTearDown = class_getInstanceMethod(baseClass, @selector(gtx_tearDown));
+    struct objc_method_description *desc = method_getDescription(defaultGTXTearDown);
+    IMP gtxTearDownIMP = [baseClass instanceMethodForSelector:@selector(gtx_tearDown)];
+    class_addMethod(clazz, @selector(gtx_tearDown), gtxTearDownIMP, desc->types);
+  }
+  [self gtx_swizzleInstanceMethod:@selector(tearDown)
+                       withMethod:@selector(gtx_tearDown)
+                          inClass:clazz];
+}
+
+/**
+ Restores @c clazz to its original state (opposite of +gtx_wireUpTeardownInClass).
+ */
++ (void)gtx_unWireTeardownInClass:(Class)clazz {
+  // Swizzle again to restore tearDown/gtx_tearDown to their original IMPs.
+  [self gtx_swizzleInstanceMethod:@selector(tearDown)
+                       withMethod:@selector(gtx_tearDown)
+                          inClass:clazz];
+}
+
+#pragma mark - Swizzled methods
+
+/**
+ Swizzled method for -invokeTest method that posts notifications when test begins.
+ */
+- (void)gtx_invokeTest {
+  id actualSelf = self; // Note that self is of type XCTestCase
+  Class classWithTearDown =
+      [[GTXPluginXCTestCase class] gtx_classWithInstanceTearDown:[self class]];
+  [[GTXPluginXCTestCase class] gtx_wireUpTeardownInClass:classWithTearDown];
+  [[NSNotificationCenter defaultCenter] postNotificationName:gtxTestCaseDidBeginNotification
+                                                      object:self
+                                                    userInfo:@{gtxTestClassUserInfoKey:
+                                                                 [self class],
+                                                               gtxTestInvocationUserInfoKey:
+                                                                 [actualSelf invocation]}];
+  @try {
+    typedef void (*InvokeTestMethodType)(id, SEL);
+    InvokeTestMethodType method =
+        (InvokeTestMethodType)[self methodForSelector:@selector(gtx_invokeTest)];
+    method(self, _cmd);
+  } @finally {
+    [[GTXPluginXCTestCase class] gtx_unWireTeardownInClass:classWithTearDown];
+  }
+}
+
+/**
+ Swizzled method for -tearDown method that posts notifications for teardown.
+ */
+- (void)gtx_tearDown {
+  id actualSelf = self; // Note that self is of type XCTestCase
+  [[NSNotificationCenter defaultCenter] postNotificationName:gtxTestCaseDidEndNotification
+                                                      object:self
+                                                    userInfo:@{gtxTestClassUserInfoKey:
+                                                                 [self class],
+                                                               gtxTestInvocationUserInfoKey:
+                                                                 [actualSelf invocation]}];
+  typedef void (*TearDownMethodType)(id, SEL);
+  TearDownMethodType method =
+      (TearDownMethodType)[self methodForSelector:@selector(gtx_tearDown)];
+  method(self, _cmd);
+}
+
+@end
diff --git a/Classes/GTXTestCase.h b/Classes/GTXTestCase.h
new file mode 100644
index 0000000..72b5df4
--- /dev/null
+++ b/Classes/GTXTestCase.h
@@ -0,0 +1,45 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import <Foundation/Foundation.h>
+
+/**
+ Class to hold all the data related to a single test case.
+ */
+@interface GTXTestCase : NSObject
+
+/**
+ The test class to which this test belongs.
+ */
+@property (nonatomic, strong) Class testClass;
+
+/**
+ The selector to the test method in the mentioned test class.
+ */
+@property (nonatomic, assign) SEL testCaseSelector;
+
+- (instancetype)init NS_UNAVAILABLE;
+
+/**
+ Initialize a new instance, use this instead of init.
+
+ @param testClass The test class to which this test belongs.
+ @param testCaseSelector The selector to the test method in the mentioned test class.
+ @return A new GTXTestCase instance.
+ */
+- (instancetype)initWithTestClass:(Class)testClass selector:(SEL)testCaseSelector;
+
+@end
diff --git a/Classes/GTXTestCase.m b/Classes/GTXTestCase.m
new file mode 100644
index 0000000..faf1aa2
--- /dev/null
+++ b/Classes/GTXTestCase.m
@@ -0,0 +1,30 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "GTXTestCase.h"
+
+@implementation GTXTestCase
+
+- (instancetype)initWithTestClass:(Class)testClass selector:(SEL)testCaseSelector {
+  self = [super init];
+  if (self) {
+    _testClass = testClass;
+    _testCaseSelector = testCaseSelector;
+  }
+  return self;
+}
+
+@end
diff --git a/Classes/GTXTestSuite.h b/Classes/GTXTestSuite.h
new file mode 100644
index 0000000..5ece0dc
--- /dev/null
+++ b/Classes/GTXTestSuite.h
@@ -0,0 +1,80 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "GTXTestCase.h"
+
+/**
+ An immutable collection of test cases (GTXTestCase objects).
+ */
+@interface GTXTestSuite : NSObject
+
+/**
+ Array of test cases in this suite.
+ */
+@property (nonatomic, strong) NSArray<GTXTestCase *> *tests;
+
+/**
+ @return A test suite with all the test methods in the given class.
+ */
++ (instancetype)suiteWithAllTestsInClass:(Class)testClass;
+
+/**
+ @return A test suite with all the test methods in the given class and all classes inherited from
+         it.
+ */
++ (instancetype)suiteWithAllTestsFromAllClassesInheritedFromClass:(Class)baseClass;
+
+/**
+ Returns a test suite containing the given methods of the given class, test method selectors must
+ be passed in and must be terminated by nil.
+
+ @param testClass The class to get the test methods from.
+ @param testMethod1 Selector of the test method.
+ @return A test suite containing the given methods of the given class
+ */
++ (instancetype)suiteWithClass:(Class)testClass
+                      andTests:(SEL)testMethod1,... NS_REQUIRES_NIL_TERMINATION;
+
+/**
+ Returns a test suite containing all test methods of the given class except the ones specified.
+
+ @param testClass The class to get the test methods from.
+ @param testMethod1 Selector of the test method to be ignored.
+ @return A test suite containing all the tests of the given class except the ones specified.
+ */
++ (instancetype)suiteWithClass:(Class)testClass
+                   exceptTests:(SEL)testMethod1,... NS_REQUIRES_NIL_TERMINATION;
+
+/**
+ @return A new suite with all the test cases of @c suite added to it.
+ */
+- (GTXTestSuite *)suiteByAppendingSuite:(GTXTestSuite *)suite;
+
+/**
+ Checks to dind if the specified testcase is part of the given test suite.
+
+ @param testClass Class that the test method belongs to.
+ @param testMethod Test method
+ @return @c YES if test case is part of the given test suite, @c NO otherwise.
+ */
+- (BOOL)hasTestCaseWithClass:(Class)testClass testMethod:(SEL)testMethod;
+
+/**
+ @return A suite with the intersection of tests of the current suite and the provided suite.
+ */
+- (GTXTestSuite *)intersection:(GTXTestSuite *)suite;
+
+@end
diff --git a/Classes/GTXTestSuite.m b/Classes/GTXTestSuite.m
new file mode 100644
index 0000000..42fafa3
--- /dev/null
+++ b/Classes/GTXTestSuite.m
@@ -0,0 +1,196 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "GTXTestSuite.h"
+
+#import <objc/runtime.h>
+
+@implementation GTXTestSuite {
+  NSMutableArray<GTXTestCase *> *mutableTests;
+}
+
+- (instancetype)init {
+  self = [super init];
+  if (self) {
+    mutableTests = [[NSMutableArray alloc] init];
+    _tests = mutableTests;
+  }
+  return self;
+}
+
++ (instancetype)suiteWithAllTestsFromAllClassesInheritedFromClass:(Class)baseClass {
+  // Get a list of all classes.
+  int totalClassesCount = objc_getClassList(NULL, 0);
+  Class *allClasses =
+      (__unsafe_unretained Class *)malloc(sizeof(Class) * (unsigned long)totalClassesCount);
+  totalClassesCount = objc_getClassList(allClasses, totalClassesCount);
+  NSMutableSet *applicableClasses = [[NSMutableSet alloc] initWithObjects:baseClass, nil];
+  for (NSInteger i = 0; i < totalClassesCount; i++) {
+    // Add all classes that inherit from the given baseClass
+    Class currentClass = allClasses[i];
+    Class currentSuperClass = currentClass;
+    do {
+      currentSuperClass = class_getSuperclass(currentSuperClass);
+    } while(currentSuperClass && currentSuperClass != baseClass);
+
+    if (currentSuperClass == baseClass) {
+      [applicableClasses addObject:currentClass];
+    }
+  }
+
+  // Create a suite with all the tests in applicableClasses set.
+  GTXTestSuite *suite = [[GTXTestSuite alloc] init];
+  for (Class currentClass in applicableClasses) {
+    for (NSString *methodName in [self _testMethodsInAllBaseClassesFromClass:currentClass]) {
+      [suite->mutableTests addObject:
+          [[GTXTestCase alloc] initWithTestClass:currentClass
+                                        selector:NSSelectorFromString(methodName)]];
+    }
+  }
+
+  free(allClasses);
+  return suite;
+}
+
++ (instancetype)suiteWithAllTestsInClass:(Class)testClass {
+  GTXTestSuite *newSuite = [[GTXTestSuite alloc] init];
+  for (NSString *methodName in [self _testMethodsInClass:testClass]) {
+    GTXTestCase *testCase =
+        [[GTXTestCase alloc] initWithTestClass:testClass
+                                      selector:NSSelectorFromString(methodName)];
+    [newSuite->mutableTests addObject:testCase];
+  }
+  return newSuite;
+}
+
++ (instancetype)suiteWithClass:(Class)testClass
+                      andTests:(SEL)testMethod1, ... NS_REQUIRES_NIL_TERMINATION {
+  GTXTestSuite *newSuite = [[GTXTestSuite alloc] init];
+
+  va_list args;
+  va_start(args, testMethod1);
+  SEL testMethod = testMethod1;
+  while (testMethod) {
+    NSAssert([testClass instancesRespondToSelector:testMethod],
+             @"Method %@ does not exist in %@", NSStringFromSelector(testMethod), testClass);
+    GTXTestCase *testCase = [[GTXTestCase alloc] initWithTestClass:testClass selector:testMethod];
+    [newSuite->mutableTests addObject:testCase];
+    testMethod = va_arg(args, SEL);
+  }
+
+  va_end(args);
+  return newSuite;
+}
+
++ (instancetype)suiteWithClass:(Class)testClass
+                   exceptTests:(SEL)testMethod1, ... NS_REQUIRES_NIL_TERMINATION {
+  GTXTestSuite *suite = [GTXTestSuite suiteWithAllTestsInClass:testClass];
+
+  // Remove the specified tests from the suite.
+  va_list args;
+  va_start(args, testMethod1);
+  SEL testMethod = testMethod1;
+  while (testMethod) {
+    GTXTestCase *testCaseWithTestMethod;
+    for (GTXTestCase *testCase in suite.tests) {
+      if (testCase.testCaseSelector == testMethod) {
+        testCaseWithTestMethod = testCase;
+        break;
+      }
+    }
+    NSAssert(testCaseWithTestMethod, @"Test %@ doesnot exist in %@ class",
+             NSStringFromSelector(testMethod), testClass);
+    [suite->mutableTests removeObject:testCaseWithTestMethod];
+    testMethod = va_arg(args, SEL);
+  }
+  va_end(args);
+  return suite;
+}
+
+- (GTXTestSuite *)suiteByAppendingSuite:(GTXTestSuite *)suite {
+  GTXTestSuite *newSuite = [[GTXTestSuite alloc] init];
+  [newSuite->mutableTests addObjectsFromArray:self.tests];
+  [newSuite->mutableTests addObjectsFromArray:suite.tests];
+  return newSuite;
+}
+
+- (BOOL)hasTestCaseWithClass:(Class)testClass testMethod:(SEL)testMethod {
+  for (GTXTestCase *testCase in self.tests) {
+    if (testCase.testCaseSelector == testMethod && testCase.testClass == testClass) {
+      return YES;
+    }
+  }
+  return NO;
+}
+
+- (GTXTestSuite *)intersection:(GTXTestSuite *)suite {
+  GTXTestSuite *intersection = [[GTXTestSuite alloc] init];
+  for (GTXTestCase *first in self.tests) {
+    for (GTXTestCase *second in suite.tests) {
+      if (first.testClass == second.testClass &&
+          first.testCaseSelector == second.testCaseSelector) {
+        [intersection->mutableTests addObject:first];
+      }
+    }
+  }
+  return intersection;
+}
+
+- (NSString *)description {
+  NSMutableArray *items = [[NSMutableArray alloc] init];
+  for (GTXTestCase *testcase in self.tests) {
+    [items addObject:[NSString stringWithFormat:@"<Testcase Class=%@, Method=%@>",
+                                                testcase.testClass,
+                                                NSStringFromSelector(testcase.testCaseSelector)]];
+  }
+  return [NSString stringWithFormat:@"@[\n%@\n]", [items componentsJoinedByString:@"\n"]];
+}
+
+#pragma mark - Private
+
+/**
+ @return An array of all test methods in the given class and all of its super classes.
+ */
++ (NSArray *)_testMethodsInAllBaseClassesFromClass:(Class)inClass {
+  Class currentClass = inClass;
+  NSMutableSet *testMethods = [[NSMutableSet alloc] init];
+  while (currentClass) {
+    [testMethods addObjectsFromArray:[self _testMethodsInClass:currentClass]];
+    currentClass = [currentClass superclass];
+  }
+  return [testMethods allObjects];
+}
+
+/**
+ @return An array of all test methods only in the given class (methods defined in its super classes
+         are omitted).
+ */
++ (NSArray *)_testMethodsInClass:(Class)inClass {
+  NSMutableSet *testMethods = [[NSMutableSet alloc] init];
+  unsigned int allMethodsCount = 0;
+  Method *allMethods = class_copyMethodList(inClass, &allMethodsCount);
+  for (unsigned int i = 0; i < allMethodsCount; i++) {
+    SEL methodSel = method_getName(allMethods[i]);
+    NSString *methodName = NSStringFromSelector(methodSel);
+    if ([methodName hasPrefix:@"test"]) {
+      [testMethods addObject:methodName];
+    }
+  }
+  free(allMethods);
+  return [testMethods allObjects];
+}
+
+@end
diff --git a/Classes/GTXToolKit.h b/Classes/GTXToolKit.h
new file mode 100644
index 0000000..ca799f2
--- /dev/null
+++ b/Classes/GTXToolKit.h
@@ -0,0 +1,87 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import <UIKit/UIKit.h>
+
+#import "GTXChecking.h"
+#import "GTXCheckBlock.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ GTXToolKit can be used for custom implementation of a checking mechanism, @class GTAxe uses
+ GTXToolKit for performing the checks.
+ */
+@interface GTXToolKit : NSObject
+
+
+/**
+ Creates a check.
+
+ @param name Name of the check.
+ @param block Block that performs the check, returns NO on failure.
+ @return A newly created check.
+ */
++ (id<GTXChecking>)checkWithName:(NSString *)name block:(GTXCheckHandlerBlock)block;
+
+
+/**
+ Registers the given check to be executed on all elements this instance is used on.
+
+ @param check The check to be registered.
+ */
+- (void)registerCheck:(id<GTXChecking>)check;
+
+
+/**
+ Applies the registered checks on the given element while respecting ignored elements.
+
+ @param element element to be checked.
+ @param errorOrNil Error object to be filled with error info on check failures.
+ @return @c YES if all checks passed @c NO otherwise.
+ */
+- (BOOL)checkElement:(id)element error:(GTXErrorRefType)errorOrNil;
+
+
+/**
+ Applies the registered checks on all elements in the accessibility tree under the given root
+ elements while respecting ignored elements.
+
+ @param rootElements An array of root elements whose accessibility trees are to be checked.
+ @param errorOrNil Error object to be filled with error info on check failures.
+ @return @c YES if all checks passed on all elements @c NO otherwise.
+ */
+- (BOOL)checkAllElementsFromRootElements:(NSArray *)rootElements
+                                   error:(GTXErrorRefType)errorOrNil;
+
+/**
+ Configures this instance to skip elements of the given class name from all checks.
+
+ @param className The name of the class to be ignored.
+ */
+- (void)ignoreElementsOfClassNamed:(NSString *)className;
+
+/**
+ Configures this instance to skip elements of the given class name from a specific check.
+
+ @param className The name of the class to be ignored.
+ @param skipCheckName The name of the check for which this element is to be ignored.
+ */
+- (void)ignoreElementsOfClassNamed:(NSString *)className forCheckNamed:(NSString *)skipCheckName;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Classes/GTXToolKit.m b/Classes/GTXToolKit.m
new file mode 100644
index 0000000..414e552
--- /dev/null
+++ b/Classes/GTXToolKit.m
@@ -0,0 +1,245 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "GTXToolKit.h"
+
+#import "GTXAccessibilityTree.h"
+#import "GTXAnalytics.h"
+#import "NSError+GTXAdditions.h"
+
+/**
+ A matcher block that determines if the given element must be ignored for the given check.
+
+ @param element Element to be looked up if it needs to be ignored.
+ @param checkName Name of the check for which element must be looked up.
+ @return @c YES if element needs to be ignored for the given check @c NO other wise.
+ */
+typedef BOOL(^GTXIgnoreElementMatcher)(id element, NSString *checkName);
+
+#pragma mark - Implementation
+
+@implementation GTXToolKit {
+  NSMutableArray<id<GTXChecking>> *_checks;
+  NSMutableArray<GTXIgnoreElementMatcher> *_blackListMatchers;
+}
+
+- (instancetype)init {
+  self = [super init];
+  if (self) {
+    _checks = [[NSMutableArray alloc] init];
+    _blackListMatchers = [[NSMutableArray alloc] init];
+  }
+  return self;
+}
+
++ (id<GTXChecking>)checkWithName:(NSString *)name block:(GTXCheckHandlerBlock)block {
+  return [GTXCheckBlock GTXCheckWithName:name block:block];
+}
+
+- (void)registerCheck:(id<GTXChecking>)check {
+  // Verify the newly added check is unique.
+  for (id<GTXChecking> existingCheck in _checks) {
+    NSAssert(![[existingCheck name] isEqualToString:[check name]],
+             @"Check named %@ already exists!", [check name]);
+  }
+  [_checks addObject:check];
+}
+
+- (BOOL)checkElement:(id)element error:(GTXErrorRefType)errorOrNil {
+  return [self _checkElement:element analyticsEnabled:YES error:errorOrNil];
+}
+
+- (BOOL)checkAllElementsFromRootElements:(NSArray *)rootElements
+                                   error:(GTXErrorRefType)errorOrNil {
+  GTXAccessibilityTree *tree = [[GTXAccessibilityTree alloc] initWithRootElements:rootElements];
+  NSMutableArray *errors;
+  // Check each element and collect all failures into the errors array.
+  for (id element in tree) {
+    NSError *error;
+    if (![self _checkElement:element analyticsEnabled:NO error:&error]) {
+      if (!error) {
+        NSString *genericErrorDescription =
+        @"One or more Gtx checks failed, error description was not provided by the check.";
+        error = [NSError errorWithDomain:kGTXErrorDomain
+                                    code:GTXCheckErrorCodeGenericError
+                                userInfo:@{ kGTXErrorFailingElementKey : element,
+                                            NSLocalizedDescriptionKey : genericErrorDescription }];
+      }
+      if (!errors) {
+        errors = [[NSMutableArray alloc] init];
+      }
+      [errors addObject:error];
+    }
+  }
+
+  [GTXAnalytics invokeAnalyticsEvent:(errors ?
+                                      GTXAnalyticsEventChecksFailed :
+                                      GTXAnalyticsEventChecksPerformed)];
+  if (errors) {
+    // Combine the error descriptions from all the errors into the following format ('.' is an
+    // indent):
+    // . . Element: <element description>
+    // . . . . Error: <error description>
+    // . . . . Error: <error description>
+    // . . Element: <element description>
+    // . . . . Error: <error description>
+    // . . . . Error: <error description>
+
+    NSMutableString *errorString =
+    [[NSMutableString alloc] initWithString:@"One or more elements FAILED the accessibility "
+                                            @"checks:\n"];
+    for (NSError *error in errors) {
+      id element = [[error userInfo] objectForKey:kGTXErrorFailingElementKey];
+      if (element) {
+        // Add element description with an indent.
+        [errorString appendFormat:@"  %@\n", element];
+        for (NSError *underlyingError in
+             // Add element's error description with twice the indent.
+             [[error userInfo] objectForKey:kGTXErrorUnderlyingErrorsKey]) {
+          [errorString appendFormat:@"    + %@\n", [underlyingError localizedDescription]];
+        }
+      }
+    }
+    NSError *error;
+    [NSError gtx_logOrSetError:&error
+                   description:errorString
+                          code:GTXCheckErrorCodeGenericError
+                      userInfo:@{ kGTXErrorUnderlyingErrorsKey : errors }];
+    if (errorOrNil) {
+      *errorOrNil = error;
+    }
+  }
+  return errors == nil;
+}
+
+- (void)ignoreElementsOfClassNamed:(NSString *)className {
+  Class classObject = NSClassFromString(className);
+  NSAssert(classObject, @"Class named %@ does not exist!", className);
+  GTXIgnoreElementMatcher matcher = ^BOOL(id element, NSString *checkName) {
+    return [element isKindOfClass:classObject];
+  };
+  [_blackListMatchers addObject:matcher];
+}
+
+- (void)ignoreElementsOfClassNamed:(NSString *)className forCheckNamed:(NSString *)skipCheckName {
+  NSParameterAssert(skipCheckName);
+  Class classObject = NSClassFromString(className);
+  NSAssert(classObject, @"Class named %@ does not exist!", className);
+  GTXIgnoreElementMatcher matcher = ^BOOL(id element, NSString *checkName) {
+    return [element isKindOfClass:classObject] && [checkName isEqualToString:skipCheckName];
+  };
+  [_blackListMatchers addObject:matcher];
+}
+
+#pragma mark - private
+
+/**
+ Applies the registered checks on the given element while respecting ignored elements.
+
+ @param element element to be checked.
+ @param checkAnalyticsEnabled Boolean that indicates if analytics events are to be invoked.
+ @param errorOrNil Error object to be filled with error info on check failures.
+ @return @c YES if all checks passed @c NO otherwise.
+ */
+- (BOOL)_checkElement:(id)element
+     analyticsEnabled:(BOOL)checkAnalyticsEnabled
+                error:(GTXErrorRefType)errorOrNil {
+  NSParameterAssert(element);
+  if ([element respondsToSelector:@selector(isAccessibilityElement)] &&
+      ![element isAccessibilityElement]) {
+    // Currently all checks are only applicable to accessibility elements.
+    return YES;
+  }
+
+  NSMutableArray *failedCheckErrors;
+  for (id<GTXChecking> checker in _checks) {
+    BOOL shouldSkipThisCheck = NO;
+    for (GTXIgnoreElementMatcher matcher in _blackListMatchers) {
+      if (matcher(element, [checker name])) {
+        shouldSkipThisCheck = YES;
+        break;
+      }
+    }
+    if (shouldSkipThisCheck) {
+      continue;
+    }
+
+    NSError *error = nil;
+    if (![checker check:element error:&error]) {
+      if (!error) {
+        // Check failed but an error description was not provided, generate a generic description.
+        NSString *errorDescription =
+        [NSString stringWithFormat:@"Check \"%@\" failed, no description was provided by the "
+                                   @"check implementation.", [checker name]];
+        error = [NSError errorWithDomain:kGTXErrorDomain
+                                    code:GTXCheckErrorCodeAccessibilityCheckFailed
+                                userInfo:@{ NSLocalizedDescriptionKey: errorDescription }];
+      }
+      if (!failedCheckErrors) {
+        failedCheckErrors = [[NSMutableArray alloc] init];
+      }
+      [failedCheckErrors addObject:error];
+    }
+  }
+  if (checkAnalyticsEnabled) {
+    [GTXAnalytics invokeAnalyticsEvent:(failedCheckErrors ?
+                                        GTXAnalyticsEventChecksFailed :
+                                        GTXAnalyticsEventChecksPerformed)];
+  }
+  if (!failedCheckErrors) {
+    // All checks passed.
+    return YES;
+  }
+
+  // We have some failures, construct an error description from all the failures and error.
+  NSString *errorDescription = [self _errorDescriptionForElement:element
+                                                  gtxCheckErrors:failedCheckErrors];
+  NSError *error = nil;
+  [NSError gtx_logOrSetError:&error
+                 description:errorDescription
+                        code:GTXCheckErrorCodeAccessibilityCheckFailed
+                    userInfo:@{ kGTXErrorFailingElementKey : element,
+                                kGTXErrorUnderlyingErrorsKey : failedCheckErrors}];
+
+  if (errorOrNil) {
+    *errorOrNil = error;
+  }
+  return NO;
+}
+
+/**
+ Creates an error description from an array of errors.
+
+ @param element Failing element for which description is to be created.
+ @param errors An array of errors that the element has.
+ @return The created error description.
+ */
+- (NSString *)_errorDescriptionForElement:(id)element gtxCheckErrors:(NSArray *)errors {
+  NSMutableString *localizedDescription =
+  [NSMutableString stringWithFormat:@"%d accessibility error(s) were found in %@:\n",
+   (int)[errors count],
+   element];
+  for (NSUInteger index = 0; index < [errors count]; index++) {
+    [localizedDescription appendString:
+     [NSString stringWithFormat:@"%d. %@\n",
+      (int)(index + 1),
+      [errors[index] localizedDescription]]];
+  }
+  return localizedDescription;
+}
+
+@end
+
diff --git a/Classes/NSError+GTXAdditions.h b/Classes/NSError+GTXAdditions.h
new file mode 100644
index 0000000..0cfe713
--- /dev/null
+++ b/Classes/NSError+GTXAdditions.h
@@ -0,0 +1,101 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import <UIKit/UIKit.h>
+
+#import "GTXCommon.h"
+
+/**
+ *  The error domain for all errors related to GTX.
+ */
+FOUNDATION_EXTERN NSString *const kGTXErrorDomain;
+
+/**
+ *  The key to the value in NSError's @c userInfo object containing the failing element.
+ */
+FOUNDATION_EXTERN NSString *const kGTXErrorFailingElementKey;
+
+/**
+ *  The key to the value in NSError's @c userInfo object containing an array of NSError objects
+ *  each representing a separate underlying error that occurred.
+ */
+FOUNDATION_EXTERN NSString *const kGTXErrorUnderlyingErrorsKey;
+
+/**
+ *  The key to the value in NSError's @c userInfo object containing the name of the failing
+ *  GTXCheck.
+ */
+FOUNDATION_EXTERN NSString *const kGTXErrorCheckNameKey;
+
+/**
+ *  Error codes for various errors that can occur when GTXChecks are performed.
+ */
+typedef NS_ENUM(NSInteger, GTXCheckErrorCode) {
+  /**
+   *  The GTXSystem has been configured to skip all checks.
+   */
+  GTXCheckErrorCodeSkippedAllChecks,
+  /**
+   *  The element provided for accessibility checking failed a particular check. Error details
+   *  provided in the NSError's description field will indicate the specific check that was failed.
+   */
+  GTXCheckErrorCodeAccessibilityCheckFailed,
+  /**
+   *  The element provided for accessibility checking cannot be focused by VoiceOver.
+   */
+  GTXCheckErrorCodeNotFocusableElement,
+  /**
+   *  An error code for all errors not related to the above.
+   */
+  GTXCheckErrorCodeGenericError,
+};
+
+/**
+ *  Additions to NSError to make it easy to create error objects for GTXSystem.
+ */
+@interface NSError (GTXAdditions)
+
+/**
+ *  Creates an NSError object describing the failure or logs the error if @c errorOrNil is @c nil.
+ *
+ *  @param[out] errorOrNil    The error object created.
+ *  @param      description   The descripton of the error.
+ *  @param      errorCode     The error code for the error.
+ *  @param      userInfoOrNil An optional userInfo object to be added to the error.
+ *
+ *  @return @c YES if the error object was created, @c NO otherwise.
+ */
++ (BOOL)gtx_logOrSetError:(GTXErrorRefType)errorOrNil
+              description:(NSString *)description
+                     code:(NSInteger)errorCode
+                 userInfo:(NSDictionary *)userInfoOrNil;
+
+/**
+ *  Creates an NSError object describing an GTXCheck failure or logs the error if @c errorOrNil is
+ *  nil.
+ *
+ *  @param[out] errorOrNil  The error object created.
+ *  @param      element     The target element on which GTX check is to be performed.
+ *  @param      name        The name of the GTXCheck
+ *  @param      description A description of the error along with instructions on fixing it.
+ *
+ *  @return @c YES if the error object was created, @c NO otherwise.
+ */
++ (BOOL)gtx_logOrSetGTXCheckFailedError:(GTXErrorRefType)errorOrNil
+                                element:(id)element
+                                   name:(NSString *)name
+                            description:(NSString *)description;
+@end
diff --git a/Classes/NSError+GTXAdditions.m b/Classes/NSError+GTXAdditions.m
new file mode 100644
index 0000000..0e35935
--- /dev/null
+++ b/Classes/NSError+GTXAdditions.m
@@ -0,0 +1,75 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "NSError+GTXAdditions.h"
+
+#import "GTXLogging.h"
+
+#pragma mark - Externs
+
+NSString *const kGTXErrorDomain = @"com.google.gtxchecker";
+NSString *const kGTXErrorFailingElementKey = @"kGTXErrorFailingElementKey";
+NSString *const kGTXErrorUnderlyingErrorsKey= @"kGTXErrorUnderlyingErrorsKey";
+NSString *const kGTXErrorFailuresKey = @"kGTXErrorFailuresKey";
+NSString *const kGTXErrorCheckNameKey = @"kGTXErrorCheckNameKey";
+
+#pragma mark - Implementation
+
+@implementation NSError (GTXAdditions)
+
++ (BOOL)gtx_logOrSetError:(GTXErrorRefType)errorOrNil
+              description:(NSString *)description
+                     code:(NSInteger)errorCode
+                 userInfo:(NSDictionary *)userInfoOrNil {
+  NSParameterAssert(description);
+  NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:userInfoOrNil];
+  userInfo[NSLocalizedDescriptionKey] = description;
+  NSError *error = [NSError errorWithDomain:kGTXErrorDomain
+                                       code:errorCode
+                                   userInfo:userInfo];
+  if (errorOrNil) {
+    *errorOrNil = error;
+  } else {
+    GTX_LOG(@"%@", error);
+  }
+  return YES;
+}
+
++ (BOOL)gtx_logOrSetGTXCheckFailedError:(GTXErrorRefType)errorOrNil
+                                element:(id)element
+                                   name:(NSString *)name
+                            description:(NSString *)description {
+  NSParameterAssert(name);
+  NSParameterAssert(element);
+  NSParameterAssert(description);
+  NSString *fullDescription =
+      [NSString stringWithFormat:@"Check \"%@\" failed, %@",
+                                 name, description];
+  NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : fullDescription,
+                              kGTXErrorFailingElementKey: [element description],
+                              kGTXErrorCheckNameKey: name };
+  NSError *error = [NSError errorWithDomain:kGTXErrorDomain
+                                       code:GTXCheckErrorCodeAccessibilityCheckFailed
+                                   userInfo:userInfo];
+  if (errorOrNil) {
+    *errorOrNil = error;
+  } else {
+    GTX_LOG(@"%@", error);
+  }
+  return YES;
+}
+
+@end
diff --git a/FrameworkFiles/Info.plist b/FrameworkFiles/Info.plist
new file mode 100644
index 0000000..c4c0fdd
--- /dev/null
+++ b/FrameworkFiles/Info.plist
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+  <key>CFBundleDevelopmentRegion</key>
+  <string>$(DEVELOPMENT_LANGUAGE)</string>
+  <key>CFBundleExecutable</key>
+  <string>$(EXECUTABLE_NAME)</string>
+  <key>CFBundleIdentifier</key>
+  <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+  <key>CFBundleInfoDictionaryVersion</key>
+  <string>6.0</string>
+  <key>CFBundleName</key>
+  <string>$(PRODUCT_NAME)</string>
+  <key>CFBundlePackageType</key>
+  <string>FMWK</string>
+  <key>CFBundleShortVersionString</key>
+  <string>1.0</string>
+  <key>CFBundleVersion</key>
+  <string>$(CURRENT_PROJECT_VERSION)</string>
+  <key>NSPrincipalClass</key>
+  <string></string>
+</dict>
+</plist>
diff --git a/GTAxe.xcodeproj/project.pbxproj b/GTAxe.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..212385f
--- /dev/null
+++ b/GTAxe.xcodeproj/project.pbxproj
@@ -0,0 +1,609 @@
+// !$*UTF8*$!
+{
+	archiveVersion = 1;
+	classes = {
+	};
+	objectVersion = 48;
+	objects = {
+
+/* Begin PBXBuildFile section */
+		610CA3FD204DFC76008BAAA1 /* GTXPluginXCTestCase.h in Headers */ = {isa = PBXBuildFile; fileRef = 610CA3FB204DFC76008BAAA1 /* GTXPluginXCTestCase.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		610CA3FE204DFC76008BAAA1 /* GTXPluginXCTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 610CA3FC204DFC76008BAAA1 /* GTXPluginXCTestCase.m */; };
+		610CA405204E04AC008BAAA1 /* GTXAxeCore.h in Headers */ = {isa = PBXBuildFile; fileRef = 610CA403204E04AB008BAAA1 /* GTXAxeCore.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		610CA406204E04AC008BAAA1 /* GTXAxeCore.m in Sources */ = {isa = PBXBuildFile; fileRef = 610CA404204E04AB008BAAA1 /* GTXAxeCore.m */; };
+		6115E78B204F5BA3003E32F9 /* GTAxe.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 613C3F41204A09E8007D44A8 /* GTAxe.framework */; };
+		6115E79B204F5BC9003E32F9 /* GTXToolKitTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6115E791204F5BC6003E32F9 /* GTXToolKitTests.m */; };
+		6115E79C204F5BC9003E32F9 /* GTXTestSuiteTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6115E792204F5BC6003E32F9 /* GTXTestSuiteTests.m */; };
+		6115E79D204F5BC9003E32F9 /* GTXChecksCollectionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6115E793204F5BC7003E32F9 /* GTXChecksCollectionTests.m */; };
+		6115E79E204F5BC9003E32F9 /* GTXAccessibilityTreeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6115E794204F5BC7003E32F9 /* GTXAccessibilityTreeTests.m */; };
+		6115E79F204F5BC9003E32F9 /* GTXAnalyticsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6115E795204F5BC8003E32F9 /* GTXAnalyticsTests.m */; };
+		6115E7A0204F5BC9003E32F9 /* GTXTestAccessibilityElements.m in Sources */ = {isa = PBXBuildFile; fileRef = 6115E798204F5BC8003E32F9 /* GTXTestAccessibilityElements.m */; };
+		6115E7A1204F5BC9003E32F9 /* GTXImageAndColorUtilsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6115E799204F5BC8003E32F9 /* GTXImageAndColorUtilsTests.m */; };
+		6115E7A2204F5BC9003E32F9 /* GTXBaseTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 6115E79A204F5BC9003E32F9 /* GTXBaseTestCase.m */; };
+		61ABAEB0204A0B0B006DBF0A /* GTXAnalyticsUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 61ABAE8E204A0B02006DBF0A /* GTXAnalyticsUtils.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		61ABAEB1204A0B0B006DBF0A /* GTXAnalytics.h in Headers */ = {isa = PBXBuildFile; fileRef = 61ABAE8F204A0B02006DBF0A /* GTXAnalytics.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		61ABAEB2204A0B0B006DBF0A /* GTXErrorReporter.h in Headers */ = {isa = PBXBuildFile; fileRef = 61ABAE90204A0B02006DBF0A /* GTXErrorReporter.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		61ABAEB3204A0B0B006DBF0A /* GTXImageAndColorUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 61ABAE91204A0B02006DBF0A /* GTXImageAndColorUtils.m */; };
+		61ABAEB4204A0B0B006DBF0A /* GTXTestSuite.m in Sources */ = {isa = PBXBuildFile; fileRef = 61ABAE92204A0B03006DBF0A /* GTXTestSuite.m */; };
+		61ABAEB5204A0B0B006DBF0A /* GTXAccessibilityTree.h in Headers */ = {isa = PBXBuildFile; fileRef = 61ABAE93204A0B03006DBF0A /* GTXAccessibilityTree.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		61ABAEB6204A0B0B006DBF0A /* GTXCommon.h in Headers */ = {isa = PBXBuildFile; fileRef = 61ABAE94204A0B03006DBF0A /* GTXCommon.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		61ABAEB7204A0B0B006DBF0A /* GTXLogging.h in Headers */ = {isa = PBXBuildFile; fileRef = 61ABAE95204A0B03006DBF0A /* GTXLogging.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		61ABAEB8204A0B0B006DBF0A /* GTXAnalyticsUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 61ABAE96204A0B03006DBF0A /* GTXAnalyticsUtils.m */; };
+		61ABAEB9204A0B0B006DBF0A /* GTXChecking.h in Headers */ = {isa = PBXBuildFile; fileRef = 61ABAE97204A0B04006DBF0A /* GTXChecking.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		61ABAEBA204A0B0B006DBF0A /* GTXElementBlacklist.m in Sources */ = {isa = PBXBuildFile; fileRef = 61ABAE98204A0B04006DBF0A /* GTXElementBlacklist.m */; };
+		61ABAEBB204A0B0B006DBF0A /* GTXTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 61ABAE99204A0B04006DBF0A /* GTXTestCase.m */; };
+		61ABAEBC204A0B0B006DBF0A /* GTXAccessibilityTree.m in Sources */ = {isa = PBXBuildFile; fileRef = 61ABAE9A204A0B04006DBF0A /* GTXAccessibilityTree.m */; };
+		61ABAEBD204A0B0B006DBF0A /* GTXAssertions.h in Headers */ = {isa = PBXBuildFile; fileRef = 61ABAE9B204A0B05006DBF0A /* GTXAssertions.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		61ABAEBE204A0B0B006DBF0A /* GTXImageRGBAData.h in Headers */ = {isa = PBXBuildFile; fileRef = 61ABAE9C204A0B05006DBF0A /* GTXImageRGBAData.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		61ABAEBF204A0B0B006DBF0A /* NSError+GTXAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 61ABAE9D204A0B05006DBF0A /* NSError+GTXAdditions.m */; };
+		61ABAEC0204A0B0B006DBF0A /* GTXCheckBlock.m in Sources */ = {isa = PBXBuildFile; fileRef = 61ABAE9E204A0B06006DBF0A /* GTXCheckBlock.m */; };
+		61ABAEC1204A0B0B006DBF0A /* GTXTestSuite.h in Headers */ = {isa = PBXBuildFile; fileRef = 61ABAE9F204A0B06006DBF0A /* GTXTestSuite.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		61ABAEC2204A0B0B006DBF0A /* GTXToolKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 61ABAEA0204A0B06006DBF0A /* GTXToolKit.m */; };
+		61ABAEC3204A0B0B006DBF0A /* GTXTestCase.h in Headers */ = {isa = PBXBuildFile; fileRef = 61ABAEA1204A0B06006DBF0A /* GTXTestCase.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		61ABAEC4204A0B0B006DBF0A /* GTXImageAndColorUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 61ABAEA2204A0B06006DBF0A /* GTXImageAndColorUtils.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		61ABAEC6204A0B0B006DBF0A /* GTXChecksCollection.m in Sources */ = {isa = PBXBuildFile; fileRef = 61ABAEA4204A0B07006DBF0A /* GTXChecksCollection.m */; };
+		61ABAEC8204A0B0B006DBF0A /* NSError+GTXAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 61ABAEA6204A0B08006DBF0A /* NSError+GTXAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		61ABAEC9204A0B0B006DBF0A /* GTXAnalytics.m in Sources */ = {isa = PBXBuildFile; fileRef = 61ABAEA7204A0B08006DBF0A /* GTXAnalytics.m */; };
+		61ABAECA204A0B0B006DBF0A /* GTXChecksCollection.h in Headers */ = {isa = PBXBuildFile; fileRef = 61ABAEA8204A0B08006DBF0A /* GTXChecksCollection.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		61ABAECB204A0B0B006DBF0A /* GTXCheckBlock.h in Headers */ = {isa = PBXBuildFile; fileRef = 61ABAEA9204A0B09006DBF0A /* GTXCheckBlock.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		61ABAECC204A0B0B006DBF0A /* GTXErrorReporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 61ABAEAA204A0B09006DBF0A /* GTXErrorReporter.m */; };
+		61ABAECD204A0B0B006DBF0A /* GTXToolKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 61ABAEAB204A0B09006DBF0A /* GTXToolKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		61ABAECE204A0B0B006DBF0A /* GTXImageRGBAData.m in Sources */ = {isa = PBXBuildFile; fileRef = 61ABAEAC204A0B0A006DBF0A /* GTXImageRGBAData.m */; };
+		61ABAECF204A0B0B006DBF0A /* GTAxe.h in Headers */ = {isa = PBXBuildFile; fileRef = 61ABAEAD204A0B0A006DBF0A /* GTAxe.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		61ABAED1204A0B0B006DBF0A /* GTXElementBlacklist.h in Headers */ = {isa = PBXBuildFile; fileRef = 61ABAEAF204A0B0A006DBF0A /* GTXElementBlacklist.h */; settings = {ATTRIBUTES = (Public, ); }; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+		6115E78C204F5BA3003E32F9 /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = 613C3F38204A09E7007D44A8 /* Project object */;
+			proxyType = 1;
+			remoteGlobalIDString = 613C3F40204A09E7007D44A8;
+			remoteInfo = GTAxe;
+		};
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXFileReference section */
+		610CA3FB204DFC76008BAAA1 /* GTXPluginXCTestCase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GTXPluginXCTestCase.h; path = Classes/GTXPluginXCTestCase.h; sourceTree = SOURCE_ROOT; };
+		610CA3FC204DFC76008BAAA1 /* GTXPluginXCTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTXPluginXCTestCase.m; path = Classes/GTXPluginXCTestCase.m; sourceTree = SOURCE_ROOT; };
+		610CA403204E04AB008BAAA1 /* GTXAxeCore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GTXAxeCore.h; path = Classes/GTXAxeCore.h; sourceTree = SOURCE_ROOT; };
+		610CA404204E04AB008BAAA1 /* GTXAxeCore.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTXAxeCore.m; path = Classes/GTXAxeCore.m; sourceTree = SOURCE_ROOT; };
+		6115E786204F5BA2003E32F9 /* GTAxeUnitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = GTAxeUnitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+		6115E791204F5BC6003E32F9 /* GTXToolKitTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTXToolKitTests.m; path = Tests/UnitTests/GTXToolKitTests.m; sourceTree = SOURCE_ROOT; };
+		6115E792204F5BC6003E32F9 /* GTXTestSuiteTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTXTestSuiteTests.m; path = Tests/UnitTests/GTXTestSuiteTests.m; sourceTree = SOURCE_ROOT; };
+		6115E793204F5BC7003E32F9 /* GTXChecksCollectionTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTXChecksCollectionTests.m; path = Tests/UnitTests/GTXChecksCollectionTests.m; sourceTree = SOURCE_ROOT; };
+		6115E794204F5BC7003E32F9 /* GTXAccessibilityTreeTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTXAccessibilityTreeTests.m; path = Tests/UnitTests/GTXAccessibilityTreeTests.m; sourceTree = SOURCE_ROOT; };
+		6115E795204F5BC8003E32F9 /* GTXAnalyticsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTXAnalyticsTests.m; path = Tests/UnitTests/GTXAnalyticsTests.m; sourceTree = SOURCE_ROOT; };
+		6115E796204F5BC8003E32F9 /* GTXTestAccessibilityElements.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GTXTestAccessibilityElements.h; path = Tests/UnitTests/GTXTestAccessibilityElements.h; sourceTree = SOURCE_ROOT; };
+		6115E797204F5BC8003E32F9 /* GTXBaseTestCase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GTXBaseTestCase.h; path = Tests/UnitTests/GTXBaseTestCase.h; sourceTree = SOURCE_ROOT; };
+		6115E798204F5BC8003E32F9 /* GTXTestAccessibilityElements.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTXTestAccessibilityElements.m; path = Tests/UnitTests/GTXTestAccessibilityElements.m; sourceTree = SOURCE_ROOT; };
+		6115E799204F5BC8003E32F9 /* GTXImageAndColorUtilsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTXImageAndColorUtilsTests.m; path = Tests/UnitTests/GTXImageAndColorUtilsTests.m; sourceTree = SOURCE_ROOT; };
+		6115E79A204F5BC9003E32F9 /* GTXBaseTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTXBaseTestCase.m; path = Tests/UnitTests/GTXBaseTestCase.m; sourceTree = SOURCE_ROOT; };
+		613C3F41204A09E8007D44A8 /* GTAxe.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = GTAxe.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+		61ABAE8A204A0AA3006DBF0A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = FrameworkFiles/Info.plist; sourceTree = SOURCE_ROOT; };
+		61ABAE8E204A0B02006DBF0A /* GTXAnalyticsUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GTXAnalyticsUtils.h; path = Classes/GTXAnalyticsUtils.h; sourceTree = SOURCE_ROOT; };
+		61ABAE8F204A0B02006DBF0A /* GTXAnalytics.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GTXAnalytics.h; path = Classes/GTXAnalytics.h; sourceTree = SOURCE_ROOT; };
+		61ABAE90204A0B02006DBF0A /* GTXErrorReporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GTXErrorReporter.h; path = Classes/GTXErrorReporter.h; sourceTree = SOURCE_ROOT; };
+		61ABAE91204A0B02006DBF0A /* GTXImageAndColorUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTXImageAndColorUtils.m; path = Classes/GTXImageAndColorUtils.m; sourceTree = SOURCE_ROOT; };
+		61ABAE92204A0B03006DBF0A /* GTXTestSuite.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTXTestSuite.m; path = Classes/GTXTestSuite.m; sourceTree = SOURCE_ROOT; };
+		61ABAE93204A0B03006DBF0A /* GTXAccessibilityTree.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GTXAccessibilityTree.h; path = Classes/GTXAccessibilityTree.h; sourceTree = SOURCE_ROOT; };
+		61ABAE94204A0B03006DBF0A /* GTXCommon.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GTXCommon.h; path = Classes/GTXCommon.h; sourceTree = SOURCE_ROOT; };
+		61ABAE95204A0B03006DBF0A /* GTXLogging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GTXLogging.h; path = Classes/GTXLogging.h; sourceTree = SOURCE_ROOT; };
+		61ABAE96204A0B03006DBF0A /* GTXAnalyticsUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTXAnalyticsUtils.m; path = Classes/GTXAnalyticsUtils.m; sourceTree = SOURCE_ROOT; };
+		61ABAE97204A0B04006DBF0A /* GTXChecking.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GTXChecking.h; path = Classes/GTXChecking.h; sourceTree = SOURCE_ROOT; };
+		61ABAE98204A0B04006DBF0A /* GTXElementBlacklist.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTXElementBlacklist.m; path = Classes/GTXElementBlacklist.m; sourceTree = SOURCE_ROOT; };
+		61ABAE99204A0B04006DBF0A /* GTXTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTXTestCase.m; path = Classes/GTXTestCase.m; sourceTree = SOURCE_ROOT; };
+		61ABAE9A204A0B04006DBF0A /* GTXAccessibilityTree.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTXAccessibilityTree.m; path = Classes/GTXAccessibilityTree.m; sourceTree = SOURCE_ROOT; };
+		61ABAE9B204A0B05006DBF0A /* GTXAssertions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GTXAssertions.h; path = Classes/GTXAssertions.h; sourceTree = SOURCE_ROOT; };
+		61ABAE9C204A0B05006DBF0A /* GTXImageRGBAData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GTXImageRGBAData.h; path = Classes/GTXImageRGBAData.h; sourceTree = SOURCE_ROOT; };
+		61ABAE9D204A0B05006DBF0A /* NSError+GTXAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSError+GTXAdditions.m"; path = "Classes/NSError+GTXAdditions.m"; sourceTree = SOURCE_ROOT; };
+		61ABAE9E204A0B06006DBF0A /* GTXCheckBlock.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTXCheckBlock.m; path = Classes/GTXCheckBlock.m; sourceTree = SOURCE_ROOT; };
+		61ABAE9F204A0B06006DBF0A /* GTXTestSuite.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GTXTestSuite.h; path = Classes/GTXTestSuite.h; sourceTree = SOURCE_ROOT; };
+		61ABAEA0204A0B06006DBF0A /* GTXToolKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTXToolKit.m; path = Classes/GTXToolKit.m; sourceTree = SOURCE_ROOT; };
+		61ABAEA1204A0B06006DBF0A /* GTXTestCase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GTXTestCase.h; path = Classes/GTXTestCase.h; sourceTree = SOURCE_ROOT; };
+		61ABAEA2204A0B06006DBF0A /* GTXImageAndColorUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GTXImageAndColorUtils.h; path = Classes/GTXImageAndColorUtils.h; sourceTree = SOURCE_ROOT; };
+		61ABAEA4204A0B07006DBF0A /* GTXChecksCollection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTXChecksCollection.m; path = Classes/GTXChecksCollection.m; sourceTree = SOURCE_ROOT; };
+		61ABAEA6204A0B08006DBF0A /* NSError+GTXAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSError+GTXAdditions.h"; path = "Classes/NSError+GTXAdditions.h"; sourceTree = SOURCE_ROOT; };
+		61ABAEA7204A0B08006DBF0A /* GTXAnalytics.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTXAnalytics.m; path = Classes/GTXAnalytics.m; sourceTree = SOURCE_ROOT; };
+		61ABAEA8204A0B08006DBF0A /* GTXChecksCollection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GTXChecksCollection.h; path = Classes/GTXChecksCollection.h; sourceTree = SOURCE_ROOT; };
+		61ABAEA9204A0B09006DBF0A /* GTXCheckBlock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GTXCheckBlock.h; path = Classes/GTXCheckBlock.h; sourceTree = SOURCE_ROOT; };
+		61ABAEAA204A0B09006DBF0A /* GTXErrorReporter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTXErrorReporter.m; path = Classes/GTXErrorReporter.m; sourceTree = SOURCE_ROOT; };
+		61ABAEAB204A0B09006DBF0A /* GTXToolKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GTXToolKit.h; path = Classes/GTXToolKit.h; sourceTree = SOURCE_ROOT; };
+		61ABAEAC204A0B0A006DBF0A /* GTXImageRGBAData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTXImageRGBAData.m; path = Classes/GTXImageRGBAData.m; sourceTree = SOURCE_ROOT; };
+		61ABAEAD204A0B0A006DBF0A /* GTAxe.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GTAxe.h; path = Classes/GTAxe.h; sourceTree = SOURCE_ROOT; };
+		61ABAEAF204A0B0A006DBF0A /* GTXElementBlacklist.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GTXElementBlacklist.h; path = Classes/GTXElementBlacklist.h; sourceTree = SOURCE_ROOT; };
+		61B7F4A2204A15D30062DF65 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/iPhoneOS.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+		6115E783204F5BA2003E32F9 /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				6115E78B204F5BA3003E32F9 /* GTAxe.framework in Frameworks */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		613C3F3D204A09E7007D44A8 /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+		613C3F37204A09E7007D44A8 = {
+			isa = PBXGroup;
+			children = (
+				613C3F43204A09E8007D44A8 /* GTAxe */,
+				61BC5526204E2736004D2FB9 /* GTAxeUnitTests */,
+				613C3F42204A09E8007D44A8 /* Products */,
+				61B7F4A1204A15D30062DF65 /* Frameworks */,
+			);
+			sourceTree = "<group>";
+		};
+		613C3F42204A09E8007D44A8 /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				613C3F41204A09E8007D44A8 /* GTAxe.framework */,
+				6115E786204F5BA2003E32F9 /* GTAxeUnitTests.xctest */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
+		613C3F43204A09E8007D44A8 /* GTAxe */ = {
+			isa = PBXGroup;
+			children = (
+				61ABAE8D204A0AF1006DBF0A /* Classes */,
+				61ABAE8A204A0AA3006DBF0A /* Info.plist */,
+			);
+			path = GTAxe;
+			sourceTree = "<group>";
+		};
+		61ABAE8D204A0AF1006DBF0A /* Classes */ = {
+			isa = PBXGroup;
+			children = (
+				61ABAEAD204A0B0A006DBF0A /* GTAxe.h */,
+				610CA403204E04AB008BAAA1 /* GTXAxeCore.h */,
+				610CA404204E04AB008BAAA1 /* GTXAxeCore.m */,
+				61ABAE93204A0B03006DBF0A /* GTXAccessibilityTree.h */,
+				61ABAE9A204A0B04006DBF0A /* GTXAccessibilityTree.m */,
+				61ABAE8F204A0B02006DBF0A /* GTXAnalytics.h */,
+				61ABAEA7204A0B08006DBF0A /* GTXAnalytics.m */,
+				61ABAE8E204A0B02006DBF0A /* GTXAnalyticsUtils.h */,
+				61ABAE96204A0B03006DBF0A /* GTXAnalyticsUtils.m */,
+				61ABAE9B204A0B05006DBF0A /* GTXAssertions.h */,
+				61ABAEA9204A0B09006DBF0A /* GTXCheckBlock.h */,
+				61ABAE9E204A0B06006DBF0A /* GTXCheckBlock.m */,
+				610CA3FB204DFC76008BAAA1 /* GTXPluginXCTestCase.h */,
+				610CA3FC204DFC76008BAAA1 /* GTXPluginXCTestCase.m */,
+				61ABAE97204A0B04006DBF0A /* GTXChecking.h */,
+				61ABAEA8204A0B08006DBF0A /* GTXChecksCollection.h */,
+				61ABAEA4204A0B07006DBF0A /* GTXChecksCollection.m */,
+				61ABAE94204A0B03006DBF0A /* GTXCommon.h */,
+				61ABAEAF204A0B0A006DBF0A /* GTXElementBlacklist.h */,
+				61ABAE98204A0B04006DBF0A /* GTXElementBlacklist.m */,
+				61ABAE90204A0B02006DBF0A /* GTXErrorReporter.h */,
+				61ABAEAA204A0B09006DBF0A /* GTXErrorReporter.m */,
+				61ABAEA2204A0B06006DBF0A /* GTXImageAndColorUtils.h */,
+				61ABAE91204A0B02006DBF0A /* GTXImageAndColorUtils.m */,
+				61ABAE9C204A0B05006DBF0A /* GTXImageRGBAData.h */,
+				61ABAEAC204A0B0A006DBF0A /* GTXImageRGBAData.m */,
+				61ABAE95204A0B03006DBF0A /* GTXLogging.h */,
+				61ABAEA1204A0B06006DBF0A /* GTXTestCase.h */,
+				61ABAE99204A0B04006DBF0A /* GTXTestCase.m */,
+				61ABAE9F204A0B06006DBF0A /* GTXTestSuite.h */,
+				61ABAE92204A0B03006DBF0A /* GTXTestSuite.m */,
+				61ABAEAB204A0B09006DBF0A /* GTXToolKit.h */,
+				61ABAEA0204A0B06006DBF0A /* GTXToolKit.m */,
+				61ABAEA6204A0B08006DBF0A /* NSError+GTXAdditions.h */,
+				61ABAE9D204A0B05006DBF0A /* NSError+GTXAdditions.m */,
+			);
+			path = Classes;
+			sourceTree = "<group>";
+		};
+		61B7F4A1204A15D30062DF65 /* Frameworks */ = {
+			isa = PBXGroup;
+			children = (
+				61B7F4A2204A15D30062DF65 /* XCTest.framework */,
+			);
+			name = Frameworks;
+			sourceTree = "<group>";
+		};
+		61BC5526204E2736004D2FB9 /* GTAxeUnitTests */ = {
+			isa = PBXGroup;
+			children = (
+				6115E794204F5BC7003E32F9 /* GTXAccessibilityTreeTests.m */,
+				6115E795204F5BC8003E32F9 /* GTXAnalyticsTests.m */,
+				6115E797204F5BC8003E32F9 /* GTXBaseTestCase.h */,
+				6115E79A204F5BC9003E32F9 /* GTXBaseTestCase.m */,
+				6115E793204F5BC7003E32F9 /* GTXChecksCollectionTests.m */,
+				6115E799204F5BC8003E32F9 /* GTXImageAndColorUtilsTests.m */,
+				6115E796204F5BC8003E32F9 /* GTXTestAccessibilityElements.h */,
+				6115E798204F5BC8003E32F9 /* GTXTestAccessibilityElements.m */,
+				6115E792204F5BC6003E32F9 /* GTXTestSuiteTests.m */,
+				6115E791204F5BC6003E32F9 /* GTXToolKitTests.m */,
+			);
+			path = GTAxeUnitTests;
+			sourceTree = "<group>";
+		};
+/* End PBXGroup section */
+
+/* Begin PBXHeadersBuildPhase section */
+		613C3F3E204A09E7007D44A8 /* Headers */ = {
+			isa = PBXHeadersBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				610CA405204E04AC008BAAA1 /* GTXAxeCore.h in Headers */,
+				61ABAEB5204A0B0B006DBF0A /* GTXAccessibilityTree.h in Headers */,
+				61ABAED1204A0B0B006DBF0A /* GTXElementBlacklist.h in Headers */,
+				61ABAECD204A0B0B006DBF0A /* GTXToolKit.h in Headers */,
+				61ABAEC8204A0B0B006DBF0A /* NSError+GTXAdditions.h in Headers */,
+				61ABAECF204A0B0B006DBF0A /* GTAxe.h in Headers */,
+				61ABAEC4204A0B0B006DBF0A /* GTXImageAndColorUtils.h in Headers */,
+				61ABAEB6204A0B0B006DBF0A /* GTXCommon.h in Headers */,
+				61ABAEBD204A0B0B006DBF0A /* GTXAssertions.h in Headers */,
+				610CA3FD204DFC76008BAAA1 /* GTXPluginXCTestCase.h in Headers */,
+				61ABAECB204A0B0B006DBF0A /* GTXCheckBlock.h in Headers */,
+				61ABAEB9204A0B0B006DBF0A /* GTXChecking.h in Headers */,
+				61ABAEBE204A0B0B006DBF0A /* GTXImageRGBAData.h in Headers */,
+				61ABAEB2204A0B0B006DBF0A /* GTXErrorReporter.h in Headers */,
+				61ABAEC3204A0B0B006DBF0A /* GTXTestCase.h in Headers */,
+				61ABAEB1204A0B0B006DBF0A /* GTXAnalytics.h in Headers */,
+				61ABAECA204A0B0B006DBF0A /* GTXChecksCollection.h in Headers */,
+				61ABAEC1204A0B0B006DBF0A /* GTXTestSuite.h in Headers */,
+				61ABAEB7204A0B0B006DBF0A /* GTXLogging.h in Headers */,
+				61ABAEB0204A0B0B006DBF0A /* GTXAnalyticsUtils.h in Headers */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXHeadersBuildPhase section */
+
+/* Begin PBXNativeTarget section */
+		6115E785204F5BA2003E32F9 /* GTAxeUnitTests */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 6115E78E204F5BA3003E32F9 /* Build configuration list for PBXNativeTarget "GTAxeUnitTests" */;
+			buildPhases = (
+				6115E782204F5BA2003E32F9 /* Sources */,
+				6115E783204F5BA2003E32F9 /* Frameworks */,
+				6115E784204F5BA2003E32F9 /* Resources */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+				6115E78D204F5BA3003E32F9 /* PBXTargetDependency */,
+			);
+			name = GTAxeUnitTests;
+			productName = GTAxeUnitTests;
+			productReference = 6115E786204F5BA2003E32F9 /* GTAxeUnitTests.xctest */;
+			productType = "com.apple.product-type.bundle.unit-test";
+		};
+		613C3F40204A09E7007D44A8 /* GTAxe */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 613C3F49204A09E8007D44A8 /* Build configuration list for PBXNativeTarget "GTAxe" */;
+			buildPhases = (
+				613C3F3C204A09E7007D44A8 /* Sources */,
+				613C3F3D204A09E7007D44A8 /* Frameworks */,
+				613C3F3E204A09E7007D44A8 /* Headers */,
+				613C3F3F204A09E7007D44A8 /* Resources */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+			);
+			name = GTAxe;
+			productName = GTAxe;
+			productReference = 613C3F41204A09E8007D44A8 /* GTAxe.framework */;
+			productType = "com.apple.product-type.framework";
+		};
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+		613C3F38204A09E7007D44A8 /* Project object */ = {
+			isa = PBXProject;
+			attributes = {
+				LastUpgradeCheck = 0910;
+				ORGANIZATIONNAME = "Google Inc";
+				TargetAttributes = {
+					6115E785204F5BA2003E32F9 = {
+						CreatedOnToolsVersion = 9.1;
+						ProvisioningStyle = Automatic;
+					};
+					613C3F40204A09E7007D44A8 = {
+						CreatedOnToolsVersion = 9.1;
+						ProvisioningStyle = Automatic;
+					};
+				};
+			};
+			buildConfigurationList = 613C3F3B204A09E7007D44A8 /* Build configuration list for PBXProject "GTAxe" */;
+			compatibilityVersion = "Xcode 8.0";
+			developmentRegion = en;
+			hasScannedForEncodings = 0;
+			knownRegions = (
+				en,
+			);
+			mainGroup = 613C3F37204A09E7007D44A8;
+			productRefGroup = 613C3F42204A09E8007D44A8 /* Products */;
+			projectDirPath = "";
+			projectRoot = "";
+			targets = (
+				613C3F40204A09E7007D44A8 /* GTAxe */,
+				6115E785204F5BA2003E32F9 /* GTAxeUnitTests */,
+			);
+		};
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+		6115E784204F5BA2003E32F9 /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		613C3F3F204A09E7007D44A8 /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+		6115E782204F5BA2003E32F9 /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				6115E79E204F5BC9003E32F9 /* GTXAccessibilityTreeTests.m in Sources */,
+				6115E79C204F5BC9003E32F9 /* GTXTestSuiteTests.m in Sources */,
+				6115E79D204F5BC9003E32F9 /* GTXChecksCollectionTests.m in Sources */,
+				6115E79F204F5BC9003E32F9 /* GTXAnalyticsTests.m in Sources */,
+				6115E7A1204F5BC9003E32F9 /* GTXImageAndColorUtilsTests.m in Sources */,
+				6115E79B204F5BC9003E32F9 /* GTXToolKitTests.m in Sources */,
+				6115E7A2204F5BC9003E32F9 /* GTXBaseTestCase.m in Sources */,
+				6115E7A0204F5BC9003E32F9 /* GTXTestAccessibilityElements.m in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		613C3F3C204A09E7007D44A8 /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				61ABAEC6204A0B0B006DBF0A /* GTXChecksCollection.m in Sources */,
+				61ABAECC204A0B0B006DBF0A /* GTXErrorReporter.m in Sources */,
+				61ABAEB4204A0B0B006DBF0A /* GTXTestSuite.m in Sources */,
+				61ABAEB3204A0B0B006DBF0A /* GTXImageAndColorUtils.m in Sources */,
+				61ABAEC2204A0B0B006DBF0A /* GTXToolKit.m in Sources */,
+				61ABAEC9204A0B0B006DBF0A /* GTXAnalytics.m in Sources */,
+				61ABAEBA204A0B0B006DBF0A /* GTXElementBlacklist.m in Sources */,
+				610CA3FE204DFC76008BAAA1 /* GTXPluginXCTestCase.m in Sources */,
+				61ABAEB8204A0B0B006DBF0A /* GTXAnalyticsUtils.m in Sources */,
+				61ABAEBF204A0B0B006DBF0A /* NSError+GTXAdditions.m in Sources */,
+				610CA406204E04AC008BAAA1 /* GTXAxeCore.m in Sources */,
+				61ABAEBC204A0B0B006DBF0A /* GTXAccessibilityTree.m in Sources */,
+				61ABAEC0204A0B0B006DBF0A /* GTXCheckBlock.m in Sources */,
+				61ABAEBB204A0B0B006DBF0A /* GTXTestCase.m in Sources */,
+				61ABAECE204A0B0B006DBF0A /* GTXImageRGBAData.m in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+		6115E78D204F5BA3003E32F9 /* PBXTargetDependency */ = {
+			isa = PBXTargetDependency;
+			target = 613C3F40204A09E7007D44A8 /* GTAxe */;
+			targetProxy = 6115E78C204F5BA3003E32F9 /* PBXContainerItemProxy */;
+		};
+/* End PBXTargetDependency section */
+
+/* Begin XCBuildConfiguration section */
+		6115E78F204F5BA3003E32F9 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				CODE_SIGN_STYLE = Automatic;
+				INFOPLIST_FILE = "";
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+				PRODUCT_BUNDLE_IDENTIFIER = com.test.google.GTAxeUnitTests;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				TARGETED_DEVICE_FAMILY = "1,2";
+			};
+			name = Debug;
+		};
+		6115E790204F5BA3003E32F9 /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				CODE_SIGN_STYLE = Automatic;
+				INFOPLIST_FILE = "";
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+				PRODUCT_BUNDLE_IDENTIFIER = com.test.google.GTAxeUnitTests;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				TARGETED_DEVICE_FAMILY = "1,2";
+			};
+			name = Release;
+		};
+		613C3F47204A09E8007D44A8 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_COMMA = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				CODE_SIGN_IDENTITY = "iPhone Developer";
+				COPY_PHASE_STRIP = NO;
+				CURRENT_PROJECT_VERSION = 1;
+				DEBUG_INFORMATION_FORMAT = dwarf;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				ENABLE_TESTABILITY = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu11;
+				GCC_DYNAMIC_NO_PIC = NO;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_OPTIMIZATION_LEVEL = 0;
+				GCC_PREPROCESSOR_DEFINITIONS = (
+					"DEBUG=1",
+					"$(inherited)",
+				);
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				IPHONEOS_DEPLOYMENT_TARGET = 11.1;
+				MTL_ENABLE_DEBUG_INFO = YES;
+				ONLY_ACTIVE_ARCH = YES;
+				SDKROOT = iphoneos;
+				VERSIONING_SYSTEM = "apple-generic";
+				VERSION_INFO_PREFIX = "";
+			};
+			name = Debug;
+		};
+		613C3F48204A09E8007D44A8 /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_COMMA = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				CODE_SIGN_IDENTITY = "iPhone Developer";
+				COPY_PHASE_STRIP = NO;
+				CURRENT_PROJECT_VERSION = 1;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				ENABLE_NS_ASSERTIONS = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu11;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				IPHONEOS_DEPLOYMENT_TARGET = 11.1;
+				MTL_ENABLE_DEBUG_INFO = NO;
+				SDKROOT = iphoneos;
+				VALIDATE_PRODUCT = YES;
+				VERSIONING_SYSTEM = "apple-generic";
+				VERSION_INFO_PREFIX = "";
+			};
+			name = Release;
+		};
+		613C3F4A204A09E8007D44A8 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				CODE_SIGN_IDENTITY = "";
+				CODE_SIGN_STYLE = Automatic;
+				DEFINES_MODULE = YES;
+				DYLIB_COMPATIBILITY_VERSION = 1;
+				DYLIB_CURRENT_VERSION = 1;
+				DYLIB_INSTALL_NAME_BASE = "@rpath";
+				INFOPLIST_FILE = FrameworkFiles/Info.plist;
+				INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+				PRODUCT_BUNDLE_IDENTIFIER = com.test.google.GTAxe;
+				PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+				SKIP_INSTALL = YES;
+				TARGETED_DEVICE_FAMILY = "1,2";
+			};
+			name = Debug;
+		};
+		613C3F4B204A09E8007D44A8 /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				CODE_SIGN_IDENTITY = "";
+				CODE_SIGN_STYLE = Automatic;
+				DEFINES_MODULE = YES;
+				DYLIB_COMPATIBILITY_VERSION = 1;
+				DYLIB_CURRENT_VERSION = 1;
+				DYLIB_INSTALL_NAME_BASE = "@rpath";
+				INFOPLIST_FILE = FrameworkFiles/Info.plist;
+				INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+				PRODUCT_BUNDLE_IDENTIFIER = com.test.google.GTAxe;
+				PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+				SKIP_INSTALL = YES;
+				TARGETED_DEVICE_FAMILY = "1,2";
+			};
+			name = Release;
+		};
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+		6115E78E204F5BA3003E32F9 /* Build configuration list for PBXNativeTarget "GTAxeUnitTests" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				6115E78F204F5BA3003E32F9 /* Debug */,
+				6115E790204F5BA3003E32F9 /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		613C3F3B204A09E7007D44A8 /* Build configuration list for PBXProject "GTAxe" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				613C3F47204A09E8007D44A8 /* Debug */,
+				613C3F48204A09E8007D44A8 /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		613C3F49204A09E8007D44A8 /* Build configuration list for PBXNativeTarget "GTAxe" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				613C3F4A204A09E8007D44A8 /* Debug */,
+				613C3F4B204A09E8007D44A8 /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+/* End XCConfigurationList section */
+	};
+	rootObject = 613C3F38204A09E7007D44A8 /* Project object */;
+}
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..94ac754
--- /dev/null
+++ b/README.md
@@ -0,0 +1,104 @@
+## What is GTAxe?
+GTAxe is library of APIs for iOS accessibility testing that can be easily
+embedded into any test framework or tool, for example it has XCTest integration
+and can be used with EarlGrey. GTAxe works by installing "checks" on your
+existing test cases so that before test teardown the checks are evaluated on
+your app to look for accessibility issues such as missing labels.
+
+## Getting Started
+
+To install GTAxe on all the tests of a specific test class add the following
+snippet of code to it.
+
+```
+// Include the GTAxe umbrella header.
+#import "GTAxe.h"
+
++ (void)setUp {
+  [super setUp];
+
+  // ... your other setup code (if any) comes here.
+
+  // Install GTX on all tests in *this* test class.
+  [GTAxe installOnTestSuite:[GTXTestSuite suiteWithAllTestsInClass:self]
+                     checks:[GTXChecksCollection allGTXChecks]
+          elementBlacklists:@[]];
+}
+```
+
+Once installed, GTX will run all registered accessibility checks before test
+case tearDown and fail the test if any accessibility checks fail. Note that GTX
+is being added to `+setUp` method not the instance method `-setUp` since GTX
+must only be installed once (for a given test run).
+
+## Incremental Accessibility
+
+GTAxe APIs support a practical solution for improving accessibility of large
+projects which may not have included accessibility from the get go - incremental
+accessibility. Adding GTAxe to a project that is already halfway through
+development may leads to several test failures and fixing them at once can be
+time consuming and tedious. To solve this problem incrementally:
+
++ Use above snippet to add GTAxe to all test cases but fix errors in a small
+  subset of them.
+  + Blacklist elements that you don't control using GTAxe's blacklist APIs.
++ Then use `GTXTestSuite's` `suiteWithClass:andTests:` method to
+  create a test suite with only the tests cases that have been fixed and add
+  GTAxe only to that suite.
+
+Once the code is checked into your repo GTAxe will catch any new failures in
+those tests. From this point:
+
++ Every new test being added must be added it to the suite.
++ Based on team priorities keep moving existing tests into the suite until all
+  methods are in the suite.
+
+If at any point all the tests of a test class are in the suite use
+`suiteWithAllTestsInClass:` method instead of listing all the methods, this also
+ensures that new methods added to the class are automatically under
+accessibility checking.
+
+If GTAxe is installed on every test in your project, use
+`suiteWithAllTestsFromAllClassesInheritedFromClass:` to automatically add
+accessibility checking to any test case added.
+
+## Authoring your own checks
+
+GTAxe has APIs that allow for creation of your own accessibility checks (in fact
+it does not have to be related to accessibility, for example i18n layout checks
+or even memory usage checks). To create new checks use `GTAxe's`
+`checkWithName:block:` API and provide a unique name and block that evaluates
+the check and returns YES/NO for success/failure. Add the newly created check
+to the array of checks being passed on to GTAxe via the install API call.
+
+## Dealing with GTAxe Failures
+
+When GTAxe fails it has most likely found an accessibility bug and you must fix
+it. But due to various team priorities it may not be possible to do so right
+away in which case you have the following options at your disposal:
+
++ Temporarily blacklist the test case by using
+  `suiteWithAllTestsInClass:exceptTests:`.
++ Temporarily blacklist the offending element using element blacklist APIs
+
+But if you believe GTAxe has caught a bug that is not an accessibility issue
+please let us know by [filing a bug](TODO) or better [fix it](TODO) for
+everyone.
+
+## Analytics
+
+To prioritize and improve GTAxe, the framework collects usage data and uploads
+it to Google Analytics. More specifically, the framework collects the MD5 hash
+of the test app's Bundle ID and pass/fail status of GTAxe checks. This
+information allows us to measure the volume of usage. For more detailed
+information about our analytics collection, please peruse the `GTXAnalytics.m`
+file which contains the implementation details. If they wish, users can choose
+to opt out by disabling the Analytics by adding the folling code snippet in
+test’s `+(void) setUp` method:
+
+```
+// Disable GTAxe analytics.
+[GTXAnalytics setEnabled:NO];
+```
+
+*Note: This is not an official Google product.*
diff --git a/Tests/FunctionalTests/TestApp/GTXTestApp-Info.plist b/Tests/FunctionalTests/TestApp/GTXTestApp-Info.plist
new file mode 100644
index 0000000..4cb5692
--- /dev/null
+++ b/Tests/FunctionalTests/TestApp/GTXTestApp-Info.plist
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleExecutable</key>
+	<string>${EXECUTABLE_NAME}</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>${PRODUCT_NAME}</string>
+	<key>CFBundleIdentifier</key>
+	<string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
+	<key>CFBundlePackageType</key>
+	<string>APPL</string>
+	<key>CFBundleShortVersionString</key>
+	<string>1.0</string>
+	<key>CFBundleVersion</key>
+	<string>1.0.0</string>
+	<key>NSAppTransportSecurity</key>
+	<dict>
+		<key>NSAllowsArbitraryLoads</key>
+		<true/>
+	</dict>
+	<key>UIPrerenderedIcon</key>
+	<true/>
+	<key>UIStatusBarStyle</key>
+	<string>UIStatusBarStyleBlackOpaque</string>
+	<key>UISupportedInterfaceOrientations</key>
+	<array>
+		<string>UIInterfaceOrientationPortrait</string>
+		<string>UIInterfaceOrientationLandscapeLeft</string>
+		<string>UIInterfaceOrientationLandscapeRight</string>
+		<string>UIInterfaceOrientationPortraitUpsideDown</string>
+	</array>
+	<key>UISupportedInterfaceOrientations~ipad</key>
+	<array>
+		<string>UIInterfaceOrientationPortrait</string>
+		<string>UIInterfaceOrientationLandscapeLeft</string>
+		<string>UIInterfaceOrientationLandscapeRight</string>
+		<string>UIInterfaceOrientationPortraitUpsideDown</string>
+	</array>
+	<key>UIViewControllerBasedStatusBarAppearance</key>
+	<true/>
+	<key>UILaunchImages</key>
+	<array>
+		<dict>
+			<key>UILaunchImageMinimumOSVersion</key>
+			<string>8.0</string>
+			<key>UILaunchImageName</key>
+			<string>Default</string>
+			<key>UILaunchImageOrientation</key>
+			<string>Portrait</string>
+			<key>UILaunchImageSize</key>
+			<string>{320, 480}</string>
+		</dict>
+		<dict>
+			<key>UILaunchImageMinimumOSVersion</key>
+			<string>8.0</string>
+			<key>UILaunchImageName</key>
+			<string>Default</string>
+			<key>UILaunchImageOrientation</key>
+			<string>Landscape</string>
+			<key>UILaunchImageSize</key>
+			<string>{320, 480}</string>
+		</dict>
+		<dict>
+			<key>UILaunchImageMinimumOSVersion</key>
+			<string>8.0</string>
+			<key>UILaunchImageName</key>
+			<string>Default-568h</string>
+			<key>UILaunchImageOrientation</key>
+			<string>Portrait</string>
+			<key>UILaunchImageSize</key>
+			<string>{320, 568}</string>
+		</dict>
+		<dict>
+			<key>UILaunchImageMinimumOSVersion</key>
+			<string>8.0</string>
+			<key>UILaunchImageName</key>
+			<string>Default-568h</string>
+			<key>UILaunchImageOrientation</key>
+			<string>Landscape</string>
+			<key>UILaunchImageSize</key>
+			<string>{320, 568}</string>
+		</dict>
+		<dict>
+			<key>UILaunchImageMinimumOSVersion</key>
+			<string>8.0</string>
+			<key>UILaunchImageName</key>
+			<string>Default-667h</string>
+			<key>UILaunchImageOrientation</key>
+			<string>Portrait</string>
+			<key>UILaunchImageSize</key>
+			<string>{375, 667}</string>
+		</dict>
+		<dict>
+			<key>UILaunchImageMinimumOSVersion</key>
+			<string>8.0</string>
+			<key>UILaunchImageName</key>
+			<string>Default-667h</string>
+			<key>UILaunchImageOrientation</key>
+			<string>Landscape</string>
+			<key>UILaunchImageSize</key>
+			<string>{375, 667}</string>
+		</dict>
+		<dict>
+			<key>UILaunchImageMinimumOSVersion</key>
+			<string>8.0</string>
+			<key>UILaunchImageName</key>
+			<string>Default-736h</string>
+			<key>UILaunchImageOrientation</key>
+			<string>Portrait</string>
+			<key>UILaunchImageSize</key>
+			<string>{414, 736}</string>
+		</dict>
+		<dict>
+			<key>UILaunchImageMinimumOSVersion</key>
+			<string>8.0</string>
+			<key>UILaunchImageName</key>
+			<string>Default-736h</string>
+			<key>UILaunchImageOrientation</key>
+			<string>Landscape</string>
+			<key>UILaunchImageSize</key>
+			<string>{414, 736}</string>
+		</dict>
+	</array>
+</dict>
+</plist>
diff --git a/Tests/FunctionalTests/TestApp/Sources/GTXTestApp.m b/Tests/FunctionalTests/TestApp/Sources/GTXTestApp.m
new file mode 100644
index 0000000..5e0b631
--- /dev/null
+++ b/Tests/FunctionalTests/TestApp/Sources/GTXTestApp.m
@@ -0,0 +1,24 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import <UIKit/UIKit.h>
+#import "GTXTestAppDelegate.h"
+
+int main(int argc, char * argv[]) {
+  @autoreleasepool {
+    return UIApplicationMain(argc, argv, nil, NSStringFromClass([GTXTestAppDelegate class]));
+  }
+}
diff --git a/Tests/FunctionalTests/TestApp/Sources/GTXTestAppDelegate.h b/Tests/FunctionalTests/TestApp/Sources/GTXTestAppDelegate.h
new file mode 100644
index 0000000..3077fe2
--- /dev/null
+++ b/Tests/FunctionalTests/TestApp/Sources/GTXTestAppDelegate.h
@@ -0,0 +1,23 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import <UIKit/UIKit.h>
+
+@interface GTXTestAppDelegate : UIResponder <UIApplicationDelegate>
+
+@property (strong, nonatomic) UIWindow *window;
+
+@end
diff --git a/Tests/FunctionalTests/TestApp/Sources/GTXTestAppDelegate.m b/Tests/FunctionalTests/TestApp/Sources/GTXTestAppDelegate.m
new file mode 100644
index 0000000..8f655f9
--- /dev/null
+++ b/Tests/FunctionalTests/TestApp/Sources/GTXTestAppDelegate.m
@@ -0,0 +1,32 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "GTXTestAppDelegate.h"
+
+#import "GTXTestViewController.h"
+
+@implementation GTXTestAppDelegate
+
+- (BOOL)application:(UIApplication *)application
+    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
+  GTXTestViewController *controller = [[GTXTestViewController alloc] initWithNibName:@"GTXTestViewController" bundle:nil];
+  self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
+  [self.window setRootViewController:controller];
+  [self.window makeKeyAndVisible];
+  return YES;
+}
+
+@end
diff --git a/Tests/FunctionalTests/TestApp/Sources/GTXTestStepperButton.h b/Tests/FunctionalTests/TestApp/Sources/GTXTestStepperButton.h
new file mode 100644
index 0000000..d8bc2f9
--- /dev/null
+++ b/Tests/FunctionalTests/TestApp/Sources/GTXTestStepperButton.h
@@ -0,0 +1,30 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import <UIKit/UIKit.h>
+
+/**
+ *  Test Stepper button that shows a count of taps, NOTE that this element is not accessible by
+ *  default for testing purposes.
+ */
+@interface GTXTestStepperButton : UIView
+
+/**
+ *  The count of steps (this property increments if the user taps the button).
+ */
+@property(nonatomic, assign) NSUInteger stepCount;
+
+@end
diff --git a/Tests/FunctionalTests/TestApp/Sources/GTXTestStepperButton.m b/Tests/FunctionalTests/TestApp/Sources/GTXTestStepperButton.m
new file mode 100644
index 0000000..416f171
--- /dev/null
+++ b/Tests/FunctionalTests/TestApp/Sources/GTXTestStepperButton.m
@@ -0,0 +1,47 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "GTXTestStepperButton.h"
+
+@implementation GTXTestStepperButton
+
+- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
+  [self increment];
+}
+
+- (void)increment {
+  _stepCount += 1;
+  [self setNeedsDisplay];
+}
+
+- (void)drawRect:(CGRect)rect {
+  // Render the string: "<count> ↑" into the element bounds.
+  CGContextRef context  = UIGraphicsGetCurrentContext();
+  CGContextSetFillColorWithColor(context, [UIColor lightGrayColor].CGColor);
+  CGContextFillRect(context, rect);
+  CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
+  CGContextStrokeRect(context, rect);
+  NSString *text = [NSString stringWithFormat:@"%u ↑", (unsigned int)_stepCount];
+  [text drawInRect:rect withAttributes:@{ NSForegroundColorAttributeName: [UIColor redColor],
+                                          NSFontAttributeName: [UIFont systemFontOfSize:35] }];
+}
+
+- (BOOL)accessibilityActivate {
+  [self increment];
+  return YES;
+}
+
+@end
diff --git a/Tests/FunctionalTests/TestApp/Sources/GTXTestViewController.h b/Tests/FunctionalTests/TestApp/Sources/GTXTestViewController.h
new file mode 100644
index 0000000..0351f03
--- /dev/null
+++ b/Tests/FunctionalTests/TestApp/Sources/GTXTestViewController.h
@@ -0,0 +1,113 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import <UIKit/UIKit.h>
+
+/**
+ *  Name of the action that adds an element that has no accessibilityLabel.
+ */
+FOUNDATION_EXTERN NSString *const kAddNoLabelElementActionName;
+
+/**
+ *  Name of the action that adds an element whose accessibility label has been punctuated.
+ */
+FOUNDATION_EXTERN NSString *const kAddPunctuatedLabelElementActionName;
+
+/**
+ *  Name of the action that adds an element whose accessibility label has been concatenated (with
+ *  commas).
+ */
+FOUNDATION_EXTERN NSString *const kAddConcatenatedLabelElementActionName;
+
+/**
+ *  Name of the action that adds a text field and focuses it to show a keyboard.
+ */
+FOUNDATION_EXTERN NSString *const kShowKeyboardActionName;
+
+/**
+ *  Name of the action that hides the shown keyboard.
+ */
+FOUNDATION_EXTERN NSString *const kHideKeyboardActionName;
+
+/**
+ *  Name of the action that adds a button marked as not accessible.
+ */
+FOUNDATION_EXTERN NSString *const kAddInaccessibleButton;
+
+/**
+ *  Name of the action that adds a button marked as not accessible inside a container.
+ */
+FOUNDATION_EXTERN NSString *const kAddAccessibleButtonInContainer;
+
+/**
+ *  Name of the action that adds an element with tiny tap area.
+ */
+FOUNDATION_EXTERN NSString *const kAddTinyTappableElement;
+
+/**
+ *  Name of the action that adds an element with very high contrast.
+ */
+FOUNDATION_EXTERN NSString *const kAddVeryHighContrastLabel;
+
+/**
+ *  Name of the action that adds an element with very low contrast.
+ */
+FOUNDATION_EXTERN NSString *const kAddVeryLowContrastLabel;
+
+/**
+ *  Name of the action that adds an element with barely high contrast.
+ */
+FOUNDATION_EXTERN NSString *const kAddBarelyHighContrastLabel;
+
+/**
+ *  Name of the action that adds an element with barely low contrast.
+ */
+FOUNDATION_EXTERN NSString *const kAddBarelyLowContrastLabel;
+
+/**
+ *  Name of the action that adds a low contrast background.
+ */
+FOUNDATION_EXTERN NSString *const kAddLowContrastBackground;
+
+/**
+ *  Name of the action that adds a high contrast background.
+ */
+FOUNDATION_EXTERN NSString *const kAddHighContrastBackground;
+
+// The view controller for testing GTAxe's Accessibility Checker.
+@interface GTXTestViewController : UIViewController
+
+
+/**
+ Adds the given element to the Test App's test area.
+
+ @param element Element to be added.
+ */
++ (void)addElementToTestArea:(UIView *)element;
+
+/**
+ Clears the test area
+ */
++ (void)clearTestArea;
+
+/**
+ Performs the named action.
+
+ @param actionName The name of the action to be invoked.
+ */
++ (void)performTestActionNamed:(NSString *)actionName;
+
+@end
diff --git a/Tests/FunctionalTests/TestApp/Sources/GTXTestViewController.m b/Tests/FunctionalTests/TestApp/Sources/GTXTestViewController.m
new file mode 100644
index 0000000..b1e7268
--- /dev/null
+++ b/Tests/FunctionalTests/TestApp/Sources/GTXTestViewController.m
@@ -0,0 +1,260 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "GTXTestViewController.h"
+
+#import "GTXTestStepperButton.h"
+
+NSString *const kAddNoLabelElementActionName = @"Add no-label Element";
+NSString *const kAddPunctuatedLabelElementActionName = @"Add punctuated-label Element";
+NSString *const kAddConcatenatedLabelElementActionName = @"Add concatenated-label Element";
+NSString *const kShowKeyboardActionName = @"Show Keyboard";
+NSString *const kHideKeyboardActionName = @"Hide Keyboard";
+NSString *const kAddInaccessibleButton = @"Add InAccessible button";
+NSString *const kAddAccessibleButtonInContainer = @"Add Accessible button in subview";
+NSString *const kAddTinyTappableElement = @"Add tiny element";
+NSString *const kAddVeryHighContrastLabel = @"Add very high contrast label";
+NSString *const kAddVeryLowContrastLabel = @"Add very low contrast label";
+NSString *const kAddBarelyHighContrastLabel = @"Add barely High contrast label";
+NSString *const kAddBarelyLowContrastLabel = @"Add barely Low contrast label";
+NSString *const kAddLowContrastBackground = @"Add Low contrast background";
+NSString *const kAddHighContrastBackground = @"Add High contrast backgorund";
+
+/**
+ *  The minimum size required to make UIElements accessible.
+ */
+static const CGFloat kMinimumElementSize = 48.0;
+
+/**
+ *  The margin used in the test app.
+ */
+static const CGFloat kMargin = 10.0;
+
+/**
+ *  The red component to get a red color thats barely (low contrast) distinguishable from complete
+ *  red color.
+ */
+static const CGFloat kAlmostRedColorValue = 0.5;
+
+static __weak GTXTestViewController *viewController;
+
+typedef void(^ActionHandler)(GTXTestViewController *sSelf);
+
+@interface GTXTestViewController ()<UITextFieldDelegate>
+
+@property(weak, nonatomic) IBOutlet UIScrollView *actionsContainerView;
+@property(weak, nonatomic) IBOutlet UIView *testArea;
+
+@end
+
+@implementation GTXTestViewController {
+  NSMutableDictionary *actionsToHandlers;
+}
+
+- (instancetype)init {
+  self = [super initWithNibName:@"GTXTestViewController" bundle:nil];
+  return self;
+}
+
+- (void)viewDidLoad {
+  [super viewDidLoad];
+  viewController = self;
+  self.actionsContainerView.accessibilityIdentifier = @"Actions Container";
+  actionsToHandlers = [[NSMutableDictionary alloc] init];
+  [self.navigationController setNavigationBarHidden:YES animated:NO];
+
+  [self axetest_addActionNamed:kAddNoLabelElementActionName handler:^(GTXTestViewController *sSelf) {
+    [sSelf axetest_addElementWithLabel:@""];
+  }];
+  [self axetest_addActionNamed:kAddPunctuatedLabelElementActionName
+                       handler:^(GTXTestViewController *sSelf) {
+    [sSelf axetest_addElementWithLabel:@"Foo."];
+  }];
+  [self axetest_addActionNamed:kAddConcatenatedLabelElementActionName
+                       handler:^(GTXTestViewController *sSelf) {
+    // Add an element with concatenated labels: foo and bar.
+    [sSelf axetest_addElementWithLabel:@"foo,bar."];
+  }];
+  [self axetest_addActionNamed:kShowKeyboardActionName handler:^(GTXTestViewController *sSelf) {
+    [sSelf axetest_addShowKeyboard];
+  }];
+  [self axetest_addActionNamed:kHideKeyboardActionName handler:^(GTXTestViewController *sSelf) {
+    [sSelf.testArea.subviews[0] resignFirstResponder];
+  }];
+  [self axetest_addActionNamed:kAddInaccessibleButton handler:^(GTXTestViewController *sSelf) {
+    [sSelf axetest_addInAccessibleButton];
+  }];
+  [self axetest_addActionNamed:kAddAccessibleButtonInContainer
+                       handler:^(GTXTestViewController *sSelf) {
+    [sSelf axetest_addInAccessibleButtonInSubview];
+  }];
+  [self axetest_addActionNamed:kAddTinyTappableElement handler:^(GTXTestViewController *sSelf) {
+    [sSelf axetest_addTinyTappableElement];
+  }];
+  [self axetest_addActionNamed:kAddVeryHighContrastLabel handler:^(GTXTestViewController *sSelf) {
+    // Add a high contrast label: black text on white background.
+    [sSelf axetest_addLabelWithForgroundColor:[UIColor blackColor]
+                              backgroundColor:[UIColor whiteColor]];
+  }];
+  [self axetest_addActionNamed:kAddVeryLowContrastLabel handler:^(GTXTestViewController *sSelf) {
+    // Add a low contrast label: black text on very dark grey background.
+    UIColor *veryDarkGreyColor = [UIColor colorWithWhite:0.2f alpha:1.0f];
+    [sSelf axetest_addLabelWithForgroundColor:[UIColor blackColor]
+                              backgroundColor:veryDarkGreyColor];
+  }];
+
+  UIColor *kAlmostRed = [UIColor colorWithRed:kAlmostRedColorValue green:0 blue:0 alpha:1];
+  UIColor *kAlmostRedButDarker =
+      [UIColor colorWithRed:kAlmostRedColorValue - 0.1f green:0 blue:0 alpha:1];
+  [self axetest_addActionNamed:kAddBarelyLowContrastLabel handler:^(GTXTestViewController *sSelf) {
+    [sSelf axetest_addLabelWithForgroundColor:[UIColor redColor]
+                              backgroundColor:kAlmostRed];
+  }];
+  [self axetest_addActionNamed:kAddBarelyHighContrastLabel handler:^(GTXTestViewController *sSelf) {
+    [sSelf axetest_addLabelWithForgroundColor:[UIColor redColor]
+                              backgroundColor:kAlmostRedButDarker];
+  }];
+  [self axetest_addActionNamed:kAddLowContrastBackground handler:^(GTXTestViewController *sSelf) {
+    // Add a low contrast background with respect to text added on top of it.
+    [sSelf axetest_addLabelWithForgroundColor:[UIColor redColor]
+                              backgroundColor:[UIColor clearColor]];
+    [sSelf.testArea setBackgroundColor:kAlmostRed];
+  }];
+  [self axetest_addActionNamed:kAddHighContrastBackground handler:^(GTXTestViewController *sSelf) {
+    // Add a high contrast background with respect to text added on top of it.
+    [sSelf axetest_addLabelWithForgroundColor:[UIColor redColor]
+                              backgroundColor:[UIColor clearColor]];
+    [sSelf.testArea setBackgroundColor:kAlmostRedButDarker];
+  }];
+}
+
+- (void)axetest_addActionNamed:(NSString *)name handler:(ActionHandler)handler {
+  UIButton *newButton = [[UIButton alloc] initWithFrame:CGRectZero];
+  [newButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
+  [newButton addTarget:self
+                action:@selector(userTappedActionButton:)
+      forControlEvents:UIControlEventTouchUpInside];
+  [newButton setTitle:name forState:UIControlStateNormal];
+  [newButton sizeToFit];
+  CGRect buttonFrame = newButton.frame;
+  buttonFrame.size.height = kMinimumElementSize;
+  newButton.frame = buttonFrame;
+
+  CGSize contentSize = self.actionsContainerView.contentSize;
+  buttonFrame.origin.y = contentSize.height + kMargin;
+  contentSize.height += newButton.frame.size.height + kMargin;
+  newButton.frame = buttonFrame;
+  self.actionsContainerView.contentSize = contentSize;
+  [self.actionsContainerView addSubview:newButton];
+  NSAssert(!actionsToHandlers[name], @"Action %@ was already added.", name);
+  actionsToHandlers[name] = handler;
+}
+
++ (void)performTestActionNamed:(NSString *)actionName {
+  GTXTestViewController *controller = viewController;
+  NSAssert(controller, @"View controller has not loaded yet.");
+  ActionHandler handler = controller->actionsToHandlers[actionName];
+  NSAssert(handler, @"Action named %@ does not exist", actionName);
+  handler(controller);
+}
+
+- (void)userTappedActionButton:(UIButton *)sender {
+  [[self class] performTestActionNamed:sender.titleLabel.text];
+}
+
+- (void)axetest_addElementWithLabel:(NSString *)label {
+  UIView *newElement = [[UIView alloc] initWithFrame:CGRectMake(kMargin, kMargin,
+                                                                kMinimumElementSize,
+                                                                kMinimumElementSize)];
+  newElement.isAccessibilityElement = YES;
+  newElement.accessibilityLabel = label;
+  newElement.backgroundColor = [UIColor whiteColor];
+  [self.testArea addSubview:newElement];
+}
+
+- (void)axetest_addShowKeyboard {
+  UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(kMargin, kMargin,
+                                                                      kMinimumElementSize * 2,
+                                                                      kMinimumElementSize)];
+  textView.accessibilityIdentifier = @"testTextField";
+  [self.testArea addSubview:textView];
+  [textView becomeFirstResponder];
+}
+
+- (void)axetest_addInAccessibleButton {
+  GTXTestStepperButton *stepperButton =
+      [[GTXTestStepperButton alloc] initWithFrame:CGRectMake(kMargin, kMargin,
+                                                             kMinimumElementSize,
+                                                             kMinimumElementSize)];
+  stepperButton.accessibilityIdentifier = @"inAccessibleButton";
+  [self.testArea addSubview:stepperButton];
+}
+
+- (void)axetest_addInAccessibleButtonInSubview {
+  CGRect frame = CGRectMake(kMargin, kMargin, kMinimumElementSize, kMinimumElementSize);
+  GTXTestStepperButton *stepperButton = [[GTXTestStepperButton alloc] initWithFrame:frame];
+  stepperButton.isAccessibilityElement = YES;
+  stepperButton.accessibilityLabel = @"test button";
+  UIView *inAccessibleContainer = [[UIView alloc] initWithFrame:frame];
+  inAccessibleContainer.accessibilityIdentifier = @"inAccessibleContainerID";
+  [inAccessibleContainer addSubview:stepperButton];
+  [self.testArea addSubview:inAccessibleContainer];
+}
+
+- (void)axetest_addTinyTappableElement {
+  UIButton *tinyButton = [[UIButton alloc] initWithFrame:CGRectMake(kMargin, kMargin, 10, 10)];
+  tinyButton.accessibilityLabel = @"tiny button";
+  [tinyButton setTitle:@"*" forState:UIControlStateNormal];
+  [self.testArea addSubview:tinyButton];
+}
+
+- (void)axetest_addLabelWithForgroundColor:(UIColor *)foregroundColor
+                           backgroundColor:(UIColor *)backgroundColor {
+  UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(kMargin, kMargin, 0, 0)];
+  label.font = [UIFont systemFontOfSize:60.0];
+  label.text = @"Hello";
+  label.textColor = foregroundColor;
+  label.backgroundColor = backgroundColor;
+  [label sizeToFit];
+  [self.testArea addSubview:label];
+}
+
+- (IBAction)userTappedClearFields:(UIButton *)sender {
+  [self axetest_clearAllFields];
+}
+
+- (IBAction)userTappedScrollToTop:(UIButton *)sender {
+  [self.actionsContainerView setContentOffset:CGPointZero animated:YES];
+}
+
+- (void)axetest_clearAllFields {
+  // Clear the container
+  for (UIView *subview in self.testArea.subviews) {
+    [subview removeFromSuperview];
+  }
+}
+
++ (void)addElementToTestArea:(UIView *)element {
+  NSAssert(viewController, @"View controller has not loaded yet.");
+  [viewController.testArea addSubview:element];
+}
+
++ (void)clearTestArea {
+  NSAssert(viewController, @"View controller has not loaded yet.");
+  [viewController axetest_clearAllFields];
+}
+
+@end
diff --git a/Tests/FunctionalTests/TestApp/Sources/GTXTestViewController.xib b/Tests/FunctionalTests/TestApp/Sources/GTXTestViewController.xib
new file mode 100644
index 0000000..b84c2e0
--- /dev/null
+++ b/Tests/FunctionalTests/TestApp/Sources/GTXTestViewController.xib
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="13528" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
+    <dependencies>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13526"/>
+        <capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <objects>
+        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="GTXTestViewController">
+            <connections>
+                <outlet property="actionsContainerView" destination="1a2-cF-Pv8" id="wRj-zQ-69M"/>
+                <outlet property="testArea" destination="zKb-0p-goZ" id="RXg-q4-JQl"/>
+                <outlet property="view" destination="iN0-l3-epB" id="14e-NV-T1Q"/>
+            </connections>
+        </placeholder>
+        <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
+        <view contentMode="scaleToFill" id="iN0-l3-epB">
+            <rect key="frame" x="0.0" y="0.0" width="320" height="480"/>
+            <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+            <subviews>
+                <view contentMode="scaleToFill" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="zKb-0p-goZ">
+                    <rect key="frame" x="8" y="300" width="359" height="359"/>
+                    <color key="backgroundColor" red="0.66666666666666663" green="0.66666666666666663" blue="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                    <constraints>
+                        <constraint firstAttribute="width" secondItem="zKb-0p-goZ" secondAttribute="height" multiplier="1:1" id="Yet-6y-VDN"/>
+                    </constraints>
+                </view>
+                <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="gza-Sc-brM">
+                    <rect key="frame" x="8" y="8" width="89" height="48"/>
+                    <accessibility key="accessibilityConfiguration" label="Scroll To Top"/>
+                    <constraints>
+                        <constraint firstAttribute="height" constant="48" id="O2j-dY-uEM"/>
+                    </constraints>
+                    <state key="normal" title="Scroll To Top"/>
+                    <connections>
+                        <action selector="userTappedScrollToTop:" destination="-1" eventType="touchUpInside" id="ue4-EW-Mce"/>
+                    </connections>
+                </button>
+                <scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="1a2-cF-Pv8">
+                    <rect key="frame" x="8" y="57" width="359" height="235"/>
+                </scrollView>
+                <button opaque="NO" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="e6E-IP-0vu">
+                    <rect key="frame" x="262" y="8" width="105" height="48"/>
+                    <accessibility key="accessibilityConfiguration" label="Clear Test Area"/>
+                    <constraints>
+                        <constraint firstAttribute="height" constant="48" id="GME-u0-LlX"/>
+                    </constraints>
+                    <state key="normal" title="Clear Test Area"/>
+                    <connections>
+                        <action selector="userTappedClearFields:" destination="-1" eventType="touchUpInside" id="egH-O5-JO0"/>
+                    </connections>
+                </button>
+            </subviews>
+            <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+            <constraints>
+                <constraint firstAttribute="bottom" secondItem="zKb-0p-goZ" secondAttribute="bottom" constant="8" id="2ZM-md-Qcg"/>
+                <constraint firstItem="gza-Sc-brM" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="8" id="HAt-xL-mMR"/>
+                <constraint firstItem="zKb-0p-goZ" firstAttribute="top" secondItem="1a2-cF-Pv8" secondAttribute="bottom" constant="8" id="Kif-GH-Lmu"/>
+                <constraint firstItem="e6E-IP-0vu" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" constant="8" id="Qih-b1-KdD"/>
+                <constraint firstAttribute="trailing" secondItem="e6E-IP-0vu" secondAttribute="trailing" constant="8" id="SeZ-vD-JjG"/>
+                <constraint firstItem="1a2-cF-Pv8" firstAttribute="top" secondItem="gza-Sc-brM" secondAttribute="bottom" constant="1" id="Y5L-Db-NkF"/>
+                <constraint firstAttribute="trailing" secondItem="1a2-cF-Pv8" secondAttribute="trailing" constant="8" id="YQN-mj-2Fm"/>
+                <constraint firstItem="1a2-cF-Pv8" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="8" id="Zwd-LM-HOV"/>
+                <constraint firstItem="zKb-0p-goZ" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="8" id="eMS-dM-fi4"/>
+                <constraint firstItem="gza-Sc-brM" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" constant="8" id="fPt-Fo-6L9"/>
+                <constraint firstAttribute="trailing" secondItem="zKb-0p-goZ" secondAttribute="trailing" constant="8" id="gUb-Ct-Q7M"/>
+            </constraints>
+            <point key="canvasLocation" x="306.5" y="392.5"/>
+        </view>
+    </objects>
+</document>
diff --git a/Tests/FunctionalTests/TestApp/TestSources/GTXTestAnalyticsBaseTest.h b/Tests/FunctionalTests/TestApp/TestSources/GTXTestAnalyticsBaseTest.h
new file mode 100644
index 0000000..fbca9bf
--- /dev/null
+++ b/Tests/FunctionalTests/TestApp/TestSources/GTXTestAnalyticsBaseTest.h
@@ -0,0 +1,14 @@
+#import "GTXTestBaseTest.h"
+
+
+/**
+ Base test case class for analytics related tests.
+ */
+@interface GTXTestAnalyticsBaseTest : GTXTestBaseTest
+
+/**
+ Analytics events detected so far.
+ */
+@property (nonatomic, assign) NSInteger analyticsEventCount;
+
+@end
diff --git a/Tests/FunctionalTests/TestApp/TestSources/GTXTestAnalyticsBaseTest.m b/Tests/FunctionalTests/TestApp/TestSources/GTXTestAnalyticsBaseTest.m
new file mode 100644
index 0000000..fbf5ec1
--- /dev/null
+++ b/Tests/FunctionalTests/TestApp/TestSources/GTXTestAnalyticsBaseTest.m
@@ -0,0 +1,58 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "GTXTestAnalyticsBaseTest.h"
+
+#import "GTXAnalytics.h"
+
+@implementation GTXTestAnalyticsBaseTest {
+  BOOL _prevAnalyticsEnabled;
+  GTXAnalyticsHandlerBlock _prevAnalyticsHandler;
+}
+
++ (void)setUp {
+  [super setUp];
+  [GTAxe installOnTestSuite:[GTXTestSuite suiteWithAllTestsInClass:self]
+                     checks:@[checkFailsIfFailingClass]
+          elementBlacklists:@[]];
+  [GTXTestViewController addElementToTestArea:
+      [[GTXTestFailingClass alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]];
+}
+
++ (void)tearDown {
+  [GTXTestViewController clearTestArea];
+  [super tearDown];
+}
+
+- (void)setUp {
+  [super setUp];
+
+  _prevAnalyticsEnabled = GTXAnalytics.enabled;
+  _prevAnalyticsHandler = GTXAnalytics.handler;
+  _analyticsEventCount = 0;
+  GTXAnalytics.handler = ^(GTXAnalyticsEvent event) {
+    _analyticsEventCount += 1;
+  };
+}
+
+- (void)tearDown {
+  GTXAnalytics.enabled = _prevAnalyticsEnabled;
+  GTXAnalytics.handler = _prevAnalyticsHandler;
+
+  [super tearDown];
+}
+
+@end
diff --git a/Tests/FunctionalTests/TestApp/TestSources/GTXTestAnalyticsCanBeDisabled.m b/Tests/FunctionalTests/TestApp/TestSources/GTXTestAnalyticsCanBeDisabled.m
new file mode 100644
index 0000000..052aca6
--- /dev/null
+++ b/Tests/FunctionalTests/TestApp/TestSources/GTXTestAnalyticsCanBeDisabled.m
@@ -0,0 +1,42 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "GTXTestAnalyticsBaseTest.h"
+
+#import "GTXAnalytics.h"
+
+@interface GTXTestAnalyticsCanBeDisabled : GTXTestAnalyticsBaseTest
+@end
+
+@implementation GTXTestAnalyticsCanBeDisabled
+
+- (void)setUp {
+  [super setUp];
+
+  GTXAnalytics.enabled = NO;
+}
+
+- (void)tearDown {
+  XCTAssertEqual(self.analyticsEventCount, 0);
+
+  [super tearDown];
+}
+
+- (void)testJustToTriggerTearDown {
+  // Pass
+}
+
+@end
diff --git a/Tests/FunctionalTests/TestApp/TestSources/GTXTestAnalyticsIsTriggered.m b/Tests/FunctionalTests/TestApp/TestSources/GTXTestAnalyticsIsTriggered.m
new file mode 100644
index 0000000..741985f
--- /dev/null
+++ b/Tests/FunctionalTests/TestApp/TestSources/GTXTestAnalyticsIsTriggered.m
@@ -0,0 +1,42 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "GTXTestAnalyticsBaseTest.h"
+
+#import "GTXAnalytics.h"
+
+@interface GTXTestAnalyticsIsTriggered : GTXTestAnalyticsBaseTest
+@end
+
+@implementation GTXTestAnalyticsIsTriggered
+
+- (void)setUp {
+  [super setUp];
+
+  GTXAnalytics.enabled = YES;
+}
+
+- (void)tearDown {
+  XCTAssertGreaterThan(self.analyticsEventCount, 0);
+
+  [super tearDown];
+}
+
+- (void)testJustToTriggerTearDown {
+  // Pass
+}
+
+@end
diff --git a/Tests/FunctionalTests/TestApp/TestSources/GTXTestBaseTest.h b/Tests/FunctionalTests/TestApp/TestSources/GTXTestBaseTest.h
new file mode 100644
index 0000000..cd871a9
--- /dev/null
+++ b/Tests/FunctionalTests/TestApp/TestSources/GTXTestBaseTest.h
@@ -0,0 +1,73 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import <UIKit/UIKit.h>
+#import <QuartzCore/QuartzCore.h>
+#import <XCTest/XCTest.h>
+
+#import "GTAxe.h"
+#import "GTXTestViewController.h"
+
+/**
+ Check that fails if the element is of Class @c GTXTestFailingClass.
+ */
+id<GTXChecking> checkFailsIfFailingClass;
+
+/**
+ Check that always passes.
+ */
+id<GTXChecking> alwaysFail;
+
+/**
+ Check that always fails.
+ */
+id<GTXChecking> alwaysPass;
+
+/**
+ Base test for all GTAxe functional/integration tests used to setup GTAxe and capture check
+ failures.
+ */
+@interface GTXTestBaseTest : XCTestCase
+
+
+/**
+ Assert that @c count failures were detected.
+ */
+- (void)assertFailureCount:(NSInteger)count;
+
+/**
+ Assert that no failures were detected.
+ */
+- (void)assertNoFailure;
+
+/**
+ Assert a single failure then clear the detected failures.
+ */
+- (void)assertAndClearSingleFailure;
+
+@end
+
+/**
+ Placeholder class for passing elements.
+ */
+@interface GTXTestPassingClass : UIView
+@end
+
+/**
+ Placeholder class for failing elements.
+ */
+@interface GTXTestFailingClass : UIView
+@end
diff --git a/Tests/FunctionalTests/TestApp/TestSources/GTXTestBaseTest.m b/Tests/FunctionalTests/TestApp/TestSources/GTXTestBaseTest.m
new file mode 100644
index 0000000..1675294
--- /dev/null
+++ b/Tests/FunctionalTests/TestApp/TestSources/GTXTestBaseTest.m
@@ -0,0 +1,99 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "GTXTestBaseTest.h"
+
+#import "GTXTestViewController.h"
+
+static NSInteger gFailureCount = 0;
+
+@implementation GTXTestBaseTest
+
++ (void)setUp {
+  [super setUp];
+
+  checkFailsIfFailingClass = [GTAxe checkWithName:@"checkFailsIfFailingClass"
+                                            block:^BOOL(id element, GTXErrorRefType errorOrNil) {
+    return ![element isKindOfClass:[GTXTestFailingClass class]];
+  }];
+
+  alwaysFail = [GTAxe checkWithName:@"AlwaysFail"
+                              block:^BOOL(id element, GTXErrorRefType errorOrNil) {
+    return NO;
+  }];
+  alwaysPass = [GTAxe checkWithName:@"AlwaysPass"
+                              block:^BOOL(id element, GTXErrorRefType errorOrNil) {
+    return YES;
+  }];
+
+  // Wait for view controller to be launched.
+  NSTimeInterval start = CACurrentMediaTime();
+  while (1) {
+    NSAssert(CACurrentMediaTime() - start < 4.0, @"GTXTestViewController was not launched!");
+    CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, true);
+    if ([UIApplication sharedApplication].keyWindow.rootViewController.class ==
+        [GTXTestViewController class]) {
+      break;
+    }
+  }
+
+  gFailureCount = 0;
+  [GTAxe setFailureHandler:^(NSError * error) {
+    gFailureCount += 1;
+  }];
+}
+
+- (void)assertFailureCount:(NSInteger)count {
+  XCTAssertGreaterThan(count, 0, @"For 0 failures use -assertNoFailure");
+  XCTAssertEqual(gFailureCount, count);
+}
+
+- (void)assertNoFailure {
+  XCTAssertEqual(gFailureCount, 0);
+}
+
+- (void)assertAndClearSingleFailure {
+  XCTAssertEqual(gFailureCount, 1);
+  gFailureCount = 0;
+}
+
+@end
+
+@implementation GTXTestPassingClass
+
+- (void)drawRect:(CGRect)rect {
+  [[UIColor greenColor] set];
+  CGContextFillRect(UIGraphicsGetCurrentContext(), self.bounds);
+}
+
+- (BOOL)isAccessibilityElement {
+  return YES;
+}
+
+@end
+
+@implementation GTXTestFailingClass
+
+- (void)drawRect:(CGRect)rect {
+  [[UIColor redColor] set];
+  CGContextFillRect(UIGraphicsGetCurrentContext(), self.bounds);
+}
+
+- (BOOL)isAccessibilityElement {
+  return YES;
+}
+
+@end
diff --git a/Tests/FunctionalTests/TestApp/TestSources/GTXTestFailingClassFailCheck.m b/Tests/FunctionalTests/TestApp/TestSources/GTXTestFailingClassFailCheck.m
new file mode 100644
index 0000000..f90476f
--- /dev/null
+++ b/Tests/FunctionalTests/TestApp/TestSources/GTXTestFailingClassFailCheck.m
@@ -0,0 +1,48 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "GTXTestBaseTest.h"
+
+@interface GTXTestFailingClassFailCheck : GTXTestBaseTest
+@end
+
+@implementation GTXTestFailingClassFailCheck
+
++ (void)setUp {
+  [super setUp];
+  [GTAxe installOnTestSuite:[GTXTestSuite suiteWithAllTestsInClass:self]
+                     checks:@[checkFailsIfFailingClass]
+          elementBlacklists:@[]];
+  [GTXTestViewController addElementToTestArea:
+      [[GTXTestFailingClass alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]];
+}
+
++ (void)tearDown {
+  [GTXTestViewController clearTestArea];
+  [super tearDown];
+}
+
+- (void)tearDown {
+  [self assertFailureCount:1];
+
+  [super tearDown];
+}
+
+- (void)testJustToTriggerTearDown {
+  // Pass
+}
+
+@end
diff --git a/Tests/FunctionalTests/TestApp/TestSources/GTXTestGtxCanBlacklistTestCases.m b/Tests/FunctionalTests/TestApp/TestSources/GTXTestGtxCanBlacklistTestCases.m
new file mode 100644
index 0000000..f571a21
--- /dev/null
+++ b/Tests/FunctionalTests/TestApp/TestSources/GTXTestGtxCanBlacklistTestCases.m
@@ -0,0 +1,56 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "GTXTestBaseTest.h"
+#import "GTAxe.h"
+
+@interface GTXTestGtxCanBlacklistTestCases : GTXTestBaseTest
+@end
+
+static BOOL shouldDetectFailure = NO;
+@implementation GTXTestGtxCanBlacklistTestCases
+
++ (void)setUp {
+  [super setUp];
+  [GTAxe installOnTestSuite:[GTXTestSuite suiteWithClass:self
+                                                andTests:@selector(testMiddle), nil]
+                     checks:@[alwaysFail]
+          elementBlacklists:@[]];
+}
+
+- (void)tearDown {
+  if (shouldDetectFailure) {
+    [self assertAndClearSingleFailure];
+  } else {
+    [self assertNoFailure];
+  }
+
+  [super tearDown];
+}
+
+- (void)testTop {
+  shouldDetectFailure = NO;
+}
+
+- (void)testMiddle {
+  shouldDetectFailure = YES;
+}
+
+- (void)testBottom {
+  shouldDetectFailure = NO;
+}
+
+@end
diff --git a/Tests/FunctionalTests/TestApp/TestSources/GTXTestGtxCanDetectFailures.m b/Tests/FunctionalTests/TestApp/TestSources/GTXTestGtxCanDetectFailures.m
new file mode 100644
index 0000000..b96ef47
--- /dev/null
+++ b/Tests/FunctionalTests/TestApp/TestSources/GTXTestGtxCanDetectFailures.m
@@ -0,0 +1,49 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "GTXTestBaseTest.h"
+#import "GTAxe.h"
+
+@interface GTXTestGtxCanDetectFailures : GTXTestBaseTest
+@end
+
+@implementation GTXTestGtxCanDetectFailures
+
++ (void)setUp {
+  [super setUp];
+  [GTAxe installOnTestSuite:[GTXTestSuite suiteWithAllTestsInClass:self]
+                     checks:@[alwaysFail]
+          elementBlacklists:@[]];
+}
+
+- (void)tearDown {
+  static NSInteger expectedFailureCount = 1;
+  [self assertFailureCount:expectedFailureCount];
+  expectedFailureCount += 1; // Each test must cause exactly one failure.
+
+  [super tearDown];
+}
+
+- (void)testFirstEmpty {
+  // This test exists to ensure teardown is called.
+}
+
+- (void)testSecondEmpty {
+  // This test exists to ensure teardown is called.
+}
+
+@end
+
diff --git a/Tests/FunctionalTests/TestApp/TestSources/GTXTestGtxCanDetectFailuresInInheritedTests.m b/Tests/FunctionalTests/TestApp/TestSources/GTXTestGtxCanDetectFailuresInInheritedTests.m
new file mode 100644
index 0000000..91e5d1f
--- /dev/null
+++ b/Tests/FunctionalTests/TestApp/TestSources/GTXTestGtxCanDetectFailuresInInheritedTests.m
@@ -0,0 +1,76 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "GTXTestBaseTest.h"
+#import "GTAxe.h"
+
+/**
+ A global boolean that is used to ensure that at least one test is run.
+ */
+static BOOL gTeardownCalledAtleastOnce = NO;
+
+#pragma mark - Interfaces
+
+/**
+ A super class that contains a single test method.
+ */
+@interface GTXTestSuperClassWithATestMethod : GTXTestBaseTest
+@end
+
+/**
+ Class with tests that are only inherited.
+ */
+@interface GTXTestGtxCanDetectFailuresInInheritedTests : GTXTestSuperClassWithATestMethod
+@end
+
+#pragma mark - Implementations
+
+@implementation GTXTestGtxCanDetectFailuresInInheritedTests
+
++ (void)setUp {
+  [super setUp];
+  GTXTestSuite *suite = [GTXTestSuite suiteWithAllTestsFromAllClassesInheritedFromClass:
+      [GTXTestSuperClassWithATestMethod class]];
+  [GTAxe installOnTestSuite:suite
+                     checks:@[alwaysFail]
+          elementBlacklists:@[]];
+  gTeardownCalledAtleastOnce = NO;
+}
+
+- (void)tearDown {
+  static NSInteger expectedFailureCount = 1;
+  [self assertFailureCount:expectedFailureCount];
+  expectedFailureCount += 1; // Each test must cause exactly one failure.
+  gTeardownCalledAtleastOnce = YES;
+
+  [super tearDown];
+}
+
++ (void)tearDown {
+  NSAssert(gTeardownCalledAtleastOnce, @"No tests were run!");
+
+  [super tearDown];
+}
+
+@end
+
+@implementation GTXTestSuperClassWithATestMethod
+
+- (void)testFirstEmpty {
+  // This test exists to ensure teardown is called.
+}
+
+@end
diff --git a/Tests/FunctionalTests/TestApp/TestSources/GTXTestGtxCanIgnoreElements.m b/Tests/FunctionalTests/TestApp/TestSources/GTXTestGtxCanIgnoreElements.m
new file mode 100644
index 0000000..42cdd3b
--- /dev/null
+++ b/Tests/FunctionalTests/TestApp/TestSources/GTXTestGtxCanIgnoreElements.m
@@ -0,0 +1,52 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "GTXTestBaseTest.h"
+#import "GTAxe.h"
+
+@interface GTXTestGtxCanIgnoreElements : GTXTestBaseTest
+@end
+
+@implementation GTXTestGtxCanIgnoreElements
+
++ (void)setUp {
+  [super setUp];
+  NSArray *blacklist =
+      @[[GTAxe blacklistForElementsOfClassNamed:NSStringFromClass([GTXTestFailingClass class])]];
+  [GTAxe installOnTestSuite:[GTXTestSuite suiteWithAllTestsInClass:self]
+                     checks:@[checkFailsIfFailingClass]
+          elementBlacklists:blacklist];
+  [GTXTestViewController addElementToTestArea:
+      [[GTXTestFailingClass alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]];
+}
+
++ (void)tearDown {
+  [GTXTestViewController clearTestArea];
+  [super tearDown];
+}
+
+- (void)tearDown {
+  [self assertNoFailure];
+
+  [super tearDown];
+}
+
+- (void)testJustToTriggerTearDown {
+  // Pass
+}
+
+@end
+
diff --git a/Tests/FunctionalTests/TestApp/TestSources/GTXTestGtxCanIgnoreElementsOnSpecificCheck.m b/Tests/FunctionalTests/TestApp/TestSources/GTXTestGtxCanIgnoreElementsOnSpecificCheck.m
new file mode 100644
index 0000000..eaf8251
--- /dev/null
+++ b/Tests/FunctionalTests/TestApp/TestSources/GTXTestGtxCanIgnoreElementsOnSpecificCheck.m
@@ -0,0 +1,56 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "GTXTestBaseTest.h"
+#import "GTAxe.h"
+
+@interface GTXTestGtxCanIgnoreElementsOnSpecificCheck : GTXTestBaseTest
+@end
+
+@implementation GTXTestGtxCanIgnoreElementsOnSpecificCheck
+
++ (void)setUp {
+  [super setUp];
+  NSArray *blacklist =
+      @[[GTAxe blacklistForElementsOfClassNamed:NSStringFromClass([GTXTestFailingClass class])
+                                  forCheckNamed:checkFailsIfFailingClass.name]];
+  id<GTXChecking> secondFailingCheck = [GTAxe checkWithName:@"secondFailingCheck"
+                                            block:^BOOL(id element, GTXErrorRefType errorOrNil) {
+    return ![element isKindOfClass:[GTXTestFailingClass class]];
+  }];
+  [GTAxe installOnTestSuite:[GTXTestSuite suiteWithAllTestsInClass:self]
+                     checks:@[checkFailsIfFailingClass, secondFailingCheck]
+          elementBlacklists:blacklist];
+  [GTXTestViewController addElementToTestArea:
+      [[GTXTestFailingClass alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]];
+}
+
++ (void)tearDown {
+  [GTXTestViewController clearTestArea];
+  [super tearDown];
+}
+
+- (void)tearDown {
+  [self assertAndClearSingleFailure];
+
+  [super tearDown];
+}
+
+- (void)testJustToTriggerTearDown {
+  // Pass
+}
+
+@end
diff --git a/Tests/FunctionalTests/TestApp/TestSources/GTXTestGtxCanWhitelistTestCases.m b/Tests/FunctionalTests/TestApp/TestSources/GTXTestGtxCanWhitelistTestCases.m
new file mode 100644
index 0000000..c2fc4af
--- /dev/null
+++ b/Tests/FunctionalTests/TestApp/TestSources/GTXTestGtxCanWhitelistTestCases.m
@@ -0,0 +1,58 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "GTXTestBaseTest.h"
+#import "GTAxe.h"
+
+static BOOL shouldDetectFailure = NO;
+
+@interface GTXTestGtxCanWhitelistTestCases : GTXTestBaseTest
+@end
+
+@implementation GTXTestGtxCanWhitelistTestCases
+
++ (void)setUp {
+  [super setUp];
+  [GTAxe installOnTestSuite:[GTXTestSuite suiteWithClass:self
+                                                andTests:@selector(testMiddle), nil]
+                     checks:@[alwaysFail]
+          elementBlacklists:@[]];
+}
+
+- (void)tearDown {
+  if (shouldDetectFailure) {
+    [self assertAndClearSingleFailure];
+  } else {
+    [self assertNoFailure];
+  }
+
+  [super tearDown];
+}
+
+- (void)testTop {
+  shouldDetectFailure = NO;
+}
+
+- (void)testMiddle {
+  shouldDetectFailure = YES;
+}
+
+- (void)testBottom {
+  shouldDetectFailure = NO;
+}
+
+@end
+
diff --git a/Tests/FunctionalTests/TestApp/TestSources/GTXTestGtxWithDefaultChecks.m b/Tests/FunctionalTests/TestApp/TestSources/GTXTestGtxWithDefaultChecks.m
new file mode 100644
index 0000000..833a327
--- /dev/null
+++ b/Tests/FunctionalTests/TestApp/TestSources/GTXTestGtxWithDefaultChecks.m
@@ -0,0 +1,128 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "GTXTestBaseTest.h"
+#import "GTAxe.h"
+
+@interface GTXTestGtxWithDefaultChecks : GTXTestBaseTest
+@end
+
+@implementation GTXTestGtxWithDefaultChecks {
+  BOOL _expectErrors;
+  BOOL _foundErrors;
+}
+
++ (void)setUp {
+  [super setUp];
+
+  // Install all the default checks on the current test class.
+  [GTAxe installOnTestSuite:[GTXTestSuite suiteWithAllTestsInClass:self]
+                     checks:[GTXChecksCollection allGTXChecks]
+          elementBlacklists:@[]];
+}
+
+- (void)setUp {
+  [super setUp];
+
+  [GTXTestViewController clearTestArea];
+  [self _waitForAppEvents:0.5];
+
+  // Set up failure handler to simple detect if errors were found.
+  _expectErrors = NO;
+  _foundErrors = NO;
+  [GTAxe setFailureHandler:^(NSError * _Nonnull error) {
+    _foundErrors = YES;
+  }];
+}
+
+- (void)tearDown {
+  XCTAssertEqual(_expectErrors, _foundErrors);
+
+  [super tearDown];
+}
+
+- (void)testNoLableElementsCauseFailures {
+  [self _performTestActionNamed:kAddNoLabelElementActionName];
+  _expectErrors = YES;
+}
+
+- (void)testPunctuatedLablesCauseFailures {
+  [self _performTestActionNamed:kAddPunctuatedLabelElementActionName];
+  _expectErrors = YES;
+}
+
+- (void)testDefaultKeyboardDoesNotCauseFailures {
+  [self _performTestActionNamed:kShowKeyboardActionName];
+  [self _waitForAppEvents:1.0];
+  _expectErrors = NO;
+}
+
+- (void)testButtonsMarkedInaccessibleAreSkippedByDefault {
+  [self _performTestActionNamed:kAddInaccessibleButton];
+  _expectErrors = NO;
+}
+
+- (void)testAccessibleButtonsInsideContainersDoesNotCauseFailures {
+  [self _performTestActionNamed:kAddAccessibleButtonInContainer];
+  _expectErrors = NO;
+}
+
+- (void)testTinyTappableAreasCauseErrors {
+  [self _performTestActionNamed:kAddTinyTappableElement];
+  _expectErrors = YES;
+}
+
+- (void)testLowContrastElementsCauseErrors {
+  [self _performTestActionNamed:kAddVeryLowContrastLabel];
+  _expectErrors = YES;
+}
+
+- (void)testBarelyContrastElementsCauseErrors {
+  [self _performTestActionNamed:kAddBarelyLowContrastLabel];
+  _expectErrors = YES;
+}
+
+- (void)testHighContrastElementsDoesNotCauseFailures {
+  [self _performTestActionNamed:kAddVeryHighContrastLabel];
+  _expectErrors = NO;
+}
+
+- (void)testBarelyHighContrastElementsDoesNotCauseFailures {
+  [self _performTestActionNamed:kAddBarelyHighContrastLabel];
+  _expectErrors = NO;
+}
+
+#pragma mark - private
+
+/**
+ Performs the provided test action on the test app and waits for its completion.
+ */
+- (void)_performTestActionNamed:(NSString *)testAction {
+  [GTXTestViewController performTestActionNamed:testAction];
+  [self _waitForAppEvents:0.5];
+}
+
+/**
+ Waits given time interval for any app events to be processed.
+ */
+- (void)_waitForAppEvents:(NSTimeInterval)seconds {
+  NSTimeInterval start = CACurrentMediaTime();
+  while (CACurrentMediaTime() - start < seconds) {
+    CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.01, false);
+  }
+}
+
+@end
diff --git a/Tests/FunctionalTests/TestApp/TestSources/GTXTestGtxWorksForPassingTests.m b/Tests/FunctionalTests/TestApp/TestSources/GTXTestGtxWorksForPassingTests.m
new file mode 100644
index 0000000..6b58a4d
--- /dev/null
+++ b/Tests/FunctionalTests/TestApp/TestSources/GTXTestGtxWorksForPassingTests.m
@@ -0,0 +1,46 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "GTXTestBaseTest.h"
+#import "GTAxe.h"
+
+@interface GTXTestGtxWorksForPassingTests : GTXTestBaseTest
+@end
+
+@implementation GTXTestGtxWorksForPassingTests
+
++ (void)setUp {
+  [super setUp];
+  [GTAxe installOnTestSuite:[GTXTestSuite suiteWithAllTestsInClass:self]
+                     checks:@[alwaysPass]
+          elementBlacklists:@[]];
+}
+
+- (void)tearDown {
+  [self assertNoFailure];
+
+  [super tearDown];
+}
+
+- (void)testFirstEmpty {
+  // This test exists to ensure teardown is called.
+}
+
+- (void)testSecondEmpty {
+  // This test exists to ensure teardown is called.
+}
+
+@end
diff --git a/Tests/UnitTests/GTXAccessibilityTreeTests.m b/Tests/UnitTests/GTXAccessibilityTreeTests.m
new file mode 100644
index 0000000..33efb29
--- /dev/null
+++ b/Tests/UnitTests/GTXAccessibilityTreeTests.m
@@ -0,0 +1,242 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import <CoreGraphics/CoreGraphics.h>
+#import <UIKit/UIKit.h>
+#import <XCTest/XCTest.h>
+
+#import "GTXAccessibilityTree.h"
+#import "GTXTestAccessibilityElements.h"
+
+@interface GTXAccessibilityTreeTests : XCTestCase
+@end
+
+@implementation GTXAccessibilityTreeTests
+
+- (void)testGTXAccessibilityTreeWorksWithEmptyLists {
+  for (id element in [[GTXAccessibilityTree alloc] initWithRootElements:@[]]) {
+    XCTAssertTrue(FALSE, @"Unknown element found %@", element);
+  }
+}
+
+- (void)testGTXAccessibilityTreeWorksWithSingleElement {
+  id element = [self newUIElementWithAccessibility:YES];
+  [self assertGTXAccessibilityTreeWithRootElements:@[element]
+                                            yields:@[element]];
+}
+
+- (void)testGTXAccessibilityTreeWorksWithMultipleElementsAtSameLevel {
+  id element1 = [self newUIElementWithAccessibility:YES];
+  id element2 = [self newUIElementWithAccessibility:YES];
+  NSArray *elements = @[element1, element2];
+  [self assertGTXAccessibilityTreeWithRootElements:elements
+                                            yields:elements];
+}
+
+- (void)testGTXAccessibilityTreeWorksWithAOneLevelTree {
+  id element1 = [self newUIElementWithAccessibility:NO];
+  id element2 = [self newUIElementWithAccessibility:NO];
+  id element3 = [self newUIElementWithAccessibility:NO];
+  [(UIView *)element1 addSubview:element2];
+  [(UIView *)element1 addSubview:element3];
+
+  NSArray *elements = @[element1, element2, element3];
+  [self assertGTXAccessibilityTreeWithRootElements:@[element1]
+                                            yields:elements];
+}
+
+- (void)testGTXAccessibilityTreeSkipsChildrenOfAccessibilityElements {
+  UIView *root = [self newTestElementWithChildren];
+  UIView *subTreeView = root.subviews[0];
+  [subTreeView setIsAccessibilityElement:YES];
+
+  NSArray *actualElements = [self accessibilityElementsInTree:root];
+  XCTAssertFalse([actualElements containsObject:subTreeView.subviews[0]]);
+  XCTAssertFalse([actualElements containsObject:subTreeView.subviews[1]]);
+
+  [root setIsAccessibilityElement:YES];
+  // Enumeration should now contain just the root element.
+  actualElements = [self accessibilityElementsInTree:root];
+  XCTAssertTrue([actualElements containsObject:root]);
+  XCTAssertEqual([actualElements count], (NSUInteger)1);
+}
+
+- (void)testGTXAccessibilityTreeSkipsHiddenElements {
+  UIView *root = [self newTestElementWithChildren];
+  [root setHidden:YES];
+
+  // Enumeration should be empty.
+  NSArray *actualElements = [self accessibilityElementsInTree:root];
+  XCTAssertEqual([actualElements count], (NSUInteger)0);
+}
+
+- (void)testGTXAccessibilityTreeSkipsAccessibilityHiddenElements {
+  UIView *root = [self newTestElementWithChildren];
+  [root setAccessibilityElementsHidden:YES];
+
+  // Enumeration should be empty.
+  NSArray *actualElements = [self accessibilityElementsInTree:root];
+  XCTAssertEqual([actualElements count], (NSUInteger)0);
+}
+
+- (void)testGTXAccessibilityTreeSkipsEmptyFrameElements {
+  UIView *root = [self newTestElementWithChildren];
+
+  // Enumeration should be empty for zero width.
+  root.accessibilityFrame = CGRectMake(0, 0, 0, 1);
+  root.frame = CGRectMake(0, 0, 0, 1);
+  NSArray *actualElements = [self accessibilityElementsInTree:root];
+  XCTAssertEqual([actualElements count], (NSUInteger)0);
+
+  // Enumeration should be empty for zero height.
+  root.accessibilityFrame = CGRectMake(0, 0, 1, 0);
+  root.frame = CGRectMake(0, 0, 1, 0);
+  actualElements = [self accessibilityElementsInTree:root];
+  XCTAssertEqual([actualElements count], (NSUInteger)0);
+
+  // Enumeration should be empty for zero rect.
+  root.accessibilityFrame = CGRectZero;
+  root.frame = CGRectZero;
+  actualElements = [self accessibilityElementsInTree:root];
+  XCTAssertEqual([actualElements count], (NSUInteger)0);
+}
+
+- (void)testGTXAccessibilityTreeNotNonEmptyFrameElements {
+  UIView *root = [self newTestElementWithChildren];
+
+  // Enumeration should not be empty if frame is non-empty.
+  CGRect nonZeroRect = CGRectMake(0, 0, 1, 1);
+  root.accessibilityFrame = CGRectZero;
+  root.frame = nonZeroRect;
+  NSArray *actualElements = [self accessibilityElementsInTree:root];
+  XCTAssertNotEqual([actualElements count], (NSUInteger)0);
+
+  // Enumeration should not be empty if accessibilityFrame is non-empty.
+  root.accessibilityFrame = nonZeroRect;
+  root.frame = CGRectZero;
+  actualElements = [self accessibilityElementsInTree:root];
+  XCTAssertNotEqual([actualElements count], (NSUInteger)0);
+}
+
+- (void)testGTXAccessibilityTreeSkipsChildrenOfAccessibilityHiddenElements {
+  // Accessibility element which provide same elements via element.accessibilityElements and
+  // element.accessibilityElementAtIndex: are considered consistent.
+  GTXTestAccessibilityElementFull *root = [[GTXTestAccessibilityElementFull alloc] init];
+  id element1 = [self newUIElementWithAccessibility:YES];
+  id element2 = [self newUIElementWithAccessibility:YES];
+
+  root.accessibilityElementsOveride = @[element1, element2];
+  root.accessibilityElementsAtIndexOveride = @[element2, element1];
+  root.isAccessibilityElementOveride = NO;
+  [self assertGTXAccessibilityTreeWithRootElements:@[root]
+                                            yields:@[root, element1, element2]];
+}
+
+- (void)testGTXAccessibilityTreeSkipsChildrenOfHiddenElements {
+  UIView *root = [self newTestElementWithChildren];
+  UIView *subTreeView = root.subviews[0];
+  [subTreeView setHidden:YES];
+
+  NSArray *actualElements = [self accessibilityElementsInTree:root];
+  XCTAssertFalse([actualElements containsObject:subTreeView.subviews[0]]);
+  XCTAssertFalse([actualElements containsObject:subTreeView.subviews[1]]);
+}
+
+- (void)testGTXAccessibilityTreeFailsForInConsistentTrees {
+  GTXTestAccessibilityElementFull *root = [[GTXTestAccessibilityElementFull alloc] init];
+  root.accessibilityElementsOveride = @[[self newUIElementWithAccessibility:YES]];
+  root.accessibilityElementsAtIndexOveride = @[[self newUIElementWithAccessibility:YES]];
+  root.isAccessibilityElementOveride = NO;
+
+  XCTAssertThrows([[[GTXAccessibilityTree alloc] initWithRootElements:@[root]] nextObject]);
+}
+
+- (void)testGTXAccessibilityTreeWorksForConsistentTrees {
+  // Accessibility element which provide same elements via element.accessibilityElements and
+  // element.accessibilityElementAtIndex: are considered consistent.
+  GTXTestAccessibilityElementFull *root = [[GTXTestAccessibilityElementFull alloc] init];
+  id element1 = [self newUIElementWithAccessibility:YES];
+  id element2 = [self newUIElementWithAccessibility:YES];
+
+  root.accessibilityElementsOveride = @[element1, element2];
+  root.accessibilityElementsAtIndexOveride = @[element2, element1];
+  root.isAccessibilityElementOveride = NO;
+  [self assertGTXAccessibilityTreeWithRootElements:@[root]
+                                            yields:@[root, element1, element2]];
+}
+
+- (void)testGTXAccessibilityTreeWorksWithAccessibilityElements {
+  GTXTestAccessibilityElementA *root = [[GTXTestAccessibilityElementA alloc] init];
+  root.isAccessibilityElementOveride = NO;
+  id element = [self newUIElementWithAccessibility:YES];
+  root.accessibilityElementsOveride = @[element];
+  NSArray *elements = @[root, element];
+  [self assertGTXAccessibilityTreeWithRootElements:@[root]
+                                            yields:elements];
+}
+
+- (void)testGTXAccessibilityTreeWorksWithAccessibilityElementsAtIndex {
+  GTXTestAccessibilityElementB *root = [[GTXTestAccessibilityElementB alloc] init];
+  root.isAccessibilityElementOveride = NO;
+  id element1 = [self newUIElementWithAccessibility:YES];
+  id element2 = [self newUIElementWithAccessibility:YES];
+  root.accessibilityElementsAtIndexOveride = @[element1, element2];
+  NSArray *elements = @[root, element1, element2];
+  [self assertGTXAccessibilityTreeWithRootElements:@[root]
+                                            yields:elements];
+}
+
+#pragma mark - Private
+
+- (void)assertGTXAccessibilityTreeWithRootElements:(NSArray *)rootElements
+                                            yields:(NSArray *)expectedElements {
+  NSMutableArray *actualElemets = [[NSMutableArray alloc] init];
+  for (id element in [[GTXAccessibilityTree alloc] initWithRootElements:rootElements]) {
+    [actualElemets addObject:element];
+  }
+  XCTAssertEqualObjects(expectedElements, actualElemets);
+}
+
+- (id)newTestElementWithChildren {
+  UIView *leftSubTreeRoot = [self newUIElementWithAccessibility:NO];
+  [leftSubTreeRoot addSubview:[self newUIElementWithAccessibility:NO]];
+  [leftSubTreeRoot addSubview:[self newUIElementWithAccessibility:NO]];
+
+  UIView *rightSubTreeRoot = [self newUIElementWithAccessibility:NO];
+  [rightSubTreeRoot addSubview:[self newUIElementWithAccessibility:NO]];
+  [rightSubTreeRoot addSubview:[self newUIElementWithAccessibility:NO]];
+
+  UIView *root = [self newUIElementWithAccessibility:NO];
+  [root addSubview:leftSubTreeRoot];
+  [root addSubview:rightSubTreeRoot];
+  return root;
+}
+
+- (id)newUIElementWithAccessibility:(BOOL)isAccessibilityElement {
+  UIView *element = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 1, 1)];
+  [element setIsAccessibilityElement:isAccessibilityElement];
+  return element;
+}
+
+- (NSArray *)accessibilityElementsInTree:(id)root {
+  NSMutableArray *accessibilityElementsArray = [[NSMutableArray alloc] init];
+  for (id element in [[GTXAccessibilityTree alloc] initWithRootElements:@[root]]) {
+    [accessibilityElementsArray addObject:element];
+  }
+  return accessibilityElementsArray;
+}
+
+@end
diff --git a/Tests/UnitTests/GTXAnalyticsTests.m b/Tests/UnitTests/GTXAnalyticsTests.m
new file mode 100644
index 0000000..350be3e
--- /dev/null
+++ b/Tests/UnitTests/GTXAnalyticsTests.m
@@ -0,0 +1,179 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import <UIKit/UIKit.h>
+#import <XCTest/XCTest.h>
+
+#import "GTXAnalytics.h"
+#import "GTXToolKit.h"
+#import "GTXBaseTestCase.h"
+
+@interface GTXAnalyticsTests : GTXBaseTestCase
+@end
+
+@implementation GTXAnalyticsTests {
+  BOOL prevAnalyticsEnabled;
+  GTXAnalyticsHandlerBlock prevAnalyticsHandler;
+}
+
+- (void)setUp {
+  [super setUp];
+
+  prevAnalyticsEnabled = GTXAnalytics.enabled;
+  prevAnalyticsHandler = GTXAnalytics.handler;
+}
+
+- (void)tearDown {
+  GTXAnalytics.enabled = prevAnalyticsEnabled;
+  GTXAnalytics.handler = prevAnalyticsHandler;
+
+  [super tearDown];
+}
+
+- (void)testCheckElementReportsAnalyticsCorrectly {
+  GTXToolKit *toolkit = [[GTXToolKit alloc] init];
+  __block NSInteger successEventsCount = 0;
+  __block NSInteger failureEventsCount = 0;
+  [GTXAnalytics setHandler:^(GTXAnalyticsEvent event) {
+    if (event == GTXAnalyticsEventChecksPerformed) {
+      successEventsCount += 1;
+    } else {
+      failureEventsCount += 1;
+    }
+  }];
+  NSObject *failingElement = [self newAccessibleElement];
+  NSObject *passingElement = [self newAccessibleElement];
+  id<GTXChecking> check = [GTXToolKit checkWithName:@"Foo"
+                                              block:^BOOL(id _Nonnull element,
+                                                          GTXErrorRefType errorOrNil) {
+                                                return element == passingElement;
+                                              }];
+  [toolkit registerCheck:check];
+  NSError *error;
+  XCTAssertEqual(successEventsCount, 0);
+  XCTAssertEqual(failureEventsCount, 0);
+
+  XCTAssertTrue([toolkit checkElement:passingElement error:nil]);
+  XCTAssertEqual(successEventsCount, 1);
+  XCTAssertEqual(failureEventsCount, 0);
+
+  XCTAssertTrue([toolkit checkElement:passingElement error:&error]);
+  XCTAssertEqual(successEventsCount, 2);
+  XCTAssertEqual(failureEventsCount, 0);
+
+  XCTAssertFalse([toolkit checkElement:failingElement error:nil]);
+  XCTAssertEqual(successEventsCount, 2);
+  XCTAssertEqual(failureEventsCount, 1);
+
+  XCTAssertFalse([toolkit checkElement:failingElement error:&error]);
+  XCTAssertEqual(successEventsCount, 2);
+  XCTAssertEqual(failureEventsCount, 2);
+}
+
+- (void)testCheckElementsFromRootElementsReportsAnalyticsCorrectly {
+  GTXToolKit *toolkit = [[GTXToolKit alloc] init];
+  __block NSInteger successEventsCount = 0;
+  __block NSInteger failureEventsCount = 0;
+  [GTXAnalytics setHandler:^(GTXAnalyticsEvent event) {
+    if (event == GTXAnalyticsEventChecksPerformed) {
+      successEventsCount += 1;
+    } else {
+      failureEventsCount += 1;
+    }
+  }];
+  NSObject *root = [self newInAccessibleElement];
+  NSObject *child1 = [self newAccessibleElement];
+  NSObject *child2 = [self newInAccessibleElement];
+  id<GTXChecking> checkFailIfChild1 = [GTXToolKit checkWithName:@"Foo"
+                                                          block:^BOOL(id _Nonnull element,
+                                                                      GTXErrorRefType errorOrNil) {
+                                                            return element != child1;
+                                                          }];
+  [self createTreeFromPreOrderTraversal:@[root,
+                                                  child1, child2, [NSNull null],
+                                                  ]];
+  [toolkit registerCheck:checkFailIfChild1];
+  NSError *error;
+  XCTAssertEqual(successEventsCount, 0);
+  XCTAssertEqual(failureEventsCount, 0);
+
+  XCTAssertFalse([toolkit checkAllElementsFromRootElements:@[root] error:nil]);
+  XCTAssertEqual(successEventsCount, 0);
+  XCTAssertEqual(failureEventsCount, 1);
+
+  XCTAssertFalse([toolkit checkAllElementsFromRootElements:@[root] error:&error]);
+  XCTAssertEqual(successEventsCount, 0);
+  XCTAssertEqual(failureEventsCount, 2);
+
+  XCTAssertTrue([toolkit checkAllElementsFromRootElements:@[child2] error:nil]);
+  XCTAssertEqual(successEventsCount, 1);
+  XCTAssertEqual(failureEventsCount, 2);
+
+  XCTAssertTrue([toolkit checkAllElementsFromRootElements:@[child2] error:&error]);
+  XCTAssertEqual(successEventsCount, 2);
+  XCTAssertEqual(failureEventsCount, 2);
+}
+
+- (void)testAnalyticsCanBeDisabled {
+  GTXToolKit *toolkit = [[GTXToolKit alloc] init];
+  __block NSInteger successEventsCount = 0;
+  __block NSInteger failureEventsCount = 0;
+  [GTXAnalytics setHandler:^(GTXAnalyticsEvent event) {
+    if (event == GTXAnalyticsEventChecksPerformed) {
+      successEventsCount += 1;
+    } else {
+      failureEventsCount += 1;
+    }
+  }];
+
+  GTXAnalytics.enabled = NO;
+
+  NSObject *root = [self newInAccessibleElement];
+  NSObject *child1 = [self newAccessibleElement];
+  NSObject *child2 = [self newInAccessibleElement];
+  id<GTXChecking> checkFailIfChild1 = [GTXToolKit checkWithName:@"Foo"
+                                                          block:^BOOL(id _Nonnull element,
+                                                                      GTXErrorRefType errorOrNil) {
+                                                            return element != child1;
+                                                          }];
+  [self createTreeFromPreOrderTraversal:@[root,
+                                                  child1, child2, [NSNull null],
+                                                  ]];
+  [toolkit registerCheck:checkFailIfChild1];
+  NSError *error;
+  XCTAssertEqual(successEventsCount, 0);
+  XCTAssertEqual(failureEventsCount, 0);
+
+  XCTAssertTrue([toolkit checkElement:root error:nil]);
+  XCTAssertTrue([toolkit checkElement:root error:&error]);
+  XCTAssertFalse([toolkit checkElement:child1 error:nil]);
+  XCTAssertFalse([toolkit checkElement:child1 error:&error]);
+  XCTAssertFalse([toolkit checkAllElementsFromRootElements:@[root] error:nil]);
+  XCTAssertFalse([toolkit checkAllElementsFromRootElements:@[root] error:&error]);
+  XCTAssertTrue([toolkit checkAllElementsFromRootElements:@[child2] error:nil]);
+  XCTAssertTrue([toolkit checkAllElementsFromRootElements:@[child2] error:&error]);
+
+  XCTAssertEqual(successEventsCount, 0);
+  XCTAssertEqual(failureEventsCount, 0);
+}
+
+- (void)testInvokingDefaultAnalyticsHandlerFailsWhenAnalyticsDisabled {
+  GTXAnalytics.enabled = NO;
+  XCTAssertThrows(GTXAnalytics.handler(GTXAnalyticsEventChecksPerformed));
+  XCTAssertThrows(GTXAnalytics.handler(GTXAnalyticsEventChecksFailed));
+}
+
+@end
diff --git a/Tests/UnitTests/GTXBaseTestCase.h b/Tests/UnitTests/GTXBaseTestCase.h
new file mode 100644
index 0000000..74fd27c
--- /dev/null
+++ b/Tests/UnitTests/GTXBaseTestCase.h
@@ -0,0 +1,31 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import <UIKit/UIKit.h>
+#import <XCTest/XCTest.h>
+
+#import "GTXCheckBlock.h"
+
+extern GTXCheckHandlerBlock noOpCheckBlock;
+
+@interface GTXBaseTestCase : XCTestCase
+
+- (NSObject *)newAccessibleElement;
+- (NSObject *)newInAccessibleElement;
+- (void)createTreeFromPreOrderTraversal:(NSArray *)preOrderTraversal;
+
+@end
+
diff --git a/Tests/UnitTests/GTXBaseTestCase.m b/Tests/UnitTests/GTXBaseTestCase.m
new file mode 100644
index 0000000..79b0a44
--- /dev/null
+++ b/Tests/UnitTests/GTXBaseTestCase.m
@@ -0,0 +1,64 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import <UIKit/UIKit.h>
+#import <XCTest/XCTest.h>
+
+#import "GTXBaseTestCase.h"
+
+GTXCheckHandlerBlock noOpCheckBlock = ^BOOL(id _Nonnull element, GTXErrorRefType errorOrNil) {
+  return YES;
+};
+
+@implementation GTXBaseTestCase
+
+- (NSObject *)newAccessibleElement {
+  NSObject *element = [[NSObject alloc] init];
+  element.isAccessibilityElement = YES;
+  return element;
+}
+
+- (NSObject *)newInAccessibleElement {
+  NSObject *element = [[NSObject alloc] init];
+  element.isAccessibilityElement = NO;
+  return element;
+}
+
+- (void)createTreeFromPreOrderTraversal:(NSArray *)preOrderTraversal {
+  //   A
+  //  / \
+  // B   C
+  //    / \
+  //   D   E
+  // Example Traversal: A, B, C, NSNull, NSNull, D, E, NSNull, (D's children).
+  NSMutableArray *queue = [[NSMutableArray alloc] init];
+  NSMutableArray *children = [[NSMutableArray alloc] init];
+  for (id element in preOrderTraversal) {
+    if ([element isKindOfClass:[NSNull class]]) {
+      // End of list has been reached.
+      [[queue firstObject] setAccessibilityElements:children];
+      [queue removeObjectAtIndex:0];
+      children = [[NSMutableArray alloc] init];
+    } else {
+      if ([queue count]) {
+        [children addObject:element];
+      }
+      [queue addObject:element];
+    }
+  }
+}
+
+@end
diff --git a/Tests/UnitTests/GTXChecksCollectionTests.m b/Tests/UnitTests/GTXChecksCollectionTests.m
new file mode 100644
index 0000000..39c445c
--- /dev/null
+++ b/Tests/UnitTests/GTXChecksCollectionTests.m
@@ -0,0 +1,243 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import <UIKit/UIKit.h>
+#import <XCTest/XCTest.h>
+
+#import "GTXChecksCollection.h"
+
+NSString * const kExpectedErrorDescription = @"Check \"Accessibility Label Not Punctuated\" failed";
+
+@interface GTXChecksCollectionTests : XCTestCase
+@end
+
+@implementation GTXChecksCollectionTests
+
+- (void)testGtxCheckForAXLabelNotPunctuated {
+  // Valid label.
+  [self assertGtxCheckNamed:kGTXCheckNameAccessibilityLabelNotPunctuated
+                   succeeds:YES
+                withElement:[self uiAccessibilityElementWithLabel:@"foo"]
+           errorDescription:nil];
+  // Empty label.
+  [self assertGtxCheckNamed:kGTXCheckNameAccessibilityLabelNotPunctuated
+                   succeeds:YES
+                withElement:[self uiAccessibilityElementWithLabel:@""]
+           errorDescription:nil];
+  // nil label.
+  [self assertGtxCheckNamed:kGTXCheckNameAccessibilityLabelNotPunctuated
+                   succeeds:YES
+                withElement:[self uiAccessibilityElementWithLabel:nil]
+           errorDescription:nil];
+  // Label ending in period.
+  [self assertGtxCheckNamed:kGTXCheckNameAccessibilityLabelNotPunctuated
+                   succeeds:NO
+                withElement:[self uiAccessibilityElementWithLabel:@"foo."]
+           errorDescription:kExpectedErrorDescription];
+  // Label ending in period with trailing space.
+  [self assertGtxCheckNamed:kGTXCheckNameAccessibilityLabelNotPunctuated
+                   succeeds:NO
+                withElement:[self uiAccessibilityElementWithLabel:@"foo. "]
+           errorDescription:kExpectedErrorDescription];
+  // Single character label ending in period.
+  [self assertGtxCheckNamed:kGTXCheckNameAccessibilityLabelNotPunctuated
+                   succeeds:NO
+                withElement:[self uiAccessibilityElementWithLabel:@"f."]
+           errorDescription:kExpectedErrorDescription];
+  // Single character label with just period.
+  [self assertGtxCheckNamed:kGTXCheckNameAccessibilityLabelNotPunctuated
+                   succeeds:NO
+                withElement:[self uiAccessibilityElementWithLabel:@"."]
+           errorDescription:kExpectedErrorDescription];
+  // UILabel with text ending in period must pass.
+  UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 1, 1)];
+  label.text = @"foo.";
+  [self assertGtxCheckNamed:kGTXCheckNameAccessibilityLabelNotPunctuated
+                   succeeds:YES
+                withElement:label
+           errorDescription:kExpectedErrorDescription];
+}
+
+- (void)testGtxCheckForAXLabelNotPunctuatedWorksWithAttributedStrings {
+  id accessibilityLabel = [[NSAttributedString alloc] initWithString:@"foo"];
+  [self assertGtxCheckNamed:kGTXCheckNameAccessibilityLabelNotPunctuated
+                   succeeds:YES
+                withElement:[self uiAccessibilityElementWithLabel:accessibilityLabel]
+           errorDescription:nil];
+
+  accessibilityLabel = [[NSAttributedString alloc] initWithString:@"foo."];
+  [self assertGtxCheckNamed:kGTXCheckNameAccessibilityLabelNotPunctuated
+                   succeeds:NO
+                withElement:[self uiAccessibilityElementWithLabel:accessibilityLabel]
+           errorDescription:kExpectedErrorDescription];
+}
+
+- (void)testGtxCheckForAXLabelNotPunctuatedWorksWithMutableStrings {
+  id accessibilityLabel = [NSMutableString stringWithString:@"foo"];
+  [self assertGtxCheckNamed:kGTXCheckNameAccessibilityLabelNotPunctuated
+                   succeeds:YES
+                withElement:[self uiAccessibilityElementWithLabel:accessibilityLabel]
+           errorDescription:nil];
+
+  accessibilityLabel = [NSMutableString stringWithString:@"foo."];
+  [self assertGtxCheckNamed:kGTXCheckNameAccessibilityLabelNotPunctuated
+                   succeeds:NO
+                withElement:[self uiAccessibilityElementWithLabel:accessibilityLabel]
+           errorDescription:kExpectedErrorDescription];
+}
+
+- (void)testGtxCheckForAXLabelPresent {
+  NSString * const expectedErrorDescription = @"Check \"Accessibility Label Present\" failed";
+  // Valid label.
+  [self assertGtxCheckNamed:kGTXCheckNameAccessibilityLabelPresent
+                   succeeds:YES
+                withElement:[self uiAccessibilityElementWithLabel:@"foo"]
+           errorDescription:nil];
+  // Label with just space.
+  [self assertGtxCheckNamed:kGTXCheckNameAccessibilityLabelPresent
+                   succeeds:NO
+                withElement:[self uiAccessibilityElementWithLabel:@" "]
+           errorDescription:expectedErrorDescription];
+  // Empty string label.
+  [self assertGtxCheckNamed:kGTXCheckNameAccessibilityLabelPresent
+                   succeeds:NO
+                withElement:[self uiAccessibilityElementWithLabel:@""]
+           errorDescription:expectedErrorDescription];
+  // Element with no label.
+  [self assertGtxCheckNamed:kGTXCheckNameAccessibilityLabelPresent
+                   succeeds:NO
+                withElement:[self uiAccessibilityElementWithLabel:nil]
+           errorDescription:expectedErrorDescription];
+}
+
+- (void)testGtxCheckForAXLabelPresentWorksForTextElementsWithNotText {
+  [self assertGtxCheckNamed:kGTXCheckNameAccessibilityLabelPresent
+                   succeeds:YES
+                withElement:[[UILabel alloc] initWithFrame:CGRectZero]
+           errorDescription:nil];
+  [self assertGtxCheckNamed:kGTXCheckNameAccessibilityLabelPresent
+                   succeeds:YES
+                withElement:[[UITextField alloc] initWithFrame:CGRectZero]
+           errorDescription:nil];
+  [self assertGtxCheckNamed:kGTXCheckNameAccessibilityLabelPresent
+                   succeeds:YES
+                withElement:[[UITextView alloc] initWithFrame:CGRectZero]
+           errorDescription:nil];
+  UIAccessibilityElement *textElement = [self uiAccessibilityElementWithLabel:@""];
+  textElement.accessibilityTraits = UIAccessibilityTraitStaticText;
+  [self assertGtxCheckNamed:kGTXCheckNameAccessibilityLabelPresent
+                   succeeds:YES
+                withElement:textElement
+           errorDescription:nil];
+}
+
+- (void)testGtxCheckForAXLabelPresentWorksWithAttributedStrings {
+  id accessibilityLabel = [[NSAttributedString alloc] initWithString:@"foo"];
+  [self assertGtxCheckNamed:kGTXCheckNameAccessibilityLabelPresent
+                   succeeds:YES
+                withElement:[self uiAccessibilityElementWithLabel:accessibilityLabel]
+           errorDescription:nil];
+}
+
+- (void)testGtxCheckForAXLabelPresentWorksWithMutableStrings {
+  id accessibilityLabel = [NSMutableString stringWithString:@"foo"];
+  [self assertGtxCheckNamed:kGTXCheckNameAccessibilityLabelPresent
+                   succeeds:YES
+                withElement:[self uiAccessibilityElementWithLabel:accessibilityLabel]
+           errorDescription:nil];
+}
+
+- (void)testGtxCheckForAXTraitsConflict {
+  NSString * const expectedErrorDescription =
+      @"Check \"Accessibility Traits Don't Conflict\" failed";
+  // Check for a valid trait (in valid range, no conflict).
+  [self assertGtxCheckNamed:kGTXCheckNameAccessibilityTraitsDontConflict
+                   succeeds:YES
+                withElement:[self uiAccessibilityElementWithTraits:UIAccessibilityTraitButton]
+           errorDescription:nil];
+  [self assertGtxCheckNamed:kGTXCheckNameAccessibilityTraitsDontConflict
+                   succeeds:YES
+                withElement:[self uiAccessibilityElementWithTraits:
+                             (UIAccessibilityTraitLink | UIAccessibilityTraitAdjustable)]
+           errorDescription:nil];
+  // Check for conflict rule no.1 (conflict among button, link, search field, keyboard key).
+  [self assertGtxCheckNamed:kGTXCheckNameAccessibilityTraitsDontConflict
+                   succeeds:NO
+                withElement:[self uiAccessibilityElementWithTraits:
+                             (UIAccessibilityTraitKeyboardKey | UIAccessibilityTraitSearchField)]
+           errorDescription:expectedErrorDescription];
+  // Check for conflict rule no.2 (conflict between button and adjustable (slider)).
+  [self assertGtxCheckNamed:kGTXCheckNameAccessibilityTraitsDontConflict
+                   succeeds:NO
+                withElement:[self uiAccessibilityElementWithTraits:
+                             (UIAccessibilityTraitAdjustable | UIAccessibilityTraitButton)]
+           errorDescription:expectedErrorDescription];
+}
+
+#pragma mark - Private
+
+/**
+ *  @return An accessibility element whose accessibility label is set to the specified @c label.
+ */
+- (UIAccessibilityElement *)uiAccessibilityElementWithLabel:(id)label {
+  NSAssert(!label || [label isKindOfClass:[NSString class]] ||
+           [label isKindOfClass:[NSAttributedString class]],
+           @"Provided label must be a NSString or NSAttributedString object.");
+  NSObject *container = [[NSObject alloc] init];
+  UIAccessibilityElement *element =
+      [[UIAccessibilityElement alloc] initWithAccessibilityContainer:container];
+  element.accessibilityLabel = label;
+  return element;
+}
+
+/**
+ *  @return An accessibility element whose accessibility trait is set to the specified traits.
+ */
+- (UIAccessibilityElement *)uiAccessibilityElementWithTraits:(UIAccessibilityTraits)traits {
+  NSObject *container = [[NSObject alloc] init];
+  UIAccessibilityElement *element =
+      [[UIAccessibilityElement alloc] initWithAccessibilityContainer:container];
+  element.accessibilityTraits = traits;
+  return element;
+}
+
+/**
+ *  Asserts that the named GTXCheck succeeds or fails with the specified element.
+ *
+ *  @param name             The name of the check.
+ *  @param expectedSuccess  A BOOL indicating if the check must succeed or not.
+ *  @param element          The element on which to apply the check.
+ *  @param descriptionOrNil An optional expected error description if the check is expected to fail
+ *                          and the error description must also be verified.
+ */
+- (void)assertGtxCheckNamed:(NSString *)name
+                   succeeds:(BOOL)expectedSuccess
+                withElement:(id)element
+           errorDescription:(nullable NSString *)descriptionOrNil {
+  id<GTXChecking> gtxCheck = [GTXChecksCollection GTXCheckWithName:name];
+  NSError *error = nil;
+  BOOL success = [gtxCheck check:element error:&error];
+  XCTAssertEqual(success, expectedSuccess);
+  if (expectedSuccess) {
+    XCTAssertNil(error);
+  } else {
+    XCTAssertNotNil(error);
+    XCTAssertTrue([[error description] containsString:descriptionOrNil],
+                  @"%@ was not present in %@", descriptionOrNil, [error description]);
+  }
+}
+
+@end
diff --git a/Tests/UnitTests/GTXImageAndColorUtilsTests.m b/Tests/UnitTests/GTXImageAndColorUtilsTests.m
new file mode 100644
index 0000000..c18702b
--- /dev/null
+++ b/Tests/UnitTests/GTXImageAndColorUtilsTests.m
@@ -0,0 +1,170 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import <UIKit/UIKit.h>
+#import <XCTest/XCTest.h>
+
+#import "GTXImageAndColorUtils.h"
+
+@interface GTXImageAndColorUtils (ExposedForTesting)
++ (UIColor *)gtx_shiftedColorWithColor:(UIColor *)color;
++ (CGFloat)gtx_contrastRatioWithTextElementImage:(UIImage *)original
+                    textElementColorShiftedImage:(UIImage *)colorShifted;
+@end
+
+@interface GTXImageAndColorUtilsTests : XCTestCase
+@end
+
+@implementation GTXImageAndColorUtilsTests
+
+- (void)testLuminanceAlgorithmWorks {
+  const CGFloat kAccuracy = 0.01f;
+  // Values computed using http://springmeier.org/www/contrastcalculator/index.php
+  XCTAssertEqualWithAccuracy([GTXImageAndColorUtils luminanceWithColor:[UIColor redColor]],
+                             0.21f, kAccuracy);
+  XCTAssertEqualWithAccuracy([GTXImageAndColorUtils luminanceWithColor:[UIColor blueColor]],
+                             0.07f, kAccuracy);
+  XCTAssertEqualWithAccuracy([GTXImageAndColorUtils luminanceWithColor:[UIColor greenColor]],
+                             0.72f, kAccuracy);
+  XCTAssertEqualWithAccuracy([GTXImageAndColorUtils luminanceWithColor:[UIColor whiteColor]],
+                             1.0f, kAccuracy);
+  XCTAssertEqualWithAccuracy([GTXImageAndColorUtils luminanceWithColor:[UIColor blackColor]],
+                             0, kAccuracy);
+  XCTAssertEqualWithAccuracy(
+      [GTXImageAndColorUtils luminanceWithColor:[UIColor colorWithWhite:0.5f alpha:1.0f]],
+      0.22f, kAccuracy);
+
+  // We are using colorWithRed:green:blue:alpha: instead of UIColor::orangeColor to ensure known
+  // and testable values for red, green and blue.
+  UIColor *orange = [UIColor colorWithRed:1.0f green:0.5f blue:0.0f alpha:1.0f];
+  XCTAssertEqualWithAccuracy([GTXImageAndColorUtils luminanceWithColor:orange], 0.37f, kAccuracy);
+
+  // We are using colorWithRed:green:blue:alpha: instead of UIColor::purpleColor to ensure known
+  // and testable values for red, green and blue.
+  UIColor *purple = [UIColor colorWithRed:0.5f green:0.25f blue:0.5f alpha:1.0f];
+  XCTAssertEqualWithAccuracy([GTXImageAndColorUtils luminanceWithColor:purple], 0.1f, kAccuracy);
+}
+
+- (void)testShiftedColorsAreDifferent {
+  [self gtxtest_assertColor:[UIColor blackColor]
+       isDifferentFromColor:[GTXImageAndColorUtils gtx_shiftedColorWithColor:[UIColor blackColor]]];
+  [self gtxtest_assertColor:[UIColor whiteColor]
+       isDifferentFromColor:[GTXImageAndColorUtils gtx_shiftedColorWithColor:[UIColor whiteColor]]];
+  UIColor *grey = [UIColor colorWithWhite:0.5f alpha:1.0f];
+  [self gtxtest_assertColor:grey
+       isDifferentFromColor:[GTXImageAndColorUtils gtx_shiftedColorWithColor:grey]];
+}
+
+- (void)testContrastRatioIsComputedAccuratelyForLargeImages {
+  // Create a large grey image with light grey rect inside it.
+  CGSize imageSize = CGSizeMake(1000, 1000);
+  CGRect rect = CGRectMake(200, 200, 500, 500);
+  UIColor *testDarkGrey = [UIColor colorWithWhite:0.4 alpha:1.0];
+  UIColor *testLightGrey = [UIColor colorWithWhite:0.5 alpha:1.0];
+  [self gtx_assertContrastRatioIsSufficientlyAccurateInImageOfSize:imageSize
+                                                             color:testLightGrey
+                                                          withRect:rect
+                                                           ofColor:testDarkGrey];
+}
+
+- (void)testContrastRatioIsComputedAccuratelyForSmallImages {
+  // Create a small red image with purple rect inside it.
+  CGSize imageSize = CGSizeMake(40, 40);
+  CGRect rect = CGRectMake(0, 0, 10, 10);
+  [self gtx_assertContrastRatioIsSufficientlyAccurateInImageOfSize:imageSize
+                                                             color:[UIColor redColor]
+                                                          withRect:rect
+                                                           ofColor:[UIColor purpleColor]];
+}
+
+#pragma mark - Private
+
+/**
+ *  Asserts that the contrast ratio of colors in an image is same that computed from colors
+ *  directly.
+ *
+ *  @param size            Image size.
+ *  @param backgroundColor Background color on the image.
+ *  @param rect            Rectangle to be filled with @c rectColor.
+ *  @param rectColor       Fill color for @c rect.
+ */
+
+- (void)gtx_assertContrastRatioIsSufficientlyAccurateInImageOfSize:(CGSize)size
+                                                             color:(UIColor *)backgroundColor
+                                                          withRect:(CGRect)rect
+                                                           ofColor:(UIColor *)rectColor {
+  // Create an image with the given colors.
+  UIImage *imageWithRect = [self gtxtest_imageOfColor:backgroundColor
+                                               inSize:size
+                                      withRectOfColor:rectColor
+                                                   atRect:rect];
+
+  // Create the same image with rectColor replaced by its shifted color.
+  UIColor *shiftedColor = [GTXImageAndColorUtils gtx_shiftedColorWithColor:rectColor];
+  UIImage *imageWithShiftedColorRect = [self gtxtest_imageOfColor:backgroundColor
+                                                           inSize:size
+                                                  withRectOfColor:shiftedColor
+                                                               atRect:rect];
+
+  // Assert that contrast ratio of the image is almost the same as contrast ratio of individual
+  // colors.
+  CGFloat luminanceOfFirstColor = [GTXImageAndColorUtils luminanceWithColor:backgroundColor];
+  CGFloat luminanceOfSecondColor = [GTXImageAndColorUtils luminanceWithColor:rectColor];
+  CGFloat contrastRatioOfColors =
+      [GTXImageAndColorUtils contrastRatioWithLuminaceOfFirstColor:luminanceOfFirstColor
+                                         andLuminanceOfSecondColor:luminanceOfSecondColor];
+  CGFloat contrastRatioInImage =
+      [GTXImageAndColorUtils gtx_contrastRatioWithTextElementImage:imageWithRect
+                                      textElementColorShiftedImage:imageWithShiftedColorRect];
+  XCTAssertEqualWithAccuracy(contrastRatioInImage, contrastRatioOfColors, kContrastRatioAccuracy);
+}
+
+/**
+ *  Create an image with the given color and draws a rect filled with the given fill color.
+ *
+ *  @param backgroundColor Background color on the image.
+ *  @param size            Image size.
+ *  @param rectColor       Fill color for @c rect.
+ *  @param rect            Rectangle to be filled with @c rectColor.
+ *
+ *  @return An UIImage with the given color.
+ */
+
+- (UIImage *)gtxtest_imageOfColor:(UIColor *)backgroundColor
+                           inSize:(CGSize)imageSize
+                  withRectOfColor:(UIColor *)rectColor
+                           atRect:(CGRect)rect {
+  UIGraphicsBeginImageContext(imageSize);
+  CGContextRef context = UIGraphicsGetCurrentContext();
+  CGContextSetFillColorWithColor(context, backgroundColor.CGColor);
+  CGContextFillRect(context, CGRectMake(0, 0, imageSize.width, imageSize.height));
+  CGContextSetFillColorWithColor(context, rectColor.CGColor);
+  CGContextFillRect(context, rect);
+  return UIGraphicsGetImageFromCurrentImageContext();
+}
+
+- (void)gtxtest_assertColor:(UIColor *)first isDifferentFromColor:(UIColor *)second {
+  CGFloat firstRed, firstGreen, firstBlue;
+  CGFloat secondRed, secondGreen, secondBlue;
+  [first getRed:&firstRed green:&firstGreen blue:&firstBlue alpha:NULL];
+  [second getRed:&secondRed green:&secondGreen blue:&secondBlue alpha:NULL];
+  const CGFloat accuracy = 0.001f;
+  XCTAssertNotEqualWithAccuracy(firstRed, secondRed, accuracy);
+  XCTAssertNotEqualWithAccuracy(firstBlue, secondBlue, accuracy);
+  XCTAssertNotEqualWithAccuracy(firstGreen, secondGreen, accuracy);
+}
+
+@end
diff --git a/Tests/UnitTests/GTXTestAccessibilityElements.h b/Tests/UnitTests/GTXTestAccessibilityElements.h
new file mode 100644
index 0000000..1cc70cf
--- /dev/null
+++ b/Tests/UnitTests/GTXTestAccessibilityElements.h
@@ -0,0 +1,48 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+/**
+ *  Interfaces to create various test accessibility elements.
+ */
+#import <UIKit/UIKit.h>
+
+/**
+ *  Interface for an UI element that provides child accessibility elements via
+ *  @selector(accessibilityElements).
+ */
+@interface GTXTestAccessibilityElementA : NSObject
+@property(nonatomic, assign) BOOL isAccessibilityElementOveride;
+@property(nonatomic, strong) NSArray *accessibilityElementsOveride;
+@end
+
+/**
+ *  Interface for an UI element that provides child accessibility elements via
+ *  @selector(accessibilityElementAtIndex:).
+ */
+@interface GTXTestAccessibilityElementB : NSObject
+@property(nonatomic, assign) BOOL isAccessibilityElementOveride;
+@property(nonatomic, strong) NSArray *accessibilityElementsAtIndexOveride;
+@end
+
+/**
+ *  Interface for an UI element that provides child accessibility elements via
+ *  @selector(accessibilityElementAtIndex:) and @selector(accessibilityElements).
+ */
+@interface GTXTestAccessibilityElementFull : NSObject
+@property(nonatomic, assign) BOOL isAccessibilityElementOveride;
+@property(nonatomic, strong) NSArray *accessibilityElementsOveride;
+@property(nonatomic, strong) NSArray *accessibilityElementsAtIndexOveride;
+@end
diff --git a/Tests/UnitTests/GTXTestAccessibilityElements.m b/Tests/UnitTests/GTXTestAccessibilityElements.m
new file mode 100644
index 0000000..e51f318
--- /dev/null
+++ b/Tests/UnitTests/GTXTestAccessibilityElements.m
@@ -0,0 +1,91 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "GTXTestAccessibilityElements.h"
+
+#import <UIKit/UIKit.h>
+
+@implementation GTXTestAccessibilityElementA
+
+- (instancetype)init {
+  self = [super init];
+  if (self) {
+    _isAccessibilityElementOveride = YES;
+  }
+  return self;
+}
+
+- (BOOL)isAccessibilityElement {
+  return self.isAccessibilityElementOveride;
+}
+
+- (NSArray *)accessibilityElements {
+  return self.accessibilityElementsOveride;
+}
+
+@end
+
+@implementation GTXTestAccessibilityElementB
+
+- (instancetype)init {
+  self = [super init];
+  if (self) {
+    _isAccessibilityElementOveride = YES;
+  }
+  return self;
+}
+
+- (BOOL)isAccessibilityElement {
+  return self.isAccessibilityElementOveride;
+}
+
+- (NSInteger)accessibilityElementCount {
+  return (NSInteger)[self.accessibilityElementsAtIndexOveride count];
+}
+
+- (NSArray *)accessibilityElementAtIndex:(NSInteger)index {
+  return [self.accessibilityElementsAtIndexOveride objectAtIndex:(NSUInteger)index];
+}
+
+@end
+
+@implementation GTXTestAccessibilityElementFull
+
+- (instancetype)init {
+  self = [super init];
+  if (self) {
+    _isAccessibilityElementOveride = YES;
+  }
+  return self;
+}
+
+- (BOOL)isAccessibilityElement {
+  return self.isAccessibilityElementOveride;
+}
+
+- (NSArray *)accessibilityElements {
+  return self.accessibilityElementsOveride;
+}
+
+- (NSInteger)accessibilityElementCount {
+  return (NSInteger)[self.accessibilityElementsAtIndexOveride count];
+}
+
+- (NSArray *)accessibilityElementAtIndex:(NSInteger)index {
+  return [self.accessibilityElementsAtIndexOveride objectAtIndex:(NSUInteger)index];
+}
+
+@end
diff --git a/Tests/UnitTests/GTXTestSuiteTests.m b/Tests/UnitTests/GTXTestSuiteTests.m
new file mode 100644
index 0000000..474d1c0
--- /dev/null
+++ b/Tests/UnitTests/GTXTestSuiteTests.m
@@ -0,0 +1,201 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import <XCTest/XCTest.h>
+
+#import "GTXTestSuite.h"
+
+#pragma mark - Test Classes
+
+@interface GTXTestSuiteWithNoTests : NSObject
+@end
+
+@interface GTXTestSuiteWithOneTest : NSObject
+@end
+
+@interface GTXTestSuiteWithTwoTest : NSObject
+@end
+
+@interface GTXTestEmptyInheritedSuite : GTXTestSuiteWithOneTest
+@end
+
+@interface GTXTestBaseSuiteWithOneTest : NSObject
+@end
+
+@interface GTXTestInheritedSuiteWithOneTest : GTXTestBaseSuiteWithOneTest
+@end
+
+#pragma mark - Test Class Implementations
+
+@implementation GTXTestSuiteWithNoTests
+@end
+
+@implementation GTXTestSuiteWithOneTest
+
+- (void)dummyMethodWithWordtest {
+  // nothing to do
+}
+
+- (void)testFirst {
+  // nothing to test
+}
+
+@end
+
+@implementation GTXTestSuiteWithTwoTest
+
+- (void)testFirst {
+  // nothing to test
+}
+
+- (void)dummyMethodWithWordtest {
+  // nothing to do
+}
+
+- (void)testSecond {
+  // nothing to test
+}
+
+- (void)secondDummyMethodWithWordtest {
+  // nothing to do
+}
+
+@end
+
+@implementation GTXTestEmptyInheritedSuite
+@end
+
+@implementation GTXTestBaseSuiteWithOneTest
+
+- (void)testBaseSuite {
+  // nothing to test
+}
+
+@end
+
+@implementation GTXTestInheritedSuiteWithOneTest
+
+- (void)testInheritedSuite {
+  // nothing to test
+}
+
+@end
+
+#pragma mark - Tests
+
+@interface GTXTestSuiteTests : XCTestCase
+@end
+
+@implementation GTXTestSuiteTests
+
+- (void)testGTXTestSuiteCanLoadEmptySuite {
+  GTXTestSuite *suite =
+      [GTXTestSuite suiteWithAllTestsInClass:[GTXTestSuiteWithNoTests class]];
+  XCTAssertEqual(suite.tests.count, 0u);
+
+  suite = [GTXTestSuite suiteWithClass:[GTXTestSuiteWithNoTests class] andTests:nil];
+  XCTAssertEqual(suite.tests.count, 0u);
+}
+
+- (void)testGTXTestSuiteCanLoadOneTestCase {
+  GTXTestSuite *suite =
+      [GTXTestSuite suiteWithAllTestsInClass:[GTXTestSuiteWithOneTest class]];
+  XCTAssertEqual(suite.tests.count, 1u);
+
+  suite = [GTXTestSuite suiteWithClass:[GTXTestSuiteWithOneTest class]
+                              andTests:@selector(testFirst), nil];
+  XCTAssertEqual(suite.tests.count, 1u);
+
+  suite = [GTXTestSuite suiteWithClass:[GTXTestSuiteWithTwoTest class]
+                              andTests:@selector(testFirst), nil];
+  XCTAssertEqual(suite.tests.count, 1u);
+}
+
+- (void)testGTXTestSuiteCanSkipLoadingTestsFromSuiteWithOneTest {
+  GTXTestSuite *suite = [GTXTestSuite suiteWithClass:[GTXTestSuiteWithOneTest class]
+                                         exceptTests:@selector(testFirst), nil];
+  XCTAssertEqual(suite.tests.count, 0u);
+}
+
+- (void)testGTXTestSuiteCanSkipLoadingTestsFromSuiteWithTwoTests {
+  // Skip one test.
+  GTXTestSuite *suite = [GTXTestSuite suiteWithClass:[GTXTestSuiteWithTwoTest class]
+                                         exceptTests:@selector(testFirst), nil];
+  XCTAssertEqual(suite.tests.count, 1u);
+
+  // Skip two tests.
+  suite =
+      [GTXTestSuite suiteWithClass:[GTXTestSuiteWithTwoTest class]
+                       exceptTests:@selector(testFirst), @selector(testSecond), nil];
+  XCTAssertEqual(suite.tests.count, 0u);
+}
+
+- (void)testGTXTestSuiteCanLoadAllTests {
+  GTXTestSuite *suite =
+      [GTXTestSuite suiteWithAllTestsInClass:[GTXTestSuiteWithOneTest class]];
+  XCTAssertEqual(suite.tests.count, 1u);
+
+  suite = [GTXTestSuite suiteWithAllTestsInClass:[GTXTestSuiteWithTwoTest class]];;
+  XCTAssertEqual(suite.tests.count, 2u);
+}
+
+- (void)testGTXTestSuiteCanBeAppended {
+  GTXTestSuite *suite1 =
+      [GTXTestSuite suiteWithAllTestsInClass:[GTXTestSuiteWithOneTest class]];
+  GTXTestSuite *suite2 =
+      [GTXTestSuite suiteWithAllTestsInClass:[GTXTestSuiteWithTwoTest class]];
+  XCTAssertEqual([suite2 suiteByAppendingSuite:suite1].tests.count, 3u);
+  XCTAssertEqual([suite1 suiteByAppendingSuite:suite2].tests.count, 3u);
+}
+
+- (void)testGTXTestSuiteOnlyAllowsValidMethods {
+  void (^createInvalidSuite)(void) = ^ {
+    [GTXTestSuite suiteWithClass:[GTXTestSuiteWithOneTest class]
+                        andTests:@selector(testSecond), nil];
+  };
+  XCTAssertThrows(createInvalidSuite());
+}
+
+- (void)testGTXTestSuiteDoesNotLoadInheritedTestsByDefault {
+  Class inheritedClass = [GTXTestEmptyInheritedSuite class];
+  Class baseClass = [GTXTestSuiteWithOneTest class];
+  XCTAssertEqual([inheritedClass superclass], baseClass);
+
+  GTXTestSuite *baseSuite = [GTXTestSuite suiteWithAllTestsInClass:baseClass];
+  XCTAssertEqual(baseSuite.tests.count, 1u);
+  GTXTestSuite *inheritedSuite = [GTXTestSuite suiteWithAllTestsInClass:inheritedClass];
+  XCTAssertEqual(inheritedSuite.tests.count, 0u);
+}
+
+- (void)testGTXTestSuiteCanLoadInheritedTests {
+  Class baseClass = [GTXTestBaseSuiteWithOneTest class];
+  Class inheritedClass = [GTXTestInheritedSuiteWithOneTest class];
+  XCTAssertEqual([inheritedClass superclass], baseClass);
+
+  GTXTestSuite *baseSuite = [GTXTestSuite suiteWithAllTestsInClass:baseClass];
+  XCTAssertEqual(baseSuite.tests.count, 1u);
+  GTXTestSuite *inheritedSuite = [GTXTestSuite suiteWithAllTestsInClass:inheritedClass];
+  XCTAssertEqual(inheritedSuite.tests.count, 1u);
+  GTXTestSuite *fullSuite =
+      [GTXTestSuite suiteWithAllTestsFromAllClassesInheritedFromClass:baseClass];
+  // Expected count has baseSuite.tests.count twice because base class tests are also part of
+  // inherited class tests.
+  XCTAssertEqual(fullSuite.tests.count,
+                 baseSuite.tests.count +
+                 (baseSuite.tests.count + inheritedSuite.tests.count));
+}
+
+@end
diff --git a/Tests/UnitTests/GTXToolKitTests.m b/Tests/UnitTests/GTXToolKitTests.m
new file mode 100644
index 0000000..7712494
--- /dev/null
+++ b/Tests/UnitTests/GTXToolKitTests.m
@@ -0,0 +1,155 @@
+//
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import <UIKit/UIKit.h>
+#import <XCTest/XCTest.h>
+
+#import "GTXToolKit.h"
+#import "GTXAnalytics.h"
+#import "GTXBaseTestCase.h"
+
+@interface GTXTestElementClass1 : NSObject
+@end
+
+@implementation GTXTestElementClass1
+@end
+
+@interface GTXTestElementClass2 : NSObject
+@end
+
+@implementation GTXTestElementClass2
+@end
+
+@interface GTXToolKitTests : GTXBaseTestCase
+@end
+
+@implementation GTXToolKitTests
+
+- (void)testRegisterCheckRaisesExceptionForDuplicateCheckNames {
+  GTXToolKit *toolkit = [[GTXToolKit alloc] init];
+  NSString *checkName = @"foo";
+  [toolkit registerCheck:[GTXToolKit checkWithName:checkName block:noOpCheckBlock]];
+  XCTAssertThrows([toolkit registerCheck:[GTXToolKit checkWithName:checkName
+                                                             block:noOpCheckBlock]]);
+}
+
+- (void)testCheckElementReportsFailures {
+  GTXToolKit *toolkit = [[GTXToolKit alloc] init];
+  NSObject *failingElement = [self newAccessibleElement];
+  NSObject *passingElement = [self newAccessibleElement];
+  id<GTXChecking> check = [GTXToolKit checkWithName:@"Foo"
+                                              block:^BOOL(id _Nonnull element,
+                                                          GTXErrorRefType errorOrNil) {
+                                                return element == passingElement;
+                                              }];
+  [toolkit registerCheck:check];
+  NSError *error;
+  XCTAssertTrue([toolkit checkElement:passingElement error:nil]);
+  XCTAssertTrue([toolkit checkElement:passingElement error:&error]);
+  XCTAssertFalse([toolkit checkElement:failingElement error:nil]);
+  XCTAssertFalse([toolkit checkElement:failingElement error:&error]);
+}
+
+- (void)testCheckElementsFromRootElementsReportsFailures {
+  GTXToolKit *toolkit = [[GTXToolKit alloc] init];
+  NSObject *root = [self newInAccessibleElement];
+  NSObject *child1 = [self newAccessibleElement];
+  NSObject *child2 = [self newInAccessibleElement];
+  id<GTXChecking> checkFailIfChild1 = [GTXToolKit checkWithName:@"Foo"
+                                                           block:^BOOL(id _Nonnull element,
+                                                                       GTXErrorRefType errorOrNil) {
+                                                             return element != child1;
+                                                           }];
+  [self createTreeFromPreOrderTraversal:@[root,
+                                                  child1, child2, [NSNull null],
+                                                  ]];
+  [toolkit registerCheck:checkFailIfChild1];
+  NSError *error;
+  XCTAssertFalse([toolkit checkAllElementsFromRootElements:@[root] error:nil]);
+  XCTAssertFalse([toolkit checkAllElementsFromRootElements:@[root] error:&error]);
+  XCTAssertTrue([toolkit checkAllElementsFromRootElements:@[child2] error:nil]);
+  XCTAssertTrue([toolkit checkAllElementsFromRootElements:@[child2] error:&error]);
+}
+
+- (void)testCheckElementsFromRootElementsSkipsHiddenAXElements {
+  GTXToolKit *toolkit = [[GTXToolKit alloc] init];
+  NSObject *root = [self newAccessibleElement];
+  // Since root is an accessibile element its children are hidden.
+  NSObject *child1 = [self newAccessibleElement];
+  NSObject *child2 = [self newInAccessibleElement];
+  id<GTXChecking> checkFailIfChild1 = [GTXToolKit checkWithName:@"Foo"
+                                                          block:^BOOL(id _Nonnull element,
+                                                                      GTXErrorRefType errorOrNil) {
+                                                            return element != child1;
+                                                          }];
+  [self createTreeFromPreOrderTraversal:@[root,
+                                                  child1, child2, [NSNull null],
+                                                  ]];
+  [toolkit registerCheck:checkFailIfChild1];
+  NSError *error;
+  XCTAssertTrue([toolkit checkAllElementsFromRootElements:@[root] error:nil]);
+  XCTAssertTrue([toolkit checkAllElementsFromRootElements:@[root] error:&error]);
+}
+
+- (void)testIgnoreElementAPIIgnoresElementsFromChecks {
+  GTXToolKit *toolkit = [[GTXToolKit alloc] init];
+  NSObject *failingElement = [self newAccessibleElement];
+  id<GTXChecking> check = [GTXToolKit checkWithName:@"Foo"
+                                              block:^BOOL(id _Nonnull element,
+                                                          GTXErrorRefType errorOrNil) {
+                                                return NO;
+                                              }];
+  [toolkit registerCheck:check];
+  XCTAssertFalse([toolkit checkElement:failingElement error:nil]);
+  [toolkit ignoreElementsOfClassNamed:NSStringFromClass([failingElement class])];
+  XCTAssertTrue([toolkit checkElement:failingElement error:nil]);
+}
+
+- (void)testIgnoreElementAPIIgnoresElementsFromSpecificChecks {
+  GTXTestElementClass1 *check1FailingElement = [[GTXTestElementClass1 alloc] init];
+  check1FailingElement.isAccessibilityElement = YES;
+
+  GTXTestElementClass2 *allChecksFailingElement = [[GTXTestElementClass2 alloc] init];
+  allChecksFailingElement.isAccessibilityElement = YES;
+
+  NSString *check1Name = @"Check 1";
+  NSString *check2Name = @"Check 2";
+  id<GTXChecking> check1 = [GTXToolKit checkWithName:check1Name
+                                               block:^BOOL(id _Nonnull element,
+                                                           GTXErrorRefType errorOrNil) {
+                                                 return NO;
+                                               }];
+  id<GTXChecking> check2 = [GTXToolKit checkWithName:check2Name
+                                               block:^BOOL(id _Nonnull element,
+                                                           GTXErrorRefType errorOrNil) {
+                                                 return element != allChecksFailingElement;
+                                               }];
+
+  GTXToolKit *toolkit1 = [[GTXToolKit alloc] init];
+  [toolkit1 registerCheck:check1];
+  [toolkit1 registerCheck:check2];
+  XCTAssertFalse([toolkit1 checkElement:check1FailingElement error:nil]);
+  XCTAssertFalse([toolkit1 checkElement:allChecksFailingElement error:nil]);
+
+  [toolkit1 ignoreElementsOfClassNamed:NSStringFromClass([check1FailingElement class])];
+  XCTAssertTrue([toolkit1 checkElement:check1FailingElement error:nil]);
+  XCTAssertFalse([toolkit1 checkElement:allChecksFailingElement error:nil]);
+
+  [toolkit1 ignoreElementsOfClassNamed:NSStringFromClass([allChecksFailingElement class])];
+  XCTAssertTrue([toolkit1 checkElement:allChecksFailingElement error:nil]);
+}
+
+@end
diff --git a/g3doc/features.md b/g3doc/features.md
new file mode 100644
index 0000000..26af1c5
--- /dev/null
+++ b/g3doc/features.md
@@ -0,0 +1,61 @@
+GtxChecker is the entry point for running accessibility checks. Understand some
+of its features can help you write your own tools or test frameworks that make
+use of it. In some situations you may want to disable some checks or add some
+of your own, here is how you do it.
+
+
+### Disabling a *single* check for all elements
+
+```
+// 1. Create a settings object with appropriate settings.
+GTXSettings *settings = [[GTXSettings alloc] init];
+[settings disableCheckWithName:<the check's name> reason:<a reason>];
+
+// 2. Create a checker with the settings object.
+GTXChecker *checker = [[GTXChecker alloc] initWithSettings:settings];
+
+// This checker will not check the disabled checks.
+```
+
+
+### Disabling a check for a *single* element
+
+This is currently not supported in Gtx but if think you have a good use case or
+are in need of a workaround, please ask on g/ios-accessibility
+
+
+### Adding custom checks
+
+```
+// 1. Create a custom check using GTXCheckBlock
+GTXCheckBlock *customCheck =
+    [GTXCheckBlock GTXCheckWithName:<unique check name>
+                              block:^BOOL(id  _Nonnull element, GTXErrorRefType errorOrNil) {
+    // perform checks on the element here...
+    // return YES if checks passed NO for failure.
+  }];
+
+// 2. Register the new check.
+[GTXChecksCollection registerCheck:customCheck];
+
+// 3. Any checkers created next will include the newly registered check.
+GTXChecker *checker = [[GTXChecker alloc] initWithSettings:settings];
+```
+
+
+### Custom error reporting
+
+If you use any APIs that take an NSError object and pass nil for the error,
+the errors are automatically logged and failures are asserted, to customize the
+error reporting simply pass an NSError object and assert on it.
+
+```
+NSError *errorObj;
+// Assume checker is a valid GTXChecker object.
+[checker checkAllElementsFromRootElements:<some root elements> error:&errorObj];
+if (errorObj) {
+  // Error(s) have occurred process/log errorObj.
+} else {
+  // All checks have passed
+}
+```
diff --git a/g3doc/gtxchecks.md b/g3doc/gtxchecks.md
new file mode 100644
index 0000000..406fded
--- /dev/null
+++ b/g3doc/gtxchecks.md
@@ -0,0 +1,52 @@
+# Gtx Checks
+This doc is go/all-gtx-checks.
+
+All Gtx checks are designed with the following constraints:
+
+* Gtx check failures are 100% repeatable.
+* Fixing Gtx checks will lead to better GAR score.
+* Fixing Gtx checks will lead to a more accessible iOS App.
+* All failing Gtx checks will log helpful info on how to fix it.
+
+If you find an Gtx check that does not satisfy the above please notify us at
+ios-accessibility@google.com
+
+Currently Gtx has support for the following checks:
+
+## 1. kGTXCheckNameAccessibilityLabelPresent
+This check verifies that all the accessibility elements in the UI have a non-nil accessibility
+label.
+
+## 2. kGTXCheckNameAccessibilityLabelNotPunctuated
+This check that verifies that the accessibility labels on non-text elements are not punctuated
+(i.e. they do not end with a period). A good example of non-text element is a button.
+
+## 3. kGTXCheckNameAccessibilityTraitsDontConflict
+This check that verifies that the accessibility elements in the UI do not have any
+accessibilityTraits that conflict with each other. The following sets traits are mutually exclusive
+and cannot be used together:
+
+* Set 1
+  * UIAccessibilityTraitButton
+  * UIAccessibilityTraitLink
+  * UIAccessibilityTraitSearchField
+  * UIAccessibilityTraitKeyboardKey
+* Set 2
+  * UIAccessibilityTraitButton
+  * UIAccessibilityTraitAdjustable
+
+## 4. kGTXCheckNameMinimumTappableArea
+This check that verifies that the accessibility elements have a minimum tappabel area (as defined
+by accessibility frame) of 48X48. NOTE that this check is available under GTXSystemVersionLatest not
+GTXSystemVersionStable.
+
+## 5. kGTXCheckNameMinimumContrastRatio
+This check that verifies that text elements have a minimum contrast ratio of 1:3 against their
+background. NOTE that this check is available under GTXSystemVersionLatest not
+GTXSystemVersionStable.
+
+## Other Checks
+While Gtx is scanning the UI it also verifies if the the App uses `accessibilityElements` selector
+and `accessibilityElementAtIndex` consistently, i.e. any given element must either use only one
+of the APIs to provide accessibility children or must provide the same set of elements from both
+the APIs.