Internal Sync for 2.0
diff --git a/Classes/GTXAccessibilityTree.m b/Classes/GTXAccessibilityTree.m
index c86ff1f..483d1c9 100644
--- a/Classes/GTXAccessibilityTree.m
+++ b/Classes/GTXAccessibilityTree.m
@@ -23,7 +23,7 @@
  * 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;
+static const NSInteger kAccessibilityChildrenUpperBound = 50000;
 
 @implementation GTXAccessibilityTree {
   // A queue of elements to be visited.
@@ -89,6 +89,10 @@
                  accessibilityElementsSet,
                  @([nextInQueue accessibilityElementCount]),
                  accessibilityElementsFromIndicesSet);
+
+        // Ensure accessibilityElements* are marked as used even if NSAssert is removed.
+        (void)accessibilityElementsSet;
+        (void)accessibilityElementsFromIndicesSet;
       } else {
         // Set accessibilityElements to whichever is non nil or leave it as is.
         axElements = axElementsFromIndices ? axElementsFromIndices : axElements;
diff --git a/Classes/GTXBlacklistBlock.h b/Classes/GTXBlacklistBlock.h
new file mode 100644
index 0000000..1e073cd
--- /dev/null
+++ b/Classes/GTXBlacklistBlock.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>
+
+#import "GTXBlacklisting.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ 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);
+
+@interface GTXBlacklistBlock: NSObject<GTXBlacklisting>
+
+/**
+ *  GTXBlacklistBlock::init is disabled, instead use GTXBlacklistBlock::blacklistWithBlock:
+ *  method to create GTXBlacklists.
+ */
+- (instancetype)init __attribute__((unavailable("Use blacklistWithBlock: instead.")));
+
+/**
+ *  Creates an GTXBlacklist with the given @c block that determines if an element should be ignored.
+ *
+ *  @param block A block that determines if an element should be ignored.
+ *
+ *  @return A GTXBlacklist object.
+ */
++ (id<GTXBlacklisting>)blacklistWithBlock:(GTXIgnoreElementMatcher)block;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Classes/GTXBlacklistBlock.m b/Classes/GTXBlacklistBlock.m
new file mode 100644
index 0000000..3c16499
--- /dev/null
+++ b/Classes/GTXBlacklistBlock.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 "GTXBlacklistBlock.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@implementation GTXBlacklistBlock {
+  GTXIgnoreElementMatcher _block;
+}
+
++ (id<GTXBlacklisting>)blacklistWithBlock:(GTXIgnoreElementMatcher)block {
+  return [[GTXBlacklistBlock alloc] initWithBlock:block];
+}
+
+- (instancetype)initWithBlock:(GTXIgnoreElementMatcher)block {
+  NSParameterAssert(block);
+
+  self = [super init];
+  if (self) {
+    _block = block;
+  }
+  return self;
+}
+
+#pragma mark - GTXBlacklisting
+
+- (BOOL)shouldIgnoreElement:(id)element forCheckNamed:(NSString *)check {
+  return _block(element, check);
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Classes/GTXBlacklistFactory.h b/Classes/GTXBlacklistFactory.h
new file mode 100644
index 0000000..0fe3a83
--- /dev/null
+++ b/Classes/GTXBlacklistFactory.h
@@ -0,0 +1,61 @@
+//
+// 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 "GTXBlacklisting.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ *  Factory for GTXBlacklisting objects for blacklisting elements.
+ */
+@interface GTXBlacklistFactory : NSObject
+
+- (instancetype)init NS_UNAVAILABLE;
+/**
+ *  Returns a GTXBlacklisting object that ignores all elements that are instances of
+ *  a given @c elementClassName.
+ *
+ *  @param elementClassName The name of the class that should be blacklisted.
+ *  @return A GTXBlacklisting object that ignores all elements of the given class.
+ */
++ (id<GTXBlacklisting>)blacklistWithClassName:(NSString *)elementClassName;
+/**
+ *  Returns a GTXBlacklisting object that ignores all elements that are instances of
+ *  a given @c elementClassName, but only for GTXChecking objects with a given @c checkName.
+ *
+ *  @param elementClassName The name of the class that should be blacklisted.
+ *  @param skipCheckName The name of the check that should ignore elements of the given class.
+ *  @return A GTXBlacklisting object that ignores all elements of the given class when running the
+ *  given check.
+ */
++ (id<GTXBlacklisting>)blacklistWithClassName:(NSString *)elementClassName
+                                    checkName:(NSString *)skipCheckName;
+/**
+ *  Returns a GTXBlacklisting object that ignores all elements with a given accessibility
+ *  identifier, but only for GTXChecking objects with a given @c checkName.
+ *
+ *  @param accessibilityId The accessibility identifier of the element that should be blacklisted.
+ *  @param skipCheckName The name of the check that should ignore elements with the given
+ *  accessibility identifier.
+ */
++ (id<GTXBlacklisting>)blacklistWithAccessibilityIdentifier:(NSString *)accessibilityId
+                                                  checkName:(NSString *)skipCheckName;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Classes/GTXBlacklistFactory.m b/Classes/GTXBlacklistFactory.m
new file mode 100644
index 0000000..5c4d2d8
--- /dev/null
+++ b/Classes/GTXBlacklistFactory.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 "GTXBlacklistFactory.h"
+
+#import "GTXBlacklistBlock.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@implementation GTXBlacklistFactory
+
++ (id<GTXBlacklisting>)blacklistWithClassName:(NSString *)elementClassName {
+  Class classObject = NSClassFromString(elementClassName);
+  NSAssert(classObject, @"Class named %@ does not exist!", elementClassName);
+  GTXIgnoreElementMatcher matcher = ^BOOL(id element, NSString *checkName) {
+    return [element isKindOfClass:classObject];
+  };
+  return [GTXBlacklistBlock blacklistWithBlock:matcher];
+}
+
++ (id<GTXBlacklisting>)blacklistWithClassName:(NSString *)elementClassName
+                                    checkName:(NSString *)skipCheckName {
+  NSParameterAssert(elementClassName);
+  Class classObject = NSClassFromString(elementClassName);
+  NSAssert(classObject, @"Class named %@ does not exist!", elementClassName);
+  GTXIgnoreElementMatcher matcher = ^BOOL(id element, NSString *checkName) {
+    return [element isKindOfClass:classObject] && [checkName isEqualToString:skipCheckName];
+  };
+  return [GTXBlacklistBlock blacklistWithBlock:matcher];
+}
+
++ (id<GTXBlacklisting>)blacklistWithAccessibilityIdentifier:(NSString *)accessibilityId
+                                                  checkName:(NSString *)skipCheckName {
+  NSParameterAssert(accessibilityId);
+  NSParameterAssert(skipCheckName);
+  GTXIgnoreElementMatcher matcher = ^BOOL(id element, NSString *checkName) {
+    return [[element accessibilityIdentifier] isEqualToString:accessibilityId] &&
+        [checkName isEqualToString:skipCheckName];
+  };
+  return [GTXBlacklistBlock blacklistWithBlock:matcher];
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Classes/GTXBlacklisting.h b/Classes/GTXBlacklisting.h
new file mode 100644
index 0000000..401d86f
--- /dev/null
+++ b/Classes/GTXBlacklisting.h
@@ -0,0 +1,38 @@
+//
+// 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
+
+/**
+ *  Protocol for blacklisting certain types of UI elements in accessibility checks.
+ */
+@protocol GTXBlacklisting<NSObject>
+
+/**
+ *  Determines if the given @c check should not be run on the given @c element.
+ *
+ *  @param      element    The target element on which the GTX check is to be performed.
+ *  @param[out] checkName  The name of the check being run.
+ *
+ *  @return @c YES if the check should NOT be performed on the given element, NO otherwise.
+ */
+- (BOOL)shouldIgnoreElement:(id)element forCheckNamed:(NSString *)checkName;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Classes/GTXChecksCollection.h b/Classes/GTXChecksCollection.h
index 3f48a8a..9a4c331 100644
--- a/Classes/GTXChecksCollection.h
+++ b/Classes/GTXChecksCollection.h
@@ -42,7 +42,8 @@
  *  Organizes all checks provided by GTX, developers can use these as a starting point
  *  for implementing their own checks. These checks are based on recommendations found in
  *  "Accessibility Programming Guide for iOS"
-    @link https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/iPhoneAccessibility/Introduction/Introduction.html
+    @link
+ https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/iPhoneAccessibility/Introduction/Introduction.html // NOLINT
  *  and on WCAG.
  */
 @interface GTXChecksCollection : NSObject
@@ -50,12 +51,12 @@
 /**
  *  @return An array of all supported GTXChecks for the given @c version.
  */
-+ (NSArray<GTXChecking> *)allChecksForVersion:(GTXVersion)version;
++ (NSArray<id<GTXChecking>> *)allChecksForVersion:(GTXVersion)version;
 
 /**
  *  @return An array of all supported GTXChecks.
  */
-+ (NSArray<GTXChecking> *)allGTXChecks;
++ (NSArray<id<GTXChecking>> *)allGTXChecks;
 
 /**
  *  @return a check that verifies that accessibility label is present on all accessibility elements.
@@ -69,6 +70,12 @@
 + (id<GTXChecking>)checkForAXLabelNotPunctuated;
 
 /**
+ *  @return a check that verifies that accessibility labels do not end in strings that VoiceOver
+ *  announces via the element's accessibility traits.
+ */
++ (id<GTXChecking>)checkForAXLabelNotRedundantWithTraits;
+
+/**
  *  @return a check that verifies that accessibility traits dont conflict with each other as
  *  recommended by "Accessibility Programming Guide for iOS" (see class docs).
  */
diff --git a/Classes/GTXChecksCollection.m b/Classes/GTXChecksCollection.m
index 47cf6a9..1d6379d 100644
--- a/Classes/GTXChecksCollection.m
+++ b/Classes/GTXChecksCollection.m
@@ -26,6 +26,8 @@
 NSString *const kGTXCheckNameAccessibilityLabelPresent = @"Accessibility Label Present";
 NSString *const kGTXCheckNameAccessibilityLabelNotPunctuated =
     @"Accessibility Label Not Punctuated";
+NSString *const kGTXCheckNameAccessibilityLabelIsNotRedundantWithTraits =
+    @"Accessibility Label is Not Redundant with Traits";
 NSString *const kGTXCheckNameAccessibilityTraitsDontConflict =
     @"Accessibility Traits Don't Conflict";
 NSString *const kGTXCheckNameMinimumTappableArea = @"Element has Minimum Tappable Area";
@@ -34,21 +36,23 @@
 #pragma mark - Globals
 
 /**
- *  The minimum size (width or height) for a given element to be easily accessible.
+ *  The minimum size (width or height) for a given element to be easily accessible. Based on
+ *  Material design guidelines:
+ *  https://material.io/design/layout/spacing-methods.html#touch-click-targets
  */
-static const float kMinSizeForAccessibleElements = 48.0;
+static const float kGTXMinSizeForAccessibleElements = 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;
+static const float kGTXMinContrastRatioForAccessibleText = 3.0;
 
 #pragma mark - Implementations
 
 @implementation GTXChecksCollection
 
-+ (NSArray<GTXChecking> *)allChecksForVersion:(GTXVersion)version {
++ (NSArray<id<GTXChecking>> *)allChecksForVersion:(GTXVersion)version {
   switch (version) {
     case GTXVersionLatest: return [self allGTXChecks];
     case GTXVersionPreRelease: return [self allGTXChecks];
@@ -59,6 +63,7 @@
 + (NSArray<id<GTXChecking>> *)allGTXChecks {
   return @[[self checkForAXLabelPresent],
            [self checkForAXLabelNotPunctuated],
+           [self checkForAXLabelNotRedundantWithTraits],
            [self checkForAXTraitDontConflict],
            [self checkForMinimumTappableArea],
            [self checkForSufficientContrastRatio]];
@@ -141,6 +146,66 @@
   return check;
 }
 
+// @todo Include all UIAccessibilityTraits that announce themselves (image, search
+// field, etc.), and find a more robust way to determine if the label is redundant. Currently, the
+// suffix is compared to a single string, which causes false positives when that string occurs
+// elsewhere in the label or synonyms are used. Additionally, the hardcoded string only works in
+// English. This method also needs to be updated for i18n.
++ (id<GTXChecking>)checkForAXLabelNotRedundantWithTraits {
+  id<GTXChecking> check =
+    [GTXCheckBlock GTXCheckWithName:kGTXCheckNameAccessibilityLabelIsNotRedundantWithTraits
+                              block:^BOOL(id element, GTXErrorRefType errorOrNil) {
+    UIAccessibilityTraits elementAXTraits = [element accessibilityTraits];
+    NSString *elementAXLabel = [element accessibilityLabel];
+    NSDictionary<NSNumber *, NSString *> const *redundantLabelsDictionary =
+        [self traitsToRedundantLabelsDictionary];
+    NSMutableArray<NSString *> *redundantTextList = [[NSMutableArray alloc] init];
+    NSMutableArray<NSString *> *redundantTraitNameList = [[NSMutableArray alloc] init];
+    for (NSNumber *testTrait in redundantLabelsDictionary) {
+      NSString* redundantText = [redundantLabelsDictionary objectForKey:testTrait];
+      UIAccessibilityTraits testUITrait = [testTrait unsignedLongLongValue];
+      if ((BOOL)(elementAXTraits & testUITrait)) {
+        if ([GTXChecksCollection caseInsensitive:elementAXLabel hasSuffix:redundantText]) {
+          NSError *error;
+          NSString *stringValue = [self stringValueOfUIAccessibilityTraits:testUITrait
+                                                                     error:&error];
+          if (error) {
+            if (errorOrNil) {
+              *errorOrNil = error;
+            }
+            return NO;
+          }
+
+          [redundantTextList addObject:redundantText];
+          [redundantTraitNameList addObject:stringValue];
+        }
+      }
+    }
+    if ([redundantTraitNameList count] > 0) {
+      NSString* stringOfRedundantTextList = [redundantTextList componentsJoinedByString:@", "];
+      NSString* stringOfRedundantTraitNameList =
+          [redundantTraitNameList componentsJoinedByString:@", "];
+      NSString *description = [NSString stringWithFormat:@"Suggest removing '%@' from the "
+                                                         @"accessibility label '%@'. The element "
+                                                         @"already contains the accessibility "
+                                                         @"trait(s) %@. VoiceOver announces those "
+                                                         @"traits to the user, so putting "
+                                                         @"redundant words in the label causes "
+                                                         @"VoiceOver to repeat them.",
+                                                         stringOfRedundantTextList,
+                                                         elementAXLabel,
+                                                         stringOfRedundantTraitNameList];
+      [NSError gtx_logOrSetGTXCheckFailedError:errorOrNil
+              element:element
+                 name:kGTXCheckNameAccessibilityLabelIsNotRedundantWithTraits
+          description:description];
+      return NO;
+    }
+    return YES;
+  }];
+  return check;
+}
+
 + (id<GTXChecking>)checkForAXTraitDontConflict {
   id<GTXChecking> check =
       [GTXCheckBlock GTXCheckWithName:kGTXCheckNameAccessibilityTraitsDontConflict
@@ -200,8 +265,8 @@
     }
     if ([element respondsToSelector:@selector(accessibilityFrame)]) {
       CGRect frame = [element accessibilityFrame];
-      BOOL hasSmallWidth = CGRectGetWidth(frame) < kMinSizeForAccessibleElements;
-      BOOL hasSmallHeight = CGRectGetHeight(frame) < kMinSizeForAccessibleElements;
+      BOOL hasSmallWidth = CGRectGetWidth(frame) < kGTXMinSizeForAccessibleElements;
+      BOOL hasSmallHeight = CGRectGetHeight(frame) < kGTXMinSizeForAccessibleElements;
       if (hasSmallWidth || hasSmallHeight) {
         NSString *dimensionsToBeFixed;
         // Append a suggestion to the error description.
@@ -219,9 +284,9 @@
             [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];
+                                       (int)kGTXMinSizeForAccessibleElements,
+                                       (int)kGTXMinSizeForAccessibleElements,
+                                       (int)kGTXMinSizeForAccessibleElements];
 
         [NSError gtx_logOrSetGTXCheckFailedError:errorOrNil
                                          element:element
@@ -244,13 +309,13 @@
     }
     CGFloat ratio = [GTXImageAndColorUtils contrastRatioOfUILabel:element];
     BOOL hasSufficientContrast =
-        (ratio >= kMinContrastRatioForAccessibleText - kContrastRatioAccuracy);
+        (ratio >= kGTXMinContrastRatioForAccessibleText - kGTXContrastRatioAccuracy);
     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];
+                                     (float)kGTXMinContrastRatioForAccessibleText, (float)ratio];
       [NSError gtx_logOrSetGTXCheckFailedError:errorOrNil
                                        element:element
                                           name:kGTXCheckNameMinimumContrastRatio
@@ -386,6 +451,17 @@
 }
 
 /**
+ *  @return The UIAccessibilityTraits to redundant NSString accessibility label mapping dictionary
+ *          as type NSDictionary<NSNumber *, NSString *> const *.
+ */
++ (NSDictionary<NSNumber *, NSString *> const *)traitsToRedundantLabelsDictionary {
+  return @{
+    @(UIAccessibilityTraitButton):
+      @"button",
+  };
+}
+
+/**
  *  @return @c YES if @c element is tappable (for ex button) @c NO otherwise.
  */
 + (BOOL)gtx_isTappableNonLinkElement:(id)element {
@@ -419,4 +495,12 @@
           hasTextTrait);
 }
 
+/**
+ *  @return @c YES if the last characters of @c string equals @c suffix. Comparison is done case
+ *  insensitively.
+ */
++ (BOOL)caseInsensitive:(NSString*)string hasSuffix:(NSString*)suffix {
+  return [[string lowercaseString] hasSuffix:suffix];
+}
+
 @end
diff --git a/Classes/GTXImageAndColorUtils.h b/Classes/GTXImageAndColorUtils.h
index 34ce3c4..5d2792c 100644
--- a/Classes/GTXImageAndColorUtils.h
+++ b/Classes/GTXImageAndColorUtils.h
@@ -16,7 +16,7 @@
 
 #import <UIKit/UIKit.h>
 
-extern const CGFloat kContrastRatioAccuracy;
+extern const CGFloat kGTXContrastRatioAccuracy;
 
 /**
  *  Collection of accessibility related utils that use colors and/or images.
diff --git a/Classes/GTXImageAndColorUtils.m b/Classes/GTXImageAndColorUtils.m
index 8fd91ff..cd9bb9d 100644
--- a/Classes/GTXImageAndColorUtils.m
+++ b/Classes/GTXImageAndColorUtils.m
@@ -21,7 +21,7 @@
 /**
  *  Accuracy of the contrast ratios provided by the APIs in this class.
  */
-const CGFloat kContrastRatioAccuracy = 0.05f;
+const CGFloat kGTXContrastRatioAccuracy = 0.05f;
 
 @implementation GTXImageAndColorUtils
 
diff --git a/Classes/GTXPluginXCTestCase.m b/Classes/GTXPluginXCTestCase.m
index a0b25bf..4f52fd8 100644
--- a/Classes/GTXPluginXCTestCase.m
+++ b/Classes/GTXPluginXCTestCase.m
@@ -16,8 +16,9 @@
 
 #import "GTXPluginXCTestCase.h"
 
-#import "GTXiLibCore.h"
 #import "GTXAssertions.h"
+#import "GTXiLibCore.h"
+#import "GTXTestEnvironment.h"
 
 #import <objc/runtime.h>
 
@@ -44,6 +45,7 @@
     [self gtx_swizzleInstanceMethod:@selector(invokeTest)
                          withMethod:@selector(gtx_invokeTest)
                             inClass:gXCTestCaseClass];
+    [GTXTestEnvironment setupEnvironment];
   });
 }
 
@@ -81,6 +83,7 @@
                                  typeEncoding);
   NSAssert(success, @"Failed to add %@ from %@ to %@",
                     NSStringFromSelector(methodSelector), srcClass, destClass);
