XCUIElement+FBUtilities.m 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  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+FBUtilities.h"
  10. #import <objc/runtime.h>
  11. #import "FBConfiguration.h"
  12. #import "FBExceptions.h"
  13. #import "FBImageUtils.h"
  14. #import "FBElementUtils.h"
  15. #import "FBLogger.h"
  16. #import "FBMacros.h"
  17. #import "FBMathUtils.h"
  18. #import "FBRunLoopSpinner.h"
  19. #import "FBSettings.h"
  20. #import "FBScreenshot.h"
  21. #import "FBXCAXClientProxy.h"
  22. #import "FBXCodeCompatibility.h"
  23. #import "FBXCElementSnapshot.h"
  24. #import "FBXCElementSnapshotWrapper+Helpers.h"
  25. #import "XCUIApplication.h"
  26. #import "XCUIApplication+FBQuiescence.h"
  27. #import "XCUIApplicationImpl.h"
  28. #import "XCUIApplicationProcess.h"
  29. #import "XCTElementSetTransformer-Protocol.h"
  30. #import "XCTestPrivateSymbols.h"
  31. #import "XCTRunnerDaemonSession.h"
  32. #import "XCUIApplicationProcess+FBQuiescence.h"
  33. #import "XCUIApplication.h"
  34. #import "XCUIElement+FBCaching.h"
  35. #import "XCUIElement+FBWebDriverAttributes.h"
  36. #import "XCUIElementQuery.h"
  37. #import "XCUIElementQuery+FBHelpers.h"
  38. #import "XCUIElement+FBUID.h"
  39. #import "XCUIScreen.h"
  40. #import "XCUIElement+FBResolve.h"
  41. @implementation XCUIElement (FBUtilities)
  42. - (id<FBXCElementSnapshot>)fb_takeSnapshot:(BOOL)isCustom
  43. {
  44. __block id<FBXCElementSnapshot> snapshot = nil;
  45. @autoreleasepool {
  46. NSError *error = nil;
  47. snapshot = isCustom
  48. ? [self.fb_query fb_uniqueSnapshotWithError:&error]
  49. : (id<FBXCElementSnapshot>)[self snapshotWithError:&error];
  50. if (nil == snapshot) {
  51. [self fb_raiseStaleElementExceptionWithError:error];
  52. }
  53. }
  54. self.lastSnapshot = snapshot;
  55. return self.lastSnapshot;
  56. }
  57. - (id<FBXCElementSnapshot>)fb_standardSnapshot
  58. {
  59. return [self fb_takeSnapshot:NO];
  60. }
  61. - (id<FBXCElementSnapshot>)fb_customSnapshot
  62. {
  63. return [self fb_takeSnapshot:YES];
  64. }
  65. - (id<FBXCElementSnapshot>)fb_nativeSnapshot
  66. {
  67. NSError *error = nil;
  68. BOOL isSuccessful = [self resolveOrRaiseTestFailure:NO error:&error];
  69. if (nil == self.lastSnapshot || !isSuccessful) {
  70. [self fb_raiseStaleElementExceptionWithError:error];
  71. }
  72. return self.lastSnapshot;
  73. }
  74. - (id<FBXCElementSnapshot>)fb_cachedSnapshot
  75. {
  76. return [self.query fb_cachedSnapshot];
  77. }
  78. - (NSArray<XCUIElement *> *)fb_filterDescendantsWithSnapshots:(NSArray<id<FBXCElementSnapshot>> *)snapshots
  79. onlyChildren:(BOOL)onlyChildren
  80. {
  81. if (0 == snapshots.count) {
  82. return @[];
  83. }
  84. NSMutableArray<NSString *> *matchedIds = [NSMutableArray new];
  85. for (id<FBXCElementSnapshot> snapshot in snapshots) {
  86. @autoreleasepool {
  87. NSString *uid = [FBXCElementSnapshotWrapper wdUIDWithSnapshot:snapshot];
  88. if (nil != uid) {
  89. [matchedIds addObject:uid];
  90. }
  91. }
  92. }
  93. NSMutableArray<XCUIElement *> *matchedElements = [NSMutableArray array];
  94. NSString *uid = nil == self.lastSnapshot
  95. ? self.fb_uid
  96. : [FBXCElementSnapshotWrapper wdUIDWithSnapshot:self.lastSnapshot];
  97. if (nil != uid && [matchedIds containsObject:uid]) {
  98. XCUIElement *stableSelf = [self fb_stableInstanceWithUid:uid];
  99. if (1 == snapshots.count) {
  100. return @[stableSelf];
  101. }
  102. [matchedElements addObject:stableSelf];
  103. }
  104. XCUIElementType type = XCUIElementTypeAny;
  105. NSArray<NSNumber *> *uniqueTypes = [snapshots valueForKeyPath:[NSString stringWithFormat:@"@distinctUnionOfObjects.%@", FBStringify(XCUIElement, elementType)]];
  106. if (uniqueTypes && [uniqueTypes count] == 1) {
  107. type = [uniqueTypes.firstObject intValue];
  108. }
  109. XCUIElementQuery *query = onlyChildren
  110. ? [self.fb_query childrenMatchingType:type]
  111. : [self.fb_query descendantsMatchingType:type];
  112. NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%K IN %@",FBStringify(FBXCElementSnapshotWrapper, fb_uid), matchedIds];
  113. [matchedElements addObjectsFromArray:[query matchingPredicate:predicate].allElementsBoundByIndex];
  114. for (XCUIElement *el in matchedElements) {
  115. el.fb_isResolvedNatively = @NO;
  116. }
  117. return matchedElements.copy;
  118. }
  119. - (void)fb_waitUntilStable
  120. {
  121. [self fb_waitUntilStableWithTimeout:FBConfiguration.waitForIdleTimeout];
  122. }
  123. - (void)fb_waitUntilStableWithTimeout:(NSTimeInterval)timeout
  124. {
  125. if (timeout < DBL_EPSILON) {
  126. return;
  127. }
  128. NSTimeInterval previousTimeout = FBConfiguration.waitForIdleTimeout;
  129. BOOL previousQuiescence = self.application.fb_shouldWaitForQuiescence;
  130. FBConfiguration.waitForIdleTimeout = timeout;
  131. if (!previousQuiescence) {
  132. self.application.fb_shouldWaitForQuiescence = YES;
  133. }
  134. [[[self.application applicationImpl] currentProcess]
  135. fb_waitForQuiescenceIncludingAnimationsIdle:YES];
  136. if (previousQuiescence != self.application.fb_shouldWaitForQuiescence) {
  137. self.application.fb_shouldWaitForQuiescence = previousQuiescence;
  138. }
  139. FBConfiguration.waitForIdleTimeout = previousTimeout;
  140. }
  141. - (void)fb_raiseStaleElementExceptionWithError:(NSError *)error __attribute__((noreturn))
  142. {
  143. NSString *hintText = @"Make sure the application UI has the expected state";
  144. if (nil != error && [error.localizedDescription containsString:@"Identity Binding"]) {
  145. hintText = [NSString stringWithFormat:@"%@. You could also try to switch the binding strategy using the 'boundElementsByIndex' setting for the element lookup", hintText];
  146. }
  147. NSString *reason = [NSString stringWithFormat:@"The previously found element \"%@\" is not present in the current view anymore. %@",
  148. self.description, hintText];
  149. if (nil != error) {
  150. reason = [NSString stringWithFormat:@"%@. Original error: %@", reason, error.localizedDescription];
  151. }
  152. @throw [NSException exceptionWithName:FBStaleElementException reason:reason userInfo:@{}];
  153. }
  154. @end