Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Refactor snapshotting mechanism #970

Merged
merged 11 commits into from
Jan 16, 2025
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
- (void)_XCT_requestElementAtPoint:(CGPoint)arg1 reply:(void (^)(id/*XCAccessibilityElement*/, NSError *))arg2;
- (void)_XCT_fetchParameterizedAttributeForElement:(id/*XCAccessibilityElement*/)arg1 attributes:(NSNumber *)arg2 parameter:(id)arg3 reply:(void (^)(id, NSError *))arg4;
- (void)_XCT_setAttribute:(NSNumber *)arg1 value:(id)arg2 element:(id/*XCAccessibilityElement*/)arg3 reply:(void (^)(BOOL, NSError *))arg4;
- (void)_XCT_fetchAttributes:(id)attributes forElement:(id)element reply:(void (^)(NSDictionary *, NSError *))reply;
- (void)_XCT_fetchAttributesForElement:(id/*XCAccessibilityElement*/)arg1 attributes:(NSArray *)arg2 reply:(void (^)(NSDictionary *, NSError *))arg3;
- (void)_XCT_terminateApplicationWithBundleID:(NSString *)arg1 completion:(void (^)(NSError *))arg2;
- (void)_XCT_performAccessibilityAction:(int)arg1 onElement:(id/*XCAccessibilityElement*/)arg2 withValue:(id)arg3 reply:(void (^)(NSError *))arg4;
Expand Down
12 changes: 12 additions & 0 deletions WebDriverAgent.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,10 @@
71A7EAFA1E224648001DA4F2 /* FBClassChainQueryParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 71A7EAF81E224648001DA4F2 /* FBClassChainQueryParser.m */; };
71A7EAFC1E229302001DA4F2 /* FBClassChainTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 71A7EAFB1E229302001DA4F2 /* FBClassChainTests.m */; };
71ACF5B8242F2FDC00F0AAD4 /* FBSafariAlertTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 71ACF5B7242F2FDC00F0AAD4 /* FBSafariAlertTests.m */; };
71AE3CF72D38EE8E0039FC36 /* XCUIElement+FBVisibleFrame.h in Headers */ = {isa = PBXBuildFile; fileRef = 71AE3CF52D38EE8E0039FC36 /* XCUIElement+FBVisibleFrame.h */; };
71AE3CF82D38EE8E0039FC36 /* XCUIElement+FBVisibleFrame.m in Sources */ = {isa = PBXBuildFile; fileRef = 71AE3CF62D38EE8E0039FC36 /* XCUIElement+FBVisibleFrame.m */; };
71AE3CF92D38EE8E0039FC36 /* XCUIElement+FBVisibleFrame.h in Headers */ = {isa = PBXBuildFile; fileRef = 71AE3CF52D38EE8E0039FC36 /* XCUIElement+FBVisibleFrame.h */; };
71AE3CFA2D38EE8E0039FC36 /* XCUIElement+FBVisibleFrame.m in Sources */ = {isa = PBXBuildFile; fileRef = 71AE3CF62D38EE8E0039FC36 /* XCUIElement+FBVisibleFrame.m */; };
71B155DA23070ECF00646AFB /* FBHTTPStatusCodes.h in Headers */ = {isa = PBXBuildFile; fileRef = 71B155D923070ECF00646AFB /* FBHTTPStatusCodes.h */; settings = {ATTRIBUTES = (Public, ); }; };
71B155DC230711E900646AFB /* FBCommandStatus.m in Sources */ = {isa = PBXBuildFile; fileRef = 71B155DB230711E900646AFB /* FBCommandStatus.m */; };
71B155DF23080CA600646AFB /* FBProtocolHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 71B155DD23080CA600646AFB /* FBProtocolHelpers.h */; };
Expand Down Expand Up @@ -1050,6 +1054,8 @@
71A7EAF81E224648001DA4F2 /* FBClassChainQueryParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBClassChainQueryParser.m; sourceTree = "<group>"; };
71A7EAFB1E229302001DA4F2 /* FBClassChainTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBClassChainTests.m; sourceTree = "<group>"; };
71ACF5B7242F2FDC00F0AAD4 /* FBSafariAlertTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FBSafariAlertTests.m; sourceTree = "<group>"; };
71AE3CF52D38EE8E0039FC36 /* XCUIElement+FBVisibleFrame.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "XCUIElement+FBVisibleFrame.h"; sourceTree = "<group>"; };
71AE3CF62D38EE8E0039FC36 /* XCUIElement+FBVisibleFrame.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "XCUIElement+FBVisibleFrame.m"; sourceTree = "<group>"; };
71B155D923070ECF00646AFB /* FBHTTPStatusCodes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FBHTTPStatusCodes.h; sourceTree = "<group>"; };
71B155DB230711E900646AFB /* FBCommandStatus.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FBCommandStatus.m; sourceTree = "<group>"; };
71B155DD23080CA600646AFB /* FBProtocolHelpers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FBProtocolHelpers.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1781,6 +1787,8 @@
71B49EC61ED1A58100D51AD6 /* XCUIElement+FBUID.m */,
EEE3763F1D59F81400ED88DD /* XCUIElement+FBUtilities.h */,
EEE376401D59F81400ED88DD /* XCUIElement+FBUtilities.m */,
71AE3CF52D38EE8E0039FC36 /* XCUIElement+FBVisibleFrame.h */,
71AE3CF62D38EE8E0039FC36 /* XCUIElement+FBVisibleFrame.m */,
EEE376471D59FAE900ED88DD /* XCUIElement+FBWebDriverAttributes.h */,
EEE376481D59FAE900ED88DD /* XCUIElement+FBWebDriverAttributes.m */,
641EE7042240CDCF00173FCB /* XCUIElement+FBTVFocuse.h */,
Expand Down Expand Up @@ -2399,6 +2407,7 @@
641EE6A42240C5CA00173FCB /* FBCommandHandler.h in Headers */,
641EE6A52240C5CA00173FCB /* FBSessionCommands.h in Headers */,
641EE70C2240CE2D00173FCB /* FBTVNavigationTracker.h in Headers */,
71AE3CF72D38EE8E0039FC36 /* XCUIElement+FBVisibleFrame.h in Headers */,
641EE6A62240C5CA00173FCB /* FBImageProcessor.h in Headers */,
641EE6A72240C5CA00173FCB /* FBSession-Private.h in Headers */,
641EE6A82240C5CA00173FCB /* NSString+FBXMLSafeString.h in Headers */,
Expand Down Expand Up @@ -2585,6 +2594,7 @@
714EAA0D2673FDFE005C5B47 /* FBCapabilities.h in Headers */,
EE35AD5C1E3B77D600A02D78 /* XCTNSPredicateExpectation.h in Headers */,
EE35AD521E3B77D600A02D78 /* XCTestObservationCenter.h in Headers */,
71AE3CF92D38EE8E0039FC36 /* XCUIElement+FBVisibleFrame.h in Headers */,
EE35AD5B1E3B77D600A02D78 /* XCTNSNotificationExpectation.h in Headers */,
E444DC97249131D40060D7EB /* HTTPServer.h in Headers */,
E444DCAE24913C220060D7EB /* HTTPResponseProxy.h in Headers */,
Expand Down Expand Up @@ -3160,6 +3170,7 @@
641EE60E2240C5CA00173FCB /* XCUIElement+FBTyping.m in Sources */,
641EE60F2240C5CA00173FCB /* XCUIElement+FBAccessibility.m in Sources */,
641EE6102240C5CA00173FCB /* FBImageUtils.m in Sources */,
71AE3CF82D38EE8E0039FC36 /* XCUIElement+FBVisibleFrame.m in Sources */,
641EE6112240C5CA00173FCB /* FBSession.m in Sources */,
641EE6122240C5CA00173FCB /* FBFindElementCommands.m in Sources */,
71A5C67629A4F39600421C37 /* XCTIssue+FBPatcher.m in Sources */,
Expand Down Expand Up @@ -3232,6 +3243,7 @@
713AE576243A53BE0000D657 /* FBW3CActionsHelpers.m in Sources */,
71B155E123080CA600646AFB /* FBProtocolHelpers.m in Sources */,
EE158AB11CBD456F00A3E3F0 /* XCUIElement+FBIsVisible.m in Sources */,
71AE3CFA2D38EE8E0039FC36 /* XCUIElement+FBVisibleFrame.m in Sources */,
EEBBD48C1D47746D00656A81 /* XCUIElement+FBFind.m in Sources */,
EE158ADD1CBD456F00A3E3F0 /* FBResponsePayload.m in Sources */,
E444DCB524913C220060D7EB /* RouteRequest.m in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,14 @@ NS_ASSUME_NONNULL_BEGIN

@param attribute attribute's accessibility identifier. Can be one of
`XC_kAXXCAttribute`-prefixed attribute names.
@return value for given accessibility property identifier
@param timeout The maximum time is flota seconds to wait until XCTest/Accessbility framework
returns the value of the requested attribute
@param error Error instance in case of a failure
@return value for given accessibility property identifier or nil in case of failure
*/
- (nullable id)fb_attributeValue:(NSString *)attribute;
- (nullable id)fb_attributeValue:(NSString *)attribute
timeout:(NSTimeInterval)timeout
error:(NSError **)error;