+  (void)success; // Ensures 'success' is marked as used even if NSAssert is removed.
 }
 
 /**
diff --git a/Classes/GTXTestEnvironment.h b/Classes/GTXTestEnvironment.h
new file mode 100644
index 0000000..bde20a5
--- /dev/null
+++ b/Classes/GTXTestEnvironment.h
@@ -0,0 +1,41 @@
+//
+// 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>
+
+#import "GTXCommon.h"
+
+/**
+ *  A class for setting up enviornment (devices, simulators) etc that GTX runs on.
+ */
+@interface GTXTestEnvironment : NSObject
+
+/**
+ *  Sets up the enviornment for use by GTX.
+ *
+ *  @param[out] errorOrNil A pointer to an NSError object to populate if there is an error, or nil
+ *                         if errors are not being handled.
+ *  @return YES if the environment was set up successfully, NO otherwise, If NO and @c errorOrNil
+ *              is not nil, @c errorOrNil is populated with a description of the error.
+ */
++ (BOOL)setupEnvironmentWithError:(GTXErrorRefType)errorOrNil;
+/**
+ *  Sets up the enviornment for use by GTX. If the environment cannot be setup, then an exception
+ *  is raised.
+ */
++ (void)setupEnvironment;
+
+@end
diff --git a/Classes/GTXTestEnvironment.m b/Classes/GTXTestEnvironment.m
new file mode 100644
index 0000000..7af591f
--- /dev/null
+++ b/Classes/GTXTestEnvironment.m
@@ -0,0 +1,222 @@
+//
+// 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 "GTXTestEnvironment.h"
+
+#import "GTXLogging.h"
+#import "NSError+GTXAdditions.h"
+
+#include <dlfcn.h>
+
+#pragma mark - Exposed Interfaces
+
+/**
+ *  An Exposed internal class used for enabling accessibility on simulators.
+ */
+@interface AXBackBoardServer
+
+/**
+ *  Returns current backboard server instance.
+ */
++ (instancetype)server;
+
+/**
+ *  Sets the given preference and posts the given notification.
+ *
+ *  @param key Name of the key whose value is to be set.
+ *  @param value The value to be set to.
+ *  @param notification The notification to be raised.
+ */
+- (void)setAccessibilityPreferenceAsMobile:(CFStringRef)key
+                                     value:(CFBooleanRef)value
+                              notification:(CFStringRef)notification;
+
+@end
+
+#pragma mark - Globals
+
+/**
+ *  Path to accessibility utils framework on the simulator.
+ */
+static NSString *const kPathToAXUtils =
+    @"/System/Library/PrivateFrameworks/AccessibilityUtilities.framework/AccessibilityUtilities";
+
+/**
+ * Class name of the private class: XCAXClient_iOS class.
+ */
+static NSString *const kXCAXClientClassName = @"XCAXClient_iOS";
+
+#pragma mark - Implementations
+
+@implementation GTXTestEnvironment
+
++ (BOOL)setupEnvironmentWithError:(GTXErrorRefType)errorOrNil {
+  static dispatch_once_t onceToken;
+  __block BOOL setupSuccessful = YES;
+  dispatch_once(&onceToken, ^{
+#if TARGET_OS_SIMULATOR
+    setupSuccessful = [self _enableAccessibilityForSimulatorWithError:errorOrNil];
+#else
+    setupSuccessful = [self _enableAccessibilityOnDeviceWithError:errorOrNil];
+#endif
+  });
+  return setupSuccessful;
+}
+
++ (void)setupEnvironment {
+  NSAssert([GTXTestEnvironment setupEnvironmentWithError:nil],
+           @"Test environment could not be set up");
+}
+
+/**
+ *  Enables accessibility to allow using accessibility properties on simulators.
+ *
+ *  @param[out] errorOrNil A pointer to an error object to return information on failure, or nil.
+ *  @return YES if accessibility was enabled, NO if there was an error.
+ */
++ (BOOL)_enableAccessibilityForSimulatorWithError:(GTXErrorRefType)errorOrNil {
+  // Set the preferences that turn on Accessibility.
+  BOOL setSuccessful = YES;
+  setSuccessful =
+      [self _setAccessibilityPreference:(CFStringRef) @"ApplicationAccessibilityEnabled"
+                                  value:kCFBooleanTrue
+                           notification:(CFStringRef) @"com.apple.accessibility.cache.app.ax"
+                                  error:errorOrNil];
+  if (!setSuccessful) {
+    return NO;
+  }
+  setSuccessful =
+      [self _setAccessibilityPreference:(CFStringRef) @"AccessibilityEnabled"
+                                  value:kCFBooleanTrue
+                           notification:(CFStringRef) @"com.apple.accessibility.cache.ax"
+                                  error:errorOrNil];
+  return setSuccessful;
+}
+
+/**
+ *  Sets the given preference and posts the given notification using @c AXBackBoardServer.
+ *
+ *  @param key Name of the key whose value is to be set.
+ *  @param value The value to be set to.
+ *  @param name The name of the notification to be raised.
+ *  @param[out] errorOrNil A pointer to an error object to return information on failure, or nil.
+ *  @return YES if the preference was set, NO if there was an error.
+ */
++ (BOOL)_setAccessibilityPreference:(CFStringRef)key
+                              value:(CFBooleanRef)value
+                       notification:(CFStringRef)name
+                              error:(GTXErrorRefType)errorOrNil {
+  __block BOOL setupSuccessful = YES;
+  static AXBackBoardServer *server;
+  static dispatch_once_t onceToken;
+  dispatch_once(&onceToken, ^{
+    char const *const localPath = [kPathToAXUtils fileSystemRepresentation];
+    void *handle = dlopen(localPath, RTLD_LOCAL);
+    if (!handle) {
+      NSString *description =
+          [NSString stringWithFormat:@"Could not load AccessibilityUtilities at %s", localPath];
+      [NSError gtx_logOrSetError:errorOrNil
+                     description:description
+                            code:GTXCheckErrorCodeInvalidTestEnvironment
+                        userInfo:nil];
+      setupSuccessful = NO;
+      return;
+    }
+    (void)handle;  // Ensures 'handle' is marked as used even if NSError is removed.
+    Class AXBackBoardServerClass = NSClassFromString(@"AXBackBoardServer");
+    if (!AXBackBoardServerClass) {
+      [NSError gtx_logOrSetError:errorOrNil
+                     description:@"AXBackBoardServer class not found"
+                            code:GTXCheckErrorCodeInvalidTestEnvironment
+                        userInfo:nil];
+      setupSuccessful = NO;
+      return;
+    }
+    server = [AXBackBoardServerClass performSelector:@selector(server)];
+    if (!server) {
+      [NSError gtx_logOrSetError:errorOrNil
+                     description:@"Could not retrieve AXBackBoardServer object"
+                            code:GTXCheckErrorCodeInvalidTestEnvironment
+                        userInfo:nil];
+      setupSuccessful = NO;
+      return;
+    }
+  });
+  [server setAccessibilityPreferenceAsMobile:key
+                                       value:value
+                                notification:name];
+  return setupSuccessful;
+}
+
+/**
+ *  Enables accessibility to allow using accessibility properties on devices.
+ */
++ (BOOL)_enableAccessibilityOnDeviceWithError:(GTXErrorRefType)errorOrNil {
+  Class XCAXClientClass = NSClassFromString(kXCAXClientClassName);
+  if (XCAXClientClass == nil) {
+    NSString *description = [NSString stringWithFormat:@"%@ class not found", kXCAXClientClassName];
+    [NSError gtx_logOrSetError:errorOrNil
+                   description:description
+                          code:GTXCheckErrorCodeInvalidTestEnvironment
+                      userInfo:nil];
+    return NO;
+  }
+  id XCAXClient = [XCAXClientClass sharedClient];
+  if (XCAXClient == nil) {
+    NSString *description =
+        [NSString stringWithFormat:@"%@ sharedClient doesn't exist", kXCAXClientClassName];
+    [NSError gtx_logOrSetError:errorOrNil
+                   description:description
+                          code:GTXCheckErrorCodeInvalidTestEnvironment
+                      userInfo:nil];
+    return NO;
+  }
+  // The method may not be available on versions older than iOS 9.1
+  if ([XCAXClient respondsToSelector:@selector(loadAccessibility:)]) {
+    typedef void (*MethodType)(id, SEL, void*);
+    SEL selector = @selector(loadAccessibility:);
+    static void *unused = 0;
+    MethodType method = (MethodType)[XCAXClient methodForSelector:selector];
+    method(XCAXClient, selector, &unused);
+  } else {
+    [NSError gtx_logOrSetError:errorOrNil
+                   description:@"Could not enable accessibility! iOS version must be >= 9.1"
+                          code:GTXCheckErrorCodeInvalidTestEnvironment
+                      userInfo:nil];
+    return NO;
+  }
+  return YES;
+}
+
+#pragma mark - unused methods
+
+/**
+ *  Unused method only used for selector name.
+ */
+- (id)sharedClient {
+  NSAssert(NO, @"This method must not be invoked directly, its only used for exposing selector");
+  return nil;
+}
+
+/**
+ *  Unused method only used for selector name.
+ */
+- (BOOL)loadAccessibility:(void **)unused {
+  NSAssert(NO, @"This method must not be invoked directly, its only used for exposing selector");
+  return NO;
+}
+
+@end
diff --git a/Classes/GTXToolKit.h b/Classes/GTXToolKit.h
index 6f20081..ac9ea49 100644
--- a/Classes/GTXToolKit.h
+++ b/Classes/GTXToolKit.h
@@ -16,8 +16,10 @@
 
 #import <UIKit/UIKit.h>
 
