FBXCElementSnapshotWrapper+Helpers.m 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. /**
  2. * Copyright (c) 2018-present, Facebook, Inc.
  3. * All rights reserved.
  4. *
  5. * This source code is licensed under the BSD-style license found in the
  6. * LICENSE file in the root directory of this source tree. An additional grant
  7. * of patent rights can be found in the PATENTS file in the same directory.
  8. */
  9. #import "FBXCElementSnapshotWrapper+Helpers.h"
  10. #import "FBFindElementCommands.h"
  11. #import "FBRunLoopSpinner.h"
  12. #import "FBLogger.h"
  13. #import "FBXCElementSnapshot.h"
  14. #import "FBXCAXClientProxy.h"
  15. #import "XCTestDriver.h"
  16. #import "XCTestPrivateSymbols.h"
  17. #import "XCUIElement.h"
  18. #import "XCUIElement+FBWebDriverAttributes.h"
  19. #import "XCUIHitPointResult.h"
  20. inline static BOOL isSnapshotTypeAmongstGivenTypes(id<FBXCElementSnapshot> snapshot,
  21. NSArray<NSNumber *> *types);
  22. @implementation FBXCElementSnapshotWrapper (Helpers)
  23. - (NSString *)fb_description
  24. {
  25. NSString *result = [NSString stringWithFormat:@"%@", self.wdType];
  26. if (nil != self.wdName) {
  27. result = [NSString stringWithFormat:@"%@ (%@)", result, self.wdName];
  28. }
  29. return result;
  30. }
  31. - (NSArray<id<FBXCElementSnapshot>> *)fb_descendantsMatchingType:(XCUIElementType)type
  32. {
  33. return [self descendantsByFilteringWithBlock:^BOOL(id<FBXCElementSnapshot> snapshot) {
  34. return snapshot.elementType == type;
  35. }];
  36. }
  37. - (id<FBXCElementSnapshot>)fb_parentMatchingType:(XCUIElementType)type
  38. {
  39. NSArray *acceptedParents = @[@(type)];
  40. return [self fb_parentMatchingOneOfTypes:acceptedParents];
  41. }
  42. - (id<FBXCElementSnapshot>)fb_parentMatchingOneOfTypes:(NSArray<NSNumber *> *)types
  43. {
  44. return [self fb_parentMatchingOneOfTypes:types filter:^(id<FBXCElementSnapshot> snapshot) {
  45. return YES;
  46. }];
  47. }
  48. - (id<FBXCElementSnapshot>)fb_parentMatchingOneOfTypes:(NSArray<NSNumber *> *)types
  49. filter:(BOOL(^)(id<FBXCElementSnapshot> snapshot))filter
  50. {
  51. id<FBXCElementSnapshot> snapshot = self.parent;
  52. while (snapshot && !(isSnapshotTypeAmongstGivenTypes(snapshot, types) && filter(snapshot))) {
  53. snapshot = snapshot.parent;
  54. }
  55. return snapshot;
  56. }
  57. - (id)fb_attributeValue:(NSString *)attribute
  58. {
  59. NSDictionary *result = [FBXCAXClientProxy.sharedClient attributesForElement:[self accessibilityElement]
  60. attributes:@[attribute]];
  61. return result[attribute];
  62. }
  63. inline static BOOL areValuesEqual(id value1, id value2);
  64. inline static BOOL areValuesEqualOrBlank(id value1, id value2);
  65. inline static BOOL isNilOrEmpty(id value);
  66. - (BOOL)fb_framelessFuzzyMatchesElement:(id<FBXCElementSnapshot>)snapshot
  67. {
  68. // Pure payload-based comparison sometimes yield false negatives, therefore relying on it only if all of the identifying properties are blank
  69. if (isNilOrEmpty(self.identifier)
  70. && isNilOrEmpty(self.title)
  71. && isNilOrEmpty(self.label)
  72. && isNilOrEmpty(self.value)
  73. && isNilOrEmpty(self.placeholderValue)) {
  74. return [self.wdUID isEqualToString:([FBXCElementSnapshotWrapper ensureWrapped:snapshot].wdUID ?: @"")];
  75. }
  76. // Sometimes value and placeholderValue of a correct match from different snapshots are not the same (one is nil and one is a blank string)
  77. // Therefore taking it into account when comparing
  78. return self.elementType == snapshot.elementType &&
  79. areValuesEqual(self.identifier, snapshot.identifier) &&
  80. areValuesEqual(self.title, snapshot.title) &&
  81. areValuesEqual(self.label, snapshot.label) &&
  82. areValuesEqualOrBlank(self.value, snapshot.value) &&
  83. areValuesEqualOrBlank(self.placeholderValue, snapshot.placeholderValue);
  84. }
  85. - (NSArray<id<FBXCElementSnapshot>> *)fb_descendantsCellSnapshots
  86. {
  87. NSArray<id<FBXCElementSnapshot>> *cellSnapshots = [self fb_descendantsMatchingType:XCUIElementTypeCell];
  88. if (cellSnapshots.count == 0) {
  89. // For the home screen, cells are actually of type XCUIElementTypeIcon
  90. cellSnapshots = [self fb_descendantsMatchingType:XCUIElementTypeIcon];
  91. }
  92. if (cellSnapshots.count == 0) {
  93. // In some cases XCTest will not report Cell Views. In that case grab all descendants and try to figure out scroll directon from them.
  94. cellSnapshots = self._allDescendants;
  95. }
  96. return cellSnapshots;
  97. }
  98. - (NSArray<id<FBXCElementSnapshot>> *)fb_ancestors
  99. {
  100. NSMutableArray<id<FBXCElementSnapshot>> *ancestors = [NSMutableArray array];
  101. id<FBXCElementSnapshot> parent = self.parent;
  102. while (parent) {
  103. [ancestors addObject:parent];
  104. parent = parent.parent;
  105. }
  106. return ancestors.copy;
  107. }
  108. - (id<FBXCElementSnapshot>)fb_parentCellSnapshot
  109. {
  110. id<FBXCElementSnapshot> targetCellSnapshot = self.snapshot;
  111. // XCUIElementTypeIcon is the cell type for homescreen icons
  112. NSArray<NSNumber *> *acceptableElementTypes = @[
  113. @(XCUIElementTypeCell),
  114. @(XCUIElementTypeIcon),
  115. ];
  116. if (self.elementType != XCUIElementTypeCell && self.elementType != XCUIElementTypeIcon) {
  117. targetCellSnapshot = [self fb_parentMatchingOneOfTypes:acceptableElementTypes];
  118. }
  119. return targetCellSnapshot;
  120. }
  121. - (CGRect)fb_visibleFrameWithFallback
  122. {
  123. CGRect thisVisibleFrame = [self visibleFrame];
  124. if (!CGRectIsEmpty(thisVisibleFrame)) {
  125. return thisVisibleFrame;
  126. }
  127. NSDictionary *visibleFrameDict = (NSDictionary*)[self fb_attributeValue:@"XC_kAXXCAttributeVisibleFrame"];
  128. if (visibleFrameDict == nil) {
  129. return thisVisibleFrame;
  130. }
  131. id x = [visibleFrameDict objectForKey:@"X"];
  132. id y = [visibleFrameDict objectForKey:@"Y"];
  133. id height = [visibleFrameDict objectForKey:@"Height"];
  134. id width = [visibleFrameDict objectForKey:@"Width"];
  135. if (x != nil && y != nil && height != nil && width != nil) {
  136. return CGRectMake([x doubleValue], [y doubleValue], [width doubleValue], [height doubleValue]);
  137. }
  138. return thisVisibleFrame;
  139. }
  140. - (NSValue *)fb_hitPoint
  141. {
  142. NSError *error;
  143. XCUIHitPointResult *result = [self hitPoint:&error];
  144. if (nil != error) {
  145. [FBLogger logFmt:@"Failed to fetch hit point for %@ - %@", self.fb_description, error.localizedDescription];
  146. return nil;
  147. }
  148. return [NSValue valueWithCGPoint:result.hitPoint];
  149. }
  150. @end
  151. inline static BOOL isSnapshotTypeAmongstGivenTypes(id<FBXCElementSnapshot> snapshot, NSArray<NSNumber *> *types)
  152. {
  153. for (NSUInteger i = 0; i < types.count; i++) {
  154. if([@(snapshot.elementType) isEqual: types[i]] || [types[i] isEqual: @(XCUIElementTypeAny)]){
  155. return YES;
  156. }
  157. }
  158. return NO;
  159. }
  160. inline static BOOL areValuesEqual(id value1, id value2)
  161. {
  162. return value1 == value2 || [value1 isEqual:value2];
  163. }
  164. inline static BOOL areValuesEqualOrBlank(id value1, id value2)
  165. {
  166. return areValuesEqual(value1, value2) || (isNilOrEmpty(value1) && isNilOrEmpty(value2));
  167. }
  168. inline static BOOL isNilOrEmpty(id value)
  169. {
  170. if ([value isKindOfClass:NSString.class]) {
  171. return [(NSString*)value length] == 0;
  172. }
  173. return value == nil;
  174. }