Files
custom_wda/WebDriverAgentLib/Utilities/FBConfiguration.m
2026-02-03 16:52:44 +08:00

678 lines
19 KiB
Objective-C

/**
* 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.
*/
#import "FBConfiguration.h"
#import "AXSettings.h"
#import "UIKeyboardImpl.h"
#import "TIPreferencesController.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 = 8567;
static NSUInteger const DefaultMjpegServerPort = 9567;
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 BOOL FBShouldRespectSystemAlerts = NO;
static CGFloat FBMjpegScalingFactor = 100.0;
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 BOOL FBShouldUseFirstMatch;
static BOOL FBShouldBoundElementsByIndex;
static BOOL FBIncludeNonModalElements;
static NSString *FBAcceptAlertButtonSelector;
static NSString *FBDismissAlertButtonSelector;
static NSString *FBAutoClickAlertSelector;
static NSTimeInterval FBWaitForIdleTimeout;
static NSTimeInterval FBAnimationCoolOffTimeout;
static BOOL FBShouldUseCompactResponses;
static NSString *FBElementResponseAttributes;
static BOOL FBUseClearTextShortcut;
static BOOL FBLimitXpathContextScope = YES;
#if !TARGET_OS_TV
static UIInterfaceOrientation FBScreenshotOrientation;
#endif
static BOOL FBShouldIncludeHittableInPageSource = NO;
static BOOL FBShouldIncludeNativeFrameInPageSource = NO;
static BOOL FBShouldIncludeMinMaxValueInPageSource = NO;
@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;
}
+ (CGFloat)mjpegScalingFactor
{
return FBMjpegScalingFactor;
}
+ (void)setMjpegScalingFactor:(CGFloat)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)setSnapshotMaxDepth:(int)maxDepth
{
FBSetCustomParameterForElementSnapshot(FBSnapshotMaxDepthKey, @(maxDepth));
}
+ (int)snapshotMaxDepth
{
return [FBGetCustomParameterForElementSnapshot(FBSnapshotMaxDepthKey) intValue];
}
+ (void)setShouldRespectSystemAlerts:(BOOL)value
{
FBShouldRespectSystemAlerts = value;
}
+ (BOOL)shouldRespectSystemAlerts
{
return FBShouldRespectSystemAlerts;
}
+ (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;
}
+ (void)setAutoClickAlertSelector:(NSString *)classChainSelector
{
FBAutoClickAlertSelector = classChainSelector;
}
+ (NSString *)autoClickAlertSelector
{
return FBAutoClickAlertSelector;
}
+ (void)setUseClearTextShortcut:(BOOL)enabled
{
FBUseClearTextShortcut = enabled;
}
+ (BOOL)useClearTextShortcut
{
return FBUseClearTextShortcut;
}
+ (BOOL)limitXpathContextScope
{
return FBLimitXpathContextScope;
}
+ (void)setLimitXpathContextScope:(BOOL)enabled
{
FBLimitXpathContextScope = enabled;
}
#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;
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 = @"";
FBAutoClickAlertSelector = @"";
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);
FBUseClearTextShortcut = YES;
FBLimitXpathContextScope = YES;
#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;
}
+ (void)setIncludeHittableInPageSource:(BOOL)enabled
{
FBShouldIncludeHittableInPageSource = enabled;
}
+ (BOOL)includeHittableInPageSource
{
return FBShouldIncludeHittableInPageSource;
}
+ (void)setIncludeNativeFrameInPageSource:(BOOL)enabled
{
FBShouldIncludeNativeFrameInPageSource = enabled;
}
+ (BOOL)includeNativeFrameInPageSource
{
return FBShouldIncludeNativeFrameInPageSource;
}
+ (void)setIncludeMinMaxValueInPageSource:(BOOL)enabled
{
FBShouldIncludeMinMaxValueInPageSource = enabled;
}
+ (BOOL)includeMinMaxValueInPageSource
{
return FBShouldIncludeMinMaxValueInPageSource;
}
@end