-#import "GTXChecking.h"
+#import "GTXBlacklistBlock.h"
+#import "GTXBlacklisting.h"
 #import "GTXCheckBlock.h"
+#import "GTXChecking.h"
 
 NS_ASSUME_NONNULL_BEGIN
 
@@ -45,9 +47,17 @@
  */
 - (void)registerCheck:(id<GTXChecking>)check;
 
+/**
+ Registers the given blacklist to be executed on all elements this instance is used on. Registered
+ checks are not performed on blacklisted elements.
+
+ @param blacklist The blacklist to be registered.
+ */
+- (void)registerBlacklist:(id<GTXBlacklisting>)blacklist;
+
 
 /**
- Applies the registered checks on the given element while respecting ignored elements.
+ Applies the registered checks on the given element while respecting blacklisted elements.
 
  @param element element to be checked.
  @param errorOrNil Error object to be filled with error info on check failures.
@@ -58,7 +68,7 @@
 
 /**
  Applies the registered checks on all elements in the accessibility tree under the given root
- elements while respecting ignored elements.
+ elements while respecting blacklisted 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.
@@ -67,21 +77,6 @@
 - (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
index 414e552..6857114 100644
--- a/Classes/GTXToolKit.m
+++ b/Classes/GTXToolKit.m
@@ -18,29 +18,22 @@
 
 #import "GTXAccessibilityTree.h"
 #import "GTXAnalytics.h"
+#import "GTXBlacklistBlock.h"
+#import "GTXBlacklistFactory.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;
+  NSMutableArray<id<GTXBlacklisting>> *_blacklists;
 }
 
 - (instancetype)init {
   self = [super init];
   if (self) {
     _checks = [[NSMutableArray alloc] init];
-    _blackListMatchers = [[NSMutableArray alloc] init];
+    _blacklists = [[NSMutableArray alloc] init];
   }
   return self;
 }
@@ -54,10 +47,15 @@
   for (id<GTXChecking> existingCheck in _checks) {
     NSAssert(![[existingCheck name] isEqualToString:[check name]],
              @"Check named %@ already exists!", [check name]);
+    (void)existingCheck; // Ensures 'existingCheck' is marked as used even if NSAssert is removed.
   }
   [_checks addObject:check];
 }
 
+- (void)registerBlacklist:(id<GTXBlacklisting>)blacklist {
+  [_blacklists addObject:blacklist];
+}
+
 - (BOOL)checkElement:(id)element error:(GTXErrorRefType)errorOrNil {
   return [self _checkElement:element analyticsEnabled:YES error:errorOrNil];
 }
@@ -125,29 +123,10 @@
   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.
+ Applies the registered checks on the given element while respecting blacklisted elements.
 
  @param element element to be checked.
  @param checkAnalyticsEnabled Boolean that indicates if analytics events are to be invoked.
@@ -167,8 +146,8 @@
   NSMutableArray *failedCheckErrors;
   for (id<GTXChecking> checker in _checks) {
     BOOL shouldSkipThisCheck = NO;
-    for (GTXIgnoreElementMatcher matcher in _blackListMatchers) {
-      if (matcher(element, [checker name])) {
+    for (id<GTXBlacklisting> blacklist in _blacklists) {
+      if ([blacklist shouldIgnoreElement:element forCheckNamed:[checker name]]) {
         shouldSkipThisCheck = YES;
         break;
       }
@@ -186,7 +165,8 @@
                                    @"check implementation.", [checker name]];
         error = [NSError errorWithDomain:kGTXErrorDomain
                                     code:GTXCheckErrorCodeAccessibilityCheckFailed
-                                userInfo:@{ NSLocalizedDescriptionKey: errorDescription }];
+                                userInfo:@{ NSLocalizedDescriptionKey: errorDescription,
+                                            kGTXErrorFailingElementKey: element }];
       }
       if (!failedCheckErrors) {
         failedCheckErrors = [[NSMutableArray alloc] init];
diff --git a/Classes/GTXiLib.h b/Classes/GTXiLib.h
index 4a84dee..22964be 100644
--- a/Classes/GTXiLib.h
+++ b/Classes/GTXiLib.h
@@ -33,7 +33,7 @@
 #import <GTXiLib/GTXCheckBlock.h>
 #import <GTXiLib/GTXChecksCollection.h>
 #import <GTXiLib/GTXCommon.h>
-#import <GTXiLib/GTXElementBlacklist.h>
+#import <GTXiLib/GTXBlacklistFactory.h>
 #import <GTXiLib/GTXErrorReporter.h>
 #import <GTXiLib/GTXImageRGBAData.h>
 #import <GTXiLib/GTXImageAndColorUtils.h>
diff --git a/Classes/GTXiLibCore.h b/Classes/GTXiLibCore.h
index 56ea83b..a601a22 100644
--- a/Classes/GTXiLibCore.h
+++ b/Classes/GTXiLibCore.h
@@ -16,10 +16,10 @@
 
 #import "GTXAnalytics.h"
 #import "GTXAccessibilityTree.h"
+#import "GTXBlacklisting.h"
 #import "GTXCheckBlock.h"
 #import "GTXChecksCollection.h"
 #import "GTXCommon.h"
-#import "GTXElementBlacklist.h"
 #import "GTXErrorReporter.h"
 #import "GTXTestSuite.h"
 #import "NSError+GTXAdditions.h"
@@ -78,7 +78,7 @@
  */
 + (void)installOnTestSuite:(GTXTestSuite *)suite
                     checks:(NSArray<id<GTXChecking>> *)checks