/**
Method used to determine whether given element matches receiver by comparing it's parameters except frame.
Expand All @@ -87,13 +92,6 @@ NS_ASSUME_NONNULL_BEGIN
/**! Human-readable snapshot description */
- (NSString *)fb_description;

/**
Returns the snapshot visibleFrame with a fallback to direct attribute retrieval from FBXCAXClient in case of a snapshot fault (nil visibleFrame)

@return the snapshot visibleFrame
*/
- (CGRect)fb_visibleFrameWithFallback;

/**
Wrapper for Apple's hitpoint, thats resolves few known issues

Expand Down
61 changes: 34 additions & 27 deletions WebDriverAgentLib/Categories/FBXCElementSnapshotWrapper+Helpers.m
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@
#import "FBXCElementSnapshotWrapper+Helpers.h"

#import "FBFindElementCommands.h"
#import "FBErrorBuilder.h"
#import "FBRunLoopSpinner.h"
#import "FBLogger.h"
#import "FBXCElementSnapshot.h"
#import "FBXCTestDaemonsProxy.h"
#import "FBXCAXClientProxy.h"
#import "XCTestDriver.h"
#import "XCTestPrivateSymbols.h"
Expand Down Expand Up @@ -65,10 +67,38 @@ - (NSString *)fb_description
}

- (id)fb_attributeValue:(NSString *)attribute
{
NSDictionary *result = [FBXCAXClientProxy.sharedClient attributesForElement:[self accessibilityElement]
attributes:@[attribute]];
return result[attribute];
timeout:(NSTimeInterval)timeout
error:(NSError **)error
{
id<XCTestManager_ManagerInterface> proxy = [FBXCTestDaemonsProxy testRunnerProxy];
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
__block NSDictionary *result = nil;
__block NSError *blockError;
[proxy _XCT_fetchAttributes:@[attribute]
forElement:[self accessibilityElement]
reply:^(NSDictionary *innerResult, NSError *innerError) {
if (nil == innerError) {
result = innerResult;
} else {
blockError = innerError;
}
dispatch_semaphore_signal(sem);
}];
if (0 != dispatch_semaphore_wait(sem, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC)))) {
NSString *timeoutMsg = [NSString stringWithFormat:@"Cannot fetch %@ attribute of '%@' within %@s timeout",
attribute, self.fb_description, @(timeout)];
[[[FBErrorBuilder builder]
withDescription:timeoutMsg]
buildError:error];
return nil;
}
if (nil != result) {
return [result objectForKey:attribute];
}
if (error) {
*error = blockError;
}
return nil;
}

inline static BOOL areValuesEqual(id value1, id value2);
Expand Down Expand Up @@ -140,29 +170,6 @@ - (BOOL)fb_framelessFuzzyMatchesElement:(id<FBXCElementSnapshot>)snapshot
return targetCellSnapshot;
}

- (CGRect)fb_visibleFrameWithFallback
{
CGRect thisVisibleFrame = [self visibleFrame];
if (!CGRectIsEmpty(thisVisibleFrame)) {
return thisVisibleFrame;
}

NSDictionary *visibleFrameDict = (NSDictionary*)[self fb_attributeValue:@"XC_kAXXCAttributeVisibleFrame"];
if (visibleFrameDict == nil) {
return thisVisibleFrame;
}

id x = [visibleFrameDict objectForKey:@"X"];
id y = [visibleFrameDict objectForKey:@"Y"];
id height = [visibleFrameDict objectForKey:@"Height"];
id width = [visibleFrameDict objectForKey:@"Width"];
if (x != nil && y != nil && height != nil && width != nil) {
return CGRectMake([x doubleValue], [y doubleValue], [width doubleValue], [height doubleValue]);
}

return thisVisibleFrame;
}

- (NSValue *)fb_hitPoint
{
NSError *error;
Expand Down
4 changes: 2 additions & 2 deletions WebDriverAgentLib/Categories/XCUIApplication+FBAlert.m
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ - (nullable XCUIElement *)fb_alertElementFromSafariWithScrollView:(XCUIElement *
// and conatins at least one text view
__block NSUInteger buttonsCount = 0;
__block NSUInteger textViewsCount = 0;
id<FBXCElementSnapshot> snapshot = candidate.fb_cachedSnapshot ?: candidate.fb_takeSnapshot;
id<FBXCElementSnapshot> snapshot = candidate.fb_cachedSnapshot ?: [candidate fb_takeSnapshot:YES];
[snapshot enumerateDescendantsUsingBlock:^(id<FBXCElementSnapshot> descendant) {
XCUIElementType curType = descendant.elementType;
if (curType == XCUIElementTypeButton) {
Expand All @@ -73,7 +73,7 @@ - (XCUIElement *)fb_alertElement
if (nil == alert) {
return nil;
}
id<FBXCElementSnapshot> alertSnapshot = alert.fb_cachedSnapshot ?: alert.fb_takeSnapshot;
id<FBXCElementSnapshot> alertSnapshot = alert.fb_cachedSnapshot ?: [alert fb_takeSnapshot:YES];

if (alertSnapshot.elementType == XCUIElementTypeAlert) {
return alert;
Expand Down
24 changes: 4 additions & 20 deletions WebDriverAgentLib/Categories/XCUIApplication+FBHelpers.m
Original file line number Diff line number Diff line change
Expand Up @@ -176,31 +176,15 @@ - (NSDictionary *)fb_tree

- (NSDictionary *)fb_tree:(nullable NSSet<NSString *> *)excludedAttributes
{
// This set includes XCTest-specific internal attribute names,
// while the `excludedAttributes` arg contains human-readable ones
NSMutableSet* includedAttributeNames = [NSMutableSet setWithArray:FBCustomAttributeNames()];
[includedAttributeNames addObjectsFromArray:FBStandardAttributeNames()];
if (nil != excludedAttributes) {
for (NSString *attr in excludedAttributes) {
NSString *mappedName = [customExclusionAttributesMap() objectForKey:attr];
if (nil != mappedName) {
[includedAttributeNames removeObject:attr];
}
}
}
id<FBXCElementSnapshot> snapshot = nil == excludedAttributes
? [self fb_snapshotWithAllAttributesAndMaxDepth:nil]
: [self fb_snapshotWithAttributes:[includedAttributeNames allObjects] maxDepth:nil];
id<FBXCElementSnapshot> snapshot = [self fb_takeSnapshot:YES];
return [self.class dictionaryForElement:snapshot
recursive:YES
excludedAttributes:excludedAttributes];
}

- (NSDictionary *)fb_accessibilityTree
{
id<FBXCElementSnapshot> snapshot = self.fb_isResolvedFromCache.boolValue
? self.lastSnapshot
: [self fb_snapshotWithAllAttributesAndMaxDepth:nil];
id<FBXCElementSnapshot> snapshot = [self fb_takeSnapshot:YES];
return [self.class accessibilityInfoForElement:snapshot];
}

Expand Down Expand Up @@ -445,8 +429,8 @@ - (BOOL)fb_dismissKeyboardWithKeyNames:(nullable NSArray<NSString *> *)keyNames

id extractedElement = extractIssueProperty(issue, @"element");

id<FBXCElementSnapshot> elementSnapshot = [extractedElement fb_cachedSnapshot] ?: [extractedElement fb_takeSnapshot];
NSDictionary *elementAttributes = elementSnapshot
id<FBXCElementSnapshot> elementSnapshot = [extractedElement fb_cachedSnapshot] ?: [extractedElement fb_takeSnapshot:NO];
NSDictionary *elementAttributes = elementSnapshot
? [self.class dictionaryForElement:elementSnapshot
recursive:NO
excludedAttributes:customAttributesToExclude]
Expand Down
22 changes: 18 additions & 4 deletions WebDriverAgentLib/Categories/XCUIElement+FBAccessibility.m
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@
#import "XCUIElement+FBUtilities.h"
#import "FBXCElementSnapshotWrapper+Helpers.h"

#define AX_FETCH_TIMEOUT 0.3

@implementation XCUIElement (FBAccessibility)

- (BOOL)fb_isAccessibilityElement
{
id<FBXCElementSnapshot> snapshot = [self fb_snapshotWithAttributes:@[FB_XCAXAIsElementAttributeName]
maxDepth:@1];
id<FBXCElementSnapshot> snapshot = [self fb_takeSnapshot:NO];
return [FBXCElementSnapshotWrapper ensureWrapped:snapshot].fb_isAccessibilityElement;
}

Expand All @@ -33,8 +34,21 @@ - (BOOL)fb_isAccessibilityElement
if (nil != isAccessibilityElement) {
return isAccessibilityElement.boolValue;
}

return [(NSNumber *)[self fb_attributeValue:FB_XCAXAIsElementAttributeName] boolValue];

NSError *error;
NSNumber *attributeValue = [self fb_attributeValue:FB_XCAXAIsElementAttributeName
timeout:AX_FETCH_TIMEOUT
error:&error];
if (nil != attributeValue) {
NSMutableDictionary *updatedValue = [NSMutableDictionary dictionaryWithDictionary:self.additionalAttributes ?: @{}];
[updatedValue setObject:attributeValue forKey:FB_XCAXAIsElementAttribute];
self.additionalAttributes = updatedValue.copy;
return [attributeValue boolValue];
}

NSLog(@"Cannot determine '%@' accessibility natively: %@. Defaulting to: %@",
self.fb_description, error.description, @(NO));
return NO;
}

@end
3 changes: 0 additions & 3 deletions WebDriverAgentLib/Categories/XCUIElement+FBCaching.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@ NS_ASSUME_NONNULL_BEGIN

@interface XCUIElement (FBCaching)

/*! This property is set to YES if the given element has been resolved from the cache, so it is safe to use the `lastSnapshot` property */
@property (nullable, nonatomic) NSNumber *fb_isResolvedFromCache;

