FBSessionCommands.m 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587
  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 "FBSessionCommands.h"
  10. #import "FBCapabilities.h"
  11. #import "FBClassChainQueryParser.h"
  12. #import "FBConfiguration.h"
  13. #import "FBExceptions.h"
  14. #import "FBLogger.h"
  15. #import "FBProtocolHelpers.h"
  16. #import "FBRouteRequest.h"
  17. #import "FBSession.h"
  18. #import "FBSettings.h"
  19. #import "FBRuntimeUtils.h"
  20. #import "FBActiveAppDetectionPoint.h"
  21. #import "FBXCodeCompatibility.h"
  22. #import "XCUIApplication+FBHelpers.h"
  23. #import "XCUIApplication+FBQuiescence.h"
  24. #import "XCUIDevice.h"
  25. #import "XCUIDevice+FBHealthCheck.h"
  26. #import "XCUIDevice+FBHelpers.h"
  27. #import "XCUIApplicationProcessDelay.h"
  28. @implementation FBSessionCommands
  29. #pragma mark - <FBCommandHandler>
  30. + (NSArray *)routes
  31. {
  32. return
  33. @[
  34. [[FBRoute POST:@"/url"] respondWithTarget:self action:@selector(handleOpenURL:)],
  35. [[FBRoute POST:@"/session"].withoutSession respondWithTarget:self action:@selector(handleCreateSession:)],
  36. [[FBRoute POST:@"/wda/apps/launch"] respondWithTarget:self action:@selector(handleSessionAppLaunch:)],
  37. [[FBRoute POST:@"/wda/apps/activate"] respondWithTarget:self action:@selector(handleSessionAppActivate:)],
  38. [[FBRoute POST:@"/wda/apps/terminate"] respondWithTarget:self action:@selector(handleSessionAppTerminate:)],
  39. [[FBRoute POST:@"/wda/apps/state"] respondWithTarget:self action:@selector(handleSessionAppState:)],
  40. [[FBRoute GET:@"/wda/apps/list"] respondWithTarget:self action:@selector(handleGetActiveAppsList:)],
  41. [[FBRoute GET:@""] respondWithTarget:self action:@selector(handleGetActiveSession:)],
  42. [[FBRoute DELETE:@""] respondWithTarget:self action:@selector(handleDeleteSession:)],
  43. [[FBRoute GET:@"/status"].withoutSession respondWithTarget:self action:@selector(handleGetStatus:)],
  44. // Health check might modify simulator state so it should only be called in-between testing sessions
  45. [[FBRoute GET:@"/wda/healthcheck"].withoutSession respondWithTarget:self action:@selector(handleGetHealthCheck:)],
  46. // Settings endpoints
  47. [[FBRoute GET:@"/appium/settings"] respondWithTarget:self action:@selector(handleGetSettings:)],
  48. [[FBRoute POST:@"/appium/settings"] respondWithTarget:self action:@selector(handleSetSettings:)],
  49. ];
  50. }
  51. #pragma mark - Commands
  52. + (id<FBResponsePayload>)handleOpenURL:(FBRouteRequest *)request
  53. {
  54. NSString *urlString = request.arguments[@"url"];
  55. if (!urlString) {
  56. return FBResponseWithStatus([FBCommandStatus invalidArgumentErrorWithMessage:@"URL is required" traceback:nil]);
  57. }
  58. NSString* bundleId = request.arguments[@"bundleId"];
  59. NSNumber* idleTimeoutMs = request.arguments[@"idleTimeoutMs"];
  60. NSError *error;
  61. if (nil == bundleId) {
  62. if (![XCUIDevice.sharedDevice fb_openUrl:urlString error:&error]) {
  63. return FBResponseWithUnknownError(error);
  64. }
  65. } else {
  66. if (![XCUIDevice.sharedDevice fb_openUrl:urlString withApplication:bundleId error:&error]) {
  67. return FBResponseWithUnknownError(error);
  68. }
  69. if (idleTimeoutMs.doubleValue > 0) {
  70. XCUIApplication *app = [[XCUIApplication alloc] initWithBundleIdentifier:bundleId];
  71. [app fb_waitUntilStableWithTimeout:FBMillisToSeconds(idleTimeoutMs.doubleValue)];
  72. }
  73. }
  74. return FBResponseWithOK();
  75. }
  76. + (id<FBResponsePayload>)handleCreateSession:(FBRouteRequest *)request
  77. {
  78. if (nil != FBSession.activeSession) {
  79. [FBSession.activeSession kill];
  80. }
  81. NSDictionary<NSString *, id> *capabilities;
  82. NSError *error;
  83. if (![request.arguments[@"capabilities"] isKindOfClass:NSDictionary.class]) {
  84. return FBResponseWithStatus([FBCommandStatus sessionNotCreatedError:@"'capabilities' is mandatory to create a new session"
  85. traceback:nil]);
  86. }
  87. if (nil == (capabilities = FBParseCapabilities((NSDictionary *)request.arguments[@"capabilities"], &error))) {
  88. return FBResponseWithStatus([FBCommandStatus sessionNotCreatedError:error.localizedDescription traceback:nil]);
  89. }
  90. [FBConfiguration resetSessionSettings];
  91. [FBConfiguration setShouldUseTestManagerForVisibilityDetection:[capabilities[FB_CAP_USE_TEST_MANAGER_FOR_VISIBLITY_DETECTION] boolValue]];
  92. if (capabilities[FB_SETTING_USE_COMPACT_RESPONSES]) {
  93. [FBConfiguration setShouldUseCompactResponses:[capabilities[FB_SETTING_USE_COMPACT_RESPONSES] boolValue]];
  94. }
  95. NSString *elementResponseAttributes = capabilities[FB_SETTING_ELEMENT_RESPONSE_ATTRIBUTES];
  96. if (elementResponseAttributes) {
  97. [FBConfiguration setElementResponseAttributes:elementResponseAttributes];
  98. }
  99. if (capabilities[FB_CAP_MAX_TYPING_FREQUENCY]) {
  100. [FBConfiguration setMaxTypingFrequency:[capabilities[FB_CAP_MAX_TYPING_FREQUENCY] unsignedIntegerValue]];
  101. }
  102. if (capabilities[FB_CAP_USE_SINGLETON_TEST_MANAGER]) {
  103. [FBConfiguration setShouldUseSingletonTestManager:[capabilities[FB_CAP_USE_SINGLETON_TEST_MANAGER] boolValue]];
  104. }
  105. if (capabilities[FB_CAP_DISABLE_AUTOMATIC_SCREENSHOTS]) {
  106. if ([capabilities[FB_CAP_DISABLE_AUTOMATIC_SCREENSHOTS] boolValue]) {
  107. [FBConfiguration disableScreenshots];
  108. } else {
  109. [FBConfiguration enableScreenshots];
  110. }
  111. }
  112. if (capabilities[FB_CAP_SHOULD_TERMINATE_APP]) {
  113. [FBConfiguration setShouldTerminateApp:[capabilities[FB_CAP_SHOULD_TERMINATE_APP] boolValue]];
  114. }
  115. NSNumber *delay = capabilities[FB_CAP_EVENT_LOOP_IDLE_DELAY_SEC];
  116. if ([delay doubleValue] > 0.0) {
  117. [XCUIApplicationProcessDelay setEventLoopHasIdledDelay:[delay doubleValue]];
  118. } else {
  119. [XCUIApplicationProcessDelay disableEventLoopDelay];
  120. }
  121. if (nil != capabilities[FB_SETTING_WAIT_FOR_IDLE_TIMEOUT]) {
  122. FBConfiguration.waitForIdleTimeout = [capabilities[FB_SETTING_WAIT_FOR_IDLE_TIMEOUT] doubleValue];
  123. }
  124. if (nil == capabilities[FB_CAP_FORCE_SIMULATOR_SOFTWARE_KEYBOARD_PRESENCE] ||
  125. [capabilities[FB_CAP_FORCE_SIMULATOR_SOFTWARE_KEYBOARD_PRESENCE] boolValue]) {
  126. [FBConfiguration forceSimulatorSoftwareKeyboardPresence];
  127. }
  128. NSString *bundleID = capabilities[FB_CAP_BUNDLE_ID];
  129. NSString *initialUrl = capabilities[FB_CAP_INITIAL_URL];
  130. XCUIApplication *app = nil;
  131. if (bundleID != nil) {
  132. app = [[XCUIApplication alloc] initWithBundleIdentifier:bundleID];
  133. BOOL forceAppLaunch = YES;
  134. if (nil != capabilities[FB_CAP_FORCE_APP_LAUNCH]) {
  135. forceAppLaunch = [capabilities[FB_CAP_FORCE_APP_LAUNCH] boolValue];
  136. }
  137. XCUIApplicationState appState = app.state;
  138. BOOL isAppRunning = appState >= XCUIApplicationStateRunningBackground;
  139. if (!isAppRunning || (isAppRunning && forceAppLaunch)) {
  140. app.fb_shouldWaitForQuiescence = nil == capabilities[FB_CAP_SHOULD_WAIT_FOR_QUIESCENCE]
  141. || [capabilities[FB_CAP_SHOULD_WAIT_FOR_QUIESCENCE] boolValue];
  142. app.launchArguments = (NSArray<NSString *> *)capabilities[FB_CAP_ARGUMENTS] ?: @[];
  143. app.launchEnvironment = (NSDictionary <NSString *, NSString *> *)capabilities[FB_CAP_ENVIRNOMENT] ?: @{};
  144. if (nil != initialUrl) {
  145. if (app.running) {
  146. [app terminate];
  147. }
  148. id<FBResponsePayload> errorResponse = [self openDeepLink:initialUrl
  149. withApplication:bundleID
  150. timeout:capabilities[FB_CAP_APP_LAUNCH_STATE_TIMEOUT_SEC]];
  151. if (nil != errorResponse) {
  152. return errorResponse;
  153. }
  154. } else {
  155. NSTimeInterval defaultTimeout = _XCTApplicationStateTimeout();
  156. if (nil != capabilities[FB_CAP_APP_LAUNCH_STATE_TIMEOUT_SEC]) {
  157. _XCTSetApplicationStateTimeout([capabilities[FB_CAP_APP_LAUNCH_STATE_TIMEOUT_SEC] doubleValue]);
  158. }
  159. @try {
  160. [app launch];
  161. } @catch (NSException *e) {
  162. return FBResponseWithStatus([FBCommandStatus sessionNotCreatedError:e.reason traceback:nil]);
  163. } @finally {
  164. if (nil != capabilities[FB_CAP_APP_LAUNCH_STATE_TIMEOUT_SEC]) {
  165. _XCTSetApplicationStateTimeout(defaultTimeout);
  166. }
  167. }
  168. }
  169. if (!app.running) {
  170. NSString *errorMsg = [NSString stringWithFormat:@"Cannot launch %@ application. Make sure the correct bundle identifier has been provided in capabilities and check the device log for possible crash report occurrences", bundleID];
  171. return FBResponseWithStatus([FBCommandStatus sessionNotCreatedError:errorMsg
  172. traceback:nil]);
  173. }
  174. } else if (appState == XCUIApplicationStateRunningBackground && !forceAppLaunch) {
  175. if (nil != initialUrl) {
  176. id<FBResponsePayload> errorResponse = [self openDeepLink:initialUrl
  177. withApplication:bundleID
  178. timeout:nil];
  179. if (nil != errorResponse) {
  180. return errorResponse;
  181. }
  182. } else {
  183. [app activate];
  184. }
  185. }
  186. }
  187. if (nil != initialUrl && nil == bundleID) {
  188. id<FBResponsePayload> errorResponse = [self openDeepLink:initialUrl
  189. withApplication:nil
  190. timeout:capabilities[FB_CAP_APP_LAUNCH_STATE_TIMEOUT_SEC]];
  191. if (nil != errorResponse) {
  192. return errorResponse;
  193. }
  194. }
  195. if (capabilities[FB_SETTING_DEFAULT_ALERT_ACTION]) {
  196. [FBSession initWithApplication:app
  197. defaultAlertAction:(id)capabilities[FB_SETTING_DEFAULT_ALERT_ACTION]];
  198. } else {
  199. [FBSession initWithApplication:app];
  200. }
  201. if (nil != capabilities[FB_CAP_USE_NATIVE_CACHING_STRATEGY]) {
  202. FBSession.activeSession.useNativeCachingStrategy = [capabilities[FB_CAP_USE_NATIVE_CACHING_STRATEGY] boolValue];
  203. }
  204. return FBResponseWithObject(FBSessionCommands.sessionInformation);
  205. }
  206. + (id<FBResponsePayload>)handleSessionAppLaunch:(FBRouteRequest *)request
  207. {
  208. [request.session launchApplicationWithBundleId:(id)request.arguments[@"bundleId"]
  209. shouldWaitForQuiescence:request.arguments[@"shouldWaitForQuiescence"]
  210. arguments:request.arguments[@"arguments"]
  211. environment:request.arguments[@"environment"]];
  212. return FBResponseWithOK();
  213. }
  214. + (id<FBResponsePayload>)handleSessionAppActivate:(FBRouteRequest *)request
  215. {
  216. [request.session activateApplicationWithBundleId:(id)request.arguments[@"bundleId"]];
  217. return FBResponseWithOK();
  218. }
  219. + (id<FBResponsePayload>)handleSessionAppTerminate:(FBRouteRequest *)request
  220. {
  221. BOOL result = [request.session terminateApplicationWithBundleId:(id)request.arguments[@"bundleId"]];
  222. return FBResponseWithObject(@(result));
  223. }
  224. + (id<FBResponsePayload>)handleSessionAppState:(FBRouteRequest *)request
  225. {
  226. NSUInteger state = [request.session applicationStateWithBundleId:(id)request.arguments[@"bundleId"]];
  227. return FBResponseWithObject(@(state));
  228. }
  229. + (id<FBResponsePayload>)handleGetActiveAppsList:(FBRouteRequest *)request
  230. {
  231. return FBResponseWithObject([XCUIApplication fb_activeAppsInfo]);
  232. }
  233. + (id<FBResponsePayload>)handleGetActiveSession:(FBRouteRequest *)request
  234. {
  235. return FBResponseWithObject(FBSessionCommands.sessionInformation);
  236. }
  237. + (id<FBResponsePayload>)handleDeleteSession:(FBRouteRequest *)request
  238. {
  239. [request.session kill];
  240. return FBResponseWithOK();
  241. }
  242. + (id<FBResponsePayload>)handleGetStatus:(FBRouteRequest *)request
  243. {
  244. // For updatedWDABundleId capability by Appium
  245. NSString *productBundleIdentifier = @"com.facebook.WebDriverAgentRunner";
  246. NSString *envproductBundleIdentifier = NSProcessInfo.processInfo.environment[@"WDA_PRODUCT_BUNDLE_IDENTIFIER"];
  247. if (envproductBundleIdentifier && [envproductBundleIdentifier length] != 0) {
  248. productBundleIdentifier = NSProcessInfo.processInfo.environment[@"WDA_PRODUCT_BUNDLE_IDENTIFIER"];
  249. }
  250. NSMutableDictionary *buildInfo = [NSMutableDictionary dictionaryWithDictionary:@{
  251. @"time" : [self.class buildTimestamp],
  252. @"productBundleIdentifier" : productBundleIdentifier,
  253. }];
  254. NSString *upgradeTimestamp = NSProcessInfo.processInfo.environment[@"UPGRADE_TIMESTAMP"];
  255. if (nil != upgradeTimestamp && upgradeTimestamp.length > 0) {
  256. [buildInfo setObject:upgradeTimestamp forKey:@"upgradedAt"];
  257. }
  258. NSDictionary *infoDict = [[NSBundle bundleForClass:self.class] infoDictionary];
  259. NSString *version = [infoDict objectForKey:@"CFBundleShortVersionString"];
  260. if (nil != version) {
  261. [buildInfo setObject:version forKey:@"version"];
  262. }
  263. return FBResponseWithObject(
  264. @{
  265. @"ready" : @YES,
  266. @"message" : @"WebDriverAgent is ready to accept commands",
  267. @"state" : @"success",
  268. @"os" :
  269. @{
  270. @"name" : [[UIDevice currentDevice] systemName],
  271. @"version" : [[UIDevice currentDevice] systemVersion],
  272. @"sdkVersion": FBSDKVersion() ?: @"unknown",
  273. @"testmanagerdVersion": @(FBTestmanagerdVersion()),
  274. },
  275. @"ios" :
  276. @{
  277. #if TARGET_OS_SIMULATOR
  278. @"simulatorVersion" : [[UIDevice currentDevice] systemVersion],
  279. #endif
  280. @"ip" : [XCUIDevice sharedDevice].fb_wifiIPAddress ?: [NSNull null]
  281. },
  282. @"build" : buildInfo.copy,
  283. @"device": [self.class deviceNameByUserInterfaceIdiom:[UIDevice currentDevice].userInterfaceIdiom]
  284. }
  285. );
  286. }
  287. + (id<FBResponsePayload>)handleGetHealthCheck:(FBRouteRequest *)request
  288. {
  289. if (![[XCUIDevice sharedDevice] fb_healthCheckWithApplication:[XCUIApplication fb_activeApplication]]) {
  290. return FBResponseWithUnknownErrorFormat(@"Health check failed");
  291. }
  292. return FBResponseWithOK();
  293. }
  294. + (id<FBResponsePayload>)handleGetSettings:(FBRouteRequest *)request
  295. {
  296. return FBResponseWithObject(
  297. @{
  298. FB_SETTING_USE_COMPACT_RESPONSES: @([FBConfiguration shouldUseCompactResponses]),
  299. FB_SETTING_ELEMENT_RESPONSE_ATTRIBUTES: [FBConfiguration elementResponseAttributes],
  300. FB_SETTING_MJPEG_SERVER_SCREENSHOT_QUALITY: @([FBConfiguration mjpegServerScreenshotQuality]),
  301. FB_SETTING_MJPEG_SERVER_FRAMERATE: @([FBConfiguration mjpegServerFramerate]),
  302. FB_SETTING_MJPEG_SCALING_FACTOR: @([FBConfiguration mjpegScalingFactor]),
  303. FB_SETTING_MJPEG_FIX_ORIENTATION: @([FBConfiguration mjpegShouldFixOrientation]),
  304. FB_SETTING_SCREENSHOT_QUALITY: @([FBConfiguration screenshotQuality]),
  305. FB_SETTING_KEYBOARD_AUTOCORRECTION: @([FBConfiguration keyboardAutocorrection]),
  306. FB_SETTING_KEYBOARD_PREDICTION: @([FBConfiguration keyboardPrediction]),
  307. FB_SETTING_SNAPSHOT_MAX_DEPTH: @([FBConfiguration snapshotMaxDepth]),
  308. FB_SETTING_USE_FIRST_MATCH: @([FBConfiguration useFirstMatch]),
  309. FB_SETTING_WAIT_FOR_IDLE_TIMEOUT: @([FBConfiguration waitForIdleTimeout]),
  310. FB_SETTING_ANIMATION_COOL_OFF_TIMEOUT: @([FBConfiguration animationCoolOffTimeout]),
  311. FB_SETTING_BOUND_ELEMENTS_BY_INDEX: @([FBConfiguration boundElementsByIndex]),
  312. FB_SETTING_REDUCE_MOTION: @([FBConfiguration reduceMotionEnabled]),
  313. FB_SETTING_DEFAULT_ACTIVE_APPLICATION: request.session.defaultActiveApplication,
  314. FB_SETTING_ACTIVE_APP_DETECTION_POINT: FBActiveAppDetectionPoint.sharedInstance.stringCoordinates,
  315. FB_SETTING_INCLUDE_NON_MODAL_ELEMENTS: @([FBConfiguration includeNonModalElements]),
  316. FB_SETTING_ACCEPT_ALERT_BUTTON_SELECTOR: FBConfiguration.acceptAlertButtonSelector,
  317. FB_SETTING_DISMISS_ALERT_BUTTON_SELECTOR: FBConfiguration.dismissAlertButtonSelector,
  318. FB_SETTING_AUTO_CLICK_ALERT_SELECTOR: FBConfiguration.autoClickAlertSelector,
  319. FB_SETTING_DEFAULT_ALERT_ACTION: request.session.defaultAlertAction ?: @"",
  320. FB_SETTING_MAX_TYPING_FREQUENCY: @([FBConfiguration maxTypingFrequency]),
  321. FB_SETTING_RESPECT_SYSTEM_ALERTS: @([FBConfiguration shouldRespectSystemAlerts]),
  322. FB_SETTING_USE_CLEAR_TEXT_SHORTCUT: @([FBConfiguration useClearTextShortcut]),
  323. FB_SETTING_INCLUDE_HITTABLE_IN_PAGE_SOURCE: @([FBConfiguration includeHittableInPageSource]),
  324. FB_SETTING_INCLUDE_NATIVE_FRAME_IN_PAGE_SOURCE: @([FBConfiguration includeNativeFrameInPageSource]),
  325. FB_SETTING_INCLUDE_MIN_MAX_VALUE_IN_PAGE_SOURCE: @([FBConfiguration includeMinMaxValueInPageSource]),
  326. FB_SETTING_LIMIT_XPATH_CONTEXT_SCOPE: @([FBConfiguration limitXpathContextScope]),
  327. #if !TARGET_OS_TV
  328. FB_SETTING_SCREENSHOT_ORIENTATION: [FBConfiguration humanReadableScreenshotOrientation],
  329. #endif
  330. }
  331. );
  332. }
  333. // TODO if we get lots more settings, handling them with a series of if-statements will be unwieldy
  334. // and this should be refactored
  335. + (id<FBResponsePayload>)handleSetSettings:(FBRouteRequest *)request
  336. {
  337. NSDictionary* settings = request.arguments[@"settings"];
  338. if (nil != [settings objectForKey:FB_SETTING_USE_COMPACT_RESPONSES]) {
  339. [FBConfiguration setShouldUseCompactResponses:[[settings objectForKey:FB_SETTING_USE_COMPACT_RESPONSES] boolValue]];
  340. }
  341. if (nil != [settings objectForKey:FB_SETTING_ELEMENT_RESPONSE_ATTRIBUTES]) {
  342. [FBConfiguration setElementResponseAttributes:(NSString *)[settings objectForKey:FB_SETTING_ELEMENT_RESPONSE_ATTRIBUTES]];
  343. }
  344. if (nil != [settings objectForKey:FB_SETTING_MJPEG_SERVER_SCREENSHOT_QUALITY]) {
  345. [FBConfiguration setMjpegServerScreenshotQuality:[[settings objectForKey:FB_SETTING_MJPEG_SERVER_SCREENSHOT_QUALITY] unsignedIntegerValue]];
  346. }
  347. if (nil != [settings objectForKey:FB_SETTING_MJPEG_SERVER_FRAMERATE]) {
  348. [FBConfiguration setMjpegServerFramerate:[[settings objectForKey:FB_SETTING_MJPEG_SERVER_FRAMERATE] unsignedIntegerValue]];
  349. }
  350. if (nil != [settings objectForKey:FB_SETTING_SCREENSHOT_QUALITY]) {
  351. [FBConfiguration setScreenshotQuality:[[settings objectForKey:FB_SETTING_SCREENSHOT_QUALITY] unsignedIntegerValue]];
  352. }
  353. if (nil != [settings objectForKey:FB_SETTING_MJPEG_SCALING_FACTOR]) {
  354. [FBConfiguration setMjpegScalingFactor:[[settings objectForKey:FB_SETTING_MJPEG_SCALING_FACTOR] floatValue]];
  355. }
  356. if (nil != [settings objectForKey:FB_SETTING_MJPEG_FIX_ORIENTATION]) {
  357. [FBConfiguration setMjpegShouldFixOrientation:[[settings objectForKey:FB_SETTING_MJPEG_FIX_ORIENTATION] boolValue]];
  358. }
  359. if (nil != [settings objectForKey:FB_SETTING_KEYBOARD_AUTOCORRECTION]) {
  360. [FBConfiguration setKeyboardAutocorrection:[[settings objectForKey:FB_SETTING_KEYBOARD_AUTOCORRECTION] boolValue]];
  361. }
  362. if (nil != [settings objectForKey:FB_SETTING_KEYBOARD_PREDICTION]) {
  363. [FBConfiguration setKeyboardPrediction:[[settings objectForKey:FB_SETTING_KEYBOARD_PREDICTION] boolValue]];
  364. }
  365. if (nil != [settings objectForKey:FB_SETTING_RESPECT_SYSTEM_ALERTS]) {
  366. [FBConfiguration setShouldRespectSystemAlerts:[[settings objectForKey:FB_SETTING_RESPECT_SYSTEM_ALERTS] boolValue]];
  367. }
  368. if (nil != [settings objectForKey:FB_SETTING_SNAPSHOT_MAX_DEPTH]) {
  369. [FBConfiguration setSnapshotMaxDepth:[[settings objectForKey:FB_SETTING_SNAPSHOT_MAX_DEPTH] intValue]];
  370. }
  371. if (nil != [settings objectForKey:FB_SETTING_USE_FIRST_MATCH]) {
  372. [FBConfiguration setUseFirstMatch:[[settings objectForKey:FB_SETTING_USE_FIRST_MATCH] boolValue]];
  373. }
  374. if (nil != [settings objectForKey:FB_SETTING_BOUND_ELEMENTS_BY_INDEX]) {
  375. [FBConfiguration setBoundElementsByIndex:[[settings objectForKey:FB_SETTING_BOUND_ELEMENTS_BY_INDEX] boolValue]];
  376. }
  377. if (nil != [settings objectForKey:FB_SETTING_REDUCE_MOTION]) {
  378. [FBConfiguration setReduceMotionEnabled:[[settings objectForKey:FB_SETTING_REDUCE_MOTION] boolValue]];
  379. }
  380. if (nil != [settings objectForKey:FB_SETTING_DEFAULT_ACTIVE_APPLICATION]) {
  381. request.session.defaultActiveApplication = (NSString *)[settings objectForKey:FB_SETTING_DEFAULT_ACTIVE_APPLICATION];
  382. }
  383. if (nil != [settings objectForKey:FB_SETTING_ACTIVE_APP_DETECTION_POINT]) {
  384. NSError *error;
  385. if (![FBActiveAppDetectionPoint.sharedInstance setCoordinatesWithString:(NSString *)[settings objectForKey:FB_SETTING_ACTIVE_APP_DETECTION_POINT]
  386. error:&error]) {
  387. return FBResponseWithStatus([FBCommandStatus invalidArgumentErrorWithMessage:error.localizedDescription
  388. traceback:nil]);
  389. }
  390. }
  391. if (nil != [settings objectForKey:FB_SETTING_INCLUDE_NON_MODAL_ELEMENTS]) {
  392. if ([XCUIElement fb_supportsNonModalElementsInclusion]) {
  393. [FBConfiguration setIncludeNonModalElements:[[settings objectForKey:FB_SETTING_INCLUDE_NON_MODAL_ELEMENTS] boolValue]];
  394. } else {
  395. [FBLogger logFmt:@"'%@' settings value cannot be assigned, because non modal elements inclusion is not supported by the current iOS SDK", FB_SETTING_INCLUDE_NON_MODAL_ELEMENTS];
  396. }
  397. }
  398. if (nil != [settings objectForKey:FB_SETTING_ACCEPT_ALERT_BUTTON_SELECTOR]) {
  399. [FBConfiguration setAcceptAlertButtonSelector:(NSString *)[settings objectForKey:FB_SETTING_ACCEPT_ALERT_BUTTON_SELECTOR]];
  400. }
  401. if (nil != [settings objectForKey:FB_SETTING_DISMISS_ALERT_BUTTON_SELECTOR]) {
  402. [FBConfiguration setDismissAlertButtonSelector:(NSString *)[settings objectForKey:FB_SETTING_DISMISS_ALERT_BUTTON_SELECTOR]];
  403. }
  404. if (nil != [settings objectForKey:FB_SETTING_AUTO_CLICK_ALERT_SELECTOR]) {
  405. FBCommandStatus *status = [self.class configureAutoClickAlertWithSelector:settings[FB_SETTING_AUTO_CLICK_ALERT_SELECTOR]
  406. forSession:request.session];
  407. if (status.hasError) {
  408. return FBResponseWithStatus(status);
  409. }
  410. }
  411. if (nil != [settings objectForKey:FB_SETTING_WAIT_FOR_IDLE_TIMEOUT]) {
  412. [FBConfiguration setWaitForIdleTimeout:[[settings objectForKey:FB_SETTING_WAIT_FOR_IDLE_TIMEOUT] doubleValue]];
  413. }
  414. if (nil != [settings objectForKey:FB_SETTING_ANIMATION_COOL_OFF_TIMEOUT]) {
  415. [FBConfiguration setAnimationCoolOffTimeout:[[settings objectForKey:FB_SETTING_ANIMATION_COOL_OFF_TIMEOUT] doubleValue]];
  416. }
  417. if ([[settings objectForKey:FB_SETTING_DEFAULT_ALERT_ACTION] isKindOfClass:NSString.class]) {
  418. request.session.defaultAlertAction = [settings[FB_SETTING_DEFAULT_ALERT_ACTION] lowercaseString];
  419. }
  420. if (nil != [settings objectForKey:FB_SETTING_MAX_TYPING_FREQUENCY]) {
  421. [FBConfiguration setMaxTypingFrequency:[[settings objectForKey:FB_SETTING_MAX_TYPING_FREQUENCY] unsignedIntegerValue]];
  422. }
  423. if (nil != [settings objectForKey:FB_SETTING_USE_CLEAR_TEXT_SHORTCUT]) {
  424. [FBConfiguration setUseClearTextShortcut:[[settings objectForKey:FB_SETTING_USE_CLEAR_TEXT_SHORTCUT] boolValue]];
  425. }
  426. if (nil != [settings objectForKey:FB_SETTING_INCLUDE_HITTABLE_IN_PAGE_SOURCE]) {
  427. [FBConfiguration setIncludeHittableInPageSource:[[settings objectForKey:FB_SETTING_INCLUDE_HITTABLE_IN_PAGE_SOURCE] boolValue]];
  428. }
  429. if (nil != [settings objectForKey:FB_SETTING_INCLUDE_NATIVE_FRAME_IN_PAGE_SOURCE]) {
  430. [FBConfiguration setIncludeNativeFrameInPageSource:[[settings objectForKey:FB_SETTING_INCLUDE_NATIVE_FRAME_IN_PAGE_SOURCE] boolValue]];
  431. }
  432. if (nil != [settings objectForKey:FB_SETTING_INCLUDE_MIN_MAX_VALUE_IN_PAGE_SOURCE]) {
  433. [FBConfiguration setIncludeMinMaxValueInPageSource:[[settings objectForKey:FB_SETTING_INCLUDE_MIN_MAX_VALUE_IN_PAGE_SOURCE] boolValue]];
  434. }
  435. if (nil != [settings objectForKey:FB_SETTING_LIMIT_XPATH_CONTEXT_SCOPE]) {
  436. [FBConfiguration setLimitXpathContextScope:[[settings objectForKey:FB_SETTING_LIMIT_XPATH_CONTEXT_SCOPE] boolValue]];
  437. }
  438. #if !TARGET_OS_TV
  439. if (nil != [settings objectForKey:FB_SETTING_SCREENSHOT_ORIENTATION]) {
  440. NSError *error;
  441. if (![FBConfiguration setScreenshotOrientation:(NSString *)[settings objectForKey:FB_SETTING_SCREENSHOT_ORIENTATION]
  442. error:&error]) {
  443. return FBResponseWithStatus([FBCommandStatus invalidArgumentErrorWithMessage:error.localizedDescription
  444. traceback:nil]);
  445. }
  446. }
  447. #endif
  448. return [self handleGetSettings:request];
  449. }
  450. #pragma mark - Helpers
  451. + (FBCommandStatus *)configureAutoClickAlertWithSelector:(NSString *)selector
  452. forSession:(FBSession *)session
  453. {
  454. if (0 == [selector length]) {
  455. [FBConfiguration setAutoClickAlertSelector:selector];
  456. [session disableAlertsMonitor];
  457. return [FBCommandStatus ok];
  458. }
  459. NSError *error;
  460. FBClassChain *parsedChain = [FBClassChainQueryParser parseQuery:selector error:&error];
  461. if (nil == parsedChain) {
  462. return [FBCommandStatus invalidSelectorErrorWithMessage:error.localizedDescription
  463. traceback:nil];
  464. }
  465. [FBConfiguration setAutoClickAlertSelector:selector];
  466. [session enableAlertsMonitor];
  467. return [FBCommandStatus ok];
  468. }
  469. + (NSString *)buildTimestamp
  470. {
  471. return [NSString stringWithFormat:@"%@ %@",
  472. [NSString stringWithUTF8String:__DATE__],
  473. [NSString stringWithUTF8String:__TIME__]
  474. ];
  475. }
  476. /**
  477. Return current session information.
  478. This response does not have any active application information.
  479. */
  480. + (NSDictionary *)sessionInformation
  481. {
  482. return
  483. @{
  484. @"sessionId" : [FBSession activeSession].identifier ?: NSNull.null,
  485. @"capabilities" : FBSessionCommands.currentCapabilities
  486. };
  487. }
  488. /*
  489. Return the device kind as lower case
  490. */
  491. + (NSString *)deviceNameByUserInterfaceIdiom:(UIUserInterfaceIdiom) userInterfaceIdiom
  492. {
  493. if (userInterfaceIdiom == UIUserInterfaceIdiomPad) {
  494. return @"ipad";
  495. } else if (userInterfaceIdiom == UIUserInterfaceIdiomTV) {
  496. return @"apple tv";
  497. } else if (userInterfaceIdiom == UIUserInterfaceIdiomPhone) {
  498. return @"iphone";
  499. }
  500. // CarPlay, Mac, Vision UI or unknown are possible
  501. return @"Unknown";
  502. }
  503. + (NSDictionary *)currentCapabilities
  504. {
  505. return
  506. @{
  507. @"device": [self.class deviceNameByUserInterfaceIdiom:[UIDevice currentDevice].userInterfaceIdiom],
  508. @"sdkVersion": [[UIDevice currentDevice] systemVersion]
  509. };
  510. }
  511. +(nullable id<FBResponsePayload>)openDeepLink:(NSString *)initialUrl
  512. withApplication:(nullable NSString *)bundleID
  513. timeout:(nullable NSNumber *)timeout
  514. {
  515. NSError *openError;
  516. NSTimeInterval defaultTimeout = _XCTApplicationStateTimeout();
  517. if (nil != timeout) {
  518. _XCTSetApplicationStateTimeout([timeout doubleValue]);
  519. }
  520. @try {
  521. BOOL result = nil == bundleID
  522. ? [XCUIDevice.sharedDevice fb_openUrl:initialUrl
  523. error:&openError]
  524. : [XCUIDevice.sharedDevice fb_openUrl:initialUrl
  525. withApplication:(id)bundleID
  526. error:&openError];
  527. if (result) {
  528. return nil;
  529. }
  530. NSString *errorMsg = [NSString stringWithFormat:@"Cannot open the URL %@ with the %@ application. Original error: %@",
  531. initialUrl, bundleID ?: @"default", openError.localizedDescription];
  532. return FBResponseWithStatus([FBCommandStatus sessionNotCreatedError:errorMsg traceback:nil]);
  533. } @finally {
  534. if (nil != timeout) {
  535. _XCTSetApplicationStateTimeout(defaultTimeout);
  536. }
  537. }
  538. }
  539. @end