-         elementBlacklists:(NSArray<GTXElementBlacklist *> *)blacklists;
+         elementBlacklists:(NSArray<id<GTXBlacklisting>> *)blacklists;
 
 /**
  Creates a check with the given name and block.
@@ -91,24 +91,6 @@
 + (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.
  */
diff --git a/Classes/GTXiLibCore.m b/Classes/GTXiLibCore.m
index 68858c0..015b3fa 100644
--- a/Classes/GTXiLibCore.m
+++ b/Classes/GTXiLibCore.m
@@ -16,12 +16,12 @@
 
 #import "GTXiLibCore.h"
 
-#import "GTXToolKit.h"
 #import "GTXAssertions.h"
 #import "GTXChecking.h"
 #import "GTXChecksCollection.h"
 #import "GTXLogging.h"
 #import "GTXPluginXCTestCase.h"
+#import "GTXToolKit.h"
 #import "NSError+GTXAdditions.h"
 
 #pragma mark - Global definitions.
@@ -81,7 +81,7 @@
 
 + (void)installOnTestSuite:(GTXTestSuite *)suite
                     checks:(NSArray<id<GTXChecking>> *)checks
-         elementBlacklists:(NSArray<GTXElementBlacklist *> *)blacklists {
+         elementBlacklists:(NSArray<id<GTXBlacklisting>> *)blacklists {
   [GTXPluginXCTestCase installPlugin];
   if (!gIntsallOptions) {
     gIntsallOptions = [[NSMutableArray alloc] init];
@@ -98,6 +98,7 @@
     NSAssert(intersection.tests.count == 0,
              @"Error! Attempting to install GTXChecks multiple times on the same test cases: %@",
              intersection);
+    (void)intersection; // Ensures 'intersection' is marked as used even if NSAssert is removed.
   }
   [gIntsallOptions addObject:options];
 
@@ -143,17 +144,6 @@
   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
 
 /**
@@ -216,13 +206,8 @@
       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];
-        }
+      for (id<GTXBlacklisting> blacklist in gCurrentOptions.elementBlacklist) {
+        [gToolkit registerBlacklist:blacklist];
       }
     }
   }
diff --git a/Classes/NSError+GTXAdditions.h b/Classes/NSError+GTXAdditions.h
index 0cfe713..731d87f 100644
--- a/Classes/NSError+GTXAdditions.h
+++ b/Classes/NSError+GTXAdditions.h
@@ -58,6 +58,10 @@
    */
   GTXCheckErrorCodeNotFocusableElement,
   /**
+   *  The test environment could not be set up before tests are run.
+   */
+  GTXCheckErrorCodeInvalidTestEnvironment,
+  /**
    *  An error code for all errors not related to the above.
    */
   GTXCheckErrorCodeGenericError,
diff --git a/Classes/NSError+GTXAdditions.m b/Classes/NSError+GTXAdditions.m
index 0e35935..2c43da9 100644
--- a/Classes/NSError+GTXAdditions.m
+++ b/Classes/NSError+GTXAdditions.m
@@ -59,7 +59,7 @@
       [NSString stringWithFormat:@"Check \"%@\" failed, %@",
                                  name, description];
   NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : fullDescription,
-                              kGTXErrorFailingElementKey: [element description],
+                              kGTXErrorFailingElementKey: element,
                               kGTXErrorCheckNameKey: name };
   NSError *error = [NSError errorWithDomain:kGTXErrorDomain
                                        code:GTXCheckErrorCodeAccessibilityCheckFailed
diff --git a/GTXiLib.xcodeproj/project.pbxproj b/GTXiLib.xcodeproj/project.pbxproj
index 33f7a2c..6650126 100644
--- a/GTXiLib.xcodeproj/project.pbxproj
+++ b/GTXiLib.xcodeproj/project.pbxproj
@@ -18,6 +18,31 @@
 		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 */; };
+		61284DCB211E3FE400F8FF29 /* GTXTestViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 61284DC3211E3FE100F8FF29 /* GTXTestViewController.m */; };
+		61284DCC211E3FE400F8FF29 /* GTXTestViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 61284DC4211E3FE200F8FF29 /* GTXTestViewController.xib */; };
+		61284DCD211E3FE400F8FF29 /* GTXTestAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 61284DC5211E3FE200F8FF29 /* GTXTestAppDelegate.m */; };
+		61284DCE211E3FE400F8FF29 /* GTXTestStepperButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 61284DC6211E3FE200F8FF29 /* GTXTestStepperButton.m */; };
+		61284DCF211E3FE400F8FF29 /* GTXTestApp.m in Sources */ = {isa = PBXBuildFile; fileRef = 61284DCA211E3FE400F8FF29 /* GTXTestApp.m */; };
+		61284DD5211E42CF00F8FF29 /* GTXBlacklistBlock.m in Sources */ = {isa = PBXBuildFile; fileRef = 61284DD2211E42CD00F8FF29 /* GTXBlacklistBlock.m */; };
+		61284DD6211E42CF00F8FF29 /* GTXBlacklistBlock.h in Headers */ = {isa = PBXBuildFile; fileRef = 61284DD3211E42CE00F8FF29 /* GTXBlacklistBlock.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		61284DD7211E42CF00F8FF29 /* GTXBlacklisting.h in Headers */ = {isa = PBXBuildFile; fileRef = 61284DD4211E42CE00F8FF29 /* GTXBlacklisting.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		61284DDA211E437800F8FF29 /* GTXTestEnvironment.m in Sources */ = {isa = PBXBuildFile; fileRef = 61284DD8211E437700F8FF29 /* GTXTestEnvironment.m */; };
+		61284DDB211E437800F8FF29 /* GTXTestEnvironment.h in Headers */ = {isa = PBXBuildFile; fileRef = 61284DD9211E437800F8FF29 /* GTXTestEnvironment.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		61284DF9211E447E00F8FF29 /* GTXTestGtxWorksForPassingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 61284DEA211E447800F8FF29 /* GTXTestGtxWorksForPassingTests.m */; };
+		61284DFA211E447E00F8FF29 /* GTXTestFailingClassFailCheck.m in Sources */ = {isa = PBXBuildFile; fileRef = 61284DEB211E447900F8FF29 /* GTXTestFailingClassFailCheck.m */; };
+		61284DFB211E447E00F8FF29 /* GTXTestAnalyticsBaseTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 61284DEC211E447900F8FF29 /* GTXTestAnalyticsBaseTest.m */; };
+		61284DFC211E447E00F8FF29 /* GTXTestGtxCanBlacklistTestCases.m in Sources */ = {isa = PBXBuildFile; fileRef = 61284DED211E447900F8FF29 /* GTXTestGtxCanBlacklistTestCases.m */; };
+		61284DFD211E447E00F8FF29 /* GTXTestGtxCanDetectFailuresInInheritedTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 61284DEE211E447A00F8FF29 /* GTXTestGtxCanDetectFailuresInInheritedTests.m */; };
+		61284DFE211E447E00F8FF29 /* GTXTestGtxCanIgnoreElements.m in Sources */ = {isa = PBXBuildFile; fileRef = 61284DEF211E447A00F8FF29 /* GTXTestGtxCanIgnoreElements.m */; };
+		61284DFF211E447E00F8FF29 /* GTXTestGtxWithDefaultChecks.m in Sources */ = {isa = PBXBuildFile; fileRef = 61284DF0211E447A00F8FF29 /* GTXTestGtxWithDefaultChecks.m */; };
+		61284E00211E447E00F8FF29 /* GTXTestBaseTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 61284DF1211E447B00F8FF29 /* GTXTestBaseTest.m */; };
+		61284E01211E447E00F8FF29 /* GTXTestGtxCanWhitelistTestCases.m in Sources */ = {isa = PBXBuildFile; fileRef = 61284DF2211E447C00F8FF29 /* GTXTestGtxCanWhitelistTestCases.m */; };
+		61284E02211E447E00F8FF29 /* GTXTestAnalyticsIsTriggered.m in Sources */ = {isa = PBXBuildFile; fileRef = 61284DF3211E447C00F8FF29 /* GTXTestAnalyticsIsTriggered.m */; };
+		61284E03211E447E00F8FF29 /* GTXTestAnalyticsCanBeDisabled.m in Sources */ = {isa = PBXBuildFile; fileRef = 61284DF5211E447C00F8FF29 /* GTXTestAnalyticsCanBeDisabled.m */; };
+		61284E04211E447E00F8FF29 /* GTXTestGtxCanIgnoreElementsOnSpecificCheck.m in Sources */ = {isa = PBXBuildFile; fileRef = 61284DF6211E447D00F8FF29 /* GTXTestGtxCanIgnoreElementsOnSpecificCheck.m */; };
+		61284E05211E447E00F8FF29 /* GTXTestGtxCanDetectFailures.m in Sources */ = {isa = PBXBuildFile; fileRef = 61284DF8211E447D00F8FF29 /* GTXTestGtxCanDetectFailures.m */; };
+		61284E0A211E45C600F8FF29 /* GTXBlacklistFactory.h in Headers */ = {isa = PBXBuildFile; fileRef = 61284E08211E45C500F8FF29 /* GTXBlacklistFactory.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		61284E0B211E45C600F8FF29 /* GTXBlacklistFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = 61284E09211E45C600F8FF29 /* GTXBlacklistFactory.m */; };
 		61A0C4DE2061896300DF0169 /* GTXiLibCore.m in Sources */ = {isa = PBXBuildFile; fileRef = 61A0C4DB2061896300DF0169 /* GTXiLibCore.m */; };
 		61A0C4DF2061896300DF0169 /* GTXiLib.h in Headers */ = {isa = PBXBuildFile; fileRef = 61A0C4DC2061896300DF0169 /* GTXiLib.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		61A0C4E02061896300DF0169 /* GTXiLibCore.h in Headers */ = {isa = PBXBuildFile; fileRef = 61A0C4DD2061896300DF0169 /* GTXiLibCore.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -31,7 +56,6 @@
 		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, ); }; };
@@ -50,7 +74,6 @@
 		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 */; };
-		61ABAED1204A0B0B006DBF0A /* GTXElementBlacklist.h in Headers */ = {isa = PBXBuildFile; fileRef = 61ABAEAF204A0B0A006DBF0A /* GTXElementBlacklist.h */; settings = {ATTRIBUTES = (Public, ); }; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -61,6 +84,20 @@
 			remoteGlobalIDString = 613C3F40204A09E7007D44A8;
 			remoteInfo = GTAxe;
 		};
+		61284DE5211E442800F8FF29 /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = 613C3F38204A09E7007D44A8 /* Project object */;
+			proxyType = 1;
+			remoteGlobalIDString = 61284DAC211E3F8300F8FF29;
+			remoteInfo = TestApp;
+		};
+		61284E06211E448F00F8FF29 /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = 613C3F38204A09E7007D44A8 /* Project object */;
+			proxyType = 1;
+			remoteGlobalIDString = 613C3F40204A09E7007D44A8;
+			remoteInfo = GTXiLib;
+		};
 /* End PBXContainerItemProxy section */
 
 /* Begin PBXFileReference section */
@@ -77,6 +114,39 @@
 		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; };
