| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606 |
- /**
- * Copyright (c) 2015-present, Facebook, Inc.
- * All rights reserved.
- *
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree. An additional grant
- * of patent rights can be found in the PATENTS file in the same directory.
- */
- #import "FBConfiguration.h"
- #include <dlfcn.h>
- #import <UIKit/UIKit.h>
- #include "TargetConditionals.h"
- #import "FBXCodeCompatibility.h"
- #import "XCAXClient_iOS+FBSnapshotReqParams.h"
- #import "XCTestPrivateSymbols.h"
- #import "XCTestConfiguration.h"
- #import "XCUIApplication+FBUIInterruptions.h"
- static NSUInteger const DefaultStartingPort = 8100;
- static NSUInteger const DefaultMjpegServerPort = 9100;
- static NSUInteger const DefaultPortRange = 100;
- static char const *const controllerPrefBundlePath = "/System/Library/PrivateFrameworks/TextInput.framework/TextInput";
- static NSString *const controllerClassName = @"TIPreferencesController";
- static NSString *const FBKeyboardAutocorrectionKey = @"KeyboardAutocorrection";
- static NSString *const FBKeyboardPredictionKey = @"KeyboardPrediction";
- static NSString *const axSettingsClassName = @"AXSettings";
- static BOOL FBShouldUseTestManagerForVisibilityDetection = NO;
- static BOOL FBShouldUseSingletonTestManager = YES;
- static NSUInteger FBMjpegScalingFactor = 100;
- static BOOL FBMjpegShouldFixOrientation = NO;
- static NSUInteger FBMjpegServerScreenshotQuality = 25;
- static NSUInteger FBMjpegServerFramerate = 10;
- // Session-specific settings
- static BOOL FBShouldTerminateApp;
- static NSNumber* FBMaxTypingFrequency;
- static NSUInteger FBScreenshotQuality;
- static NSTimeInterval FBCustomSnapshotTimeout;
- static BOOL FBShouldUseFirstMatch;
- static BOOL FBShouldBoundElementsByIndex;
- static BOOL FBIncludeNonModalElements;
- static NSString *FBAcceptAlertButtonSelector;
- static NSString *FBDismissAlertButtonSelector;
- static NSTimeInterval FBWaitForIdleTimeout;
- static NSTimeInterval FBAnimationCoolOffTimeout;
- static BOOL FBShouldUseCompactResponses;
- static NSString *FBElementResponseAttributes;
- #if !TARGET_OS_TV
- static UIInterfaceOrientation FBScreenshotOrientation;
- #endif
- @implementation FBConfiguration
- + (NSUInteger)defaultTypingFrequency
- {
- NSInteger defaultFreq = [[NSUserDefaults standardUserDefaults]
- integerForKey:@"com.apple.xctest.iOSMaximumTypingFrequency"];
- return defaultFreq > 0 ? defaultFreq : 60;
- }
- + (void)initialize
- {
- [FBConfiguration resetSessionSettings];
- }
- #pragma mark Public
- + (void)disableRemoteQueryEvaluation
- {
- [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"XCTDisableRemoteQueryEvaluation"];
- }
- + (void)disableApplicationUIInterruptionsHandling
- {
- [XCUIApplication fb_disableUIInterruptionsHandling];
- }
- + (void)enableXcTestDebugLogs
- {
- ((XCTestConfiguration *)XCTestConfiguration.activeTestConfiguration).emitOSLogs = YES;
- [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"XCTEmitOSLogs"];
- }
- + (void)disableAttributeKeyPathAnalysis
- {
- [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"XCTDisableAttributeKeyPathAnalysis"];
- }
- + (void)disableScreenshots
- {
- [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"DisableScreenshots"];
- }
- + (void)enableScreenshots
- {
- [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"DisableScreenshots"];
- }
- + (void)disableScreenRecordings
- {
- [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"DisableDiagnosticScreenRecordings"];
- }
- + (void)enableScreenRecordings
- {
- [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"DisableDiagnosticScreenRecordings"];
- }
- + (NSRange)bindingPortRange
- {
- // 'WebDriverAgent --port 8080' can be passed via the arguments to the process
- if (self.bindingPortRangeFromArguments.location != NSNotFound) {
- return self.bindingPortRangeFromArguments;
- }
- // Existence of USE_PORT in the environment implies the port range is managed by the launching process.
- if (NSProcessInfo.processInfo.environment[@"USE_PORT"] &&
- [NSProcessInfo.processInfo.environment[@"USE_PORT"] length] > 0) {
- return NSMakeRange([NSProcessInfo.processInfo.environment[@"USE_PORT"] integerValue] , 1);
- }
- return NSMakeRange(DefaultStartingPort, DefaultPortRange);
- }
- + (NSInteger)mjpegServerPort
- {
- if (self.mjpegServerPortFromArguments != NSNotFound) {
- return self.mjpegServerPortFromArguments;
- }
-
- if (NSProcessInfo.processInfo.environment[@"MJPEG_SERVER_PORT"] &&
- [NSProcessInfo.processInfo.environment[@"MJPEG_SERVER_PORT"] length] > 0) {
- return [NSProcessInfo.processInfo.environment[@"MJPEG_SERVER_PORT"] integerValue];
- }
- return DefaultMjpegServerPort;
- }
- + (NSUInteger)mjpegScalingFactor
- {
- return FBMjpegScalingFactor;
- }
- + (void)setMjpegScalingFactor:(NSUInteger)scalingFactor {
- FBMjpegScalingFactor = scalingFactor;
- }
- + (BOOL)mjpegShouldFixOrientation
- {
- return FBMjpegShouldFixOrientation;
- }
- + (void)setMjpegShouldFixOrientation:(BOOL)enabled {
- FBMjpegShouldFixOrientation = enabled;
- }
- + (BOOL)verboseLoggingEnabled
- {
- return [NSProcessInfo.processInfo.environment[@"VERBOSE_LOGGING"] boolValue];
- }
- + (void)setShouldUseTestManagerForVisibilityDetection:(BOOL)value
- {
- FBShouldUseTestManagerForVisibilityDetection = value;
- }
- + (BOOL)shouldUseTestManagerForVisibilityDetection
- {
- return FBShouldUseTestManagerForVisibilityDetection;
- }
- + (void)setShouldUseCompactResponses:(BOOL)value
- {
- FBShouldUseCompactResponses = value;
- }
- + (BOOL)shouldUseCompactResponses
- {
- return FBShouldUseCompactResponses;
- }
- + (void)setShouldTerminateApp:(BOOL)value
- {
- FBShouldTerminateApp = value;
- }
- + (BOOL)shouldTerminateApp
- {
- return FBShouldTerminateApp;
- }
- + (void)setElementResponseAttributes:(NSString *)value
- {
- FBElementResponseAttributes = value;
- }
- + (NSString *)elementResponseAttributes
- {
- return FBElementResponseAttributes;
- }
- + (void)setMaxTypingFrequency:(NSUInteger)value
- {
- FBMaxTypingFrequency = @(value);
- }
- + (NSUInteger)maxTypingFrequency
- {
- if (nil == FBMaxTypingFrequency) {
- return [self defaultTypingFrequency];
- }
- return FBMaxTypingFrequency.integerValue <= 0
- ? [self defaultTypingFrequency]
- : FBMaxTypingFrequency.integerValue;
- }
- + (void)setShouldUseSingletonTestManager:(BOOL)value
- {
- FBShouldUseSingletonTestManager = value;
- }
- + (BOOL)shouldUseSingletonTestManager
- {
- return FBShouldUseSingletonTestManager;
- }
- + (NSUInteger)mjpegServerFramerate
- {
- return FBMjpegServerFramerate;
- }
- + (void)setMjpegServerFramerate:(NSUInteger)framerate
- {
- FBMjpegServerFramerate = framerate;
- }
- + (NSUInteger)mjpegServerScreenshotQuality
- {
- return FBMjpegServerScreenshotQuality;
- }
- + (void)setMjpegServerScreenshotQuality:(NSUInteger)quality
- {
- FBMjpegServerScreenshotQuality = quality;
- }
- + (NSUInteger)screenshotQuality
- {
- return FBScreenshotQuality;
- }
- + (void)setScreenshotQuality:(NSUInteger)quality
- {
- FBScreenshotQuality = quality;
- }
- + (NSTimeInterval)waitForIdleTimeout
- {
- return FBWaitForIdleTimeout;
- }
- + (void)setWaitForIdleTimeout:(NSTimeInterval)timeout
- {
- FBWaitForIdleTimeout = timeout;
- }
- + (NSTimeInterval)animationCoolOffTimeout
- {
- return FBAnimationCoolOffTimeout;
- }
- + (void)setAnimationCoolOffTimeout:(NSTimeInterval)timeout
- {
- FBAnimationCoolOffTimeout = timeout;
- }
- // Works for Simulator and Real devices
- + (void)configureDefaultKeyboardPreferences
- {
- void *handle = dlopen(controllerPrefBundlePath, RTLD_LAZY);
- Class controllerClass = NSClassFromString(controllerClassName);
- TIPreferencesController *controller = [controllerClass sharedPreferencesController];
- // Auto-Correction in Keyboards
- // 'setAutocorrectionEnabled' Was in TextInput.framework/TIKeyboardState.h over iOS 10.3
- if ([controller respondsToSelector:@selector(setAutocorrectionEnabled:)]) {
- // Under iOS 10.2
- controller.autocorrectionEnabled = NO;
- } else if ([controller respondsToSelector:@selector(setValue:forPreferenceKey:)]) {
- // Over iOS 10.3
- [controller setValue:@NO forPreferenceKey:FBKeyboardAutocorrectionKey];
- }
- // Predictive in Keyboards
- if ([controller respondsToSelector:@selector(setPredictionEnabled:)]) {
- controller.predictionEnabled = NO;
- } else if ([controller respondsToSelector:@selector(setValue:forPreferenceKey:)]) {
- [controller setValue:@NO forPreferenceKey:FBKeyboardPredictionKey];
- }
- // To dismiss keyboard tutorial on iOS 11+ (iPad)
- if ([controller respondsToSelector:@selector(setValue:forPreferenceKey:)]) {
- [controller setValue:@YES forPreferenceKey:@"DidShowGestureKeyboardIntroduction"];
- if (isSDKVersionGreaterThanOrEqualTo(@"13.0")) {
- [controller setValue:@YES forPreferenceKey:@"DidShowContinuousPathIntroduction"];
- }
- [controller synchronizePreferences];
- }
- dlclose(handle);
- }
- + (void)forceSimulatorSoftwareKeyboardPresence
- {
- #if TARGET_OS_SIMULATOR
- // Force toggle software keyboard on.
- // This can avoid 'Keyboard is not present' error which can happen
- // when send_keys are called by client
- [[UIKeyboardImpl sharedInstance] setAutomaticMinimizationEnabled:NO];
- if ([(NSObject *)[UIKeyboardImpl sharedInstance]
- respondsToSelector:@selector(setSoftwareKeyboardShownByTouch:)]) {
- // Xcode 13 no longer has this method
- [[UIKeyboardImpl sharedInstance] setSoftwareKeyboardShownByTouch:YES];
- }
- #endif
- }
- + (FBConfigurationKeyboardPreference)keyboardAutocorrection
- {
- return [self keyboardsPreference:FBKeyboardAutocorrectionKey];
- }
- + (void)setKeyboardAutocorrection:(BOOL)isEnabled
- {
- [self configureKeyboardsPreference:isEnabled forPreferenceKey:FBKeyboardAutocorrectionKey];
- }
- + (FBConfigurationKeyboardPreference)keyboardPrediction
- {
- return [self keyboardsPreference:FBKeyboardPredictionKey];
- }
- + (void)setKeyboardPrediction:(BOOL)isEnabled
- {
- [self configureKeyboardsPreference:isEnabled forPreferenceKey:FBKeyboardPredictionKey];
- }
- + (void)setCustomSnapshotTimeout:(NSTimeInterval)timeout
- {
- FBCustomSnapshotTimeout = timeout;
- }
- + (NSTimeInterval)customSnapshotTimeout
- {
- return FBCustomSnapshotTimeout;
- }
- + (void)setSnapshotMaxDepth:(int)maxDepth
- {
- FBSetCustomParameterForElementSnapshot(FBSnapshotMaxDepthKey, @(maxDepth));
- }
- + (int)snapshotMaxDepth
- {
- return [FBGetCustomParameterForElementSnapshot(FBSnapshotMaxDepthKey) intValue];
- }
- + (void)setUseFirstMatch:(BOOL)enabled
- {
- FBShouldUseFirstMatch = enabled;
- }
- + (BOOL)useFirstMatch
- {
- return FBShouldUseFirstMatch;
- }
- + (void)setBoundElementsByIndex:(BOOL)enabled
- {
- FBShouldBoundElementsByIndex = enabled;
- }
- + (BOOL)boundElementsByIndex
- {
- return FBShouldBoundElementsByIndex;
- }
- + (void)setIncludeNonModalElements:(BOOL)isEnabled
- {
- FBIncludeNonModalElements = isEnabled;
- }
- + (BOOL)includeNonModalElements
- {
- return FBIncludeNonModalElements;
- }
- + (void)setAcceptAlertButtonSelector:(NSString *)classChainSelector
- {
- FBAcceptAlertButtonSelector = classChainSelector;
- }
- + (NSString *)acceptAlertButtonSelector
- {
- return FBAcceptAlertButtonSelector;
- }
- + (void)setDismissAlertButtonSelector:(NSString *)classChainSelector
- {
- FBDismissAlertButtonSelector = classChainSelector;
- }
- + (NSString *)dismissAlertButtonSelector
- {
- return FBDismissAlertButtonSelector;
- }
- #if !TARGET_OS_TV
- + (BOOL)setScreenshotOrientation:(NSString *)orientation error:(NSError **)error
- {
- // Only UIInterfaceOrientationUnknown is over iOS 8. Others are over iOS 2.
- // https://developer.apple.com/documentation/uikit/uiinterfaceorientation/uiinterfaceorientationunknown
- if ([orientation.lowercaseString isEqualToString:@"portrait"]) {
- FBScreenshotOrientation = UIInterfaceOrientationPortrait;
- } else if ([orientation.lowercaseString isEqualToString:@"portraitupsidedown"]) {
- FBScreenshotOrientation = UIInterfaceOrientationPortraitUpsideDown;
- } else if ([orientation.lowercaseString isEqualToString:@"landscaperight"]) {
- FBScreenshotOrientation = UIInterfaceOrientationLandscapeRight;
- } else if ([orientation.lowercaseString isEqualToString:@"landscapeleft"]) {
- FBScreenshotOrientation = UIInterfaceOrientationLandscapeLeft;
- } else if ([orientation.lowercaseString isEqualToString:@"auto"]) {
- FBScreenshotOrientation = UIInterfaceOrientationUnknown;
- } else {
- return [[FBErrorBuilder.builder withDescriptionFormat:
- @"The orientation value '%@' is not known. Only the following orientation values are supported: " \
- "'auto', 'portrait', 'portraitUpsideDown', 'landscapeRight' and 'landscapeLeft'", orientation]
- buildError:error];
- }
- return YES;
- }
- + (NSInteger)screenshotOrientation
- {
- return FBScreenshotOrientation;
- }
- + (NSString *)humanReadableScreenshotOrientation
- {
- switch (FBScreenshotOrientation) {
- case UIInterfaceOrientationPortrait:
- return @"portrait";
- case UIInterfaceOrientationPortraitUpsideDown:
- return @"portraitUpsideDown";
- case UIInterfaceOrientationLandscapeRight:
- return @"landscapeRight";
- case UIInterfaceOrientationLandscapeLeft:
- return @"landscapeLeft";
- case UIInterfaceOrientationUnknown:
- return @"auto";
- }
- }
- #endif
- + (void)resetSessionSettings
- {
- FBShouldTerminateApp = YES;
- FBShouldUseCompactResponses = YES;
- FBElementResponseAttributes = @"type,label";
- FBMaxTypingFrequency = @([self defaultTypingFrequency]);
- FBScreenshotQuality = 3;
- FBCustomSnapshotTimeout = 15.;
- FBShouldUseFirstMatch = NO;
- FBShouldBoundElementsByIndex = NO;
- // This is diabled by default because enabling it prevents the accessbility snapshot to be taken
- // (it always errors with kxIllegalArgument error)
- FBIncludeNonModalElements = NO;
- FBAcceptAlertButtonSelector = @"";
- FBDismissAlertButtonSelector = @"";
- FBWaitForIdleTimeout = 10.;
- FBAnimationCoolOffTimeout = 2.;
- // 50 should be enough for the majority of the cases. The performance is acceptable for values up to 100.
- FBSetCustomParameterForElementSnapshot(FBSnapshotMaxDepthKey, @50);
- #if !TARGET_OS_TV
- FBScreenshotOrientation = UIInterfaceOrientationUnknown;
- #endif
- }
- #pragma mark Private
- + (FBConfigurationKeyboardPreference)keyboardsPreference:(nonnull NSString *)key
- {
- Class controllerClass = NSClassFromString(controllerClassName);
- TIPreferencesController *controller = [controllerClass sharedPreferencesController];
- if ([key isEqualToString:FBKeyboardAutocorrectionKey]) {
- if ([controller respondsToSelector:@selector(boolForPreferenceKey:)]) {
- return [controller boolForPreferenceKey:FBKeyboardAutocorrectionKey]
- ? FBConfigurationKeyboardPreferenceEnabled
- : FBConfigurationKeyboardPreferenceDisabled;
- } else {
- [FBLogger log:@"Updating keyboard autocorrection preference is not supported"];
- return FBConfigurationKeyboardPreferenceNotSupported;
- }
- } else if ([key isEqualToString:FBKeyboardPredictionKey]) {
- if ([controller respondsToSelector:@selector(boolForPreferenceKey:)]) {
- return [controller boolForPreferenceKey:FBKeyboardPredictionKey]
- ? FBConfigurationKeyboardPreferenceEnabled
- : FBConfigurationKeyboardPreferenceDisabled;
- } else {
- [FBLogger log:@"Updating keyboard prediction preference is not supported"];
- return FBConfigurationKeyboardPreferenceNotSupported;
- }
- }
- @throw [[FBErrorBuilder.builder withDescriptionFormat:@"No available keyboardsPreferenceKey: '%@'", key] build];
- }
- + (void)configureKeyboardsPreference:(BOOL)enable forPreferenceKey:(nonnull NSString *)key
- {
- void *handle = dlopen(controllerPrefBundlePath, RTLD_LAZY);
- Class controllerClass = NSClassFromString(controllerClassName);
- TIPreferencesController *controller = [controllerClass sharedPreferencesController];
- if ([key isEqualToString:FBKeyboardAutocorrectionKey]) {
- // Auto-Correction in Keyboards
- if ([controller respondsToSelector:@selector(setAutocorrectionEnabled:)]) {
- controller.autocorrectionEnabled = enable;
- } else {
- [controller setValue:@(enable) forPreferenceKey:FBKeyboardAutocorrectionKey];
- }
- } else if ([key isEqualToString:FBKeyboardPredictionKey]) {
- // Predictive in Keyboards
- if ([controller respondsToSelector:@selector(setPredictionEnabled:)]) {
- controller.predictionEnabled = enable;
- } else {
- [controller setValue:@(enable) forPreferenceKey:FBKeyboardPredictionKey];
- }
- }
- [controller synchronizePreferences];
- dlclose(handle);
- }
- + (NSString*)valueFromArguments: (NSArray<NSString *> *)arguments forKey: (NSString*)key
- {
- NSUInteger index = [arguments indexOfObject:key];
- if (index == NSNotFound || index == arguments.count - 1) {
- return nil;
- }
- return arguments[index + 1];
- }
- + (NSUInteger)mjpegServerPortFromArguments
- {
- NSString *portNumberString = [self valueFromArguments: NSProcessInfo.processInfo.arguments
- forKey: @"--mjpeg-server-port"];
- NSUInteger port = (NSUInteger)[portNumberString integerValue];
- if (port == 0) {
- return NSNotFound;
- }
- return port;
- }
- + (NSRange)bindingPortRangeFromArguments
- {
- NSString *portNumberString = [self valueFromArguments:NSProcessInfo.processInfo.arguments
- forKey: @"--port"];
- NSUInteger port = (NSUInteger)[portNumberString integerValue];
- if (port == 0) {
- return NSMakeRange(NSNotFound, 0);
- }
- return NSMakeRange(port, 1);
- }
- + (void)setReduceMotionEnabled:(BOOL)isEnabled
- {
- Class settingsClass = NSClassFromString(axSettingsClassName);
- AXSettings *settings = [settingsClass sharedInstance];
- // Below does not work on real devices because of iOS security model
- // (lldb) po settings.reduceMotionEnabled = isEnabled
- // 2019-08-21 22:58:19.776165+0900 WebDriverAgentRunner-Runner[322:13361] [User Defaults] Couldn't write value for key ReduceMotionEnabled in CFPrefsPlistSource<0x28111a700> (Domain: com.apple.Accessibility, User: kCFPreferencesCurrentUser, ByHost: No, Container: (null), Contents Need Refresh: No): setting preferences outside an application's container requires user-preference-write or file-write-data sandbox access
- if ([settings respondsToSelector:@selector(setReduceMotionEnabled:)]) {
- [settings setReduceMotionEnabled:isEnabled];
- }
- }
- + (BOOL)reduceMotionEnabled
- {
- Class settingsClass = NSClassFromString(axSettingsClassName);
- AXSettings *settings = [settingsClass sharedInstance];
- if ([settings respondsToSelector:@selector(reduceMotionEnabled)]) {
- return settings.reduceMotionEnabled;
- }
- return NO;
- }
- @end
|