FBFindElementCommands.m 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  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 "FBFindElementCommands.h"
  10. #import "FBAlert.h"
  11. #import "FBApplication.h"
  12. #import "FBConfiguration.h"
  13. #import "FBElementCache.h"
  14. #import "FBExceptions.h"
  15. #import "FBMacros.h"
  16. #import "FBRouteRequest.h"
  17. #import "FBSession.h"
  18. #import "XCTestPrivateSymbols.h"
  19. #import "XCUIApplication+FBHelpers.h"
  20. #import "XCUIElement+FBClassChain.h"
  21. #import "XCUIElement+FBFind.h"
  22. #import "XCUIElement+FBIsVisible.h"
  23. #import "XCUIElement+FBUID.h"
  24. #import "XCUIElement+FBUtilities.h"
  25. #import "XCUIElement+FBWebDriverAttributes.h"
  26. static id<FBResponsePayload> FBNoSuchElementErrorResponseForRequest(FBRouteRequest *request)
  27. {
  28. return FBResponseWithStatus([FBCommandStatus noSuchElementErrorWithMessage:[NSString stringWithFormat:@"unable to find an element using '%@', value '%@'", request.arguments[@"using"], request.arguments[@"value"]]
  29. traceback:[NSString stringWithFormat:@"%@", NSThread.callStackSymbols]]);
  30. }
  31. @implementation FBFindElementCommands
  32. #pragma mark - <FBCommandHandler>
  33. + (NSArray *)routes
  34. {
  35. return
  36. @[
  37. [[FBRoute POST:@"/element"] respondWithTarget:self action:@selector(handleFindElement:)],
  38. [[FBRoute POST:@"/elements"] respondWithTarget:self action:@selector(handleFindElements:)],
  39. [[FBRoute POST:@"/element/:uuid/element"] respondWithTarget:self action:@selector(handleFindSubElement:)],
  40. [[FBRoute POST:@"/element/:uuid/elements"] respondWithTarget:self action:@selector(handleFindSubElements:)],
  41. [[FBRoute GET:@"/wda/element/:uuid/getVisibleCells"] respondWithTarget:self action:@selector(handleFindVisibleCells:)],
  42. #if TARGET_OS_TV
  43. [[FBRoute GET:@"/element/active"] respondWithTarget:self action:@selector(handleGetFocusedElement:)],
  44. #else
  45. [[FBRoute GET:@"/element/active"] respondWithTarget:self action:@selector(handleGetActiveElement:)],
  46. #endif
  47. ];
  48. }
  49. #pragma mark - Commands
  50. + (id<FBResponsePayload>)handleFindElement:(FBRouteRequest *)request
  51. {
  52. FBSession *session = request.session;
  53. XCUIElement *element = [self.class elementUsing:request.arguments[@"using"]
  54. withValue:request.arguments[@"value"]
  55. under:session.activeApplication];
  56. if (!element) {
  57. return FBNoSuchElementErrorResponseForRequest(request);
  58. }
  59. return FBResponseWithCachedElement(element, request.session.elementCache, FBConfiguration.shouldUseCompactResponses);
  60. }
  61. + (id<FBResponsePayload>)handleFindElements:(FBRouteRequest *)request
  62. {
  63. FBSession *session = request.session;
  64. NSArray *elements = [self.class elementsUsing:request.arguments[@"using"]
  65. withValue:request.arguments[@"value"]
  66. under:session.activeApplication
  67. shouldReturnAfterFirstMatch:NO];
  68. return FBResponseWithCachedElements(elements, request.session.elementCache, FBConfiguration.shouldUseCompactResponses);
  69. }
  70. + (id<FBResponsePayload>)handleFindVisibleCells:(FBRouteRequest *)request
  71. {
  72. FBElementCache *elementCache = request.session.elementCache;
  73. XCUIElement *element = [elementCache elementForUUID:(NSString *)request.parameters[@"uuid"]
  74. resolveForAdditionalAttributes:@[FB_XCAXAIsVisibleAttributeName]
  75. andMaxDepth:nil];
  76. NSArray<id<FBXCElementSnapshot>> *visibleCellSnapshots = [element.lastSnapshot descendantsByFilteringWithBlock:^BOOL(id<FBXCElementSnapshot> snapshot) {
  77. return snapshot.elementType == XCUIElementTypeCell
  78. && [FBXCElementSnapshotWrapper ensureWrapped:snapshot].wdVisible;
  79. }];
  80. NSArray *cells = [element fb_filterDescendantsWithSnapshots:visibleCellSnapshots
  81. selfUID:[FBXCElementSnapshotWrapper wdUIDWithSnapshot:element.lastSnapshot]
  82. onlyChildren:NO];
  83. return FBResponseWithCachedElements(cells, request.session.elementCache, FBConfiguration.shouldUseCompactResponses);
  84. }
  85. + (id<FBResponsePayload>)handleFindSubElement:(FBRouteRequest *)request
  86. {
  87. FBElementCache *elementCache = request.session.elementCache;
  88. XCUIElement *element = [elementCache elementForUUID:(NSString *)request.parameters[@"uuid"]];
  89. XCUIElement *foundElement = [self.class elementUsing:request.arguments[@"using"]
  90. withValue:request.arguments[@"value"]
  91. under:element];
  92. if (!foundElement) {
  93. return FBNoSuchElementErrorResponseForRequest(request);
  94. }
  95. return FBResponseWithCachedElement(foundElement, request.session.elementCache, FBConfiguration.shouldUseCompactResponses);
  96. }
  97. + (id<FBResponsePayload>)handleFindSubElements:(FBRouteRequest *)request
  98. {
  99. FBElementCache *elementCache = request.session.elementCache;
  100. XCUIElement *element = [elementCache elementForUUID:(NSString *)request.parameters[@"uuid"]];
  101. NSArray *foundElements = [self.class elementsUsing:request.arguments[@"using"]
  102. withValue:request.arguments[@"value"]
  103. under:element
  104. shouldReturnAfterFirstMatch:NO];
  105. return FBResponseWithCachedElements(foundElements, request.session.elementCache, FBConfiguration.shouldUseCompactResponses);
  106. }
  107. + (id<FBResponsePayload>)handleGetActiveElement:(FBRouteRequest *)request
  108. {
  109. XCUIElement *element = request.session.activeApplication.fb_activeElement;
  110. if (nil == element) {
  111. return FBNoSuchElementErrorResponseForRequest(request);
  112. }
  113. return FBResponseWithCachedElement(element, request.session.elementCache, FBConfiguration.shouldUseCompactResponses);
  114. }
  115. #if TARGET_OS_TV
  116. + (id<FBResponsePayload>)handleGetFocusedElement:(FBRouteRequest *)request
  117. {
  118. XCUIElement *element = request.session.activeApplication.fb_focusedElement;
  119. return element == nil
  120. ? FBNoSuchElementErrorResponseForRequest(request)
  121. : FBResponseWithCachedElement(element, request.session.elementCache, FBConfiguration.shouldUseCompactResponses);
  122. }
  123. #endif
  124. #pragma mark - Helpers
  125. + (XCUIElement *)elementUsing:(NSString *)usingText withValue:(NSString *)value under:(XCUIElement *)element
  126. {
  127. return [[self elementsUsing:usingText
  128. withValue:value
  129. under:element
  130. shouldReturnAfterFirstMatch:YES] firstObject];
  131. }
  132. + (NSArray *)elementsUsing:(NSString *)usingText
  133. withValue:(NSString *)value
  134. under:(XCUIElement *)element
  135. shouldReturnAfterFirstMatch:(BOOL)shouldReturnAfterFirstMatch
  136. {
  137. if ([usingText isEqualToString:@"partial link text"]
  138. || [usingText isEqualToString:@"link text"]) {
  139. NSArray *components = [value componentsSeparatedByString:@"="];
  140. NSString *propertyValue = components.lastObject;
  141. NSString *propertyName = (components.count < 2 ? @"name" : components.firstObject);
  142. return [element fb_descendantsMatchingProperty:propertyName
  143. value:propertyValue
  144. partialSearch:[usingText containsString:@"partial"]];
  145. } else if ([usingText isEqualToString:@"class name"]) {
  146. return [element fb_descendantsMatchingClassName:value
  147. shouldReturnAfterFirstMatch:shouldReturnAfterFirstMatch];
  148. } else if ([usingText isEqualToString:@"class chain"]) {
  149. return [element fb_descendantsMatchingClassChain:value
  150. shouldReturnAfterFirstMatch:shouldReturnAfterFirstMatch];
  151. } else if ([usingText isEqualToString:@"xpath"]) {
  152. return [element fb_descendantsMatchingXPathQuery:value
  153. shouldReturnAfterFirstMatch:shouldReturnAfterFirstMatch];
  154. } else if ([usingText isEqualToString:@"predicate string"]) {
  155. return [element fb_descendantsMatchingPredicate:[NSPredicate predicateWithFormat:value]
  156. shouldReturnAfterFirstMatch:shouldReturnAfterFirstMatch];
  157. } else if ([usingText isEqualToString:@"name"]
  158. || [usingText isEqualToString:@"id"]
  159. || [usingText isEqualToString:@"accessibility id"]) {
  160. return [element fb_descendantsMatchingIdentifier:value
  161. shouldReturnAfterFirstMatch:shouldReturnAfterFirstMatch];
  162. } else {
  163. @throw [NSException exceptionWithName:FBElementAttributeUnknownException
  164. reason:[NSString stringWithFormat:@"Invalid locator requested: %@", usingText]
  165. userInfo:nil];
  166. }
  167. }
  168. @end