+		61284DAD211E3F8300F8FF29 /* TestApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TestApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
+		61284DC3211E3FE100F8FF29 /* GTXTestViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTXTestViewController.m; path = Tests/FunctionalTests/TestApp/Sources/GTXTestViewController.m; sourceTree = SOURCE_ROOT; };
+		61284DC4211E3FE200F8FF29 /* GTXTestViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = GTXTestViewController.xib; path = Tests/FunctionalTests/TestApp/Sources/GTXTestViewController.xib; sourceTree = SOURCE_ROOT; };
+		61284DC5211E3FE200F8FF29 /* GTXTestAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTXTestAppDelegate.m; path = Tests/FunctionalTests/TestApp/Sources/GTXTestAppDelegate.m; sourceTree = SOURCE_ROOT; };
+		61284DC6211E3FE200F8FF29 /* GTXTestStepperButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTXTestStepperButton.m; path = Tests/FunctionalTests/TestApp/Sources/GTXTestStepperButton.m; sourceTree = SOURCE_ROOT; };
+		61284DC7211E3FE300F8FF29 /* GTXTestViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GTXTestViewController.h; path = Tests/FunctionalTests/TestApp/Sources/GTXTestViewController.h; sourceTree = SOURCE_ROOT; };
+		61284DC8211E3FE300F8FF29 /* GTXTestStepperButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GTXTestStepperButton.h; path = Tests/FunctionalTests/TestApp/Sources/GTXTestStepperButton.h; sourceTree = SOURCE_ROOT; };
+		61284DC9211E3FE300F8FF29 /* GTXTestAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GTXTestAppDelegate.h; path = Tests/FunctionalTests/TestApp/Sources/GTXTestAppDelegate.h; sourceTree = SOURCE_ROOT; };
+		61284DCA211E3FE400F8FF29 /* GTXTestApp.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTXTestApp.m; path = Tests/FunctionalTests/TestApp/Sources/GTXTestApp.m; sourceTree = SOURCE_ROOT; };
+		61284DD0211E3FF400F8FF29 /* GTXTestApp-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "GTXTestApp-Info.plist"; path = "Tests/FunctionalTests/TestApp/GTXTestApp-Info.plist"; sourceTree = SOURCE_ROOT; };
+		61284DD2211E42CD00F8FF29 /* GTXBlacklistBlock.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTXBlacklistBlock.m; path = Classes/GTXBlacklistBlock.m; sourceTree = SOURCE_ROOT; };
+		61284DD3211E42CE00F8FF29 /* GTXBlacklistBlock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GTXBlacklistBlock.h; path = Classes/GTXBlacklistBlock.h; sourceTree = SOURCE_ROOT; };
+		61284DD4211E42CE00F8FF29 /* GTXBlacklisting.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GTXBlacklisting.h; path = Classes/GTXBlacklisting.h; sourceTree = SOURCE_ROOT; };
+		61284DD8211E437700F8FF29 /* GTXTestEnvironment.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTXTestEnvironment.m; path = Classes/GTXTestEnvironment.m; sourceTree = SOURCE_ROOT; };
+		61284DD9211E437800F8FF29 /* GTXTestEnvironment.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GTXTestEnvironment.h; path = Classes/GTXTestEnvironment.h; sourceTree = SOURCE_ROOT; };
+		61284DE0211E442700F8FF29 /* FunctionalTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FunctionalTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+		61284DEA211E447800F8FF29 /* GTXTestGtxWorksForPassingTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTXTestGtxWorksForPassingTests.m; path = Tests/FunctionalTests/TestApp/TestSources/GTXTestGtxWorksForPassingTests.m; sourceTree = SOURCE_ROOT; };
+		61284DEB211E447900F8FF29 /* GTXTestFailingClassFailCheck.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTXTestFailingClassFailCheck.m; path = Tests/FunctionalTests/TestApp/TestSources/GTXTestFailingClassFailCheck.m; sourceTree = SOURCE_ROOT; };
+		61284DEC211E447900F8FF29 /* GTXTestAnalyticsBaseTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTXTestAnalyticsBaseTest.m; path = Tests/FunctionalTests/TestApp/TestSources/GTXTestAnalyticsBaseTest.m; sourceTree = SOURCE_ROOT; };
+		61284DED211E447900F8FF29 /* GTXTestGtxCanBlacklistTestCases.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTXTestGtxCanBlacklistTestCases.m; path = Tests/FunctionalTests/TestApp/TestSources/GTXTestGtxCanBlacklistTestCases.m; sourceTree = SOURCE_ROOT; };
+		61284DEE211E447A00F8FF29 /* GTXTestGtxCanDetectFailuresInInheritedTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTXTestGtxCanDetectFailuresInInheritedTests.m; path = Tests/FunctionalTests/TestApp/TestSources/GTXTestGtxCanDetectFailuresInInheritedTests.m; sourceTree = SOURCE_ROOT; };
+		61284DEF211E447A00F8FF29 /* GTXTestGtxCanIgnoreElements.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTXTestGtxCanIgnoreElements.m; path = Tests/FunctionalTests/TestApp/TestSources/GTXTestGtxCanIgnoreElements.m; sourceTree = SOURCE_ROOT; };
+		61284DF0211E447A00F8FF29 /* GTXTestGtxWithDefaultChecks.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTXTestGtxWithDefaultChecks.m; path = Tests/FunctionalTests/TestApp/TestSources/GTXTestGtxWithDefaultChecks.m; sourceTree = SOURCE_ROOT; };
+		61284DF1211E447B00F8FF29 /* GTXTestBaseTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTXTestBaseTest.m; path = Tests/FunctionalTests/TestApp/TestSources/GTXTestBaseTest.m; sourceTree = SOURCE_ROOT; };
+		61284DF2211E447C00F8FF29 /* GTXTestGtxCanWhitelistTestCases.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTXTestGtxCanWhitelistTestCases.m; path = Tests/FunctionalTests/TestApp/TestSources/GTXTestGtxCanWhitelistTestCases.m; sourceTree = SOURCE_ROOT; };
+		61284DF3211E447C00F8FF29 /* GTXTestAnalyticsIsTriggered.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTXTestAnalyticsIsTriggered.m; path = Tests/FunctionalTests/TestApp/TestSources/GTXTestAnalyticsIsTriggered.m; sourceTree = SOURCE_ROOT; };
+		61284DF4211E447C00F8FF29 /* GTXTestAnalyticsBaseTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GTXTestAnalyticsBaseTest.h; path = Tests/FunctionalTests/TestApp/TestSources/GTXTestAnalyticsBaseTest.h; sourceTree = SOURCE_ROOT; };
+		61284DF5211E447C00F8FF29 /* GTXTestAnalyticsCanBeDisabled.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTXTestAnalyticsCanBeDisabled.m; path = Tests/FunctionalTests/TestApp/TestSources/GTXTestAnalyticsCanBeDisabled.m; sourceTree = SOURCE_ROOT; };
+		61284DF6211E447D00F8FF29 /* GTXTestGtxCanIgnoreElementsOnSpecificCheck.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTXTestGtxCanIgnoreElementsOnSpecificCheck.m; path = Tests/FunctionalTests/TestApp/TestSources/GTXTestGtxCanIgnoreElementsOnSpecificCheck.m; sourceTree = SOURCE_ROOT; };
+		61284DF7211E447D00F8FF29 /* GTXTestBaseTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GTXTestBaseTest.h; path = Tests/FunctionalTests/TestApp/TestSources/GTXTestBaseTest.h; sourceTree = SOURCE_ROOT; };
+		61284DF8211E447D00F8FF29 /* GTXTestGtxCanDetectFailures.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTXTestGtxCanDetectFailures.m; path = Tests/FunctionalTests/TestApp/TestSources/GTXTestGtxCanDetectFailures.m; sourceTree = SOURCE_ROOT; };
+		61284E08211E45C500F8FF29 /* GTXBlacklistFactory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GTXBlacklistFactory.h; path = Classes/GTXBlacklistFactory.h; sourceTree = SOURCE_ROOT; };
+		61284E09211E45C600F8FF29 /* GTXBlacklistFactory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTXBlacklistFactory.m; path = Classes/GTXBlacklistFactory.m; sourceTree = SOURCE_ROOT; };
 		613C3F41204A09E8007D44A8 /* GTXiLib.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = GTXiLib.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		61A0C4DB2061896300DF0169 /* GTXiLibCore.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTXiLibCore.m; path = Classes/GTXiLibCore.m; sourceTree = SOURCE_ROOT; };
 		61A0C4DC2061896300DF0169 /* GTXiLib.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GTXiLib.h; path = Classes/GTXiLib.h; sourceTree = SOURCE_ROOT; };
@@ -92,7 +162,6 @@
 		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; };
@@ -111,7 +180,6 @@
 		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; };
-		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 */
 
