FBSession.m 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  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 "FBSession.h"
  10. #import "FBSession-Private.h"
  11. #import <objc/runtime.h>
  12. #import "FBXCAccessibilityElement.h"
  13. #import "FBAlertsMonitor.h"
  14. #import "FBConfiguration.h"
  15. #import "FBElementCache.h"
  16. #import "FBExceptions.h"
  17. #import "FBMacros.h"
  18. #import "FBScreenRecordingContainer.h"
  19. #import "FBScreenRecordingPromise.h"
  20. #import "FBScreenRecordingRequest.h"
  21. #import "FBXCodeCompatibility.h"
  22. #import "FBXCTestDaemonsProxy.h"
  23. #import "XCUIApplication+FBQuiescence.h"
  24. #import "XCUIElement.h"
  25. /*!
  26. The intial value for the default application property.
  27. Setting this value to `defaultActiveApplication` property forces WDA to use the internal
  28. automated algorithm to determine the active on-screen application
  29. */
  30. NSString *const FBDefaultApplicationAuto = @"auto";
  31. @interface FBSession ()
  32. @property (nullable, nonatomic) XCUIApplication *testedApplication;
  33. @property (nonatomic) BOOL isTestedApplicationExpectedToRun;
  34. @property (nonatomic) BOOL shouldAppsWaitForQuiescence;
  35. @property (nonatomic, nullable) FBAlertsMonitor *alertsMonitor;
  36. @property (nonatomic, readwrite) NSMutableDictionary<NSNumber *, NSMutableDictionary<NSString *, NSNumber *> *> *elementsVisibilityCache;
  37. @end
  38. @interface FBSession (FBAlertsMonitorDelegate)
  39. - (void)didDetectAlert:(FBAlert *)alert;
  40. @end
  41. @implementation FBSession (FBAlertsMonitorDelegate)
  42. - (void)didDetectAlert:(FBAlert *)alert
  43. {
  44. if (nil == self.defaultAlertAction || 0 == self.defaultAlertAction.length) {
  45. return;
  46. }
  47. NSError *error;
  48. if ([self.defaultAlertAction isEqualToString:@"accept"]) {
  49. if (![alert acceptWithError:&error]) {
  50. [FBLogger logFmt:@"Cannot accept the alert. Original error: %@", error.description];
  51. }
  52. } else if ([self.defaultAlertAction isEqualToString:@"dismiss"]) {
  53. if (![alert dismissWithError:&error]) {
  54. [FBLogger logFmt:@"Cannot dismiss the alert. Original error: %@", error.description];
  55. }
  56. } else {
  57. [FBLogger logFmt:@"'%@' default alert action is unsupported", self.defaultAlertAction];
  58. }
  59. }
  60. @end
  61. @implementation FBSession
  62. static FBSession *_activeSession = nil;
  63. + (instancetype)activeSession
  64. {
  65. return _activeSession;
  66. }
  67. + (void)markSessionActive:(FBSession *)session
  68. {
  69. if (_activeSession) {
  70. [_activeSession kill];
  71. }
  72. _activeSession = session;
  73. }
  74. + (instancetype)sessionWithIdentifier:(NSString *)identifier
  75. {
  76. if (!identifier) {
  77. return nil;
  78. }
  79. if (![identifier isEqualToString:_activeSession.identifier]) {
  80. return nil;
  81. }
  82. return _activeSession;
  83. }
  84. + (instancetype)initWithApplication:(XCUIApplication *)application
  85. {
  86. FBSession *session = [FBSession new];
  87. session.useNativeCachingStrategy = YES;
  88. session.alertsMonitor = nil;
  89. session.defaultAlertAction = nil;
  90. session.elementsVisibilityCache = [NSMutableDictionary dictionary];
  91. session.identifier = [[NSUUID UUID] UUIDString];
  92. session.defaultActiveApplication = FBDefaultApplicationAuto;
  93. session.testedApplication = nil;
  94. session.isTestedApplicationExpectedToRun = nil != application && application.running;
  95. if (application) {
  96. session.testedApplication = application;
  97. session.shouldAppsWaitForQuiescence = application.fb_shouldWaitForQuiescence;
  98. }
  99. session.elementCache = [FBElementCache new];
  100. [FBSession markSessionActive:session];
  101. return session;
  102. }
  103. + (instancetype)initWithApplication:(nullable XCUIApplication *)application
  104. defaultAlertAction:(NSString *)defaultAlertAction
  105. {
  106. FBSession *session = [self.class initWithApplication:application];
  107. session.alertsMonitor = [[FBAlertsMonitor alloc] init];
  108. session.alertsMonitor.delegate = (id<FBAlertsMonitorDelegate>)session;
  109. session.defaultAlertAction = [defaultAlertAction lowercaseString];
  110. [session.alertsMonitor enable];
  111. return session;
  112. }
  113. - (void)kill
  114. {
  115. if (nil == _activeSession) {
  116. return;
  117. }
  118. if (nil != self.alertsMonitor) {
  119. [self.alertsMonitor disable];
  120. self.alertsMonitor = nil;
  121. }
  122. FBScreenRecordingPromise *activeScreenRecording = FBScreenRecordingContainer.sharedInstance.screenRecordingPromise;
  123. if (nil != activeScreenRecording) {
  124. NSError *error;
  125. if (![FBXCTestDaemonsProxy stopScreenRecordingWithUUID:activeScreenRecording.identifier error:&error]) {
  126. [FBLogger logFmt:@"%@", error];
  127. }
  128. [FBScreenRecordingContainer.sharedInstance reset];
  129. }
  130. if (nil != self.testedApplication
  131. && FBConfiguration.shouldTerminateApp
  132. && self.testedApplication.running
  133. && ![self.testedApplication fb_isSameAppAs:XCUIApplication.fb_systemApplication]) {
  134. @try {
  135. [self.testedApplication terminate];
  136. } @catch (NSException *e) {
  137. [FBLogger logFmt:@"%@", e.description];
  138. }
  139. }
  140. _activeSession = nil;
  141. }
  142. - (XCUIApplication *)activeApplication
  143. {
  144. BOOL isAuto = [self.defaultActiveApplication isEqualToString:FBDefaultApplicationAuto];
  145. NSString *defaultBundleId = isAuto ? nil : self.defaultActiveApplication;
  146. if (nil != defaultBundleId && [self applicationStateWithBundleId:defaultBundleId] >= XCUIApplicationStateRunningForeground) {
  147. return [self makeApplicationWithBundleId:defaultBundleId];
  148. }
  149. if (nil != self.testedApplication) {
  150. XCUIApplicationState testedAppState = self.testedApplication.state;
  151. if (testedAppState >= XCUIApplicationStateRunningForeground) {
  152. return (XCUIApplication *)self.testedApplication;
  153. }
  154. if (self.isTestedApplicationExpectedToRun && testedAppState <= XCUIApplicationStateNotRunning) {
  155. NSString *description = [NSString stringWithFormat:@"The application under test with bundle id '%@' is not running, possibly crashed", self.testedApplication.bundleID];
  156. @throw [NSException exceptionWithName:FBApplicationCrashedException reason:description userInfo:nil];
  157. }
  158. }
  159. return [XCUIApplication fb_activeApplicationWithDefaultBundleId:defaultBundleId];
  160. }
  161. - (XCUIApplication *)launchApplicationWithBundleId:(NSString *)bundleIdentifier
  162. shouldWaitForQuiescence:(nullable NSNumber *)shouldWaitForQuiescence
  163. arguments:(nullable NSArray<NSString *> *)arguments
  164. environment:(nullable NSDictionary <NSString *, NSString *> *)environment
  165. {
  166. XCUIApplication *app = [self makeApplicationWithBundleId:bundleIdentifier];
  167. if (nil == shouldWaitForQuiescence) {
  168. // Iherit the quiescence check setting from the main app under test by default
  169. app.fb_shouldWaitForQuiescence = nil != self.testedApplication && self.shouldAppsWaitForQuiescence;
  170. } else {
  171. app.fb_shouldWaitForQuiescence = [shouldWaitForQuiescence boolValue];
  172. }
  173. if (!app.running) {
  174. app.launchArguments = arguments ?: @[];
  175. app.launchEnvironment = environment ?: @{};
  176. [app launch];
  177. } else {
  178. [app activate];
  179. }
  180. if ([app fb_isSameAppAs:self.testedApplication]) {
  181. self.isTestedApplicationExpectedToRun = YES;
  182. }
  183. return app;
  184. }
  185. - (XCUIApplication *)activateApplicationWithBundleId:(NSString *)bundleIdentifier
  186. {
  187. XCUIApplication *app = [self makeApplicationWithBundleId:bundleIdentifier];
  188. [app activate];
  189. return app;
  190. }
  191. - (BOOL)terminateApplicationWithBundleId:(NSString *)bundleIdentifier
  192. {
  193. XCUIApplication *app = [self makeApplicationWithBundleId:bundleIdentifier];
  194. if ([app fb_isSameAppAs:self.testedApplication]) {
  195. self.isTestedApplicationExpectedToRun = NO;
  196. }
  197. if (app.running) {
  198. [app terminate];
  199. return YES;
  200. }
  201. return NO;
  202. }
  203. - (NSUInteger)applicationStateWithBundleId:(NSString *)bundleIdentifier
  204. {
  205. return [self makeApplicationWithBundleId:bundleIdentifier].state;
  206. }
  207. - (XCUIApplication *)makeApplicationWithBundleId:(NSString *)bundleIdentifier
  208. {
  209. return nil != self.testedApplication && [bundleIdentifier isEqualToString:(NSString *)self.testedApplication.bundleID]
  210. ? self.testedApplication
  211. : [[XCUIApplication alloc] initWithBundleIdentifier:bundleIdentifier];
  212. }
  213. @end