FBFindElementCommands.m 8.5 KB

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