@@ -124,6 +192,20 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
+		61284DAA211E3F8300F8FF29 /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		61284DDD211E442700F8FF29 /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
 		613C3F3D204A09E7007D44A8 /* Frameworks */ = {
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
@@ -134,11 +216,51 @@
 /* End PBXFrameworksBuildPhase section */
 
 /* Begin PBXGroup section */
+		61284DAE211E3F8300F8FF29 /* TestApp */ = {
+			isa = PBXGroup;
+			children = (
+				61284DD0211E3FF400F8FF29 /* GTXTestApp-Info.plist */,
+				61284DCA211E3FE400F8FF29 /* GTXTestApp.m */,
+				61284DC9211E3FE300F8FF29 /* GTXTestAppDelegate.h */,
+				61284DC5211E3FE200F8FF29 /* GTXTestAppDelegate.m */,
+				61284DC8211E3FE300F8FF29 /* GTXTestStepperButton.h */,
+				61284DC6211E3FE200F8FF29 /* GTXTestStepperButton.m */,
+				61284DC7211E3FE300F8FF29 /* GTXTestViewController.h */,
+				61284DC3211E3FE100F8FF29 /* GTXTestViewController.m */,
+				61284DC4211E3FE200F8FF29 /* GTXTestViewController.xib */,
+			);
+			path = TestApp;
+			sourceTree = "<group>";
+		};
+		61284DE1211E442700F8FF29 /* FunctionalTests */ = {
+			isa = PBXGroup;
+			children = (
+				61284DF4211E447C00F8FF29 /* GTXTestAnalyticsBaseTest.h */,
+				61284DEC211E447900F8FF29 /* GTXTestAnalyticsBaseTest.m */,
+				61284DF5211E447C00F8FF29 /* GTXTestAnalyticsCanBeDisabled.m */,
+				61284DF3211E447C00F8FF29 /* GTXTestAnalyticsIsTriggered.m */,
+				61284DF7211E447D00F8FF29 /* GTXTestBaseTest.h */,
+				61284DF1211E447B00F8FF29 /* GTXTestBaseTest.m */,
+				61284DEB211E447900F8FF29 /* GTXTestFailingClassFailCheck.m */,
+				61284DED211E447900F8FF29 /* GTXTestGtxCanBlacklistTestCases.m */,
+				61284DF8211E447D00F8FF29 /* GTXTestGtxCanDetectFailures.m */,
+				61284DEE211E447A00F8FF29 /* GTXTestGtxCanDetectFailuresInInheritedTests.m */,
+				61284DEF211E447A00F8FF29 /* GTXTestGtxCanIgnoreElements.m */,
+				61284DF6211E447D00F8FF29 /* GTXTestGtxCanIgnoreElementsOnSpecificCheck.m */,
+				61284DF2211E447C00F8FF29 /* GTXTestGtxCanWhitelistTestCases.m */,
+				61284DF0211E447A00F8FF29 /* GTXTestGtxWithDefaultChecks.m */,
+				61284DEA211E447800F8FF29 /* GTXTestGtxWorksForPassingTests.m */,
+			);
+			path = FunctionalTests;
+			sourceTree = "<group>";
+		};
 		613C3F37204A09E7007D44A8 = {
 			isa = PBXGroup;
 			children = (
 				613C3F43204A09E8007D44A8 /* GTXiLib */,
 				61BC5526204E2736004D2FB9 /* GTXiUnitTests */,
+				61284DAE211E3F8300F8FF29 /* TestApp */,
+				61284DE1211E442700F8FF29 /* FunctionalTests */,
 				613C3F42204A09E8007D44A8 /* Products */,
 				61B7F4A1204A15D30062DF65 /* Frameworks */,
 			);
@@ -149,6 +271,8 @@
 			children = (
 				613C3F41204A09E8007D44A8 /* GTXiLib.framework */,
 				6115E786204F5BA2003E32F9 /* GTXiUnitTests.xctest */,
+				61284DAD211E3F8300F8FF29 /* TestApp.app */,
+				61284DE0211E442700F8FF29 /* FunctionalTests.xctest */,
 			);
 			name = Products;
 			sourceTree = "<group>";
@@ -168,6 +292,8 @@
 				61A0C4DC2061896300DF0169 /* GTXiLib.h */,
 				61A0C4DD2061896300DF0169 /* GTXiLibCore.h */,
 				61A0C4DB2061896300DF0169 /* GTXiLibCore.m */,
+				61284E08211E45C500F8FF29 /* GTXBlacklistFactory.h */,
+				61284E09211E45C600F8FF29 /* GTXBlacklistFactory.m */,
 				61ABAE93204A0B03006DBF0A /* GTXAccessibilityTree.h */,
 				61ABAE9A204A0B04006DBF0A /* GTXAccessibilityTree.m */,
 				61ABAE8F204A0B02006DBF0A /* GTXAnalytics.h */,
@@ -175,6 +301,9 @@
 				61ABAE8E204A0B02006DBF0A /* GTXAnalyticsUtils.h */,
 				61ABAE96204A0B03006DBF0A /* GTXAnalyticsUtils.m */,
 				61ABAE9B204A0B05006DBF0A /* GTXAssertions.h */,
+				61284DD3211E42CE00F8FF29 /* GTXBlacklistBlock.h */,
+				61284DD2211E42CD00F8FF29 /* GTXBlacklistBlock.m */,
+				61284DD4211E42CE00F8FF29 /* GTXBlacklisting.h */,
 				61ABAEA9204A0B09006DBF0A /* GTXCheckBlock.h */,
 				61ABAE9E204A0B06006DBF0A /* GTXCheckBlock.m */,
 				610CA3FB204DFC76008BAAA1 /* GTXPluginXCTestCase.h */,
@@ -183,8 +312,6 @@
 				61ABAEA8204A0B08006DBF0A /* GTXChecksCollection.h */,
 				61ABAEA4204A0B07006DBF0A /* GTXChecksCollection.m */,
 				61ABAE94204A0B03006DBF0A /* GTXCommon.h */,
-				61ABAEAF204A0B0A006DBF0A /* GTXElementBlacklist.h */,
-				61ABAE98204A0B04006DBF0A /* GTXElementBlacklist.m */,
 				61ABAE90204A0B02006DBF0A /* GTXErrorReporter.h */,
 				61ABAEAA204A0B09006DBF0A /* GTXErrorReporter.m */,
 				61ABAEA2204A0B06006DBF0A /* GTXImageAndColorUtils.h */,
@@ -192,6 +319,8 @@
 				61ABAE9C204A0B05006DBF0A /* GTXImageRGBAData.h */,
 				61ABAEAC204A0B0A006DBF0A /* GTXImageRGBAData.m */,
 				61ABAE95204A0B03006DBF0A /* GTXLogging.h */,
+				61284DD9211E437800F8FF29 /* GTXTestEnvironment.h */,
+				61284DD8211E437700F8FF29 /* GTXTestEnvironment.m */,
 				61ABAEA1204A0B06006DBF0A /* GTXTestCase.h */,
 				61ABAE99204A0B04006DBF0A /* GTXTestCase.m */,
 				61ABAE9F204A0B06006DBF0A /* GTXTestSuite.h */,
@@ -236,8 +365,8 @@
 			isa = PBXHeadersBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				61284DDB211E437800F8FF29 /* GTXTestEnvironment.h in Headers */,
 				61ABAEB5204A0B0B006DBF0A /* GTXAccessibilityTree.h in Headers */,
-				61ABAED1204A0B0B006DBF0A /* GTXElementBlacklist.h in Headers */,
 				61ABAECD204A0B0B006DBF0A /* GTXToolKit.h in Headers */,
 				61ABAEC8204A0B0B006DBF0A /* NSError+GTXAdditions.h in Headers */,
 				61ABAEC4204A0B0B006DBF0A /* GTXImageAndColorUtils.h in Headers */,
@@ -245,11 +374,14 @@
 				61ABAEBD204A0B0B006DBF0A /* GTXAssertions.h in Headers */,
 				610CA3FD204DFC76008BAAA1 /* GTXPluginXCTestCase.h in Headers */,
 				61ABAECB204A0B0B006DBF0A /* GTXCheckBlock.h in Headers */,
+				61284DD6211E42CF00F8FF29 /* GTXBlacklistBlock.h in Headers */,
 				61ABAEB9204A0B0B006DBF0A /* GTXChecking.h in Headers */,
+				61284E0A211E45C600F8FF29 /* GTXBlacklistFactory.h in Headers */,
 				61ABAEBE204A0B0B006DBF0A /* GTXImageRGBAData.h in Headers */,
 				61ABAEB2204A0B0B006DBF0A /* GTXErrorReporter.h in Headers */,
 				61ABAEC3204A0B0B006DBF0A /* GTXTestCase.h in Headers */,
 				61ABAEB1204A0B0B006DBF0A /* GTXAnalytics.h in Headers */,
+				61284DD7211E42CF00F8FF29 /* GTXBlacklisting.h in Headers */,
 				61A0C4E02061896300DF0169 /* GTXiLibCore.h in Headers */,
 				61A0C4DF2061896300DF0169 /* GTXiLib.h in Headers */,
 				61ABAECA204A0B0B006DBF0A /* GTXChecksCollection.h in Headers */,
@@ -280,6 +412,42 @@
 			productReference = 6115E786204F5BA2003E32F9 /* GTXiUnitTests.xctest */;
 			productType = "com.apple.product-type.bundle.unit-test";
 		};
+		61284DAC211E3F8300F8FF29 /* TestApp */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 61284DC2211E3F8600F8FF29 /* Build configuration list for PBXNativeTarget "TestApp" */;
+			buildPhases = (
+				61284DA9211E3F8300F8FF29 /* Sources */,
+				61284DAA211E3F8300F8FF29 /* Frameworks */,
+				61284DAB211E3F8300F8FF29 /* Resources */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+			);
+			name = TestApp;
+			productName = TestApp;
+			productReference = 61284DAD211E3F8300F8FF29 /* TestApp.app */;
+			productType = "com.apple.product-type.application";
+		};
+		61284DDF211E442700F8FF29 /* FunctionalTests */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 61284DE7211E442800F8FF29 /* Build configuration list for PBXNativeTarget "FunctionalTests" */;
+			buildPhases = (
+				61284DDC211E442700F8FF29 /* Sources */,
+				61284DDD211E442700F8FF29 /* Frameworks */,
+				61284DDE211E442700F8FF29 /* Resources */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+				61284E07211E448F00F8FF29 /* PBXTargetDependency */,
+				61284DE6211E442800F8FF29 /* PBXTargetDependency */,
+			);
+			name = FunctionalTests;
+			productName = FunctionalTests;
+			productReference = 61284DE0211E442700F8FF29 /* FunctionalTests.xctest */;
+			productType = "com.apple.product-type.bundle.unit-test";
+		};
 		613C3F40204A09E7007D44A8 /* GTXiLib */ = {
 			isa = PBXNativeTarget;
 			buildConfigurationList = 613C3F49204A09E8007D44A8 /* Build configuration list for PBXNativeTarget "GTXiLib" */;
@@ -311,6 +479,15 @@
 						CreatedOnToolsVersion = 9.1;
 						ProvisioningStyle = Automatic;
 					};
+					61284DAC211E3F8300F8FF29 = {
+						CreatedOnToolsVersion = 9.4.1;
+						ProvisioningStyle = Automatic;
+					};
+					61284DDF211E442700F8FF29 = {
+						CreatedOnToolsVersion = 9.4.1;
+						ProvisioningStyle = Automatic;
+						TestTargetID = 61284DAC211E3F8300F8FF29;
+					};
 					613C3F40204A09E7007D44A8 = {
 						CreatedOnToolsVersion = 9.1;
 						ProvisioningStyle = Automatic;
@@ -323,6 +500,7 @@
 			hasScannedForEncodings = 0;
 			knownRegions = (
 				en,
+				Base,
 			);
 			mainGroup = 613C3F37204A09E7007D44A8;
 			productRefGroup = 613C3F42204A09E8007D44A8 /* Products */;
@@ -331,6 +509,8 @@
 			targets = (
 				613C3F40204A09E7007D44A8 /* GTXiLib */,
 				6115E785204F5BA2003E32F9 /* GTXiUnitTests */,
+				61284DAC211E3F8300F8FF29 /* TestApp */,
+				61284DDF211E442700F8FF29 /* FunctionalTests */,
 			);
 		};
 /* End PBXProject section */
@@ -343,6 +523,21 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
+		61284DAB211E3F8300F8FF29 /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				61284DCC211E3FE400F8FF29 /* GTXTestViewController.xib in Resources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		61284DDE211E442700F8FF29 /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
 		613C3F3F204A09E7007D44A8 /* Resources */ = {
 			isa = PBXResourcesBuildPhase;
 			buildActionMask = 2147483647;
@@ -368,6 +563,37 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
+		61284DA9211E3F8300F8FF29 /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				61284DCD211E3FE400F8FF29 /* GTXTestAppDelegate.m in Sources */,
+				61284DCE211E3FE400F8FF29 /* GTXTestStepperButton.m in Sources */,
+				61284DCB211E3FE400F8FF29 /* GTXTestViewController.m in Sources */,
+				61284DCF211E3FE400F8FF29 /* GTXTestApp.m in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		61284DDC211E442700F8FF29 /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				61284E05211E447E00F8FF29 /* GTXTestGtxCanDetectFailures.m in Sources */,
+				61284DFD211E447E00F8FF29 /* GTXTestGtxCanDetectFailuresInInheritedTests.m in Sources */,
+				61284DFC211E447E00F8FF29 /* GTXTestGtxCanBlacklistTestCases.m in Sources */,
+				61284E00211E447E00F8FF29 /* GTXTestBaseTest.m in Sources */,
+				61284DFA211E447E00F8FF29 /* GTXTestFailingClassFailCheck.m in Sources */,
+				61284E02211E447E00F8FF29 /* GTXTestAnalyticsIsTriggered.m in Sources */,
+				61284DFF211E447E00F8FF29 /* GTXTestGtxWithDefaultChecks.m in Sources */,
+				61284DFE211E447E00F8FF29 /* GTXTestGtxCanIgnoreElements.m in Sources */,
+				61284DF9211E447E00F8FF29 /* GTXTestGtxWorksForPassingTests.m in Sources */,
+				61284DFB211E447E00F8FF29 /* GTXTestAnalyticsBaseTest.m in Sources */,
+				61284E04211E447E00F8FF29 /* GTXTestGtxCanIgnoreElementsOnSpecificCheck.m in Sources */,
+				61284E03211E447E00F8FF29 /* GTXTestAnalyticsCanBeDisabled.m in Sources */,
+				61284E01211E447E00F8FF29 /* GTXTestGtxCanWhitelistTestCases.m in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
 		613C3F3C204A09E7007D44A8 /* Sources */ = {
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
@@ -376,11 +602,13 @@
 				61ABAECC204A0B0B006DBF0A /* GTXErrorReporter.m in Sources */,
 				61ABAEB4204A0B0B006DBF0A /* GTXTestSuite.m in Sources */,
 				61ABAEB3204A0B0B006DBF0A /* GTXImageAndColorUtils.m in Sources */,
+				61284E0B211E45C600F8FF29 /* GTXBlacklistFactory.m in Sources */,
 				61ABAEC2204A0B0B006DBF0A /* GTXToolKit.m in Sources */,
 				61ABAEC9204A0B0B006DBF0A /* GTXAnalytics.m in Sources */,
-				61ABAEBA204A0B0B006DBF0A /* GTXElementBlacklist.m in Sources */,
 				610CA3FE204DFC76008BAAA1 /* GTXPluginXCTestCase.m in Sources */,
+				61284DDA211E437800F8FF29 /* GTXTestEnvironment.m in Sources */,
 				61ABAEB8204A0B0B006DBF0A /* GTXAnalyticsUtils.m in Sources */,
+				61284DD5211E42CF00F8FF29 /* GTXBlacklistBlock.m in Sources */,
 				61ABAEBF204A0B0B006DBF0A /* NSError+GTXAdditions.m in Sources */,
 				61ABAEBC204A0B0B006DBF0A /* GTXAccessibilityTree.m in Sources */,
 				61ABAEC0204A0B0B006DBF0A /* GTXCheckBlock.m in Sources */,
@@ -398,6 +626,16 @@
 			target = 613C3F40204A09E7007D44A8 /* GTXiLib */;
 			targetProxy = 6115E78C204F5BA3003E32F9 /* PBXContainerItemProxy */;
 		};
+		61284DE6211E442800F8FF29 /* PBXTargetDependency */ = {
+			isa = PBXTargetDependency;
+			target = 61284DAC211E3F8300F8FF29 /* TestApp */;
+			targetProxy = 61284DE5211E442800F8FF29 /* PBXContainerItemProxy */;
+		};
+		61284E07211E448F00F8FF29 /* PBXTargetDependency */ = {
+			isa = PBXTargetDependency;
+			target = 613C3F40204A09E7007D44A8 /* GTXiLib */;
+			targetProxy = 61284E06211E448F00F8FF29 /* PBXContainerItemProxy */;
+		};
 /* End PBXTargetDependency section */
 
 /* Begin XCBuildConfiguration section */
@@ -425,6 +663,76 @@
 			};
 			name = Release;
 		};
