XCUIElement+FBWebDriverAttributes.m 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. /**
  2. * Copyright (c) 2015-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 "XCUIElement+FBWebDriverAttributes.h"
  10. #import "FBElementTypeTransformer.h"
  11. #import "FBElementHelpers.h"
  12. #import "FBLogger.h"
  13. #import "FBMacros.h"
  14. #import "FBXCElementSnapshotWrapper.h"
  15. #import "XCUIElement+FBAccessibility.h"
  16. #import "XCUIElement+FBIsVisible.h"
  17. #import "XCUIElement+FBUID.h"
  18. #import "XCUIElement.h"
  19. #import "XCUIElement+FBUtilities.h"
  20. #import "FBElementUtils.h"
  21. #import "XCTestPrivateSymbols.h"
  22. #import "XCUIHitPointResult.h"
  23. #import "FBAccessibilityTraits.h"
  24. #import "XCUIElement+FBMinMax.h"
  25. #define BROKEN_RECT CGRectMake(-1, -1, 0, 0)
  26. @implementation XCUIElement (WebDriverAttributesForwarding)
  27. - (id<FBXCElementSnapshot>)fb_snapshotForAttributeName:(NSString *)name
  28. {
  29. // https://github.com/appium/appium-xcuitest-driver/pull/2565
  30. if ([name isEqualToString:FBStringify(XCUIElement, isWDHittable)]) {
  31. return [self fb_nativeSnapshot];
  32. }
  33. // https://github.com/appium/appium-xcuitest-driver/issues/2552
  34. BOOL isValueRequest = [name isEqualToString:FBStringify(XCUIElement, wdValue)];
  35. if ([self isKindOfClass:XCUIApplication.class] && !isValueRequest) {
  36. return [self fb_standardSnapshot];
  37. }
  38. BOOL isCustomSnapshot = [name isEqualToString:FBStringify(XCUIElement, isWDAccessible)]
  39. || [name isEqualToString:FBStringify(XCUIElement, isWDAccessibilityContainer)]
  40. || [name isEqualToString:FBStringify(XCUIElement, wdIndex)]
  41. || isValueRequest;
  42. return isCustomSnapshot ? [self fb_customSnapshot] : [self fb_standardSnapshot];
  43. }
  44. - (id)fb_valueForWDAttributeName:(NSString *)name
  45. {
  46. NSString *wdAttributeName = [FBElementUtils wdAttributeNameForAttributeName:name];
  47. id<FBXCElementSnapshot> snapshot = [self fb_snapshotForAttributeName:wdAttributeName];
  48. return [[FBXCElementSnapshotWrapper ensureWrapped:snapshot] fb_valueForWDAttributeName:name];
  49. }
  50. - (id)forwardingTargetForSelector:(SEL)aSelector
  51. {
  52. static dispatch_once_t onceToken;
  53. static NSSet<NSString *> *fbElementAttributeNames;
  54. dispatch_once(&onceToken, ^{
  55. fbElementAttributeNames = [FBElementUtils selectorNamesWithProtocol:@protocol(FBElement)];
  56. });
  57. NSString* attributeName = NSStringFromSelector(aSelector);
  58. return [fbElementAttributeNames containsObject:attributeName]
  59. ? [FBXCElementSnapshotWrapper ensureWrapped:[self fb_snapshotForAttributeName:attributeName]]
  60. : nil;
  61. }
  62. @end
  63. @implementation FBXCElementSnapshotWrapper (WebDriverAttributes)
  64. - (id)fb_valueForWDAttributeName:(NSString *)name
  65. {
  66. return [self valueForKey:[FBElementUtils wdAttributeNameForAttributeName:name]];
  67. }
  68. - (NSNumber *)wdMinValue
  69. {
  70. return self.fb_minValue;
  71. }
  72. - (NSNumber *)wdMaxValue
  73. {
  74. return self.fb_maxValue;
  75. }
  76. - (NSString *)wdValue
  77. {
  78. id value = self.value;
  79. XCUIElementType elementType = self.elementType;
  80. if (elementType == XCUIElementTypeStaticText) {
  81. NSString *label = self.label;
  82. value = FBFirstNonEmptyValue(value, label);
  83. } else if (elementType == XCUIElementTypeButton) {
  84. NSNumber *isSelected = self.isSelected ? @YES : nil;
  85. value = FBFirstNonEmptyValue(value, isSelected);
  86. } else if (elementType == XCUIElementTypeSwitch) {
  87. value = @([value boolValue]);
  88. } else if (FBDoesElementSupportInnerText(elementType)) {
  89. NSString *placeholderValue = self.placeholderValue;
  90. value = FBFirstNonEmptyValue(value, placeholderValue);
  91. }
  92. value = FBTransferEmptyStringToNil(value);
  93. if (value) {
  94. value = [NSString stringWithFormat:@"%@", value];
  95. }
  96. return value;
  97. }
  98. + (NSString *)wdNameWithSnapshot:(id<FBXCElementSnapshot>)snapshot
  99. {
  100. NSString *identifier = snapshot.identifier;
  101. if (nil != identifier && identifier.length != 0) {
  102. return identifier;
  103. }
  104. NSString *label = snapshot.label;
  105. return FBTransferEmptyStringToNil(label);
  106. }
  107. - (NSString *)wdName
  108. {
  109. return [self.class wdNameWithSnapshot:self.snapshot];
  110. }
  111. - (NSString *)wdLabel
  112. {
  113. XCUIElementType elementType = self.elementType;
  114. return (elementType == XCUIElementTypeTextField
  115. || elementType == XCUIElementTypeSecureTextField)
  116. ? self.label
  117. : FBTransferEmptyStringToNil(self.label);
  118. }
  119. - (NSString *)wdPlaceholderValue
  120. {
  121. return FBDoesElementSupportInnerText(self.elementType)
  122. ? self.placeholderValue
  123. : FBTransferEmptyStringToNil(self.placeholderValue);
  124. }
  125. - (NSString *)wdType
  126. {
  127. return [FBElementTypeTransformer stringWithElementType:self.elementType];
  128. }
  129. - (NSString *)wdUID
  130. {
  131. return self.fb_uid;
  132. }
  133. - (CGRect)wdFrame
  134. {
  135. CGRect frame = self.frame;
  136. // It is mandatory to replace all Infinity values with numbers to avoid JSON parsing
  137. // exceptions like https://github.com/facebook/WebDriverAgent/issues/639#issuecomment-314421206
  138. // caused by broken element dimensions returned by XCTest
  139. return (isinf(frame.size.width) || isinf(frame.size.height)
  140. || isinf(frame.origin.x) || isinf(frame.origin.y))
  141. ? CGRectIntegral(BROKEN_RECT)
  142. : CGRectIntegral(frame);
  143. }
  144. - (CGRect)wdNativeFrame
  145. {
  146. // To avoid confusion regarding the frame returned by `wdFrame`,
  147. // the current property is provided to represent the element's
  148. // actual rendered frame.
  149. return self.frame;
  150. }
  151. /**
  152. Returns a comma-separated string of accessibility traits for the element.
  153. This method converts the element's accessibility traits bitmask into human-readable strings
  154. using FBAccessibilityTraitsToStringsArray. The traits represent various accessibility
  155. characteristics of the element such as Button, Link, Image, etc.
  156. You can find the list of possible traits in the Apple documentation:
  157. https://developer.apple.com/documentation/uikit/uiaccessibilitytraits?language=objc
  158. @return A comma-separated string of accessibility traits, or an empty string if no traits are set
  159. */
  160. - (NSString *)wdTraits
  161. {
  162. NSArray<NSString *> *traits = FBAccessibilityTraitsToStringsArray(self.snapshot.traits);
  163. return [traits componentsJoinedByString:@", "];
  164. }
  165. - (BOOL)isWDVisible
  166. {
  167. return self.fb_isVisible;
  168. }
  169. - (BOOL)isWDFocused
  170. {
  171. return self.hasFocus;
  172. }
  173. - (BOOL)isWDAccessible
  174. {
  175. XCUIElementType elementType = self.elementType;
  176. // Special cases:
  177. // Table view cell: we consider it accessible if it's container is accessible
  178. // Text fields: actual accessible element isn't text field itself, but nested element
  179. if (elementType == XCUIElementTypeCell) {
  180. if (!self.fb_isAccessibilityElement) {
  181. id<FBXCElementSnapshot> containerView = [[self children] firstObject];
  182. if (![FBXCElementSnapshotWrapper ensureWrapped:containerView].fb_isAccessibilityElement) {
  183. return NO;
  184. }
  185. }
  186. } else if (elementType != XCUIElementTypeTextField && elementType != XCUIElementTypeSecureTextField) {
  187. if (!self.fb_isAccessibilityElement) {
  188. return NO;
  189. }
  190. }
  191. id<FBXCElementSnapshot> parentSnapshot = self.parent;
  192. while (parentSnapshot) {
  193. // In the scenario when table provides Search results controller, table could be marked as accessible element, even though it isn't
  194. // As it is highly unlikely that table view should ever be an accessibility element itself,
  195. // for now we work around that by skipping Table View in container checks
  196. if (parentSnapshot.elementType != XCUIElementTypeTable
  197. && [FBXCElementSnapshotWrapper ensureWrapped:parentSnapshot].fb_isAccessibilityElement) {
  198. return NO;
  199. }
  200. parentSnapshot = parentSnapshot.parent;
  201. }
  202. return YES;
  203. }
  204. - (BOOL)isWDAccessibilityContainer
  205. {
  206. NSArray<id<FBXCElementSnapshot>> *children = self.children;
  207. for (id<FBXCElementSnapshot> child in children) {
  208. FBXCElementSnapshotWrapper *wrappedChild = [FBXCElementSnapshotWrapper ensureWrapped:child];
  209. if (wrappedChild.isWDAccessibilityContainer || wrappedChild.fb_isAccessibilityElement) {
  210. return YES;
  211. }
  212. }
  213. return NO;
  214. }
  215. - (BOOL)isWDEnabled
  216. {
  217. return self.isEnabled;
  218. }
  219. - (BOOL)isWDSelected
  220. {
  221. return self.isSelected;
  222. }
  223. - (NSUInteger)wdIndex
  224. {
  225. if (nil != self.parent) {
  226. for (NSUInteger index = 0; index < self.parent.children.count; ++index) {
  227. if ([self.parent.children objectAtIndex:index] == self.snapshot) {
  228. return index;
  229. }
  230. }
  231. }
  232. return 0;
  233. }
  234. - (BOOL)isWDHittable
  235. {
  236. XCUIHitPointResult *result = [self hitPoint:nil];
  237. return nil == result ? NO : result.hittable;
  238. }
  239. - (NSDictionary *)wdRect
  240. {
  241. CGRect frame = self.wdFrame;
  242. return @{
  243. @"x": @(CGRectGetMinX(frame)),
  244. @"y": @(CGRectGetMinY(frame)),
  245. @"width": @(CGRectGetWidth(frame)),
  246. @"height": @(CGRectGetHeight(frame)),
  247. };
  248. }
  249. @end