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

chore: Optimize stable instance retrieval #973

Merged
merged 2 commits into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion WebDriverAgentLib/Categories/XCUIElement+FBFind.m
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,6 @@ @implementation XCUIElement (FBFind)
matchingSnapshots = @[snapshot];
}
return [self fb_filterDescendantsWithSnapshots:matchingSnapshots
selfUID:self.fb_uid
onlyChildren:NO];
}

Expand Down
3 changes: 2 additions & 1 deletion WebDriverAgentLib/Categories/XCUIElement+FBPickerWheel.m
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#import "FBRunLoopSpinner.h"
#import "FBXCElementSnapshot.h"
#import "FBXCodeCompatibility.h"
#import "XCUIElement+FBUID.h"
#import "XCUICoordinate.h"
#import "XCUIElement+FBCaching.h"
#import "XCUIElement+FBResolve.h"
Expand All @@ -33,7 +34,7 @@ - (BOOL)fb_scrollWithOffset:(CGFloat)relativeHeightOffset error:(NSError **)erro
// Fetching stable instance of an element allows it to be bounded to the
// unique element identifier (UID), so it could be found next time even if its
// id is different from the initial one. See /~https://github.com/appium/appium/issues/17569
XCUIElement *stableInstance = self.fb_stableInstance;
XCUIElement *stableInstance = [self fb_stableInstanceWithUid:[FBXCElementSnapshotWrapper wdUIDWithSnapshot:snapshot]];
[endCoord tap];
return [[[[FBRunLoopSpinner new]
timeout:VALUE_CHANGE_TIMEOUT]
Expand Down
3 changes: 2 additions & 1 deletion WebDriverAgentLib/Categories/XCUIElement+FBResolve.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@ NS_ASSUME_NONNULL_BEGIN
Although, if the cached element instance is the one returned by this API call then the same element
is going to be matched and no staleness exception will be thrown.

@param uid Element UUID
@return Either the same element instance if `fb_isResolvedNatively` was set to NO (usually the cache for elements
matched by xpath locators) or the stable instance of the self element based on the query by element's UUID.
*/
- (XCUIElement *)fb_stableInstance;
- (XCUIElement *)fb_stableInstanceWithUid:(NSString *)uid;

@end

Expand Down
22 changes: 9 additions & 13 deletions WebDriverAgentLib/Categories/XCUIElement+FBResolve.m
Original file line number Diff line number Diff line change
Expand Up @@ -32,23 +32,19 @@ - (NSNumber *)fb_isResolvedNatively
return nil == result ? @YES : result;
}

- (XCUIElement *)fb_stableInstance
- (XCUIElement *)fb_stableInstanceWithUid:(NSString *)uid
{
if (![self.fb_isResolvedNatively boolValue]) {
if (nil == uid || ![self.fb_isResolvedNatively boolValue] || [self isKindOfClass:XCUIApplication.class]) {
return self;
}

XCUIElementQuery *query = [self isKindOfClass:XCUIApplication.class]
? self.application.fb_query
: [self.application.fb_query descendantsMatchingType:XCUIElementTypeAny];
NSString *uid = nil == self.fb_cachedSnapshot
? self.fb_uid
: [FBXCElementSnapshotWrapper wdUIDWithSnapshot:(id)self.fb_cachedSnapshot];
if (nil == uid) {
return self;
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%K = %@", FBStringify(FBXCElementSnapshotWrapper, fb_uid), uid];
XCUIElementQuery *query = [self.application.fb_query descendantsMatchingType:XCUIElementTypeAny];
XCUIElement *result = [query matchingPredicate:predicate].allElementsBoundByIndex.firstObject;
if (nil != result) {
result.fb_isResolvedNatively = @NO;
return result;
}
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%K = %@",FBStringify(FBXCElementSnapshotWrapper, fb_uid), uid];
return [query matchingPredicate:predicate].allElementsBoundByIndex.firstObject ?: self;
return self;
}

@end
3 changes: 0 additions & 3 deletions WebDriverAgentLib/Categories/XCUIElement+FBUtilities.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,11 @@ NS_ASSUME_NONNULL_BEGIN
Filters elements by matching them to snapshots from the corresponding array

@param snapshots Array of snapshots to be matched with
@param selfUID Optionally the unique identifier of the current element.
Providing it as an argument improves the performance of the method.
@param onlyChildren Whether to only look for direct element children

@return Array of filtered elements, which have matches in snapshots array
*/
- (NSArray<XCUIElement *> *)fb_filterDescendantsWithSnapshots:(NSArray<id<FBXCElementSnapshot>> *)snapshots
selfUID:(nullable NSString *)selfUID
onlyChildren:(BOOL)onlyChildren;

/**
Expand Down
11 changes: 4 additions & 7 deletions WebDriverAgentLib/Categories/XCUIElement+FBUtilities.m
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ @implementation XCUIElement (FBUtilities)
}

- (NSArray<XCUIElement *> *)fb_filterDescendantsWithSnapshots:(NSArray<id<FBXCElementSnapshot>> *)snapshots
selfUID:(NSString *)selfUID
onlyChildren:(BOOL)onlyChildren
{
if (0 == snapshots.count) {
Expand All @@ -85,13 +84,11 @@ @implementation XCUIElement (FBUtilities)
}
}
NSMutableArray<XCUIElement *> *matchedElements = [NSMutableArray array];
NSString *uid = selfUID;
if (nil == uid) {
uid = self.fb_uid;
}
NSString *uid = nil == self.lastSnapshot
? self.fb_uid
: [FBXCElementSnapshotWrapper wdUIDWithSnapshot:self.lastSnapshot];
if (nil != uid && [matchedIds containsObject:uid]) {
XCUIElement *stableSelf = self.fb_stableInstance;
stableSelf.fb_isResolvedNatively = @NO;
XCUIElement *stableSelf = [self fb_stableInstanceWithUid:uid];
if (1 == snapshots.count) {
return @[stableSelf];
}
Expand Down
5 changes: 4 additions & 1 deletion WebDriverAgentLib/Commands/FBElementCommands.m
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#import "XCUIElement+FBWebDriverAttributes.h"
#import "XCUIElement+FBTVFocuse.h"
#import "XCUIElement+FBResolve.h"
#import "XCUIElement+FBUID.h"
#import "FBElementTypeTransformer.h"
#import "XCUIElement.h"
#import "XCUIElementQuery.h"
Expand Down Expand Up @@ -264,7 +265,9 @@ + (NSArray *)routes
if (focusedElement != nil) {
FBElementCache *elementCache = request.session.elementCache;
BOOL useNativeCachingStrategy = request.session.useNativeCachingStrategy;
NSString *focusedUUID = [elementCache storeElement:(useNativeCachingStrategy ? focusedElement : focusedElement.fb_stableInstance)];
NSString *focusedUUID = [elementCache storeElement:(useNativeCachingStrategy
? focusedElement
: [focusedElement fb_stableInstanceWithUid:focusedElement.fb_uid])];
if (focusedUUID && [focusedUUID isEqualToString:(id)request.parameters[@"uuid"]]) {
isFocused = YES;
}
Expand Down
1 change: 0 additions & 1 deletion WebDriverAgentLib/Commands/FBFindElementCommands.m
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ + (NSArray *)routes
&& [FBXCElementSnapshotWrapper ensureWrapped:shot].wdVisible;
}];
NSArray *cells = [element fb_filterDescendantsWithSnapshots:visibleCellSnapshots
selfUID:[FBXCElementSnapshotWrapper wdUIDWithSnapshot:snapshot]
onlyChildren:NO];
return FBResponseWithCachedElements(cells, request.session.elementCache, FBConfiguration.shouldUseCompactResponses);
}
Expand Down
24 changes: 18 additions & 6 deletions WebDriverAgentLib/Routing/FBResponsePayload.m
Original file line number Diff line number Diff line change
Expand Up @@ -34,23 +34,35 @@
return FBResponseWithStatus([FBCommandStatus okWithValue:object]);
}

id<FBResponsePayload> FBResponseWithCachedElement(XCUIElement *element, FBElementCache *elementCache, BOOL compact)
XCUIElement *maybeStable(XCUIElement *element)
{
BOOL useNativeCachingStrategy = nil == FBSession.activeSession
? YES
: FBSession.activeSession.useNativeCachingStrategy;
[elementCache storeElement:(useNativeCachingStrategy ? element : element.fb_stableInstance)];
if (useNativeCachingStrategy) {
return element;
}

XCUIElement *result = element;
id<FBXCElementSnapshot> snapshot = element.lastSnapshot ?: [element fb_cachedSnapshot] ?: [element fb_takeSnapshot:NO];
NSString *uid = [FBXCElementSnapshotWrapper wdUIDWithSnapshot:snapshot];
if (nil != uid) {
result = [element fb_stableInstanceWithUid:uid];
}
return result;
}

id<FBResponsePayload> FBResponseWithCachedElement(XCUIElement *element, FBElementCache *elementCache, BOOL compact)
{
[elementCache storeElement:maybeStable(element)];
return FBResponseWithStatus([FBCommandStatus okWithValue:FBDictionaryResponseWithElement(element, compact)]);
}

id<FBResponsePayload> FBResponseWithCachedElements(NSArray<XCUIElement *> *elements, FBElementCache *elementCache, BOOL compact)
{
NSMutableArray *elementsResponse = [NSMutableArray array];
BOOL useNativeCachingStrategy = nil == FBSession.activeSession
? YES
: FBSession.activeSession.useNativeCachingStrategy;
for (XCUIElement *element in elements) {
[elementCache storeElement:(useNativeCachingStrategy ? element : element.fb_stableInstance)];
[elementCache storeElement:maybeStable(element)];
[elementsResponse addObject:FBDictionaryResponseWithElement(element, compact)];
}
return FBResponseWithStatus([FBCommandStatus okWithValue:elementsResponse]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,7 @@ - (void)setUp
- (id<FBXCElementSnapshot>)destinationSnapshot
{
XCUIElement *matchingElement = self.testedView.buttons.allElementsBoundByIndex.firstObject;
FBAssertWaitTillBecomesTrue(nil != [matchingElement fb_takeSnapshot:YES]);

id<FBXCElementSnapshot> snapshot = matchingElement.lastSnapshot;
id<FBXCElementSnapshot> snapshot = [matchingElement fb_takeSnapshot:YES];
// Over iOS13, snapshot returns a child.
// The purpose of here is return a single element to replace children with an empty array for testing.
snapshot.children = @[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#import "FBTestMacros.h"
#import "XCUIElement.h"
#import "XCUIElement+FBFind.h"
#import "XCUIElement+FBUID.h"
#import "FBXCElementSnapshotWrapper+Helpers.h"
#import "XCUIElement+FBIsVisible.h"
#import "XCUIElement+FBClassChain.h"
Expand Down Expand Up @@ -93,7 +94,10 @@ - (void)testStableInstance
NSArray<XCUIElement *> *matchingSnapshots = [self.testedView fb_descendantsMatchingIdentifier:@"Alerts"
shouldReturnAfterFirstMatch:YES];
XCTAssertEqual(matchingSnapshots.count, 1);
for (XCUIElement *el in @[matchingSnapshots.lastObject, matchingSnapshots.lastObject.fb_stableInstance]) {
for (XCUIElement *el in @[
matchingSnapshots.lastObject,
[matchingSnapshots.lastObject fb_stableInstanceWithUid:[matchingSnapshots.lastObject fb_uid]]
]) {
XCTAssertEqual(el.elementType, XCUIElementTypeButton);
XCTAssertEqualObjects(el.label, @"Alerts");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ - (void)testDescendantsFiltering
[buttonSnapshots addObject:[buttons.firstObject fb_takeSnapshot:YES]];

NSArray<XCUIElement *> *result = [self.testedApplication fb_filterDescendantsWithSnapshots:buttonSnapshots
selfUID:nil
onlyChildren:NO];
XCTAssertEqual(1, result.count);
XCTAssertEqual([result.firstObject elementType], XCUIElementTypeButton);
Expand Down
Loading