+		61284DC0211E3F8600F8FF29 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				CLANG_ENABLE_OBJC_WEAK = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+				CODE_SIGN_STYLE = Automatic;
+				INFOPLIST_FILE = "$(SRCROOT)/Tests/FunctionalTests/TestApp/GTXTestApp-Info.plist";
+				IPHONEOS_DEPLOYMENT_TARGET = 11.4;
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+				PRODUCT_BUNDLE_IDENTIFIER = com.google.gtx.dev.TestApp;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				TARGETED_DEVICE_FAMILY = "1,2";
+			};
+			name = Debug;
+		};
+		61284DC1211E3F8600F8FF29 /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				CLANG_ENABLE_OBJC_WEAK = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+				CODE_SIGN_STYLE = Automatic;
+				INFOPLIST_FILE = "$(SRCROOT)/Tests/FunctionalTests/TestApp/GTXTestApp-Info.plist";
+				IPHONEOS_DEPLOYMENT_TARGET = 11.4;
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+				PRODUCT_BUNDLE_IDENTIFIER = com.google.gtx.dev.TestApp;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				TARGETED_DEVICE_FAMILY = "1,2";
+			};
+			name = Release;
+		};
+		61284DE8211E442800F8FF29 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				BUNDLE_LOADER = "$(TEST_HOST)";
+				CLANG_ENABLE_OBJC_WEAK = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+				CODE_SIGN_STYLE = Automatic;
+				INFOPLIST_FILE = "Tests/FunctionalTests/TestApp/GTXTestApp-Info.plist";
+				IPHONEOS_DEPLOYMENT_TARGET = 11.4;
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+				PRODUCT_BUNDLE_IDENTIFIER = com.google.gtx.dev.FunctionalTests;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				TARGETED_DEVICE_FAMILY = "1,2";
+				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TestApp.app/TestApp";
+			};
+			name = Debug;
+		};
+		61284DE9211E442800F8FF29 /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				BUNDLE_LOADER = "$(TEST_HOST)";
+				CLANG_ENABLE_OBJC_WEAK = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+				CODE_SIGN_STYLE = Automatic;
+				INFOPLIST_FILE = "Tests/FunctionalTests/TestApp/GTXTestApp-Info.plist";
+				IPHONEOS_DEPLOYMENT_TARGET = 11.4;
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+				PRODUCT_BUNDLE_IDENTIFIER = com.google.gtx.dev.FunctionalTests;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				TARGETED_DEVICE_FAMILY = "1,2";
+				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TestApp.app/TestApp";
+			};
+			name = Release;
+		};
 		613C3F47204A09E8007D44A8 /* Debug */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
@@ -585,6 +893,24 @@
 			defaultConfigurationIsVisible = 0;
 			defaultConfigurationName = Release;
 		};
