Skip to content

Commit

Permalink
feat: Refactor snapshotting mechanism (#970)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: snapshotTimeout and customSnapshotTimeout settings have been removed as a result of the custom snapshotting logic removal
  • Loading branch information
mykola-mokhnach authored Jan 16, 2025
1 parent eca2ed1 commit 08f1306
Show file tree
Hide file tree
Showing 53 changed files with 350 additions and 621 deletions.
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,11 @@ 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 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
error:(NSError **)error;

/**
Method used to determine whether given element matches receiver by comparing it's parameters except frame.
Expand All @@ -87,13 +89,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
38 changes: 13 additions & 25 deletions WebDriverAgentLib/Categories/FBXCElementSnapshotWrapper+Helpers.m
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,20 @@
#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"
#import "XCUIElement.h"
#import "XCUIElement+FBWebDriverAttributes.h"
#import "XCUIHitPointResult.h"

#define ATTRIBUTE_FETCH_WARN_TIME_LIMIT 0.05

inline static BOOL isSnapshotTypeAmongstGivenTypes(id<FBXCElementSnapshot> snapshot,
NSArray<NSNumber *> *types);

Expand Down Expand Up @@ -65,10 +69,17 @@ - (NSString *)fb_description
}

- (id)fb_attributeValue:(NSString *)attribute
error:(NSError **)error
{
NSDate *start = [NSDate date];
NSDictionary *result = [FBXCAXClientProxy.sharedClient attributesForElement:[self accessibilityElement]
attributes:@[attribute]];
return result[attribute];
attributes:@[attribute]
error:error];
NSTimeInterval elapsed = ABS([start timeIntervalSinceNow]);
if (elapsed > ATTRIBUTE_FETCH_WARN_TIME_LIMIT) {
NSLog(@"! Fetching of %@ value for %@ took %@s", attribute, self.fb_description, @(elapsed));
}
return [result objectForKey:attribute];
}

inline static BOOL areValuesEqual(id value1, id value2);
Expand Down Expand Up @@ -140,29 +151,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
19 changes: 15 additions & 4 deletions WebDriverAgentLib/Categories/XCUIElement+FBAccessibility.m
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ @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 +32,20 @@ - (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
error:&error];
if (nil != attributeValue) {
NSMutableDictionary *updatedValue = [NSMutableDictionary dictionaryWithDictionary:self.additionalAttributes ?: @{}];
[updatedValue setObject:attributeValue forKey:FB_XCAXAIsElementAttribute];
self.snapshot.additionalAttributes = updatedValue.copy;
return [attributeValue boolValue];
}

NSLog(@"Cannot determine accessibility of '%@' 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

0 comments on commit 08f1306

Please sign in to comment.