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

Add partial xpath support to ignore identifiers in the axpath #110

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
2 changes: 2 additions & 0 deletions AppiumForMac/Server/Controller/AfMSessionController.h
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ extern NSString * const kCookieDiagnosticsDirectory;
-(GDataXMLDocument*)xmlPageSourceFromRootUIElement:(PFUIElement*)rootUIElement pathMap:(NSMutableDictionary*)pathMap xPath:(NSString*)xPath;
- (NSArray *)findAllUsingAbsoluteAXPath:(NSString *)path;

-(PFUIElement*) findElementRecursively:(PFUIElement *)rootUIElement calls:(int)calls;

- (AppiumMacHTTPJSONResponse *)executeWebDriverCommandWithPath:(NSString*)path data:(NSData*)postData onMainThread:(BOOL)onMainThread commandBlock:(AppiumMacHTTPJSONResponse *(^)(AfMSessionController *session, NSDictionary *commandParams, int *statusCode))commandBlock;

- (void)setDesiredCapabilities:(NSDictionary *)desiredCapabilities;
Expand Down
112 changes: 112 additions & 0 deletions AppiumForMac/Server/Controller/AfMSessionController.m
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ @interface AfMSessionController()
@property BOOL isAltKeyDown;
@property BOOL isCommandKeyDown;

// Properties for partial AXPath fix
@property NSString* elementRole;
@property NSMutableDictionary* predicateDict;

// Properties for handling timed events such as mouse moves.
@property NSMutableArray *timedEvents;
@property NSUInteger timedEventIndex;
Expand Down Expand Up @@ -1114,6 +1118,13 @@ - (NSArray *)findAllUsingAbsoluteAXPath:(NSString *)axPath
}
}

// See if there is any partially matching XPath
if ([matchedNodes count] == 0)
{
matchedNodes = [self findAllUsingPartialAXPath:axPathComponents rootUIElement:self.currentApplication];
}
[self clearCurrentElementCache];

return matchedNodes;
}

Expand Down Expand Up @@ -1190,6 +1201,107 @@ - (NSArray *)findAllUsingAXPathComponents:(NSArray *)axPathComponents rootUIElem
return @[];
}

-(PFUIElement*) findElementRecursively:(PFUIElement *)rootUIElement calls:(int)calls
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it always make sense to mention nullability in method signatures

{
if ([self checkIfElementFound:rootUIElement])
{
return rootUIElement;
}
calls++;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what the calls variable for?


NSArray* children = rootUIElement.AXChildren;

PFUIElement* result = nil;

if (children)
{
for (PFUIElement* node in children)
{
result = [self findElementRecursively:node calls:calls];
if (result)
{
return result;
}
}
}

calls--;
return nil;
}

-(NSArray*) findAllUsingPartialAXPath:(NSArray *)axPathComponents rootUIElement:(PFUIElement *)rootUIElement
{
NSArray* childUIElements = rootUIElement.AXChildren;

if (!childUIElements || [childUIElements count] == 0)
{
return @[];
}

[self setRoleAndPredicatesOfElement:[axPathComponents lastObject]];
NSMutableArray *matchedChildren = [NSMutableArray array];
PFUIElement* foundElement = [self findElementRecursively:rootUIElement calls:0];

if ([foundElement isNotEqualTo:nil])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cannot we simply use == for comparison here?

{
[matchedChildren addObject:foundElement];
}

return matchedChildren;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

matchedChildren.copy

}

-(void) setRoleAndPredicatesOfElement:(NSString*) elementToFind
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class seems overloaded with different stuff. Would it be possible to extract lookup-specific stuff into a separate class/category? Even better, add some basic unit tests to it?

{
if (self.elementRole && self.predicateDict) {
return;
}

NSDictionary *parseElement = [self parseRoleAndPredicateString:elementToFind];

self.elementRole = parseElement[kNodeRole];
self.predicateDict = [[NSMutableDictionary alloc] init];

NSArray* predicateArray = parseElement[kNodePredicate][kPredicateOperations];

for (int i=0; i < [predicateArray count]; i++)
Copy link
Contributor

@mykola-mokhnach mykola-mokhnach Nov 23, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cannot we simple iterate over array items (for-in)?

{
id object = [predicateArray objectAtIndex:i];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it possible to have better name for this variable?

NSString* attributeName = object[kPredicateAttributeName];
NSString* attributeValue = object[kPredicateAttributeValue];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

attributeValue only should be extracted inside of if block

if (![attributeName isEqualToString:@"AXIdentifier"])
{
self.predicateDict[attributeName] = attributeValue;
}
}
}


-(void) clearCurrentElementCache
{
self.elementRole = nil;
self.predicateDict = nil;
}

-(BOOL) checkIfElementFound:(PFUIElement*) currentElement
{

if ([currentElement.AXRole isNotEqualTo:self.elementRole])
{
return NO;
}

for( NSString* key in self.predicateDict )
{
if ( ![currentElement existsAttribute:key] || [self.predicateDict[key] isNotEqualTo:[currentElement valueForAttribute:key]])
{
return NO;
}
}

return YES;

}

- (BOOL)element:(PFUIElement *)uiElement doesMatchPredicate:(NSDictionary *)predicate atIndex:(NSUInteger)elementIndex
{
BOOL doesMatch = NO;
Expand Down