+		61284DC2211E3F8600F8FF29 /* Build configuration list for PBXNativeTarget "TestApp" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				61284DC0211E3F8600F8FF29 /* Debug */,
+				61284DC1211E3F8600F8FF29 /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		61284DE7211E442800F8FF29 /* Build configuration list for PBXNativeTarget "FunctionalTests" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				61284DE8211E442800F8FF29 /* Debug */,
+				61284DE9211E442800F8FF29 /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
 		613C3F3B204A09E7007D44A8 /* Build configuration list for PBXProject "GTXiLib" */ = {
 			isa = XCConfigurationList;
 			buildConfigurations = (
diff --git a/Tests/FunctionalTests/TestApp/Sources/GTXTestViewController.m b/Tests/FunctionalTests/TestApp/Sources/GTXTestViewController.m
index b1e7268..2b67d4a 100644
--- a/Tests/FunctionalTests/TestApp/Sources/GTXTestViewController.m
+++ b/Tests/FunctionalTests/TestApp/Sources/GTXTestViewController.m
@@ -76,7 +76,8 @@
   actionsToHandlers = [[NSMutableDictionary alloc] init];
   [self.navigationController setNavigationBarHidden:YES animated:NO];
 
-  [self axetest_addActionNamed:kAddNoLabelElementActionName handler:^(GTXTestViewController *sSelf) {
+  [self axetest_addActionNamed:kAddNoLabelElementActionName
+                       handler:^(GTXTestViewController *sSelf) {
     [sSelf axetest_addElementWithLabel:@""];
   }];
   [self axetest_addActionNamed:kAddPunctuatedLabelElementActionName
@@ -152,6 +153,7 @@
   CGRect buttonFrame = newButton.frame;
   buttonFrame.size.height = kMinimumElementSize;
   newButton.frame = buttonFrame;
+  newButton.accessibilityIdentifier = name;
 
   CGSize contentSize = self.actionsContainerView.contentSize;
   buttonFrame.origin.y = contentSize.height + kMargin;
diff --git a/Tests/FunctionalTests/TestApp/TestSources/GTXTestBaseTest.h b/Tests/FunctionalTests/TestApp/TestSources/GTXTestBaseTest.h
index fec8e1a..641ac32 100644
--- a/Tests/FunctionalTests/TestApp/TestSources/GTXTestBaseTest.h
+++ b/Tests/FunctionalTests/TestApp/TestSources/GTXTestBaseTest.h
@@ -24,17 +24,17 @@
 /**
  Check that fails if the element is of Class @c GTXTestFailingClass.
  */
-id<GTXChecking> checkFailsIfFailingClass;
+extern id<GTXChecking> checkFailsIfFailingClass;
 
 /**
  Check that always passes.
  */
-id<GTXChecking> alwaysFail;
+extern id<GTXChecking> alwaysFail;
 
 /**
  Check that always fails.
  */
-id<GTXChecking> alwaysPass;
+extern id<GTXChecking> alwaysPass;
 
 /**
  Base test for all GTXiLib functional/integration tests used to setup GTXiLib and capture check
diff --git a/Tests/FunctionalTests/TestApp/TestSources/GTXTestBaseTest.m b/Tests/FunctionalTests/TestApp/TestSources/GTXTestBaseTest.m
index d0665a3..07d8c9f 100644
--- a/Tests/FunctionalTests/TestApp/TestSources/GTXTestBaseTest.m
+++ b/Tests/FunctionalTests/TestApp/TestSources/GTXTestBaseTest.m
@@ -19,6 +19,9 @@
 #import "GTXTestViewController.h"
 
 static NSInteger gFailureCount = 0;
+id<GTXChecking> checkFailsIfFailingClass;
+id<GTXChecking> alwaysFail;
+id<GTXChecking> alwaysPass;
 
 @implementation GTXTestBaseTest
 
diff --git a/Tests/FunctionalTests/TestApp/TestSources/GTXTestGtxCanIgnoreElements.m b/Tests/FunctionalTests/TestApp/TestSources/GTXTestGtxCanIgnoreElements.m
index e3dc768..b39c3a2 100644
--- a/Tests/FunctionalTests/TestApp/TestSources/GTXTestGtxCanIgnoreElements.m
+++ b/Tests/FunctionalTests/TestApp/TestSources/GTXTestGtxCanIgnoreElements.m
@@ -15,6 +15,7 @@
 //
 
 #import "GTXTestBaseTest.h"
+#import "GTXBlacklistFactory.h"
 #import "GTXiLib.h"
 
 @interface GTXTestGtxCanIgnoreElements : GTXTestBaseTest
@@ -24,8 +25,9 @@
 
 + (void)setUp {
   [super setUp];
+  NSString *className = NSStringFromClass([GTXTestFailingClass class]);
   NSArray *blacklist =
-      @[[GTXiLib blacklistForElementsOfClassNamed:NSStringFromClass([GTXTestFailingClass class])]];
+      @[[GTXBlacklistFactory blacklistWithClassName:className]];
   [GTXiLib installOnTestSuite:[GTXTestSuite suiteWithAllTestsInClass:self]
                        checks:@[checkFailsIfFailingClass]
             elementBlacklists:blacklist];
diff --git a/Tests/FunctionalTests/TestApp/TestSources/GTXTestGtxCanIgnoreElementsOnSpecificCheck.m b/Tests/FunctionalTests/TestApp/TestSources/GTXTestGtxCanIgnoreElementsOnSpecificCheck.m
index fafe612..8d3cde0 100644
--- a/Tests/FunctionalTests/TestApp/TestSources/GTXTestGtxCanIgnoreElementsOnSpecificCheck.m
+++ b/Tests/FunctionalTests/TestApp/TestSources/GTXTestGtxCanIgnoreElementsOnSpecificCheck.m
@@ -24,9 +24,10 @@
 
 + (void)setUp {
   [super setUp];
+  NSString *className = NSStringFromClass([GTXTestFailingClass class]);
   NSArray *blacklist =
-      @[[GTXiLib blacklistForElementsOfClassNamed:NSStringFromClass([GTXTestFailingClass class])
-                                    forCheckNamed:checkFailsIfFailingClass.name]];
+      @[[GTXBlacklistFactory blacklistWithClassName:className
+                                             checkName:checkFailsIfFailingClass.name]];
   id<GTXChecking> secondFailingCheck = [GTXiLib checkWithName:@"secondFailingCheck"
                                             block:^BOOL(id element, GTXErrorRefType errorOrNil) {
     return ![element isKindOfClass:[GTXTestFailingClass class]];
diff --git a/Tests/FunctionalTests/TestApp/TestSources/GTXTestGtxWithDefaultChecks.m b/Tests/FunctionalTests/TestApp/TestSources/GTXTestGtxWithDefaultChecks.m
index 534d00d..20260f5 100644
--- a/Tests/FunctionalTests/TestApp/TestSources/GTXTestGtxWithDefaultChecks.m
+++ b/Tests/FunctionalTests/TestApp/TestSources/GTXTestGtxWithDefaultChecks.m
@@ -28,10 +28,16 @@
 + (void)setUp {
   [super setUp];
 
+  NSString *notRedundantCheckName = [[GTXChecksCollection checkForAXLabelNotRedundantWithTraits]
+      name];
+  NSArray<id<GTXBlacklisting>> *blacklists = @[
+    [GTXBlacklistFactory blacklistWithAccessibilityIdentifier:kAddInaccessibleButton
+                                                    checkName:notRedundantCheckName]
+  ];
   // Install all the default checks on the current test class.
   [GTXiLib installOnTestSuite:[GTXTestSuite suiteWithAllTestsInClass:self]
                        checks:[GTXChecksCollection allGTXChecks]
-            elementBlacklists:@[]];
+            elementBlacklists:blacklists];
 }
 
 - (void)setUp {
diff --git a/Tests/UnitTests/GTXChecksCollectionTests.m b/Tests/UnitTests/GTXChecksCollectionTests.m
index 5c359f0..b209668 100644
--- a/Tests/UnitTests/GTXChecksCollectionTests.m
+++ b/Tests/UnitTests/GTXChecksCollectionTests.m
@@ -160,6 +160,61 @@
            errorDescription:nil];
 }
 
+- (void)testGtxCheckForAXLabelNotRedundantWithTraits {
+  // Label is not redundant with traits.
+  [self assertGtxCheck:[GTXChecksCollection checkForAXLabelNotRedundantWithTraits]
+              succeeds:YES
+           withElement:[self uiAccessibilityElementWithLabel:@"foo"
+                                                      traits:UIAccessibilityTraitButton]
+      errorDescription:nil];
+  // Label is redundant with traits (uppercase).
+  [self assertGtxCheck:[GTXChecksCollection checkForAXLabelNotRedundantWithTraits]
+              succeeds:NO
+           withElement:[self uiAccessibilityElementWithLabel:@"FOO BUTTON"
+                                                      traits:UIAccessibilityTraitButton]
+      errorDescription:nil];
+  // Label is redundant with traits (lowercase).
+  [self assertGtxCheck:[GTXChecksCollection checkForAXLabelNotRedundantWithTraits]
+              succeeds:NO
+           withElement:[self uiAccessibilityElementWithLabel:@"foo button"
+                                                      traits:UIAccessibilityTraitButton]
+      errorDescription:nil];
+  // Label is not redundant with traits (not suffix).
+  [self assertGtxCheck:[GTXChecksCollection checkForAXLabelNotRedundantWithTraits]
+              succeeds:YES
+           withElement:[self uiAccessibilityElementWithLabel:@"button foo"
+                                                      traits:UIAccessibilityTraitButton]
+      errorDescription:nil];
+  // Empty label (not redundant).
+  [self assertGtxCheck:[GTXChecksCollection checkForAXLabelNotRedundantWithTraits]
+              succeeds:YES
+           withElement:[self uiAccessibilityElementWithLabel:@""
+                                                      traits:UIAccessibilityTraitButton]
+      errorDescription:nil];
+  // Whitespace label (not redundant).
+  [self assertGtxCheck:[GTXChecksCollection checkForAXLabelNotRedundantWithTraits]
+              succeeds:YES
+           withElement:[self uiAccessibilityElementWithLabel:@" "
+                                                      traits:UIAccessibilityTraitButton]
+      errorDescription:nil];
+  // No traits or violating label (not redundant).
+  [self assertGtxCheck:[GTXChecksCollection checkForAXLabelNotRedundantWithTraits]
+              succeeds:YES
+           withElement:[self uiAccessibilityElementWithLabel:@"foo"]
+      errorDescription:nil];
+  // No traits (not redundant).
+  [self assertGtxCheck:[GTXChecksCollection checkForAXLabelNotRedundantWithTraits]
+              succeeds:YES
+           withElement:[self uiAccessibilityElementWithLabel:@"foo button"]
+      errorDescription:nil];
+  // Wrong traits (not redundant).
+  [self assertGtxCheck:[GTXChecksCollection checkForAXLabelNotRedundantWithTraits]
+              succeeds:YES
+           withElement:[self uiAccessibilityElementWithLabel:@"foo button"
+                                                      traits:UIAccessibilityTraitStaticText]
+      errorDescription:nil];
+}
+
 - (void)testGtxCheckForAXTraitsConflict {
   NSString * const expectedErrorDescription =
       @"Check \"Accessibility Traits Don't Conflict\" failed";
@@ -215,6 +270,17 @@
 }
 
 /**
+ *  @return An accessibility element whose accessibility label is set to the specified @c label
+ *  and accessibility trait is set to the specified @c traits.
+ */
+- (UIAccessibilityElement *)uiAccessibilityElementWithLabel:(id)label
+                                                     traits:(UIAccessibilityTraits)traits {
+  UIAccessibilityElement *element = [self uiAccessibilityElementWithLabel:label];
+  element.accessibilityTraits = traits;
+  return element;
+}
+
+/**
  *  Asserts that the given GTXCheck succeeds or fails with the specified element.
  *
  *  @param check            The check to be tested.
@@ -234,9 +300,11 @@
     XCTAssertNil(error);
   } else {
     XCTAssertNotNil(error);
-    XCTAssertTrue([[error description] containsString:descriptionOrNil],
-                  @"[Expected] was not present in [Actual]!\n Expected: %@\n Actual: %@",
-                  descriptionOrNil, [error description]);
+    if (descriptionOrNil != nil) {
+      XCTAssertTrue([[error description] containsString:descriptionOrNil],
+                    @"[Expected] was not present in [Actual]!\n Expected: %@\n Actual: %@",
+                    descriptionOrNil, [error description]);
+    }
   }
 }
 
diff --git a/Tests/UnitTests/GTXImageAndColorUtilsTests.m b/Tests/UnitTests/GTXImageAndColorUtilsTests.m
index fedb2d6..f406032 100644
--- a/Tests/UnitTests/GTXImageAndColorUtilsTests.m
+++ b/Tests/UnitTests/GTXImageAndColorUtilsTests.m
@@ -129,7 +129,9 @@
   CGFloat contrastRatioInImage =
       [GTXImageAndColorUtils gtx_contrastRatioWithTextElementImage:imageWithRect
                                       textElementColorShiftedImage:imageWithShiftedColorRect];
-  XCTAssertEqualWithAccuracy(contrastRatioInImage, contrastRatioOfColors, kContrastRatioAccuracy);
+  XCTAssertEqualWithAccuracy(contrastRatioInImage,
+                             contrastRatioOfColors,
+                             kGTXContrastRatioAccuracy);
 }
 
 /**
diff --git a/Tests/UnitTests/GTXToolKitTests.m b/Tests/UnitTests/GTXToolKitTests.m
index 7712494..224f253 100644
--- a/Tests/UnitTests/GTXToolKitTests.m
+++ b/Tests/UnitTests/GTXToolKitTests.m
@@ -19,20 +19,27 @@
 
 #import "GTXToolKit.h"
 #import "GTXAnalytics.h"
+#import "GTXBlacklistFactory.h"
 #import "GTXBaseTestCase.h"
 
-@interface GTXTestElementClass1 : NSObject
+@interface GTXTestElementClass1 : UIAccessibilityElement
 @end
 
 @implementation GTXTestElementClass1
 @end
 
-@interface GTXTestElementClass2 : NSObject
+@interface GTXTestElementClass2 : UIAccessibilityElement
 @end
 
 @implementation GTXTestElementClass2
 @end
 
+@interface GTXTestElementClass3 : UIAccessibilityElement
+@end
+
+@implementation GTXTestElementClass3
+@end
+
 @interface GTXToolKitTests : GTXBaseTestCase
 @end
 
@@ -104,7 +111,7 @@
   XCTAssertTrue([toolkit checkAllElementsFromRootElements:@[root] error:&error]);
 }
 
-- (void)testIgnoreElementAPIIgnoresElementsFromChecks {
+- (void)testBlacklistAPICanSkipElementsFromChecks {
   GTXToolKit *toolkit = [[GTXToolKit alloc] init];
   NSObject *failingElement = [self newAccessibleElement];
   id<GTXChecking> check = [GTXToolKit checkWithName:@"Foo"
@@ -114,17 +121,23 @@
                                               }];
   [toolkit registerCheck:check];
   XCTAssertFalse([toolkit checkElement:failingElement error:nil]);
-  [toolkit ignoreElementsOfClassNamed:NSStringFromClass([failingElement class])];
+  [toolkit registerBlacklist:
+      [GTXBlacklistFactory blacklistWithClassName:NSStringFromClass([failingElement class])]];
   XCTAssertTrue([toolkit checkElement:failingElement error:nil]);
 }
 
-- (void)testIgnoreElementAPIIgnoresElementsFromSpecificChecks {
+- (void)testBlacklistAPICanSkipElementsFromSpecificChecks {
   GTXTestElementClass1 *check1FailingElement = [[GTXTestElementClass1 alloc] init];
   check1FailingElement.isAccessibilityElement = YES;
+  check1FailingElement.accessibilityIdentifier = @"check1FailingElement";
 
   GTXTestElementClass2 *allChecksFailingElement = [[GTXTestElementClass2 alloc] init];
   allChecksFailingElement.isAccessibilityElement = YES;
 
+  GTXTestElementClass3 *check3FailingElement = [[GTXTestElementClass3 alloc] init];
+  check3FailingElement.isAccessibilityElement = YES;
+  check3FailingElement.accessibilityIdentifier = @"check3FailingElement";
+
   NSString *check1Name = @"Check 1";
   NSString *check2Name = @"Check 2";
   id<GTXChecking> check1 = [GTXToolKit checkWithName:check1Name
@@ -143,12 +156,26 @@
   [toolkit1 registerCheck:check2];
   XCTAssertFalse([toolkit1 checkElement:check1FailingElement error:nil]);
   XCTAssertFalse([toolkit1 checkElement:allChecksFailingElement error:nil]);
+  XCTAssertFalse([toolkit1 checkElement:check3FailingElement error:nil]);
 
-  [toolkit1 ignoreElementsOfClassNamed:NSStringFromClass([check1FailingElement class])];
+  [toolkit1 registerBlacklist:
+      [GTXBlacklistFactory blacklistWithAccessibilityIdentifier:@"check3FailingElement"
+                                                      checkName:check1Name]];
+  XCTAssertFalse([toolkit1 checkElement:check1FailingElement error:nil]);
+  XCTAssertFalse([toolkit1 checkElement:allChecksFailingElement error:nil]);
+  XCTAssertTrue([toolkit1 checkElement:check3FailingElement error:nil]);
+
+  [toolkit1 registerBlacklist:
+      [GTXBlacklistFactory blacklistWithClassName:NSStringFromClass([check1FailingElement class])]];
+  [toolkit1 registerBlacklist:
+      [GTXBlacklistFactory blacklistWithClassName:NSStringFromClass([check1FailingElement class])]];
   XCTAssertTrue([toolkit1 checkElement:check1FailingElement error:nil]);
   XCTAssertFalse([toolkit1 checkElement:allChecksFailingElement error:nil]);
+  XCTAssertTrue([toolkit1 checkElement:check3FailingElement error:nil]);
 
-  [toolkit1 ignoreElementsOfClassNamed:NSStringFromClass([allChecksFailingElement class])];
+  NSString *allChecksFailingElementClass = NSStringFromClass([allChecksFailingElement class]);
+  [toolkit1 registerBlacklist:
+      [GTXBlacklistFactory blacklistWithClassName:allChecksFailingElementClass]];
   XCTAssertTrue([toolkit1 checkElement:allChecksFailingElement error:nil]);
 }