diff --git a/WebDriverAgent.xcodeproj/project.pbxproj b/WebDriverAgent.xcodeproj/project.pbxproj index fa64f46fe..83f86b8b2 100644 --- a/WebDriverAgent.xcodeproj/project.pbxproj +++ b/WebDriverAgent.xcodeproj/project.pbxproj @@ -529,6 +529,7 @@ 71F5BE50252F14EB00EE9EBA /* FBExceptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 71F5BE4D252F14EB00EE9EBA /* FBExceptions.h */; }; 71F5BE51252F14EB00EE9EBA /* FBExceptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 71F5BE4E252F14EB00EE9EBA /* FBExceptions.m */; }; 71F5BE52252F14EB00EE9EBA /* FBExceptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 71F5BE4E252F14EB00EE9EBA /* FBExceptions.m */; }; + AABBCCDDEEFF001122334457 /* SceneDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = AABBCCDDEEFF001122334456 /* SceneDelegate.m */; }; AD35D06C1CF1C35500870A75 /* WebDriverAgentLib.framework in Copy frameworks */ = {isa = PBXBuildFile; fileRef = EE158A991CBD452B00A3E3F0 /* WebDriverAgentLib.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; AD6C26941CF2379700F8B5FF /* FBAlert.h in Headers */ = {isa = PBXBuildFile; fileRef = AD6C26921CF2379700F8B5FF /* FBAlert.h */; settings = {ATTRIBUTES = (Public, ); }; }; AD6C26951CF2379700F8B5FF /* FBAlert.m in Sources */ = {isa = PBXBuildFile; fileRef = AD6C26931CF2379700F8B5FF /* FBAlert.m */; }; @@ -799,7 +800,6 @@ EE9B76591CF7987800275851 /* FBRouteTests.m in Sources */ = {isa = PBXBuildFile; fileRef = EE9B76571CF7987300275851 /* FBRouteTests.m */; }; EE9B768E1CF7997600275851 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = EE9B76831CF7997600275851 /* AppDelegate.m */; }; EE9B768F1CF7997600275851 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = EE9B76851CF7997600275851 /* ViewController.m */; }; - AABBCCDDEEFF001122334457 /* SceneDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = AABBCCDDEEFF001122334456 /* SceneDelegate.m */; }; EE9B76911CF7997600275851 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = EE9B76871CF7997600275851 /* main.m */; }; EE9B76941CF7997600275851 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = EE9B768C1CF7997600275851 /* Main.storyboard */; }; EE9B769A1CF799F400275851 /* FBAlertTests.m in Sources */ = {isa = PBXBuildFile; fileRef = EE9B76991CF799F400275851 /* FBAlertTests.m */; }; @@ -822,6 +822,10 @@ EEE3764A1D59FAE900ED88DD /* XCUIElement+FBWebDriverAttributes.m in Sources */ = {isa = PBXBuildFile; fileRef = EEE376481D59FAE900ED88DD /* XCUIElement+FBWebDriverAttributes.m */; }; EEE9B4721CD02B88009D2030 /* FBRunLoopSpinner.h in Headers */ = {isa = PBXBuildFile; fileRef = EEE9B4701CD02B88009D2030 /* FBRunLoopSpinner.h */; settings = {ATTRIBUTES = (Public, ); }; }; EEE9B4731CD02B88009D2030 /* FBRunLoopSpinner.m in Sources */ = {isa = PBXBuildFile; fileRef = EEE9B4711CD02B88009D2030 /* FBRunLoopSpinner.m */; }; + F59CD6D42EF16E5E00F91287 /* XCUIElement+FBCustomActions.h in Headers */ = {isa = PBXBuildFile; fileRef = F59CD6D22EF16E5E00F91287 /* XCUIElement+FBCustomActions.h */; }; + F59CD6D52EF16E5E00F91287 /* XCUIElement+FBCustomActions.m in Sources */ = {isa = PBXBuildFile; fileRef = F59CD6D32EF16E5E00F91287 /* XCUIElement+FBCustomActions.m */; }; + F59CD6D62EF16E5E00F91287 /* XCUIElement+FBCustomActions.h in Headers */ = {isa = PBXBuildFile; fileRef = F59CD6D22EF16E5E00F91287 /* XCUIElement+FBCustomActions.h */; }; + F59CD6D72EF16E5E00F91287 /* XCUIElement+FBCustomActions.m in Sources */ = {isa = PBXBuildFile; fileRef = F59CD6D32EF16E5E00F91287 /* XCUIElement+FBCustomActions.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -1110,6 +1114,8 @@ 71F5BE33252E5B2200EE9EBA /* FBElementSwipingTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FBElementSwipingTests.m; sourceTree = ""; }; 71F5BE4D252F14EB00EE9EBA /* FBExceptions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FBExceptions.h; sourceTree = ""; }; 71F5BE4E252F14EB00EE9EBA /* FBExceptions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FBExceptions.m; sourceTree = ""; }; + AABBCCDDEEFF001122334455 /* SceneDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SceneDelegate.h; sourceTree = ""; }; + AABBCCDDEEFF001122334456 /* SceneDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SceneDelegate.m; sourceTree = ""; }; AD42DD2A1CF121E600806E5D /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; AD6C26921CF2379700F8B5FF /* FBAlert.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FBAlert.h; path = WebDriverAgentLib/FBAlert.h; sourceTree = SOURCE_ROOT; }; AD6C26931CF2379700F8B5FF /* FBAlert.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; name = FBAlert.m; path = WebDriverAgentLib/FBAlert.m; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; @@ -1369,8 +1375,6 @@ EE9B76581CF7987300275851 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; EE9B76821CF7997600275851 /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; EE9B76831CF7997600275851 /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; - AABBCCDDEEFF001122334455 /* SceneDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SceneDelegate.h; sourceTree = ""; }; - AABBCCDDEEFF001122334456 /* SceneDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SceneDelegate.m; sourceTree = ""; }; EE9B76841CF7997600275851 /* ViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; EE9B76851CF7997600275851 /* ViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; EE9B76861CF7997600275851 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -1404,6 +1408,8 @@ EEE9B4701CD02B88009D2030 /* FBRunLoopSpinner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBRunLoopSpinner.h; sourceTree = ""; }; EEE9B4711CD02B88009D2030 /* FBRunLoopSpinner.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBRunLoopSpinner.m; sourceTree = ""; }; EEF9882A1C486603005CA669 /* WebDriverAgentRunner.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = WebDriverAgentRunner.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + F59CD6D22EF16E5E00F91287 /* XCUIElement+FBCustomActions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "XCUIElement+FBCustomActions.h"; sourceTree = ""; }; + F59CD6D32EF16E5E00F91287 /* XCUIElement+FBCustomActions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "XCUIElement+FBCustomActions.m"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -1820,6 +1826,8 @@ 71E75E6C254824230099FC87 /* XCUIElementQuery+FBHelpers.m */, 13DE7A59287CA444003243C6 /* FBXCElementSnapshotWrapper+Helpers.h */, 13DE7A5A287CA444003243C6 /* FBXCElementSnapshotWrapper+Helpers.m */, + F59CD6D22EF16E5E00F91287 /* XCUIElement+FBCustomActions.h */, + F59CD6D32EF16E5E00F91287 /* XCUIElement+FBCustomActions.m */, ); name = Categories; path = WebDriverAgentLib/Categories; @@ -2521,6 +2529,7 @@ 641EE6E42240C5CA00173FCB /* XCKeyboardLayout.h in Headers */, 641EE6E52240C5CA00173FCB /* XCTAsyncActivity-Protocol.h in Headers */, 641EE6E62240C5CA00173FCB /* XCActivityRecord.h in Headers */, + F59CD6D62EF16E5E00F91287 /* XCUIElement+FBCustomActions.h in Headers */, 71822765258744C700661B83 /* HTTPDataResponse.h in Headers */, 641EE6E72240C5CA00173FCB /* XCUIElement+FBFind.h in Headers */, 641EE6E82240C5CA00173FCB /* XCTestManager_ManagerInterface-Protocol.h in Headers */, @@ -2556,6 +2565,7 @@ 719CD8F82126C78F00C7D0C2 /* FBAlertsMonitor.h in Headers */, EE158AE41CBD456F00A3E3F0 /* FBSession.h in Headers */, 13DE7A55287CA1EC003243C6 /* FBXCElementSnapshotWrapper.h in Headers */, + F59CD6D42EF16E5E00F91287 /* XCUIElement+FBCustomActions.h in Headers */, EE35AD0F1E3B77D600A02D78 /* _XCTestImplementation.h in Headers */, 71241D7B1FAE3D2500B9559F /* FBTouchActionCommands.h in Headers */, EE158ACA1CBD456F00A3E3F0 /* FBTouchIDCommands.h in Headers */, @@ -3117,6 +3127,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + F59CD6D72EF16E5E00F91287 /* XCUIElement+FBCustomActions.m in Sources */, 64E3502E2AC0B6EB005F3ACB /* NSDictionary+FBUtf8SafeDictionary.m in Sources */, 718226CF2587443700661B83 /* GCDAsyncSocket.m in Sources */, E444DCBC24917A5E0060D7EB /* HTTPResponseProxy.m in Sources */, @@ -3323,6 +3334,7 @@ 71D475C42538F5A8008D9401 /* XCUIApplicationProcess+FBQuiescence.m in Sources */, 641EE7082240CDEB00173FCB /* XCUIElement+FBTVFocuse.m in Sources */, 71E75E6F254824230099FC87 /* XCUIElementQuery+FBHelpers.m in Sources */, + F59CD6D52EF16E5E00F91287 /* XCUIElement+FBCustomActions.m in Sources */, 71D04DCA25356C43008A052C /* XCUIElement+FBCaching.m in Sources */, EE158AEB1CBD456F00A3E3F0 /* FBRuntimeUtils.m in Sources */, EEE376461D59F81400ED88DD /* XCUIElement+FBUtilities.m in Sources */, diff --git a/WebDriverAgentLib/Categories/XCUIApplication+FBHelpers.m b/WebDriverAgentLib/Categories/XCUIApplication+FBHelpers.m index e5abde905..bda8a8c5f 100644 --- a/WebDriverAgentLib/Categories/XCUIApplication+FBHelpers.m +++ b/WebDriverAgentLib/Categories/XCUIApplication+FBHelpers.m @@ -204,6 +204,7 @@ + (NSDictionary *)dictionaryForElement:(id)snapshot info[@"value"] = FBValueOrNull(wrappedSnapshot.wdValue); info[@"label"] = FBValueOrNull(wrappedSnapshot.wdLabel); info[@"rect"] = wrappedSnapshot.wdRect; + info[@"customActions"] = FBValueOrNull(wrappedSnapshot.wdCustomActions); NSDictionary *attributeBlocks = [self fb_attributeBlockMapForWrappedSnapshot:wrappedSnapshot]; diff --git a/WebDriverAgentLib/Categories/XCUIElement+FBCustomActions.h b/WebDriverAgentLib/Categories/XCUIElement+FBCustomActions.h new file mode 100644 index 000000000..b8d32ee82 --- /dev/null +++ b/WebDriverAgentLib/Categories/XCUIElement+FBCustomActions.h @@ -0,0 +1,27 @@ +/** + * 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 + +NS_ASSUME_NONNULL_BEGIN + +@interface XCUIElement (FBCustomActions) + +/*! Custom accessibility actions as a string – may be nil if the element does not have this attribute */ +@property (nonatomic, readonly, nullable) NSString *fb_customActions; + +@end + +@interface FBXCElementSnapshotWrapper (FBCustomActions) + +/*! Custom accessibility actions as a string – may be nil if the element does not have this attribute */ +@property (nonatomic, readonly, nullable) NSString *fb_customActions; + +@end + +NS_ASSUME_NONNULL_END diff --git a/WebDriverAgentLib/Categories/XCUIElement+FBCustomActions.m b/WebDriverAgentLib/Categories/XCUIElement+FBCustomActions.m new file mode 100644 index 000000000..dbdcb6464 --- /dev/null +++ b/WebDriverAgentLib/Categories/XCUIElement+FBCustomActions.m @@ -0,0 +1,127 @@ +/** + * 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 "XCUIElement+FBCustomActions.h" +#import "XCUIElement+FBUtilities.h" +#import "FBXCElementSnapshotWrapper+Helpers.h" +#import "FBLogger.h" +#import "FBConfiguration.h" +#import "XCTestPrivateSymbols.h" + +@interface FBXCElementSnapshotWrapper (FBCustomActionsInternal) + +- (NSString *)fb_stringAttribute:(NSString *)attributeName + symbol:(NSNumber *)symbol; + +@end + +@implementation XCUIElement (FBCustomActions) + +- (NSString *)fb_customActions +{ + @autoreleasepool { + id snapshot = [self fb_standardSnapshot]; + return [[FBXCElementSnapshotWrapper ensureWrapped:snapshot] + fb_customActions]; + } +} + +@end + + +@implementation FBXCElementSnapshotWrapper(FBCustomActions) + +- (NSString *)fb_customActions +{ + return [self fb_stringAttribute:FB_XCAXACustomActionsAttributeName + symbol:FB_XCAXACustomActionsAttribute]; +} + +- (NSString *)fb_stringAttribute:(NSString *)attributeName + symbol:(NSNumber *)symbol +{ + id cached = (self.snapshot.additionalAttributes ?: @{})[symbol]; + if ([cached isKindOfClass:[NSString class]]) { + return cached; + } + + NSError *error = nil; + id raw = [self fb_attributeValue:attributeName error:&error]; + if (raw == nil) { + [FBLogger logFmt: @"[FBCustomActions] Cannot determine string value for %@: %@", + attributeName, error.localizedDescription]; + return nil; + } + + // Case 1: Already a string + if ([raw isKindOfClass:[NSString class]]) { + return [self retrieveCustomActionsFromString:raw forSymbol:symbol]; + } + + // Case 2: Array of custom actions + if ([raw isKindOfClass:[NSArray class]]) { + return [self retrieveCustomActionsFromArray:raw forSymbol:symbol]; + } + + // Fallback: Try to cast to string + return [self retrieveCustomActionsByCastingToString:raw forSymbol:symbol]; +} + +- (NSString *)retrieveCustomActionsFromString:(NSString *)stringValue + forSymbol:(NSNumber *)symbol +{ + NSMutableDictionary *updated = + (self.additionalAttributes ?: @{}).mutableCopy; + updated[symbol] = stringValue; + self.snapshot.additionalAttributes = updated.copy; + return stringValue; +} + +- (NSString *)retrieveCustomActionsFromArray:(NSArray *)arrayValue + forSymbol:(NSNumber *)symbol +{ + NSMutableArray *stringified = [NSMutableArray array]; + for (id action in arrayValue) { + NSString *title = nil; + + if ([action isKindOfClass:[NSDictionary class]]) { + title = ((NSDictionary *)action)[@"CustomActionName"]; + } + + if (!title || ![title isKindOfClass:[NSString class]]) { + @try { + title = [action valueForKey:@"title"]; + } @catch (__unused NSException * e) { + title = @""; + } + } + + [stringified addObject:title ?: @""]; + [FBLogger logFmt: @"[FBCustomActions] Custom action title: %@", title]; + } + + NSString *joined = [stringified componentsJoinedByString:@","]; + NSMutableDictionary *updated = + (self.additionalAttributes ?: @{}).mutableCopy; + updated[symbol] = joined; + self.snapshot.additionalAttributes = updated.copy; + return joined; +} + +- (NSString *)retrieveCustomActionsByCastingToString:(id)raw + forSymbol:(NSNumber *)symbol +{ + NSString *stringValue = [NSString stringWithFormat:@"%@", raw]; + NSMutableDictionary *updated = + (self.additionalAttributes ?: @{}).mutableCopy; + updated[symbol] = stringValue; + self.snapshot.additionalAttributes = updated.copy; + return stringValue; +} + +@end diff --git a/WebDriverAgentLib/Categories/XCUIElement+FBWebDriverAttributes.m b/WebDriverAgentLib/Categories/XCUIElement+FBWebDriverAttributes.m index 107167a93..a80db0218 100644 --- a/WebDriverAgentLib/Categories/XCUIElement+FBWebDriverAttributes.m +++ b/WebDriverAgentLib/Categories/XCUIElement+FBWebDriverAttributes.m @@ -24,6 +24,7 @@ #import "XCUIHitPointResult.h" #import "FBAccessibilityTraits.h" #import "XCUIElement+FBMinMax.h" +#import "XCUIElement+FBCustomActions.h" #define BROKEN_RECT CGRectMake(-1, -1, 0, 0) @@ -285,4 +286,9 @@ - (NSDictionary *)wdRect }; } +- (NSString *)wdCustomActions +{ + return self.fb_customActions; +} + @end diff --git a/WebDriverAgentLib/Commands/FBSessionCommands.m b/WebDriverAgentLib/Commands/FBSessionCommands.m index cee0234ab..86535eb7f 100644 --- a/WebDriverAgentLib/Commands/FBSessionCommands.m +++ b/WebDriverAgentLib/Commands/FBSessionCommands.m @@ -355,6 +355,7 @@ + (NSArray *)routes FB_SETTING_INCLUDE_HITTABLE_IN_PAGE_SOURCE: @([FBConfiguration includeHittableInPageSource]), FB_SETTING_INCLUDE_NATIVE_FRAME_IN_PAGE_SOURCE: @([FBConfiguration includeNativeFrameInPageSource]), FB_SETTING_INCLUDE_MIN_MAX_VALUE_IN_PAGE_SOURCE: @([FBConfiguration includeMinMaxValueInPageSource]), + FB_SETTING_INCLUDE_CUSTOM_ACTIONS_IN_PAGE_SOURCE: @([FBConfiguration includeCustomActionsInPageSource]), FB_SETTING_ENFORCE_CUSTOM_SNAPSHOTS: @([FBConfiguration enforceCustomSnapshots]), FB_SETTING_LIMIT_XPATH_CONTEXT_SCOPE: @([FBConfiguration limitXpathContextScope]), #if !TARGET_OS_TV @@ -467,6 +468,9 @@ + (NSArray *)routes if (nil != [settings objectForKey:FB_SETTING_INCLUDE_MIN_MAX_VALUE_IN_PAGE_SOURCE]) { [FBConfiguration setIncludeMinMaxValueInPageSource:[[settings objectForKey:FB_SETTING_INCLUDE_MIN_MAX_VALUE_IN_PAGE_SOURCE] boolValue]]; } + if (nil != [settings objectForKey:FB_SETTING_INCLUDE_CUSTOM_ACTIONS_IN_PAGE_SOURCE]) { + [FBConfiguration setIncludeCustomActionsInPageSource:[[settings objectForKey:FB_SETTING_INCLUDE_CUSTOM_ACTIONS_IN_PAGE_SOURCE] boolValue]]; + } if (nil != [settings objectForKey:FB_SETTING_ENFORCE_CUSTOM_SNAPSHOTS]) { [FBConfiguration setEnforceCustomSnapshots:[[settings objectForKey:FB_SETTING_ENFORCE_CUSTOM_SNAPSHOTS] boolValue]]; } diff --git a/WebDriverAgentLib/Routing/FBElement.h b/WebDriverAgentLib/Routing/FBElement.h index b4e6a7522..2df9c6644 100644 --- a/WebDriverAgentLib/Routing/FBElement.h +++ b/WebDriverAgentLib/Routing/FBElement.h @@ -76,6 +76,9 @@ NS_ASSUME_NONNULL_BEGIN /*! Element's maximum value */ @property (nonatomic, readonly, strong, nullable) NSNumber *wdMaxValue; +/*! Element's custom actions */ +@property (nonatomic, readonly, strong, nullable) NSString *wdCustomActions; + /** Returns value of given property specified in WebDriver Spec Check the FBElement protocol to get list of supported attributes. diff --git a/WebDriverAgentLib/Utilities/FBConfiguration.h b/WebDriverAgentLib/Utilities/FBConfiguration.h index d121f6da9..20aa0a9d5 100644 --- a/WebDriverAgentLib/Utilities/FBConfiguration.h +++ b/WebDriverAgentLib/Utilities/FBConfiguration.h @@ -376,6 +376,17 @@ typedef NS_ENUM(NSInteger, FBConfigurationKeyboardPreference) { + (void)setIncludeMinMaxValueInPageSource:(BOOL)enabled; + (BOOL)includeMinMaxValueInPageSource; +/** + * Whether to include `customActions` attribute in the XML page source. + * Custom actions represent accessibility actions available on UI elements. + * This may affect performance if used on many elements. + * Disabled by default. + * + * @param enabled Either YES or NO + */ ++ (void)setIncludeCustomActionsInPageSource:(BOOL)enabled; ++ (BOOL)includeCustomActionsInPageSource; + /** * Whether to enforce the use of custom snapshots instead of standard snapshots. * When enabled, fb_customSnapshot is always invoked instead of fb_standardSnapshot diff --git a/WebDriverAgentLib/Utilities/FBConfiguration.m b/WebDriverAgentLib/Utilities/FBConfiguration.m index 1110b1fd4..1bae66e85 100644 --- a/WebDriverAgentLib/Utilities/FBConfiguration.m +++ b/WebDriverAgentLib/Utilities/FBConfiguration.m @@ -63,6 +63,7 @@ static BOOL FBShouldIncludeHittableInPageSource = NO; static BOOL FBShouldIncludeNativeFrameInPageSource = NO; static BOOL FBShouldIncludeMinMaxValueInPageSource = NO; +static BOOL FBShouldIncludeCustomActionsInPageSource = NO; static BOOL FBShouldEnforceCustomSnapshots = NO; @implementation FBConfiguration @@ -687,6 +688,16 @@ + (BOOL)includeMinMaxValueInPageSource return FBShouldIncludeMinMaxValueInPageSource; } ++ (void)setIncludeCustomActionsInPageSource:(BOOL)enabled +{ + FBShouldIncludeCustomActionsInPageSource = enabled; +} + ++ (BOOL)includeCustomActionsInPageSource +{ + return FBShouldIncludeCustomActionsInPageSource; +} + + (void)setEnforceCustomSnapshots:(BOOL)enabled { FBShouldEnforceCustomSnapshots = enabled; diff --git a/WebDriverAgentLib/Utilities/FBSettings.h b/WebDriverAgentLib/Utilities/FBSettings.h index f995895b2..afd37df97 100644 --- a/WebDriverAgentLib/Utilities/FBSettings.h +++ b/WebDriverAgentLib/Utilities/FBSettings.h @@ -42,6 +42,7 @@ extern NSString* const FB_SETTING_AUTO_CLICK_ALERT_SELECTOR; extern NSString *const FB_SETTING_INCLUDE_HITTABLE_IN_PAGE_SOURCE; extern NSString *const FB_SETTING_INCLUDE_NATIVE_FRAME_IN_PAGE_SOURCE; extern NSString *const FB_SETTING_INCLUDE_MIN_MAX_VALUE_IN_PAGE_SOURCE; +extern NSString *const FB_SETTING_INCLUDE_CUSTOM_ACTIONS_IN_PAGE_SOURCE; extern NSString *const FB_SETTING_ENFORCE_CUSTOM_SNAPSHOTS; NS_ASSUME_NONNULL_END diff --git a/WebDriverAgentLib/Utilities/FBSettings.m b/WebDriverAgentLib/Utilities/FBSettings.m index 5784ff950..89f77a69c 100644 --- a/WebDriverAgentLib/Utilities/FBSettings.m +++ b/WebDriverAgentLib/Utilities/FBSettings.m @@ -38,4 +38,5 @@ NSString* const FB_SETTING_INCLUDE_HITTABLE_IN_PAGE_SOURCE = @"includeHittableInPageSource"; NSString* const FB_SETTING_INCLUDE_NATIVE_FRAME_IN_PAGE_SOURCE = @"includeNativeFrameInPageSource"; NSString* const FB_SETTING_INCLUDE_MIN_MAX_VALUE_IN_PAGE_SOURCE = @"includeMinMaxValueInPageSource"; +NSString* const FB_SETTING_INCLUDE_CUSTOM_ACTIONS_IN_PAGE_SOURCE = @"includeCustomActionsInPageSource"; NSString* const FB_SETTING_ENFORCE_CUSTOM_SNAPSHOTS = @"enforceCustomSnapshots"; diff --git a/WebDriverAgentLib/Utilities/FBXPath.m b/WebDriverAgentLib/Utilities/FBXPath.m index 1e4ba4cc0..68db53ff2 100644 --- a/WebDriverAgentLib/Utilities/FBXPath.m +++ b/WebDriverAgentLib/Utilities/FBXPath.m @@ -131,6 +131,10 @@ @interface FBMaxValueAttribute : FBElementAttribute @end +@interface FBCustomActionsAttribute : FBElementAttribute + +@end + #if TARGET_OS_TV @interface FBFocusedAttribute : FBElementAttribute @@ -397,6 +401,10 @@ + (int)xmlRepresentationWithRootElement:(id)root [includedAttributes removeObject:FBMinValueAttribute.class]; [includedAttributes removeObject:FBMaxValueAttribute.class]; } + if (!FBConfiguration.includeCustomActionsInPageSource) { + // customActions are retrieved from accessibility attributes and may be slow on deep trees + [includedAttributes removeObject:FBCustomActionsAttribute.class]; + } if (nil != excludedAttributes) { for (NSString *excludedAttributeName in excludedAttributes) { for (Class supportedAttribute in FBElementAttribute.supportedAttributes) { @@ -660,6 +668,7 @@ + (int)recordWithWriter:(xmlTextWriterPtr)writer forValue:(nullable NSString *)v FBNativeFrameAttribute.class, FBMinValueAttribute.class, FBMaxValueAttribute.class, + FBCustomActionsAttribute.class, ]; } @@ -954,3 +963,17 @@ + (NSString *)valueForElement:(id)element } @end + +@implementation FBCustomActionsAttribute + ++ (NSString *)name +{ + return @"customActions"; +} + ++ (NSString *)valueForElement:(id)element +{ + return element.wdCustomActions; +} + +@end diff --git a/WebDriverAgentLib/Utilities/XCTestPrivateSymbols.h b/WebDriverAgentLib/Utilities/XCTestPrivateSymbols.h index 853ba414d..39cdf6b76 100644 --- a/WebDriverAgentLib/Utilities/XCTestPrivateSymbols.h +++ b/WebDriverAgentLib/Utilities/XCTestPrivateSymbols.h @@ -29,6 +29,10 @@ extern NSString *FB_XCAXACustomMinValueAttributeName; extern NSNumber *FB_XCAXACustomMaxValueAttribute; extern NSString *FB_XCAXACustomMaxValueAttributeName; +/*! Accessibility identifier for custom actions attribute */ +extern NSNumber *FB_XCAXACustomActionsAttribute; +extern NSString *FB_XCAXACustomActionsAttributeName; + /*! Getter for XCTest logger */ extern id (*XCDebugLogger)(void); diff --git a/WebDriverAgentLib/Utilities/XCTestPrivateSymbols.m b/WebDriverAgentLib/Utilities/XCTestPrivateSymbols.m index 244e3fdbf..21a9ba300 100644 --- a/WebDriverAgentLib/Utilities/XCTestPrivateSymbols.m +++ b/WebDriverAgentLib/Utilities/XCTestPrivateSymbols.m @@ -22,6 +22,8 @@ NSString *FB_XCAXACustomMinValueAttributeName = @"XC_kAXXCAttributeMinValue"; NSNumber *FB_XCAXACustomMaxValueAttribute; NSString *FB_XCAXACustomMaxValueAttributeName = @"XC_kAXXCAttributeMaxValue"; +NSNumber *FB_XCAXACustomActionsAttribute; +NSString *FB_XCAXACustomActionsAttributeName = @"XC_kAXXCAttributeCustomActions"; void (*XCSetDebugLogger)(id ); id (*XCDebugLogger)(void); @@ -49,12 +51,20 @@ NSString *XC_kAXXCAttributeMinValue = *(NSString *__autoreleasing *)FBRetrieveXCTestSymbol([FB_XCAXACustomMinValueAttributeName UTF8String]); NSString *XC_kAXXCAttributeMaxValue = *(NSString *__autoreleasing *)FBRetrieveXCTestSymbol([FB_XCAXACustomMaxValueAttributeName UTF8String]); - NSArray *minMaxAttrs = XCAXAccessibilityAttributesForStringAttributes(@[XC_kAXXCAttributeMinValue, XC_kAXXCAttributeMaxValue]); - FB_XCAXACustomMinValueAttribute = minMaxAttrs[0]; - FB_XCAXACustomMaxValueAttribute = minMaxAttrs[1]; + NSString *XC_kAXXCAttributeCustomActions = *(NSString *__autoreleasing *)FBRetrieveXCTestSymbol([FB_XCAXACustomActionsAttributeName UTF8String]); + + NSArray *customAttrs = XCAXAccessibilityAttributesForStringAttributes(@[ + XC_kAXXCAttributeMinValue, + XC_kAXXCAttributeMaxValue, + XC_kAXXCAttributeCustomActions + ]); + FB_XCAXACustomMinValueAttribute = customAttrs[0]; + FB_XCAXACustomMaxValueAttribute = customAttrs[1]; + FB_XCAXACustomActionsAttribute = customAttrs[2]; NSCAssert(FB_XCAXACustomMinValueAttribute != nil, @"Failed to retrieve FB_XCAXACustomMinValueAttribute", FB_XCAXACustomMinValueAttribute); NSCAssert(FB_XCAXACustomMaxValueAttribute != nil, @"Failed to retrieve FB_XCAXACustomMaxValueAttribute", FB_XCAXACustomMaxValueAttribute); + NSCAssert(FB_XCAXACustomActionsAttribute != nil, @"Failed to retrieve FB_XCAXACustomActionsAttribute", FB_XCAXACustomActionsAttribute); } void *FBRetrieveXCTestSymbol(const char *name) @@ -89,7 +99,8 @@ FB_XCAXAIsVisibleAttributeName, FB_XCAXAIsElementAttributeName, FB_XCAXACustomMinValueAttributeName, - FB_XCAXACustomMaxValueAttributeName + FB_XCAXACustomMaxValueAttributeName, + FB_XCAXACustomActionsAttributeName ]; }); return customNames; diff --git a/WebDriverAgentTests/IntegrationApp/Classes/ViewController.m b/WebDriverAgentTests/IntegrationApp/Classes/ViewController.m index 1267c28be..c4981acfc 100644 --- a/WebDriverAgentTests/IntegrationApp/Classes/ViewController.m +++ b/WebDriverAgentTests/IntegrationApp/Classes/ViewController.m @@ -10,10 +10,32 @@ @interface ViewController () @property (weak, nonatomic) IBOutlet UILabel *orentationLabel; +@property (weak, nonatomic) IBOutlet UIButton *button; @end @implementation ViewController +- (void)viewDidLoad +{ + [super viewDidLoad]; + + UIAccessibilityCustomAction *action1 = + [[UIAccessibilityCustomAction alloc] initWithName:@"Custom Action 1" + target:self + selector:@selector(handleCustomAction:)]; + UIAccessibilityCustomAction *action2 = + [[UIAccessibilityCustomAction alloc] initWithName:@"Custom Action 2" + target:self + selector:@selector(handleCustomAction:)]; + self.button.accessibilityCustomActions = @[action1, action2]; +} + +- (BOOL)handleCustomAction:(UIAccessibilityCustomAction *)action +{ + // Custom action handler - just return YES to indicate success + return YES; +} + - (IBAction)deadlockApp:(id)sender { dispatch_sync(dispatch_get_main_queue(), ^{ diff --git a/WebDriverAgentTests/IntegrationApp/Resources/Base.lproj/Main.storyboard b/WebDriverAgentTests/IntegrationApp/Resources/Base.lproj/Main.storyboard index 5fb805f28..40f6efb80 100644 --- a/WebDriverAgentTests/IntegrationApp/Resources/Base.lproj/Main.storyboard +++ b/WebDriverAgentTests/IntegrationApp/Resources/Base.lproj/Main.storyboard @@ -1,9 +1,9 @@ - + - + @@ -21,7 +21,7 @@ - + @@ -240,32 +240,32 @@ - + - + - + - + - + - + @@ -349,6 +349,9 @@ + + + @@ -367,7 +370,7 @@ - + @@ -377,7 +380,7 @@