FBBaseActionsSynthesizer.m 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  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 "FBBaseActionsSynthesizer.h"
  10. #import "FBErrorBuilder.h"
  11. #import "FBLogger.h"
  12. #import "FBMacros.h"
  13. #import "FBMathUtils.h"
  14. #import "FBXCElementSnapshotWrapper+Helpers.h"
  15. #import "XCUIApplication+FBHelpers.h"
  16. #import "XCUIElement.h"
  17. #import "XCUIElement+FBIsVisible.h"
  18. #import "XCUIElement+FBCaching.h"
  19. #import "XCPointerEventPath.h"
  20. #import "XCSynthesizedEventRecord.h"
  21. #import "XCUIElement+FBUtilities.h"
  22. #if !TARGET_OS_TV
  23. @implementation FBBaseActionItem
  24. + (NSString *)actionName
  25. {
  26. @throw [[FBErrorBuilder.builder withDescription:@"Override this method in subclasses"] build];
  27. return nil;
  28. }
  29. - (NSArray<XCPointerEventPath *> *)addToEventPath:(XCPointerEventPath *)eventPath
  30. allItems:(NSArray *)allItems
  31. currentItemIndex:(NSUInteger)currentItemIndex
  32. error:(NSError **)error
  33. {
  34. @throw [[FBErrorBuilder.builder withDescription:@"Override this method in subclasses"] build];
  35. return nil;
  36. }
  37. @end
  38. @implementation FBBaseGestureItem
  39. - (nullable XCUICoordinate *)hitpointWithElement:(nullable XCUIElement *)element
  40. positionOffset:(nullable NSValue *)positionOffset
  41. error:(NSError **)error
  42. {
  43. if (nil == element) {
  44. CGVector offset = CGVectorMake(positionOffset.CGPointValue.x, positionOffset.CGPointValue.y);
  45. // Only absolute offset is defined
  46. return [[self.application coordinateWithNormalizedOffset:CGVectorMake(0, 0)] coordinateWithOffset:offset];
  47. }
  48. // The offset relative to the element is defined
  49. if (nil == positionOffset) {
  50. if (element.hittable) {
  51. // short circuit element hitpoint
  52. return element.hitPointCoordinate;
  53. }
  54. [FBLogger logFmt:@"Will use the frame of '%@' for hit point calculation instead", element.debugDescription];
  55. }
  56. if (CGRectIsEmpty(element.frame)) {
  57. [FBLogger log:self.application.fb_descriptionRepresentation];
  58. NSString *description = [NSString stringWithFormat:@"The element '%@' is not visible on the screen and thus is not interactable",
  59. element.description];
  60. if (error) {
  61. *error = [[FBErrorBuilder.builder withDescription:description] build];
  62. }
  63. return nil;
  64. }
  65. if (nil == positionOffset) {
  66. return [element coordinateWithNormalizedOffset:CGVectorMake(0.5, 0.5)];
  67. }
  68. CGVector offset = CGVectorMake(positionOffset.CGPointValue.x, positionOffset.CGPointValue.y);
  69. // TODO: Shall we throw an exception if hitPoint is out of the element frame?
  70. return [[element coordinateWithNormalizedOffset:CGVectorMake(0, 0)] coordinateWithOffset:offset];
  71. }
  72. @end
  73. @implementation FBBaseActionItemsChain
  74. - (instancetype)init
  75. {
  76. self = [super init];
  77. if (self) {
  78. _items = [NSMutableArray array];
  79. _durationOffset = 0.0;
  80. }
  81. return self;
  82. }
  83. - (void)addItem:(FBBaseActionItem *)item __attribute__((noreturn))
  84. {
  85. @throw [[FBErrorBuilder.builder withDescription:@"Override this method in subclasses"] build];
  86. }
  87. - (nullable NSArray<XCPointerEventPath *> *)asEventPathsWithError:(NSError **)error
  88. {
  89. if (0 == self.items.count) {
  90. if (error) {
  91. *error = [[FBErrorBuilder.builder withDescription:@"Action items list cannot be empty"] build];
  92. }
  93. return nil;
  94. }
  95. NSMutableArray<XCPointerEventPath *> *result = [NSMutableArray array];
  96. XCPointerEventPath *previousEventPath = nil;
  97. XCPointerEventPath *currentEventPath = nil;
  98. NSUInteger index = 0;
  99. for (FBBaseActionItem *item in self.items.copy) {
  100. NSArray<XCPointerEventPath *> *currentEventPaths = [item addToEventPath:currentEventPath
  101. allItems:self.items.copy
  102. currentItemIndex:index++
  103. error:error];
  104. if (currentEventPaths == nil) {
  105. return nil;
  106. }
  107. currentEventPath = currentEventPaths.lastObject;
  108. if (nil == currentEventPath) {
  109. currentEventPath = previousEventPath;
  110. } else if (currentEventPath != previousEventPath) {
  111. [result addObjectsFromArray:currentEventPaths];
  112. previousEventPath = currentEventPath;
  113. }
  114. }
  115. return result.copy;
  116. }
  117. @end
  118. @implementation FBBaseActionsSynthesizer
  119. - (instancetype)initWithActions:(NSArray *)actions
  120. forApplication:(XCUIApplication *)application
  121. elementCache:(nullable FBElementCache *)elementCache
  122. error:(NSError **)error
  123. {
  124. self = [super init];
  125. if (self) {
  126. if ((nil == actions || 0 == actions.count) && error) {
  127. *error = [[FBErrorBuilder.builder withDescription:@"Actions list cannot be empty"] build];
  128. return nil;
  129. }
  130. _actions = actions;
  131. _application = application;
  132. _elementCache = elementCache;
  133. }
  134. return self;
  135. }
  136. - (nullable XCSynthesizedEventRecord *)synthesizeWithError:(NSError **)error
  137. {
  138. @throw [[FBErrorBuilder.builder withDescription:@"Override synthesizeWithError method in subclasses"] build];
  139. return nil;
  140. }
  141. @end
  142. #endif