@property (nonatomic, readonly) NSString *fb_cacheId;

@end
Expand Down
23 changes: 1 addition & 22 deletions WebDriverAgentLib/Categories/XCUIElement+FBCaching.m
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,6 @@

@implementation XCUIElement (FBCaching)

static char XCUIELEMENT_IS_RESOLVED_FROM_CACHE_KEY;

@dynamic fb_isResolvedFromCache;

- (void)setFb_isResolvedFromCache:(NSNumber *)isResolvedFromCache
{
objc_setAssociatedObject(self, &XCUIELEMENT_IS_RESOLVED_FROM_CACHE_KEY, isResolvedFromCache, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSNumber *)fb_isResolvedFromCache
{
return (NSNumber *)objc_getAssociatedObject(self, &XCUIELEMENT_IS_RESOLVED_FROM_CACHE_KEY);
}

static char XCUIELEMENT_CACHE_ID_KEY;

@dynamic fb_cacheId;
Expand All @@ -43,14 +29,7 @@ - (NSString *)fb_cacheId
return (NSString *)result;
}

NSString *uid;
if ([self isKindOfClass:XCUIApplication.class]) {
uid = self.fb_uid;
} else {
id<FBXCElementSnapshot> snapshot = self.fb_cachedSnapshot ?: self.fb_takeSnapshot;
uid = [FBXCElementSnapshotWrapper wdUIDWithSnapshot:snapshot];
}

NSString *uid = self.fb_uid;
objc_setAssociatedObject(self, &XCUIELEMENT_CACHE_ID_KEY, uid, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return uid;
}
Expand Down
2 changes: 1 addition & 1 deletion WebDriverAgentLib/Categories/XCUIElement+FBFind.m
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ @implementation XCUIElement (FBFind)
matchingSnapshots = @[snapshot];
}
return [self fb_filterDescendantsWithSnapshots:matchingSnapshots
selfUID:[FBXCElementSnapshotWrapper wdUIDWithSnapshot:self.lastSnapshot]
selfUID:self.fb_uid
onlyChildren:NO];
}

Expand Down
Loading
Loading